benjamin.harris il y a 6 ans
commit
03e610b9d3
100 fichiers modifiés avec 6546 ajouts et 0 suppressions
  1. 99 0
      .htaccess
  2. 3 0
      OpenWeatherMap/.gitignore
  3. 9 0
      OpenWeatherMap/.scrutinizer.yml
  4. 1 0
      OpenWeatherMap/.styleci.yml
  5. 37 0
      OpenWeatherMap/.travis.yml
  6. 748 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap.php
  7. 69 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/AbstractCache.php
  8. 142 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/CurrentWeather.php
  9. 93 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/CurrentWeatherGroup.php
  10. 26 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Exception.php
  11. 59 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Fetcher/CurlFetcher.php
  12. 37 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Fetcher/FetcherInterface.php
  13. 34 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Fetcher/FileGetContentsFetcher.php
  14. 82 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Forecast.php
  15. 107 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/History.php
  16. 56 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/UVIndex.php
  17. 66 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/City.php
  18. 48 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Location.php
  19. 52 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Sun.php
  20. 133 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Temperature.php
  21. 66 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Time.php
  22. 129 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Unit.php
  23. 93 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Weather.php
  24. 48 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Wind.php
  25. 137 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/WeatherForecast.php
  26. 141 0
      OpenWeatherMap/Cmfcmf/OpenWeatherMap/WeatherHistory.php
  27. 5 0
      OpenWeatherMap/Examples/ApiKey.ini
  28. 108 0
      OpenWeatherMap/Examples/Cache.php
  29. 260 0
      OpenWeatherMap/Examples/CurrentWeather.php
  30. 67 0
      OpenWeatherMap/Examples/WeatherForecast.php
  31. 35 0
      OpenWeatherMap/Examples/WeatherHistory.php
  32. 17 0
      OpenWeatherMap/Examples/bootstrap.php
  33. 21 0
      OpenWeatherMap/LICENSE
  34. 87 0
      OpenWeatherMap/README.md
  35. 15 0
      OpenWeatherMap/Vagrantfile
  36. 36 0
      OpenWeatherMap/composer.json
  37. 1403 0
      OpenWeatherMap/composer.lock
  38. 26 0
      OpenWeatherMap/phpunit.xml.dist
  39. 92 0
      OpenWeatherMap/tests/ExampleCacheTest.php
  40. 219 0
      OpenWeatherMap/tests/Exceptions/OpenWeatherMapExceptionTest.php
  41. 198 0
      OpenWeatherMap/tests/FakeData.php
  42. 53 0
      OpenWeatherMap/tests/Fetcher/CurlFetcherTest.php
  43. 59 0
      OpenWeatherMap/tests/Fetcher/FileGetContentsFetcherTest.php
  44. 64 0
      OpenWeatherMap/tests/OpenWeatherMap/CurrentWeatherGroupTest.php
  45. 66 0
      OpenWeatherMap/tests/OpenWeatherMap/WeatherForecastTest.php
  46. 81 0
      OpenWeatherMap/tests/OpenWeatherMap/WeatherHistoryTest.php
  47. 205 0
      OpenWeatherMap/tests/OpenWeatherMapTest.php
  48. 65 0
      OpenWeatherMap/tests/TestFetcher.php
  49. 61 0
      OpenWeatherMap/tests/Util/SunTest.php
  50. 100 0
      OpenWeatherMap/tests/Util/TemperatureTest.php
  51. 61 0
      OpenWeatherMap/tests/Util/TimeTest.php
  52. 166 0
      OpenWeatherMap/tests/Util/UnitTest.php
  53. 67 0
      OpenWeatherMap/tests/Util/WeatherTest.php
  54. 17 0
      OpenWeatherMap/tests/bootstrap.php
  55. 118 0
      api/Rest.inc.php
  56. 71 0
      api/api.php
  57. 11 0
      api/ht.htaccess
  58. 151 0
      api/updateweatherstation.php
  59. 0 0
      assets/components/ace/ace/ace.min.js
  60. 0 0
      assets/components/ace/ace/ext-beautify.js
  61. 0 0
      assets/components/ace/ace/ext-chromevox.js
  62. 0 0
      assets/components/ace/ace/ext-elastic_tabstops_lite.js
  63. 0 0
      assets/components/ace/ace/ext-emmet.js
  64. 5 0
      assets/components/ace/ace/ext-error_marker.js
  65. 0 0
      assets/components/ace/ace/ext-keybinding_menu.js
  66. 0 0
      assets/components/ace/ace/ext-language_tools.js
  67. 5 0
      assets/components/ace/ace/ext-linking.js
  68. 0 0
      assets/components/ace/ace/ext-modelist.js
  69. 0 0
      assets/components/ace/ace/ext-old_ie.js
  70. 0 0
      assets/components/ace/ace/ext-searchbox.js
  71. 0 0
      assets/components/ace/ace/ext-settings_menu.js
  72. 5 0
      assets/components/ace/ace/ext-spellcheck.js
  73. 0 0
      assets/components/ace/ace/ext-split.js
  74. 0 0
      assets/components/ace/ace/ext-static_highlight.js
  75. 5 0
      assets/components/ace/ace/ext-statusbar.js
  76. 0 0
      assets/components/ace/ace/ext-textarea.js
  77. 5 0
      assets/components/ace/ace/ext-themelist.js
  78. 0 0
      assets/components/ace/ace/ext-whitespace.js
  79. 0 0
      assets/components/ace/ace/keybinding-emacs.js
  80. 0 0
      assets/components/ace/ace/keybinding-vim.js
  81. 0 0
      assets/components/ace/ace/mode-css.js
  82. 0 0
      assets/components/ace/ace/mode-html.js
  83. 0 0
      assets/components/ace/ace/mode-javascript.js
  84. 0 0
      assets/components/ace/ace/mode-json.js
  85. 0 0
      assets/components/ace/ace/mode-less.js
  86. 0 0
      assets/components/ace/ace/mode-markdown.js
  87. 0 0
      assets/components/ace/ace/mode-php.js
  88. 0 0
      assets/components/ace/ace/mode-scss.js
  89. 0 0
      assets/components/ace/ace/mode-smarty.js
  90. 1 0
      assets/components/ace/ace/mode-sql.js
  91. 0 0
      assets/components/ace/ace/mode-svg.js
  92. 0 0
      assets/components/ace/ace/mode-text.js
  93. 0 0
      assets/components/ace/ace/mode-twig.js
  94. 0 0
      assets/components/ace/ace/mode-xml.js
  95. 0 0
      assets/components/ace/ace/theme-ambiance.js
  96. 0 0
      assets/components/ace/ace/theme-chaos.js
  97. 0 0
      assets/components/ace/ace/theme-chrome.js
  98. 0 0
      assets/components/ace/ace/theme-clouds.js
  99. 0 0
      assets/components/ace/ace/theme-clouds_midnight.js
  100. 0 0
      assets/components/ace/ace/theme-cobalt.js

+ 99 - 0
.htaccess

@@ -0,0 +1,99 @@
+# MODX supports Friendly URLs via this .htaccess file. You must serve web
+# pages via Apache with mod_rewrite to use this functionality, and you must
+# change the file name from ht.access to .htaccess.
+#
+# Make sure RewriteBase points to the directory where you installed MODX.
+# E.g., "/modx" if your installation is in a "modx" subdirectory.
+#
+# You may choose to make your URLs non-case-sensitive by adding a NC directive
+# to your rule: RewriteRule ^(.*)$ index.php?q=$1 [L,QSA,NC]
+
+RewriteEngine On
+RewriteBase /site/
+
+RewriteRule ^(api)($|/) - [L]
+
+# Rewrite www.domain.com -> domain.com -- used with SEO Strict URLs plugin
+RewriteCond %{HTTP_HOST} .
+RewriteCond %{HTTP_HOST} ^www.(.*)$ [NC]
+RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
+
+#Setup blog to show as a subdomain
+#Redirect permanent /blog http://blog.example.com/
+
+#
+# or for the opposite domain.com -> www.domain.com use the following
+# DO NOT USE BOTH
+#
+#RewriteCond %{HTTP_HOST} !^$
+#RewriteCond %{HTTP_HOST} !^www\. [NC]
+#RewriteCond %{HTTP_HOST} (.+)$
+#RewriteRule ^(.*)$ http://www.%1/$1 [R=301,L] .
+
+
+
+# Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent 
+# https://www.domain.com when your cert only allows https://secure.domain.com
+RewriteCond %{SERVER_PORT} !^443
+RewriteRule (.*) https://cropmonitor.info/$1 [R=301,L]
+
+
+
+# The Friendly URLs part
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
+
+
+
+# Make sure .htc files are served with the proper MIME type, which is critical
+# for XP SP2. Un-comment if your host allows htaccess MIME type overrides.
+
+#AddType text/x-component .htc
+
+
+
+# If your server is not already configured as such, the following directive
+# should be uncommented in order to set PHP's register_globals option to OFF.
+# This closes a major security hole that is abused by most XSS (cross-site
+# scripting) attacks. For more information: http://php.net/register_globals
+#
+# To verify that this option has been set to OFF, open the Manager and choose
+# Reports -> System Info and then click the phpinfo() link. Do a Find on Page
+# for "register_globals". The Local Value should be OFF. If the Master Value
+# is OFF then you do not need this directive here.
+#
+# IF REGISTER_GLOBALS DIRECTIVE CAUSES 500 INTERNAL SERVER ERRORS :
+#
+# Your server does not allow PHP directives to be set via .htaccess. In that
+# case you must make this change in your php.ini file instead. If you are
+# using a commercial web host, contact the administrators for assistance in
+# doing this. Not all servers allow local php.ini files, and they should
+# include all PHP configurations (not just this one), or you will effectively
+# reset everything to PHP defaults. Consult www.php.net for more detailed
+# information about setting PHP directives.
+
+#php_flag register_globals Off
+
+
+
+# For servers that support output compression, you should pick up a bit of
+# speed by un-commenting the following lines.
+
+#php_flag zlib.output_compression On
+#php_value zlib.output_compression_level 5
+
+
+
+# The following directives stop screen flicker in IE on CSS rollovers. If
+# needed, un-comment the following rules. When they're in place, you may have
+# to do a force-refresh in order to see changes in your designs.
+
+#ExpiresActive On
+#ExpiresByType image/gif A2592000
+#ExpiresByType image/jpeg A2592000
+#ExpiresByType image/png A2592000
+#BrowserMatch "MSIE" brokenvary=1
+#BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
+#BrowserMatch "Opera" !brokenvary
+#SetEnvIf brokenvary 1 force-no-vary

+ 3 - 0
OpenWeatherMap/.gitignore

@@ -0,0 +1,3 @@
+/vendor
+/.vagrant/
+/ubuntu-xenial-16.04-cloudimg-console.log

+ 9 - 0
OpenWeatherMap/.scrutinizer.yml

@@ -0,0 +1,9 @@
+checks:
+  php:
+    code_rating: true
+    duplication: true
+
+tools:
+  external_code_coverage:
+    enabled: true
+    timeout: 600

+ 1 - 0
OpenWeatherMap/.styleci.yml

@@ -0,0 +1 @@
+preset: psr2

+ 37 - 0
OpenWeatherMap/.travis.yml

@@ -0,0 +1,37 @@
+dist: trusty
+sudo: false
+
+language: php
+
+matrix:
+  include:
+    - php: hhvm
+    - php: nightly
+    - php: 7.2
+    - php: 7.1
+    - php: 7.0
+    - php: 5.6
+    - php: 5.5
+    - php: 5.4
+    - php: 5.3
+      dist: precise
+  fast_finish: true
+  allow_failures:
+    - php: nightly
+
+install:
+  - if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi
+  - if [[ ! $TRAVIS_PHP_VERSION = hhvm* ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi
+  - echo date.timezone = Europe/Berlin >> $INI_FILE
+  - echo memory_limit = -1 >> $INI_FILE
+  - composer update
+
+script:
+  - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
+
+after_success:
+  # Scrutinizer
+  - wget https://scrutinizer-ci.com/ocular.phar
+  - php ocular.phar code-coverage:upload --format=php-clover coverage.xml
+  # CodeCov
+  - bash <(curl -s https://codecov.io/bash)

+ 748 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap.php

@@ -0,0 +1,748 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf;
+
+use Cmfcmf\OpenWeatherMap\AbstractCache;
+use Cmfcmf\OpenWeatherMap\CurrentWeather;
+use Cmfcmf\OpenWeatherMap\UVIndex;
+use Cmfcmf\OpenWeatherMap\CurrentWeatherGroup;
+use Cmfcmf\OpenWeatherMap\Exception as OWMException;
+use Cmfcmf\OpenWeatherMap\Fetcher\CurlFetcher;
+use Cmfcmf\OpenWeatherMap\Fetcher\FetcherInterface;
+use Cmfcmf\OpenWeatherMap\Fetcher\FileGetContentsFetcher;
+use Cmfcmf\OpenWeatherMap\WeatherForecast;
+use Cmfcmf\OpenWeatherMap\WeatherHistory;
+
+/**
+ * Main class for the OpenWeatherMap-PHP-API. Only use this class.
+ *
+ * @api
+ */
+class OpenWeatherMap
+{
+    /**
+     * The copyright notice. This is no official text, it was created by
+     * following the guidelines at http://openweathermap.org/copyright.
+     *
+     * @var string $copyright
+     */
+    const COPYRIGHT = "Weather data from <a href=\"https://openweathermap.org\">OpenWeatherMap.org</a>";
+
+    /**
+     * @var string The basic api url to fetch weather data from.
+     */
+    private $weatherUrl = 'https://api.openweathermap.org/data/2.5/weather?';
+
+    /**
+     * @var string The basic api url to fetch weather group data from.
+     */
+    private $weatherGroupUrl = 'https://api.openweathermap.org/data/2.5/group?';
+
+    /**
+     * @var string The basic api url to fetch weekly forecast data from.
+     */
+    private $weatherHourlyForecastUrl = 'https://api.openweathermap.org/data/2.5/forecast?';
+
+    /**
+     * @var string The basic api url to fetch daily forecast data from.
+     */
+    private $weatherDailyForecastUrl = 'https://api.openweathermap.org/data/2.5/forecast/daily?';
+
+    /**
+     * @var string The basic api url to fetch history weather data from.
+     */
+    private $weatherHistoryUrl = 'https://history.openweathermap.org/data/2.5/history/city?';
+
+    /**
+     * @var string The basic api url to fetch uv index data from.
+     */
+    private $uvIndexUrl = 'https://api.openweathermap.org/v3/uvi';
+
+    /**
+     * @var AbstractCache|bool $cache The cache to use.
+     */
+    private $cache = false;
+
+    /**
+     * @var int
+     */
+    private $seconds;
+
+    /**
+     * @var bool
+     */
+    private $wasCached = false;
+
+    /**
+     * @var FetcherInterface The url fetcher.
+     */
+    private $fetcher;
+
+    /**
+     * @var string
+     */
+    private $apiKey = '';
+
+    /**
+     * Constructs the OpenWeatherMap object.
+     *
+     * @param string                $apiKey  The OpenWeatherMap API key. Required and only optional for BC.
+     * @param null|FetcherInterface $fetcher The interface to fetch the data from OpenWeatherMap. Defaults to
+     *                                       CurlFetcher() if cURL is available. Otherwise defaults to
+     *                                       FileGetContentsFetcher() using 'file_get_contents()'.
+     * @param bool|string           $cache   If set to false, caching is disabled. Otherwise this must be a class
+     *                                       extending AbstractCache. Defaults to false.
+     * @param int $seconds                   How long weather data shall be cached. Default 10 minutes.
+     *
+     * @throws \Exception If $cache is neither false nor a valid callable extending Cmfcmf\OpenWeatherMap\Util\Cache.
+     *
+     * @api
+     */
+    public function __construct($apiKey = '', $fetcher = null, $cache = false, $seconds = 600)
+    {
+        if (!is_string($apiKey) || empty($apiKey)) {
+            // BC
+            $seconds = $cache !== false ? $cache : 600;
+            $cache = $fetcher !== null ? $fetcher : false;
+            $fetcher = $apiKey !== '' ? $apiKey : null;
+        } else {
+            $this->apiKey = $apiKey;
+        }
+
+        if ($cache !== false && !($cache instanceof AbstractCache)) {
+            throw new \InvalidArgumentException('The cache class must implement the FetcherInterface!');
+        }
+        if (!is_numeric($seconds)) {
+            throw new \InvalidArgumentException('$seconds must be numeric.');
+        }
+        if (!isset($fetcher)) {
+            $fetcher = (function_exists('curl_version')) ? new CurlFetcher() : new FileGetContentsFetcher();
+        }
+        if ($seconds == 0) {
+            $cache = false;
+        }
+
+        $this->cache = $cache;
+        $this->seconds = $seconds;
+        $this->fetcher = $fetcher;
+    }
+
+    /**
+     * Sets the API Key.
+     *
+     * @param string $apiKey API key for the OpenWeatherMap account.
+     *
+     * @api
+     */
+    public function setApiKey($apiKey)
+    {
+        $this->apiKey = $apiKey;
+    }
+
+    /**
+     * Returns the API Key.
+     *
+     * @return string
+     *
+     * @api
+     */
+    public function getApiKey()
+    {
+        return $this->apiKey;
+    }
+
+    /**
+     * Returns the current weather at the place you specified.
+     *
+     * @param array|int|string $query The place to get weather information for. For possible values see below.
+     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
+     *
+     * @throws OpenWeatherMap\Exception  If OpenWeatherMap returns an error.
+     * @throws \InvalidArgumentException If an argument error occurs.
+     *
+     * @return CurrentWeather The weather object.
+     *
+     * There are four ways to specify the place to get weather information for:
+     * - Use the city name: $query must be a string containing the city name.
+     * - Use the city id: $query must be an integer containing the city id.
+     * - Use the coordinates: $query must be an associative array containing the 'lat' and 'lon' values.
+     * - Use the zip code: $query must be a string, prefixed with "zip:"
+     *
+     * Zip code may specify country. e.g., "zip:77070" (Houston, TX, US) or "zip:500001,IN" (Hyderabad, India)
+     *
+     * @api
+     */
+    public function getWeather($query, $units = 'imperial', $lang = 'en', $appid = '')
+    {
+        $answer = $this->getRawWeatherData($query, $units, $lang, $appid, 'xml');
+        $xml = $this->parseXML($answer);
+
+        return new CurrentWeather($xml, $units);
+    }
+
+    /**
+     * Returns the current weather for a group of city ids.
+     *
+     * @param array  $ids   The city ids to get weather information for
+     * @param string $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
+     *
+     * @throws OpenWeatherMap\Exception  If OpenWeatherMap returns an error.
+     * @throws \InvalidArgumentException If an argument error occurs.
+     *
+     * @return CurrentWeatherGroup
+     *
+     * @api
+     */
+    public function getWeatherGroup($ids, $units = 'imperial', $lang = 'en', $appid = '')
+    {
+        $answer = $this->getRawWeatherGroupData($ids, $units, $lang, $appid);
+        $json = $this->parseJson($answer);
+
+        return new CurrentWeatherGroup($json, $units);
+    }
+
+    /**
+     * Returns the forecast for the place you specified. DANGER: Might return
+     * fewer results than requested due to a bug in the OpenWeatherMap API!
+     *
+     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
+     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
+     * @param int              $days  For how much days you want to get a forecast. Default 1, maximum: 16.
+     *
+     * @throws OpenWeatherMap\Exception If OpenWeatherMap returns an error.
+     * @throws \InvalidArgumentException If an argument error occurs.
+     *
+     * @return WeatherForecast
+     *
+     * @api
+     */
+    public function getWeatherForecast($query, $units = 'imperial', $lang = 'en', $appid = '', $days = 1)
+    {
+        if ($days <= 5) {
+            $answer = $this->getRawHourlyForecastData($query, $units, $lang, $appid, 'xml');
+        } elseif ($days <= 16) {
+            $answer = $this->getRawDailyForecastData($query, $units, $lang, $appid, 'xml', $days);
+        } else {
+            throw new \InvalidArgumentException('Error: forecasts are only available for the next 16 days. $days must be 16 or lower.');
+        }
+        $xml = $this->parseXML($answer);
+
+        return new WeatherForecast($xml, $units, $days);
+    }
+
+    /**
+     * Returns the DAILY forecast for the place you specified. DANGER: Might return
+     * fewer results than requested due to a bug in the OpenWeatherMap API!
+     *
+     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
+     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
+     * @param int              $days  For how much days you want to get a forecast. Default 1, maximum: 16.
+     *
+     * @throws OpenWeatherMap\Exception If OpenWeatherMap returns an error.
+     * @throws \InvalidArgumentException If an argument error occurs.
+     *
+     * @return WeatherForecast
+     *
+     * @api
+     */
+    public function getDailyWeatherForecast($query, $units = 'imperial', $lang = 'en', $appid = '', $days = 1)
+    {
+        if ($days > 16) {
+            throw new \InvalidArgumentException('Error: forecasts are only available for the next 16 days. $days must be 16 or lower.');
+        }
+
+        $answer = $this->getRawDailyForecastData($query, $units, $lang, $appid, 'xml', $days);
+        $xml = $this->parseXML($answer);
+        return new WeatherForecast($xml, $units, $days);
+    }
+
+    /**
+     * Returns the weather history for the place you specified.
+     *
+     * @param array|int|string $query      The place to get weather information for. For possible values see ::getWeather.
+     * @param \DateTime        $start
+     * @param int              $endOrCount
+     * @param string           $type       Can either be 'tick', 'hour' or 'day'.
+     * @param string           $units      Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string           $lang       The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string           $appid      Your app id, default ''. See http://openweathermap.org/appid for more details.
+     *
+     * @throws OpenWeatherMap\Exception  If OpenWeatherMap returns an error.
+     * @throws \InvalidArgumentException If an argument error occurs.
+     *
+     * @return WeatherHistory
+     *
+     * @api
+     */
+    public function getWeatherHistory($query, \DateTime $start, $endOrCount = 1, $type = 'hour', $units = 'imperial', $lang = 'en', $appid = '')
+    {
+        if (!in_array($type, array('tick', 'hour', 'day'))) {
+            throw new \InvalidArgumentException('$type must be either "tick", "hour" or "day"');
+        }
+
+        $xml = json_decode($this->getRawWeatherHistory($query, $start, $endOrCount, $type, $units, $lang, $appid), true);
+
+        if ($xml['cod'] != 200) {
+            throw new OWMException($xml['message'], $xml['cod']);
+        }
+
+        return new WeatherHistory($xml, $query);
+    }
+
+    /**
+     * Returns the current uv index at the location you specified.
+     *
+     * @param float              $lat           The location's latitude.
+     * @param float              $lon           The location's longitude.
+     *
+     * @throws OpenWeatherMap\Exception  If OpenWeatherMap returns an error.
+     * @throws \InvalidArgumentException If an argument error occurs.
+     *
+     * @return UVIndex The uvi object.
+     *
+     * @api
+     */
+    public function getCurrentUVIndex($lat, $lon)
+    {
+        $answer = $this->getRawCurrentUVIndexData($lat, $lon);
+        $json = $this->parseJson($answer);
+
+        return new UVIndex($json);
+    }
+
+    /**
+     * Returns the uv index at date, time and location you specified.
+     *
+     * @param float              $lat           The location's latitude.
+     * @param float              $lon           The location's longitude.
+     * @param \DateTimeInterface $dateTime      The date and time to request data for.
+     * @param string             $timePrecision This decides about the timespan OWM will look for the uv index. The tighter
+     *                                          the timespan, the less likely it is to get a result. Can be 'year', 'month',
+     *                                          'day', 'hour', 'minute' or 'second', defaults to 'day'.
+     *
+     * @throws OpenWeatherMap\Exception  If OpenWeatherMap returns an error.
+     * @throws \InvalidArgumentException If an argument error occurs.
+     *
+     * @return UVIndex The uvi object.
+     *
+     * @api
+     */
+    public function getUVIndex($lat, $lon, $dateTime, $timePrecision = 'day')
+    {
+        $answer = $this->getRawUVIndexData($lat, $lon, $dateTime, $timePrecision);
+        $json = $this->parseJson($answer);
+
+        return new UVIndex($json);
+    }
+
+    /**
+     * Directly returns the xml/json/html string returned by OpenWeatherMap for the current weather.
+     *
+     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
+     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
+     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default).
+     *
+     * @return string Returns false on failure and the fetched data in the format you specified on success.
+     *
+     * Warning: If an error occurs, OpenWeatherMap ALWAYS returns json data.
+     *
+     * @api
+     */
+    public function getRawWeatherData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
+    {
+        $url = $this->buildUrl($query, $units, $lang, $appid, $mode, $this->weatherUrl);
+
+        return $this->cacheOrFetchResult($url);
+    }
+
+    /**
+     * Directly returns the JSON string returned by OpenWeatherMap for the group of current weather.
+     * Only a JSON response format is supported for this webservice.
+     *
+     * @param array  $ids   The city ids to get weather information for
+     * @param string $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
+     *
+     * @return string Returns false on failure and the fetched data in the format you specified on success.
+     *
+     * @api
+     */
+    public function getRawWeatherGroupData($ids, $units = 'imperial', $lang = 'en', $appid = '')
+    {
+        $url = $this->buildUrl($ids, $units, $lang, $appid, 'json', $this->weatherGroupUrl);
+
+        return $this->cacheOrFetchResult($url);
+    }
+
+    /**
+     * Directly returns the xml/json/html string returned by OpenWeatherMap for the hourly forecast.
+     *
+     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
+     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
+     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default).
+     *
+     * @return string Returns false on failure and the fetched data in the format you specified on success.
+     *
+     * Warning: If an error occurs, OpenWeatherMap ALWAYS returns json data.
+     *
+     * @api
+     */
+    public function getRawHourlyForecastData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
+    {
+        $url = $this->buildUrl($query, $units, $lang, $appid, $mode, $this->weatherHourlyForecastUrl);
+
+        return $this->cacheOrFetchResult($url);
+    }
+
+    /**
+     * Directly returns the xml/json/html string returned by OpenWeatherMap for the daily forecast.
+     *
+     * @param array|int|string $query The place to get weather information for. For possible values see ::getWeather.
+     * @param string           $units Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string           $lang  The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string           $appid Your app id, default ''. See http://openweathermap.org/appid for more details.
+     * @param string           $mode  The format of the data fetched. Possible values are 'json', 'html' and 'xml' (default)
+     * @param int              $cnt   How many days of forecast shall be returned? Maximum (and default): 16
+     *
+     * @throws \InvalidArgumentException If $cnt is higher than 16.
+     *
+     * @return string Returns false on failure and the fetched data in the format you specified on success.
+     *
+     * Warning: If an error occurs, OpenWeatherMap ALWAYS returns json data.
+     *
+     * @api
+     */
+    public function getRawDailyForecastData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml', $cnt = 16)
+    {
+        if ($cnt > 16) {
+            throw new \InvalidArgumentException('$cnt must be 16 or lower!');
+        }
+        $url = $this->buildUrl($query, $units, $lang, $appid, $mode, $this->weatherDailyForecastUrl) . "&cnt=$cnt";
+
+        return $this->cacheOrFetchResult($url);
+    }
+
+    /**
+     * Directly returns the json string returned by OpenWeatherMap for the weather history.
+     *
+     * @param array|int|string $query      The place to get weather information for. For possible values see ::getWeather.
+     * @param \DateTime        $start      The \DateTime object of the date to get the first weather information from.
+     * @param \DateTime|int    $endOrCount Can be either a \DateTime object representing the end of the period to
+     *                                     receive weather history data for or an integer counting the number of
+     *                                     reports requested.
+     * @param string           $type       The period of the weather history requested. Can be either be either "tick",
+     *                                     "hour" or "day".
+     * @param string           $units      Can be either 'metric' or 'imperial' (default). This affects almost all units returned.
+     * @param string           $lang       The language to use for descriptions, default is 'en'. For possible values see http://openweathermap.org/current#multi.
+     * @param string           $appid      Your app id, default ''. See http://openweathermap.org/appid for more details.
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return string Returns false on failure and the fetched data in the format you specified on success.
+     *
+     * Warning If an error occurred, OpenWeatherMap ALWAYS returns data in json format.
+     *
+     * @api
+     */
+    public function getRawWeatherHistory($query, \DateTime $start, $endOrCount = 1, $type = 'hour', $units = 'imperial', $lang = 'en', $appid = '')
+    {
+        if (!in_array($type, array('tick', 'hour', 'day'))) {
+            throw new \InvalidArgumentException('$type must be either "tick", "hour" or "day"');
+        }
+
+        $url = $this->buildUrl($query, $units, $lang, $appid, 'json', $this->weatherHistoryUrl);
+        $url .= "&type=$type&start={$start->format('U')}";
+        if ($endOrCount instanceof \DateTime) {
+            $url .= "&end={$endOrCount->format('U')}";
+        } elseif (is_numeric($endOrCount) && $endOrCount > 0) {
+            $url .= "&cnt=$endOrCount";
+        } else {
+            throw new \InvalidArgumentException('$endOrCount must be either a \DateTime or a positive integer.');
+        }
+
+        return $this->cacheOrFetchResult($url);
+    }
+
+    /**
+     * Directly returns the json string returned by OpenWeatherMap for the current UV index data.
+     *
+     * @param float $lat                   The location's latitude.
+     * @param float $lon                   The location's longitude.
+     *
+     * @return bool|string Returns the fetched data.
+     *
+     * @api
+     */
+    public function getRawCurrentUVIndexData($lat, $lon)
+    {
+        if (!$this->apiKey) {
+            throw new \RuntimeException('Before using this method, you must set the api key using ->setApiKey()');
+        }
+        if (!is_float($lat) || !is_float($lon)) {
+            throw new \InvalidArgumentException('$lat and $lon must be floating point numbers');
+        }
+        $url = $this->buildUVIndexUrl($lat, $lon);
+
+        return $this->cacheOrFetchResult($url);
+    }
+
+    /**
+     * Directly returns the json string returned by OpenWeatherMap for the UV index data.
+     *
+     * @param float $lat                   The location's latitude.
+     * @param float $lon                   The location's longitude.
+     * @param \DateTimeInterface $dateTime The date and time to request data for.
+     * @param string $timePrecision        This decides about the timespan OWM will look for the uv index. The tighter
+     *                                     the timespan, the less likely it is to get a result. Can be 'year', 'month',
+     *                                     'day', 'hour', 'minute' or 'second', defaults to 'day'.
+     *
+     * @return bool|string Returns the fetched data.
+     *
+     * @api
+     */
+    public function getRawUVIndexData($lat, $lon, $dateTime, $timePrecision = 'day')
+    {
+        if (!$this->apiKey) {
+            throw new \RuntimeException('Before using this method, you must set the api key using ->setApiKey()');
+        }
+        if (!is_float($lat) || !is_float($lon)) {
+            throw new \InvalidArgumentException('$lat and $lon must be floating point numbers');
+        }
+        if (interface_exists('DateTimeInterface') && !$dateTime instanceof \DateTimeInterface || !$dateTime instanceof \DateTime) {
+            throw new \InvalidArgumentException('$dateTime must be an instance of \DateTime or \DateTimeInterface');
+        }
+        $url = $this->buildUVIndexUrl($lat, $lon, $dateTime, $timePrecision);
+
+        return $this->cacheOrFetchResult($url);
+    }
+
+    /**
+     * Returns whether or not the last result was fetched from the cache.
+     *
+     * @return bool true if last result was fetched from cache, false otherwise.
+     */
+    public function wasCached()
+    {
+        return $this->wasCached;
+    }
+
+    /**
+     * @deprecated Use {@link self::getRawWeatherData()} instead.
+     */
+    public function getRawData($query, $units = 'imperial', $lang = 'en', $appid = '', $mode = 'xml')
+    {
+        return $this->getRawWeatherData($query, $units, $lang, $appid, $mode);
+    }
+
+    /**
+     * Fetches the result or delivers a cached version of the result.
+     *
+     * @param string $url
+     *
+     * @return string
+     */
+    private function cacheOrFetchResult($url)
+    {
+        if ($this->cache !== false) {
+            /** @var AbstractCache $cache */
+            $cache = $this->cache;
+            $cache->setSeconds($this->seconds);
+            if ($cache->isCached($url)) {
+                $this->wasCached = true;
+                return $cache->getCached($url);
+            }
+            $result = $this->fetcher->fetch($url);
+            $cache->setCached($url, $result);
+        } else {
+            $result = $this->fetcher->fetch($url);
+        }
+        $this->wasCached = false;
+
+        return $result;
+    }
+
+    /**
+     * Build the url to fetch weather data from.
+     *
+     * @param        $query
+     * @param        $units
+     * @param        $lang
+     * @param        $appid
+     * @param        $mode
+     * @param string $url   The url to prepend.
+     *
+     * @return bool|string The fetched url, false on failure.
+     */
+    private function buildUrl($query, $units, $lang, $appid, $mode, $url)
+    {
+        $queryUrl = $this->buildQueryUrlParameter($query);
+
+        $url = $url."$queryUrl&units=$units&lang=$lang&mode=$mode&APPID=";
+        $url .= empty($appid) ? $this->apiKey : $appid;
+
+        return $url;
+    }
+
+    /**
+     * @param float                        $lat
+     * @param float                        $lon
+     * @param \DateTime|\DateTimeImmutable $dateTime
+     * @param string                       $timePrecision
+     *
+     * @return string
+     */
+    private function buildUVIndexUrl($lat, $lon, $dateTime = null, $timePrecision = null)
+    {
+        if ($dateTime !== null) {
+            $format = '\Z';
+            switch ($timePrecision) {
+                /** @noinspection PhpMissingBreakStatementInspection */
+                case 'second':
+                    $format = ':s' . $format;
+                /** @noinspection PhpMissingBreakStatementInspection */
+                case 'minute':
+                    $format = ':i' . $format;
+                /** @noinspection PhpMissingBreakStatementInspection */
+                case 'hour':
+                    $format = '\TH' . $format;
+                /** @noinspection PhpMissingBreakStatementInspection */
+                case 'day':
+                    $format = '-d' . $format;
+                /** @noinspection PhpMissingBreakStatementInspection */
+                case 'month':
+                    $format = '-m' . $format;
+                case 'year':
+                    $format = 'Y' . $format;
+                    break;
+                default:
+                    throw new \InvalidArgumentException('$timePrecision is invalid.');
+            }
+            // OWM only accepts UTC timezones.
+            $dateTime->setTimezone(new \DateTimeZone('UTC'));
+            $dateTime = $dateTime->format($format);
+        } else {
+            $dateTime = 'current';
+        }
+
+        return sprintf($this->uvIndexUrl . '/%s,%s/%s.json?appid=%s', $lat, $lon, $dateTime, $this->apiKey);
+    }
+
+    /**
+     * Builds the query string for the url.
+     *
+     * @param mixed $query
+     *
+     * @return string The built query string for the url.
+     *
+     * @throws \InvalidArgumentException If the query parameter is invalid.
+     */
+    private function buildQueryUrlParameter($query)
+    {
+        switch ($query) {
+            case is_array($query) && isset($query['lat']) && isset($query['lon']) && is_numeric($query['lat']) && is_numeric($query['lon']):
+                return "lat={$query['lat']}&lon={$query['lon']}";
+            case is_array($query) && is_numeric($query[0]):
+                return 'id='.implode(',', $query);
+            case is_numeric($query):
+                return "id=$query";
+            case is_string($query) && strpos($query, 'zip:') === 0:
+                $subQuery = str_replace('zip:', '', $query);
+                return 'zip='.urlencode($subQuery);
+            case is_string($query):
+                return 'q='.urlencode($query);
+            default:
+                throw new \InvalidArgumentException('Error: $query has the wrong format. See the documentation of OpenWeatherMap::getWeather() to read about valid formats.');
+        }
+    }
+
+    /**
+     * @param string $answer The content returned by OpenWeatherMap.
+     *
+     * @return \SimpleXMLElement
+     * @throws OWMException If the content isn't valid XML.
+     */
+    private function parseXML($answer)
+    {
+        // Disable default error handling of SimpleXML (Do not throw E_WARNINGs).
+        libxml_use_internal_errors(true);
+        libxml_clear_errors();
+        try {
+            return new \SimpleXMLElement($answer);
+        } catch (\Exception $e) {
+            // Invalid xml format. This happens in case OpenWeatherMap returns an error.
+            // OpenWeatherMap always uses json for errors, even if one specifies xml as format.
+            $error = json_decode($answer, true);
+            if (isset($error['message'])) {
+                throw new OWMException($error['message'], isset($error['cod']) ? $error['cod'] : 0);
+            } else {
+                throw new OWMException('Unknown fatal error: OpenWeatherMap returned the following json object: ' . $answer);
+            }
+        }
+    }
+
+    /**
+     * @param string $answer The content returned by OpenWeatherMap.
+     *
+     * @return \stdClass
+     * @throws OWMException If the content isn't valid JSON.
+     */
+    private function parseJson($answer)
+    {
+        $json = json_decode($answer);
+        if (json_last_error() !== JSON_ERROR_NONE) {
+            throw new OWMException('OpenWeatherMap returned an invalid json object. JSON error was: ' . $this->json_last_error_msg());
+        }
+        if (isset($json->message)) {
+            throw new OWMException('An error occurred: '. $json->message);
+        }
+
+        return $json;
+    }
+
+    private function json_last_error_msg()
+    {
+        if (function_exists('json_last_error_msg')) {
+            return json_last_error_msg();
+        }
+
+        static $ERRORS = array(
+            JSON_ERROR_NONE => 'No error',
+            JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
+            JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
+            JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
+            JSON_ERROR_SYNTAX => 'Syntax error',
+            JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
+        );
+
+        $error = json_last_error();
+        return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error';
+    }
+}

+ 69 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/AbstractCache.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+/**
+ * Abstract cache class to be overwritten by custom cache implementations.
+ */
+abstract class AbstractCache
+{
+    /**
+     * @var int $seconds Cache time in seconds.
+     */
+    protected $seconds;
+
+    /**
+     * Checks whether a cached weather data is available.
+     *
+     * @param string $url The unique url of the cached content.
+     *
+     * @return bool False if no cached information is available, otherwise true.
+     *
+     * You need to check if a cached result is outdated here. Return false in that case.
+     */
+    abstract public function isCached($url);
+
+    /**
+     * Returns cached weather data.
+     *
+     * @param string $url The unique url of the cached content.
+     *
+     * @return string|bool The cached data if it exists, false otherwise.
+     */
+    abstract public function getCached($url);
+
+    /**
+     * Saves cached weather data.
+     *
+     * @param string $url     The unique url of the cached content.
+     * @param string $content The weather data to cache.
+     *
+     * @return bool True on success, false on failure.
+     */
+    abstract public function setCached($url, $content);
+
+    /**
+     * Set after how much seconds the cache shall expire.
+     *
+     * @param int $seconds
+     */
+    public function setSeconds($seconds)
+    {
+        $this->seconds = $seconds;
+    }
+}

+ 142 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/CurrentWeather.php

@@ -0,0 +1,142 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\Util\City;
+use Cmfcmf\OpenWeatherMap\Util\Sun;
+use Cmfcmf\OpenWeatherMap\Util\Temperature;
+use Cmfcmf\OpenWeatherMap\Util\Unit;
+use Cmfcmf\OpenWeatherMap\Util\Weather;
+use Cmfcmf\OpenWeatherMap\Util\Wind;
+
+/**
+ * Weather class used to hold the current weather data.
+ */
+class CurrentWeather
+{
+    /**
+     * The city object.
+     *
+     * @var Util\City
+     */
+    public $city;
+
+    /**
+     * The temperature object.
+     *
+     * @var Util\Temperature
+     */
+    public $temperature;
+
+    /**
+     * @var Util\Unit
+     */
+    public $humidity;
+
+    /**
+     * @var Util\Unit
+     */
+    public $pressure;
+
+    /**
+     * @var Util\Wind
+     */
+    public $wind;
+
+    /**
+     * @var Util\Unit
+     */
+    public $clouds;
+
+    /**
+     * @var Util\Unit
+     */
+    public $precipitation;
+
+    /**
+     * @var Util\Sun
+     */
+    public $sun;
+
+    /**
+     * @var Util\Weather
+     */
+    public $weather;
+
+    /**
+     * @var \DateTime
+     */
+    public $lastUpdate;
+
+    /**
+     * Create a new weather object.
+     *
+     * @param mixed  $data
+     * @param string $units
+     *
+     * @internal
+     */
+    public function __construct($data, $units)
+    {
+        // This is kind of a hack, because the units are missing in the document.
+        if ($units == 'metric') {
+            $windSpeedUnit = 'm/s';
+        } else {
+            $windSpeedUnit = 'mph';
+        }
+
+        $utctz = new \DateTimeZone('UTC');
+
+        if ($data instanceof \SimpleXMLElement) {
+            $this->city = new City($data->city['id'], $data->city['name'], $data->city->coord['lat'], $data->city->coord['lon'], $data->city->country);
+            $this->temperature = new Temperature(new Unit($data->temperature['value'], $data->temperature['unit']), new Unit($data->temperature['min'], $data->temperature['unit']), new Unit($data->temperature['max'], $data->temperature['unit']));
+            $this->humidity = new Unit($data->humidity['value'], $data->humidity['unit']);
+            $this->pressure = new Unit($data->pressure['value'], $data->pressure['unit']);
+            $this->wind = new Wind(
+                new Unit($data->wind->speed['value'], $windSpeedUnit, $data->wind->speed['name']),
+                property_exists($data->wind, 'direction') ? new Unit($data->wind->direction['value'], $data->wind->direction['code'], $data->wind->direction['name']) : null
+            );
+            $this->clouds = new Unit($data->clouds['value'], null, $data->clouds['name']);
+            $this->precipitation = new Unit($data->precipitation['value'], $data->precipitation['unit'], $data->precipitation['mode']);
+            $this->sun = new Sun(new \DateTime($data->city->sun['rise'], $utctz), new \DateTime($data->city->sun['set'], $utctz));
+            $this->weather = new Weather($data->weather['number'], $data->weather['value'], $data->weather['icon']);
+            $this->lastUpdate = new \DateTime($data->lastupdate['value'], $utctz);
+        } else {
+            $this->city = new City($data->id, $data->name, $data->coord->lat, $data->coord->lon, $data->sys->country);
+            $this->temperature = new Temperature(new Unit($data->main->temp, $units), new Unit($data->main->temp_min, $units), new Unit($data->main->temp_max, $units));
+            $this->humidity = new Unit($data->main->humidity, '%');
+            $this->pressure = new Unit($data->main->pressure, 'hPa');
+            $this->wind = new Wind(
+                new Unit($data->wind->speed, $windSpeedUnit),
+                property_exists($data->wind, 'deg') ? new Unit($data->wind->deg) : null
+            );
+            $this->clouds = new Unit($data->clouds->all, '%');
+
+            // the rain field is not always present in the JSON response
+            // and sometimes it contains the field '1h', sometimes the field '3h'
+            $rain = isset($data->rain) ? (array) $data->rain : array();
+            $rainUnit = !empty($rain) ? key($rain) : '';
+            $rainValue = !empty($rain) ? current($rain) : 0.0;
+            $this->precipitation = new Unit($rainValue, $rainUnit);
+
+            $this->sun = new Sun(\DateTime::createFromFormat('U', $data->sys->sunrise, $utctz), \DateTime::createFromFormat('U', $data->sys->sunset, $utctz));
+            $this->weather = new Weather($data->weather[0]->id, $data->weather[0]->description, $data->weather[0]->icon);
+            $this->lastUpdate = \DateTime::createFromFormat('U', $data->dt, $utctz);
+        }
+    }
+}

+ 93 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/CurrentWeatherGroup.php

@@ -0,0 +1,93 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+/**
+ * Class CurrentWeatherGroup used to hold the current weather data for a group of cities.
+ */
+class CurrentWeatherGroup implements \Iterator
+{
+    /**
+     * An array of {@link CurrentWeather} objects.
+     *
+     * @var CurrentWeather[]
+     *
+     * @see CurrentWeather The CurrentWeather class.
+     */
+    private $currentWeathers;
+
+    /**
+     * @internal
+     */
+    private $position = 0;
+
+    /**
+     * Create a new current weathers group object.
+     *
+     * @param \stdClass $json  The current weathers group json.
+     * @param string    $units The units used.
+     *
+     * @internal
+     */
+    public function __construct(\stdClass $json, $units)
+    {
+        foreach ($json->list as $currentWeather) {
+            $this->currentWeathers[] = new CurrentWeather($currentWeather, $units);
+        }
+    }
+
+    /**
+     * @internal
+     */
+    public function rewind()
+    {
+        $this->position = 0;
+    }
+
+    /**
+     * @internal
+     */
+    public function current()
+    {
+        return $this->currentWeathers[$this->position];
+    }
+
+    /**
+     * @internal
+     */
+    public function key()
+    {
+        return $this->current()->city->id;
+    }
+
+    /**
+     * @internal
+     */
+    public function next()
+    {
+        ++$this->position;
+    }
+
+    /**
+     * @internal
+     */
+    public function valid()
+    {
+        return isset($this->currentWeathers[$this->position]);
+    }
+}

+ 26 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Exception.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+/**
+ * Dummy class extending \Exception to allow checking if it is an OpenWeatherMap error
+ * or an argument error.
+ */
+class Exception extends \Exception
+{
+}

+ 59 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Fetcher/CurlFetcher.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Fetcher;
+
+/**
+ * Class CurlFetcher.
+ *
+ * @internal
+ */
+class CurlFetcher implements FetcherInterface
+{
+    /**
+     * @var array The Curl options to use.
+     */
+    private $curlOptions;
+
+    /**
+     * Create a new CurlFetcher instance.
+     *
+     * @param array $curlOptions The Curl options to use. See http://php.net/manual/de/function.curl-setopt.php
+     * for a list of available options.
+     */
+    public function __construct($curlOptions = array())
+    {
+        $this->curlOptions = $curlOptions;
+    }
+    
+    /**
+     * {@inheritdoc}
+     */
+    public function fetch($url)
+    {
+        $ch = curl_init($url);
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
+        curl_setopt_array($ch, $this->curlOptions);
+        
+        $content = curl_exec($ch);
+        curl_close($ch);
+
+        return $content;
+    }
+}

+ 37 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Fetcher/FetcherInterface.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Fetcher;
+
+/**
+ * Interface FetcherInterface.
+ *
+ * @api
+ */
+interface FetcherInterface
+{
+    /**
+     * Fetch contents from the specified url.
+     *
+     * @param string $url The url to be fetched.
+     *
+     * @return string The fetched content.
+     *
+     * @api
+     */
+    public function fetch($url);
+}

+ 34 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Fetcher/FileGetContentsFetcher.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Fetcher;
+
+/**
+ * Class FileGetContentsFetcher.
+ *
+ * @internal
+ */
+class FileGetContentsFetcher implements FetcherInterface
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function fetch($url)
+    {
+        return file_get_contents($url);
+    }
+}

+ 82 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Forecast.php

@@ -0,0 +1,82 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\Util\Temperature;
+use Cmfcmf\OpenWeatherMap\Util\Time;
+use Cmfcmf\OpenWeatherMap\Util\Unit;
+use Cmfcmf\OpenWeatherMap\Util\Weather;
+use Cmfcmf\OpenWeatherMap\Util\Wind;
+
+/**
+ * Class Forecast.
+ */
+class Forecast extends CurrentWeather
+{
+    /**
+     * @var Time The time of the forecast.
+     */
+    public $time;
+
+    /**
+     * Create a new weather object for forecasts.
+     *
+     * @param \SimpleXMLElement $xml   The forecasts xml.
+     * @param string            $units Ths units used.
+     *
+     * @internal
+     */
+    public function __construct(\SimpleXMLElement $xml, $units)
+    {
+        $utctz = new \DateTimeZone('UTC');
+
+        if ($units == 'metric') {
+            $temperatureUnit = "&deg;C";
+        } else {
+            $temperatureUnit = 'F';
+        }
+
+        $xml->temperature['value'] = round((floatval($xml->temperature['max']) + floatval($xml->temperature['min'])) / 2, 2);
+
+        $this->temperature = new Temperature(new Unit($xml->temperature['value'], $temperatureUnit), new Unit($xml->temperature['min'], $temperatureUnit), new Unit($xml->temperature['max'], $temperatureUnit), new Unit($xml->temperature['day'], $temperatureUnit), new Unit($xml->temperature['morn'], $temperatureUnit), new Unit($xml->temperature['eve'], $temperatureUnit), new Unit($xml->temperature['night'], $temperatureUnit));
+        $this->humidity = new Unit($xml->humidity['value'], $xml->humidity['unit']);
+        $this->pressure = new Unit($xml->pressure['value'], $xml->pressure['unit']);
+
+        // This is kind of a hack, because the units are missing in the xml document.
+        if ($units == 'metric') {
+            $windSpeedUnit = 'm/s';
+        } else {
+            $windSpeedUnit = 'mph';
+        }
+
+        $this->wind = new Wind(
+            new Unit($xml->windSpeed['mps'], $windSpeedUnit, $xml->windSpeed['name']),
+            property_exists($xml, 'windDirection') ? new Unit($xml->windDirection['deg'], $xml->windDirection['code'], $xml->windDirection['name']) : null
+        );
+        $this->clouds = new Unit($xml->clouds['all'], $xml->clouds['unit'], $xml->clouds['value']);
+        $this->precipitation = new Unit($xml->precipitation['value'], null, $xml->precipitation['type']);
+        $this->weather = new Weather($xml->symbol['number'], $xml->symbol['name'], $xml->symbol['var']);
+        $this->lastUpdate = new \DateTime($xml->lastupdate['value'], $utctz);
+
+        if (isset($xml['from'])) {
+            $this->time = new Time($xml['from'], $xml['to']);
+        } else {
+            $this->time = new Time($xml['day']);
+        }
+    }
+}

+ 107 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/History.php

@@ -0,0 +1,107 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\Util\Temperature;
+use Cmfcmf\OpenWeatherMap\Util\Unit;
+use Cmfcmf\OpenWeatherMap\Util\Weather;
+use Cmfcmf\OpenWeatherMap\Util\Wind;
+
+/**
+ * Class WeatherHistory.
+ */
+class History
+{
+    /**
+     * The city object.
+     *
+     * @var Util\City
+     */
+    public $city;
+
+    /**
+     * The temperature object.
+     *
+     * @var Util\Temperature
+     */
+    public $temperature;
+
+    /**
+     * @var Util\Unit
+     */
+    public $humidity;
+
+    /**
+     * @var Util\Unit
+     */
+    public $pressure;
+
+    /**
+     * @var Util\Wind
+     */
+    public $wind;
+
+    /**
+     * @var Util\Unit
+     */
+    public $clouds;
+
+    /**
+     * @var Util\Unit
+     */
+    public $precipitation;
+
+    /**
+     * @var Util\Weather
+     */
+    public $weather;
+
+    /**
+     * @var \DateTime The time of the history.
+     */
+    public $time;
+
+    /**
+     * @param $city
+     * @param $weather
+     * @param $temperature
+     * @param $pressure
+     * @param $humidity
+     * @param $clouds
+     * @param $rain
+     * @param $wind
+     * @param $time
+     *
+     * @internal
+     */
+    public function __construct($city, $weather, $temperature, $pressure, $humidity, $clouds, $rain, $wind, $time)
+    {
+        $this->city = $city;
+        $this->weather = new Weather($weather['id'], $weather['description'], $weather['icon']);
+        $this->temperature = new Temperature(new Unit($temperature['now'] - 273.15, "\xB0C"), new Unit($temperature['min'] - 273.15, "\xB0C"), new Unit($temperature['max'] - 273.15, "\xB0C"));
+        $this->pressure = new Unit($pressure, 'kPa');
+        $this->humidity = new Unit($humidity, '%');
+        $this->clouds = new Unit($clouds, '%');
+        $this->precipitation = new Unit($rain['val'], $rain['unit']);
+        $this->wind = new Wind(
+            new Unit($wind['speed']),
+            isset($wind['deg']) ? new Unit($wind['deg']) : null
+        );
+        $this->time = $time;
+    }
+}

+ 56 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/UVIndex.php

@@ -0,0 +1,56 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\Util\Location;
+
+/**
+ * UVIndex class used to hold the uv index for a given date, time and location.
+ */
+class UVIndex
+{
+    /**
+     * @var \DateTime
+     */
+    public $time;
+
+    /**
+     * @var Location
+     */
+    public $location;
+
+    /**
+     * @var float
+     */
+    public $uvIndex;
+
+    /**
+     * Create a new current uv index object.
+     *
+     * @param object $data
+     *
+     * @internal
+     */
+    public function __construct($data)
+    {
+        $utctz = new \DateTimeZone('UTC');
+        $this->time = new \DateTime($data->time, $utctz);
+        $this->location = new Location($data->location->latitude, $data->location->longitude);
+        $this->uvIndex = (float)$data->data;
+    }
+}

+ 66 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/City.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Util;
+
+/**
+ * The city class representing a city object.
+ */
+class City extends Location
+{
+    /**
+     * @var int The city id.
+     */
+    public $id;
+
+    /**
+     * @var string The name of the city.
+     */
+    public $name;
+
+    /**
+     * @var string The abbreviation of the country the city is located in.
+     */
+    public $country;
+
+    /**
+     * @var int The city's population
+     */
+    public $population;
+
+    /**
+     * Create a new city object.
+     *
+     * @param int    $id         The city id.
+     * @param string $name       The name of the city.
+     * @param float  $lat        The latitude of the city.
+     * @param float  $lon        The longitude of the city.
+     * @param string $country    The abbreviation of the country the city is located in
+     * @param int    $population The city's population.
+     *
+     * @internal
+     */
+    public function __construct($id, $name = null, $lat = null, $lon = null, $country = null, $population = null)
+    {
+        $this->id = (int)$id;
+        $this->name = isset($name) ? (string)$name : null;
+        $this->country = isset($country) ? (string)$country : null;
+        $this->population = isset($population) ? (int)$population : null;
+
+        parent::__construct($lat, $lon);
+    }
+}

+ 48 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Location.php

@@ -0,0 +1,48 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Util;
+
+/**
+ * The location class representing a location object.
+ */
+class Location
+{
+    /**
+     * @var float The latitude of the city.
+     */
+    public $lat;
+
+    /**
+     * @var float The longitude of the city.
+     */
+    public $lon;
+
+    /**
+     * Create a new location object.
+     *
+     * @param float  $lat The latitude of the city.
+     * @param float  $lon The longitude of the city.
+     *
+     * @internal
+     */
+    public function __construct($lat = null, $lon = null)
+    {
+        $this->lat = isset($lat) ? (float)$lat : null;
+        $this->lon = isset($lon) ? (float)$lon : null;
+    }
+}

+ 52 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Sun.php

@@ -0,0 +1,52 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Util;
+
+/**
+ * The sun class representing a sun object.
+ */
+class Sun
+{
+    /**
+     * @var \DateTime The time of the sun rise.
+     */
+    public $rise;
+
+    /**
+     * @var \DateTime The time of the sun set.
+     */
+    public $set;
+
+    /**
+     * Create a new sun object.
+     *
+     * @param \DateTime $rise The time of the sun rise
+     * @param \DateTime $set  The time of the sun set.
+     *
+     * @throws \LogicException If sunset is before sunrise.
+     * @internal
+     */
+    public function __construct(\DateTime $rise, \DateTime $set)
+    {
+        if ($set < $rise) {
+            throw new \LogicException('Sunset cannot be before sunrise!');
+        }
+        $this->rise = $rise;
+        $this->set = $set;
+    }
+}

+ 133 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Temperature.php

@@ -0,0 +1,133 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Util;
+
+/**
+ * The temperature class representing a temperature object.
+ */
+class Temperature
+{
+    /**
+     * @var Unit The current temperature.
+     */
+    public $now;
+
+    /**
+     * @var Unit The minimal temperature.
+     */
+    public $min;
+
+    /**
+     * @var Unit The maximal temperature.
+     */
+    public $max;
+
+    /**
+     * @var Unit The day temperature. Might not be null.
+     */
+    public $day;
+    
+    /**
+     * @var Unit The morning temperature. Might not be null.
+     */
+    public $morning;
+    
+    /**
+     * @var Unit The evening temperature. Might not be null.
+     */
+    public $evening;
+    
+    /**
+     * @var Unit The night temperature. Might not be null.
+     */
+    public $night;
+
+    /**
+     * Returns the current temperature as formatted string.
+     *
+     * @return string The current temperature as a formatted string.
+     */
+    public function __toString()
+    {
+        return $this->now->__toString();
+    }
+
+    /**
+     * Returns the current temperature's unit.
+     *
+     * @return string The current temperature's unit.
+     */
+    public function getUnit()
+    {
+        return $this->now->getUnit();
+    }
+
+    /**
+     * Returns the current temperature.
+     *
+     * @return string The current temperature.
+     */
+    public function getValue()
+    {
+        return $this->now->getValue();
+    }
+
+    /**
+     * Returns the current temperature's description.
+     *
+     * @return string The current temperature's description.
+     */
+    public function getDescription()
+    {
+        return $this->now->getDescription();
+    }
+
+    /**
+     * Returns the current temperature as formatted string.
+     *
+     * @return string The current temperature as formatted string.
+     */
+    public function getFormatted()
+    {
+        return $this->now->getFormatted();
+    }
+
+    /**
+     * Create a new temperature object.
+     *
+     * @param Unit $now The current temperature.
+     * @param Unit $min The minimal temperature.
+     * @param Unit $max The maximal temperature.
+     * @param Unit $day The day temperature. Might not be null.
+     * @param Unit $morning The morning temperature. Might not be null.
+     * @param Unit $evening The evening temperature. Might not be null.
+     * @param Unit $night The night temperature. Might not be null.
+     *
+     * @internal
+     */
+    public function __construct(Unit $now, Unit $min, Unit $max, Unit $day = null, Unit $morning = null, Unit $evening = null, Unit $night = null)
+    {
+        $this->now = $now;
+        $this->min = $min;
+        $this->max = $max;
+        $this->day = $day;
+        $this->morning = $morning;
+        $this->evening = $evening;
+        $this->night = $night;
+    }
+}

+ 66 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Time.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Util;
+
+/**
+ * The time class representing a time object.
+ */
+class Time
+{
+    /**
+     * @var \DateTime The start time for the forecast.
+     */
+    public $from;
+
+    /**
+     * @var \DateTime The end time for the forecast.
+     */
+    public $to;
+
+    /**
+     * @var \DateTime The day of the forecast.
+     */
+    public $day;
+
+    /**
+     * Create a new time object.
+     *
+     * @param string|\DateTime      $from The start time for the forecast.
+     * @param string|\DateTime|null $to   The end time for the forecast.
+     *
+     * @internal
+     */
+    public function __construct($from, $to = null)
+    {
+        $utctz = new \DateTimeZone('UTC');
+        if (isset($to)) {
+            $from = ($from instanceof \DateTime) ? $from : new \DateTime((string)$from, $utctz);
+            $to = ($to instanceof \DateTime) ? $to : new \DateTime((string)$to, $utctz);
+            $day = new \DateTime($from->format('Y-m-d'), $utctz);
+        } else {
+            $from = ($from instanceof \DateTime) ? $from : new \DateTime((string)$from, $utctz);
+            $day = $from = new \DateTime($from->format('Y-m-d'), $utctz);
+            $to = clone $from;
+            $to = $to->add(new \DateInterval('PT23H59M59S'));
+        }
+
+        $this->from = $from;
+        $this->to = $to;
+        $this->day = $day;
+    }
+}

+ 129 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Unit.php

@@ -0,0 +1,129 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Util;
+
+/**
+ * The unit class representing a unit object.
+ */
+class Unit
+{
+    /**
+     * @var float The value.
+     *
+     * @internal
+     */
+    private $value;
+
+    /**
+     * @var string The value's unit.
+     *
+     * @internal
+     */
+    private $unit;
+
+    /**
+     * @var string The value's description.
+     *
+     * @internal
+     */
+    private $description;
+
+    /**
+     * Create a new unit object.
+     *
+     * @param float  $value       The value.
+     * @param string $unit        The unit of the value.
+     * @param string $description The description of the value.
+     *
+     * @internal
+     */
+    public function __construct($value = 0.0, $unit = "", $description = "")
+    {
+        $this->value = (float)$value;
+        $this->unit = (string)$unit;
+        $this->description = (string)$description;
+    }
+
+    /**
+     * Get the value as formatted string with unit.
+     *
+     * @return string The value as formatted string with unit.
+     *
+     * The unit is not included if it is empty.
+     */
+    public function __toString()
+    {
+        return $this->getFormatted();
+    }
+
+    /**
+     * Get the value's unit.
+     *
+     * @return string The value's unit.
+     *
+     * This also converts 'celsius' to '°C' and 'fahrenheit' to 'F'.
+     */
+    public function getUnit()
+    {
+        // Units are inconsistent. Only celsius and fahrenheit are not abbreviated. This check fixes that.
+        // Also, the API started to return "metric" as temperature unit recently. Also fix that.
+        if ($this->unit == 'celsius' || $this->unit == 'metric') {
+            return "&deg;C";
+        } elseif ($this->unit == 'fahrenheit') {
+            return 'F';
+        } else {
+            return $this->unit;
+        }
+    }
+
+    /**
+     * Get the value.
+     *
+     * @return float The value.
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    /**
+     * Get the value's description.
+     *
+     * @return string The value's description.
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Get the value as formatted string with unit.
+     *
+     * @return string The value as formatted string with unit.
+     *
+     * The unit is not included if it is empty.
+     */
+    public function getFormatted()
+    {
+        if ($this->getUnit() != "") {
+            return $this->getValue() . " " . $this->getUnit();
+        } else {
+            return (string)$this->getValue();
+        }
+    }
+}

+ 93 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Weather.php

@@ -0,0 +1,93 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Util;
+
+/**
+ * The weather class representing a weather object.
+ */
+class Weather
+{
+    /**
+     * @var int The weather id.
+     */
+    public $id;
+
+    /**
+     * @var string The weather description.
+     */
+    public $description;
+
+    /**
+     * @var string the icon name.
+     */
+    public $icon;
+
+    /**
+     * @var string The url for icons.
+     *
+     * @see self::getIconUrl() to see how it is used.
+     */
+    private static $iconUrl = "//openweathermap.org/img/w/%s.png";
+
+    /**
+     * Create a new weather object.
+     *
+     * @param int    $id          The icon id.
+     * @param string $description The weather description.
+     * @param string $icon        The icon name.
+     *
+     * @internal
+     */
+    public function __construct($id, $description, $icon)
+    {
+        $this->id = (int)$id;
+        $this->description = (string)$description;
+        $this->icon = (string)$icon;
+    }
+
+    /**
+     * Get the weather description.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Get the icon url.
+     *
+     * @return string The icon url.
+     */
+    public function getIconUrl()
+    {
+        return sprintf(self::$iconUrl, $this->icon);
+    }
+
+    /**
+     * Change the url template for icon urls. Please note: This will change the url template for
+     * all instances of this library.
+     *
+     * @param string $iconUrl The url template to build the icon urls.
+     */
+    public static function setIconUrlTemplate($iconUrl)
+    {
+        self::$iconUrl = $iconUrl;
+    }
+}

+ 48 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/Util/Wind.php

@@ -0,0 +1,48 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Util;
+
+/**
+ * The wind class representing a wind object.
+ */
+class Wind
+{
+    /**
+     * @var Unit The wind speed.
+     */
+    public $speed;
+
+    /**
+     * @var Unit The wind direction.
+     */
+    public $direction;
+
+    /**
+     * Create a new wind object.
+     *
+     * @param Unit $speed     The wind speed.
+     * @param Unit $direction The wind direction.
+     *
+     * @internal
+     */
+    public function __construct(Unit $speed, Unit $direction = null)
+    {
+        $this->speed = $speed;
+        $this->direction = $direction;
+    }
+}

+ 137 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/WeatherForecast.php

@@ -0,0 +1,137 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\Util\City;
+use Cmfcmf\OpenWeatherMap\Util\Sun;
+
+/**
+ * Weather class returned by Cmfcmf\OpenWeatherMap->getWeather().
+ *
+ * @see \Cmfcmf\OpenWeatherMap::getWeather() The function using it.
+ */
+class WeatherForecast implements \Iterator
+{
+    /**
+     * A city object.
+     *
+     * @var Util\City
+     */
+    public $city;
+
+    /**
+     * A sun object
+     *
+     * @var Util\Sun
+     */
+    public $sun;
+
+    /**
+     * The time of the last update of this weather data.
+     *
+     * @var \DateTime
+     */
+    public $lastUpdate;
+
+    /**
+     * An array of {@link Forecast} objects.
+     *
+     * @var Forecast[]
+     *
+     * @see Forecast The Forecast class.
+     */
+    private $forecasts;
+
+    /**
+     * @internal
+     */
+    private $position = 0;
+
+    /**
+     * Create a new Forecast object.
+     *
+     * @param        $xml
+     * @param string $units
+     * @param int    $days How many days of forecast to receive.
+     *
+     * @internal
+     */
+    public function __construct($xml, $units, $days)
+    {
+        $this->city = new City($xml->location->location['geobaseid'], $xml->location->name, $xml->location->location['latitude'], $xml->location->location['longitude'], $xml->location->country);
+        $utctz = new \DateTimeZone('UTC');
+        $this->sun = new Sun(new \DateTime($xml->sun['rise'], $utctz), new \DateTime($xml->sun['set'], $utctz));
+        $this->lastUpdate = new \DateTime($xml->meta->lastupdate, $utctz);
+
+        $counter = 0;
+        foreach ($xml->forecast->time as $time) {
+            $forecast = new Forecast($time, $units);
+            $forecast->city = $this->city;
+            $forecast->sun = $this->sun;
+            $this->forecasts[] = $forecast;
+
+            $counter++;
+            // Make sure to only return the requested number of days.
+            if ($days <= 5 && $counter == $days * 8) {
+                break;
+            } elseif ($days > 5 && $counter == $days) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * @internal
+     */
+    public function rewind()
+    {
+        $this->position = 0;
+    }
+
+    /**
+     * @internal
+     */
+    public function current()
+    {
+        return $this->forecasts[$this->position];
+    }
+
+    /**
+     * @internal
+     */
+    public function key()
+    {
+        return $this->position;
+    }
+
+    /**
+     * @internal
+     */
+    public function next()
+    {
+        ++$this->position;
+    }
+
+    /**
+     * @internal
+     */
+    public function valid()
+    {
+        return isset($this->forecasts[$this->position]);
+    }
+}

+ 141 - 0
OpenWeatherMap/Cmfcmf/OpenWeatherMap/WeatherHistory.php

@@ -0,0 +1,141 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\Util\City;
+
+/**
+ * Class WeatherHistory.
+ */
+class WeatherHistory implements \Iterator
+{
+    /**
+     * The city object. IMPORTANT: Not all values will be set
+     *
+     * @var Util\City
+     */
+    public $city;
+
+    /**
+     * The time needed to calculate the request data.
+     *
+     * @var float
+     */
+    public $calctime;
+
+    /**
+     * An array of {@link WeatherHistory} objects.
+     *
+     * @var array
+     *
+     * @see WeatherForecast The WeatherForecast class.
+     */
+    private $histories;
+
+    /**
+     * @internal
+     */
+    private $position = 0;
+
+    public function __construct($weatherHistory, $query)
+    {
+        if (isset($weatherHistory['list'][0]['city'])) {
+            $country = $weatherHistory['list'][0]['city']['country'];
+            $population = $weatherHistory['list'][0]['city']['population'];
+        } else {
+            $country = null;
+            $population = null;
+        }
+
+        $this->city = new City(
+            $weatherHistory['city_id'],
+            (is_string($query)) ? $query : null,
+            (isset($query['lat'])) ? $query['lat'] : null,
+            (isset($query['lon'])) ? $query['lon'] : null,
+            $country,
+            $population
+        );
+        $this->calctime = $weatherHistory['calctime'];
+
+        $utctz = new \DateTimeZone('UTC');
+        foreach ($weatherHistory['list'] as $history) {
+            if (isset($history['rain'])) {
+                $units = array_keys($history['rain']);
+            } else {
+                $units = array(0 => null);
+            }
+
+            $this->histories[] = new History(
+                $this->city,
+                $history['weather'][0],
+                array(
+                    'now' => $history['main']['temp'],
+                    'min' => $history['main']['temp_min'],
+                    'max' => $history['main']['temp_max']
+                ),
+                $history['main']['pressure'],
+                $history['main']['humidity'],
+                $history['clouds']['all'],
+                isset($history['rain']) ? array(
+                    'val' => $history['rain'][($units[0])],
+                    'unit' => $units[0]) : null,
+                $history['wind'],
+                \DateTime::createFromFormat('U', $history['dt'], $utctz));
+        }
+    }
+
+    /**
+     * @internal
+     */
+    public function rewind()
+    {
+        $this->position = 0;
+    }
+
+    /**
+     * @internal
+     */
+    public function current()
+    {
+        return $this->histories[$this->position];
+    }
+
+    /**
+     * @internal
+     */
+    public function key()
+    {
+        return $this->position;
+    }
+
+    /**
+     * @internal
+     */
+    public function next()
+    {
+        ++$this->position;
+    }
+
+    /**
+     * @internal
+     */
+    public function valid()
+    {
+        return isset($this->histories[$this->position]);
+    }
+}

+ 5 - 0
OpenWeatherMap/Examples/ApiKey.ini

@@ -0,0 +1,5 @@
+[OpenWeatherMap]
+; Get an API Key from http://home.openweathermap.org/
+; DO NOT use this API key for any production!
+api_key = a164361b43628b08cfa06c0a9fd03a02
+

+ 108 - 0
OpenWeatherMap/Examples/Cache.php

@@ -0,0 +1,108 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+use Cmfcmf\OpenWeatherMap;
+use Cmfcmf\OpenWeatherMap\AbstractCache;
+
+require_once __DIR__ . '/bootstrap.php';
+
+/**
+ * Example cache implementation.
+ *
+ * @ignore
+ */
+class ExampleCache extends AbstractCache
+{
+    protected $tmp;
+
+    public function __construct()
+    {
+        $this->tmp = sys_get_temp_dir();
+    }
+
+    private function urlToPath($url)
+    {
+        $dir = $this->tmp . DIRECTORY_SEPARATOR . "OpenWeatherMapPHPAPI";
+        if (!is_dir($dir)) {
+            mkdir($dir);
+        }
+
+        $path = $dir . DIRECTORY_SEPARATOR . md5($url);
+
+        return $path;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function isCached($url)
+    {
+        $path = $this->urlToPath($url);
+        if (!file_exists($path) || filectime($path) + $this->seconds < time()) {
+            echo "Weather data is NOT cached!\n";
+
+            return false;
+        }
+
+        echo "Weather data is cached!\n";
+
+        return true;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getCached($url)
+    {
+        return file_get_contents($this->urlToPath($url));
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setCached($url, $content)
+    {
+        file_put_contents($this->urlToPath($url), $content);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setTempPath($path)
+    {
+        if (!is_dir($path)) {
+            mkdir($path);
+        }
+        
+        $this->tmp = $path;
+    }
+}
+
+// Language of data (try your own language here!):
+$lang = 'de';
+
+// Units (can be 'metric' or 'imperial' [default]):
+$units = 'metric';
+
+// Example 1: Use your own cache implementation. Cache for 10 seconds only in this example.
+$cache = new ExampleCache();
+$cache->setTempPath(__DIR__.'/temps');
+$owm = new OpenWeatherMap($myApiKey, null, $cache, 10);
+
+$weather = $owm->getWeather('Berlin', $units, $lang);
+echo "EXAMPLE 1<hr />\n\n\n";
+echo $weather->temperature;

+ 260 - 0
OpenWeatherMap/Examples/CurrentWeather.php

@@ -0,0 +1,260 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+use Cmfcmf\OpenWeatherMap;
+use Cmfcmf\OpenWeatherMap\Exception as OWMException;
+
+require_once __DIR__ . '/bootstrap.php';
+
+$cli = false;
+$lf = '<br>';
+if (php_sapi_name() === 'cli') {
+    $lf = "\n";
+    $cli = true;
+}
+
+// Language of data (try your own language here!):
+$lang = 'de';
+
+// Units (can be 'metric' or 'imperial' [default]):
+$units = 'metric';
+
+// Get OpenWeatherMap object. Don't use caching (take a look into Example_Cache.php to see how it works).
+$owm = new OpenWeatherMap();
+$owm->setApiKey($myApiKey);
+
+// Example 1: Get current temperature in Berlin.
+$weather = $owm->getWeather('Berlin', $units, $lang);
+echo "EXAMPLE 1$lf";
+
+// $weather contains all available weather information for Berlin.
+// Let's get the temperature:
+
+// Returns it as formatted string (using __toString()):
+echo $weather->temperature;
+echo $lf;
+
+// Returns it as formatted string (using a method):
+echo $weather->temperature->getFormatted();
+echo $lf;
+
+// Returns the value only:
+echo $weather->temperature->getValue();
+echo $lf;
+
+// Returns the unit only:
+echo $weather->temperature->getUnit();
+echo $lf;
+
+/*
+ * In the example above we're using a "shortcut". OpenWeatherMap returns the minimum temperature of a day,
+ * the maximum temperature and the temperature right now. If you don't specify which temperature you want, it will default
+ * to the current temperature. See below how to access the other values. Notice that each of them has implemented the methods
+ * "getFormatted()", "getValue()", "getUnit()".
+ */
+
+// Returns the current temperature:
+echo 'Current: '.$weather->temperature->now;
+echo $lf;
+
+// Returns the minimum temperature:
+echo 'Minimum: '.$weather->temperature->min;
+echo $lf;
+
+// Returns the maximum temperature:
+echo 'Maximum: '.$weather->temperature->max;
+echo $lf;
+
+/*
+ * When speaking about "current" and "now", this means when the weather data was last updated. You can get this
+ * via a DateTime object:
+ */
+echo 'Last update: '.$weather->lastUpdate->format('r');
+echo $lf;
+
+// Example 2: Get current pressure and humidity in Hongkong.
+$weather = $owm->getWeather('Hongkong', $units, $lang);
+echo "$lf$lf EXAMPLE 2$lf";
+
+/*
+ * You can use the methods above to only get the value or the unit.
+ */
+
+echo 'Pressure: '.$weather->pressure;
+echo $lf;
+echo 'Humidity: '.$weather->humidity;
+echo $lf;
+
+// Example 3: Get today's sunrise and sunset times.
+echo "$lf$lf EXAMPLE 3$lf";
+
+/*
+ * These functions return a DateTime object.
+ */
+
+echo 'Sunrise: '.$weather->sun->rise->format('r');
+echo $lf;
+echo 'Sunset: '.$weather->sun->set->format('r');
+echo $lf;
+
+// Example 4: Get current temperature from coordinates (Greenland :-) ).
+$weather = $owm->getWeather(array('lat' => 77.73038, 'lon' => 41.89604), $units, $lang);
+echo "$lf$lf EXAMPLE 4$lf";
+
+echo 'Temperature: '.$weather->temperature;
+echo $lf;
+
+// Example 5: Get current temperature from city id. The city is an internal id used by OpenWeatherMap. See example 6 too.
+$weather = $owm->getWeather(2172797, $units, $lang);
+echo "$lf$lf EXAMPLE 5$lf";
+
+echo 'City: '.$weather->city->name;
+echo $lf;
+
+echo 'Temperature: '.$weather->temperature;
+echo $lf;
+
+// Example 5.1: Get current temperature from zip code (Hyderabad, India).
+$weather = $owm->getWeather('zip:500001,IN', $units, $lang);
+echo "$lf$lf EXAMPLE 5.1$lf";
+
+echo 'City: '.$weather->city->name;
+echo $lf;
+
+echo 'Temperature: '.$weather->temperature;
+echo $lf;
+
+// Example 6: Get information about a city.
+$weather = $owm->getWeather('Paris', $units, $lang);
+echo "$lf$lf EXAMPLE 6$lf";
+
+echo 'Id: '.$weather->city->id;
+echo $lf;
+
+echo 'Name: '.$weather->city->name;
+echo $lf;
+
+echo 'Lon: '.$weather->city->lon;
+echo $lf;
+
+echo 'Lat: '.$weather->city->lat;
+echo $lf;
+
+echo 'Country: '.$weather->city->country;
+echo $lf;
+
+// Example 7: Get wind information.
+echo "$lf$lf EXAMPLE 7$lf";
+
+echo 'Speed: '.$weather->wind->speed;
+echo $lf;
+
+echo 'Direction: '.$weather->wind->direction;
+echo $lf;
+
+/*
+ * For speed and direction there is a description available, which isn't always translated.
+ */
+
+echo 'Speed: '.$weather->wind->speed->getDescription();
+echo $lf;
+
+echo 'Direction: '.$weather->wind->direction->getDescription();
+echo $lf;
+
+// Example 8: Get information about the clouds.
+echo "$lf$lf EXAMPLE 8$lf";
+
+// The number in braces seems to be an indicator how cloudy the sky is.
+echo 'Clouds: '.$weather->clouds->getDescription().' ('.$weather->clouds.')';
+echo $lf;
+
+// Example 9: Get information about precipitation.
+echo "$lf$lf EXAMPLE 9$lf";
+
+echo 'Precipation: '.$weather->precipitation->getDescription().' ('.$weather->precipitation.')';
+echo $lf;
+
+// Example 10: Show copyright notice. WARNING: This is no official text. This hint was created by looking at http://www.http://openweathermap.org/copyright .
+echo "$lf$lf EXAMPLE 10$lf";
+
+echo $owm::COPYRIGHT;
+echo $lf;
+
+// Example 11: Retrieve weather icons.
+echo "$lf$lf EXAMPLE 11$lf";
+$weather = $owm->getWeather('Berlin');
+echo $weather->weather->icon;
+echo $lf;
+echo $weather->weather->getIconUrl();
+
+// Example 12: Get raw xml data.
+echo "$lf$lf EXAMPLE 12$lf";
+
+$xml = $owm->getRawWeatherData('Berlin', $units, $lang, null, 'xml');
+if ($cli) {
+    echo $xml;
+} else {
+    echo '<pre><code>'.htmlspecialchars($xml).'</code></pre>';
+}
+echo $lf;
+
+// Example 13: Get raw json data.
+echo "$lf$lf EXAMPLE 13$lf";
+
+$json = $owm->getRawWeatherData('Berlin', $units, $lang, null, 'json');
+if ($cli) {
+    echo $json;
+} else {
+    echo '<pre><code>'.htmlspecialchars($json).'</code></pre>';
+}
+echo $lf;
+
+// Example 14: Get raw html data.
+echo "$lf$lf EXAMPLE 14$lf";
+
+echo $owm->getRawWeatherData('Berlin', $units, $lang, null, 'html');
+echo $lf;
+
+// Example 15: Error handling.
+echo "$lf$lf EXAMPLE 15$lf";
+
+// Try wrong city name.
+try {
+    $weather = $owm->getWeather('ThisCityNameIsNotValidAndDoesNotExist', $units, $lang);
+} catch (OWMException $e) {
+    echo $e->getMessage().' (Code '.$e->getCode().').';
+    echo $lf;
+}
+
+// Try invalid $query.
+try {
+    $weather = $owm->getWeather(new \DateTime('now'), $units, $lang);
+} catch (\Exception $e) {
+    echo $e->getMessage().' (Code '.$e->getCode().').';
+    echo $lf;
+}
+
+// Full error handling would look like this:
+try {
+    $weather = $owm->getWeather(-1, $units, $lang);
+} catch (OWMException $e) {
+    echo 'OpenWeatherMap exception: '.$e->getMessage().' (Code '.$e->getCode().').';
+    echo $lf;
+} catch (\Exception $e) {
+    echo 'General exception: '.$e->getMessage().' (Code '.$e->getCode().').';
+    echo $lf;
+}

+ 67 - 0
OpenWeatherMap/Examples/WeatherForecast.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+use Cmfcmf\OpenWeatherMap;
+
+require_once __DIR__ . '/bootstrap.php';
+
+// Language of data (try your own language here!):
+$lang = 'de';
+
+// Units (can be 'metric' or 'imperial' [default]):
+$units = 'metric';
+
+// Get OpenWeatherMap object. Don't use caching (take a look into Example_Cache.php to see how it works).
+$owm = new OpenWeatherMap($myApiKey);
+
+// Example 1: Get forecast for the next 5 days for Berlin.
+$forecast = $owm->getWeatherForecast('Berlin', $units, $lang, '', 5);
+echo "EXAMPLE 1<hr />\n\n\n";
+
+echo "City: " . $forecast->city->name;
+echo "<br />\n";
+echo "LastUpdate: " . $forecast->lastUpdate->format('d.m.Y H:i');
+echo "<br />\n";
+echo "Sunrise : " . $forecast->sun->rise->format("H:i:s (e)") . " Sunset : " . $forecast->sun->set->format("H:i:s (e)");
+echo "<br />\n";
+echo "<br />\n";
+
+foreach ($forecast as $weather) {
+    // Each $weather contains a Cmfcmf\ForecastWeather object which is almost the same as the Cmfcmf\Weather object.
+    // Take a look into 'Examples_Current.php' to see the available options.
+    echo "Weather forecast at " . $weather->time->day->format('d.m.Y') . " from " . $weather->time->from->format('H:i') . " to " . $weather->time->to->format('H:i');
+    echo "<br />\n";
+    echo $weather->temperature;
+    echo "<br />\n";
+    echo "Sun rise: " . $weather->sun->rise->format('d.m.Y H:i (e)');
+    echo "<br />\n";
+    echo "---";
+    echo "<br />\n";
+}
+
+// Example 2: Get forecast for the next 3 days for Berlin.
+$forecast = $owm->getWeatherForecast('Berlin', $units, $lang, '', 3);
+echo "EXAMPLE 2<hr />\n\n\n";
+
+foreach ($forecast as $weather) {
+    echo "Weather forecast at " . $weather->time->day->format('d.m.Y') . " from " . $weather->time->from->format('H:i') . " to " . $weather->time->to->format('H:i') . "<br />";
+    echo $weather->temperature . "<br />\n";
+    echo "<br />\n";
+    echo "Sun rise: " . $weather->sun->rise->format('d.m.Y H:i (e)');
+    echo "<br />\n";
+    echo "---<br />\n";
+}

+ 35 - 0
OpenWeatherMap/Examples/WeatherHistory.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+use Cmfcmf\OpenWeatherMap;
+
+require_once __DIR__ . '/bootstrap.php';
+
+// Language of data (try your own language here!):
+$lang = 'en';
+
+// Units (can be 'metric' or 'imperial' [default]):
+$units = 'metric';
+
+// Get OpenWeatherMap object. Don't use caching (take a look into Example_Cache.php to see how it works).
+$owm = new OpenWeatherMap($myApiKey);
+
+// Example 1: Get hourly weather history between 2014-01-01 and today.
+$history = $owm->getWeatherHistory('Berlin', new \DateTime('2014-01-01'), new \DateTime('now'), 'hour', $units, $lang);
+
+foreach ($history as $weather) {
+    echo 'Average temperature at '.$weather->time->format('d.m.Y H:i').': '.$weather->temperature."\n\r<br />";
+}

+ 17 - 0
OpenWeatherMap/Examples/bootstrap.php

@@ -0,0 +1,17 @@
+<?php
+
+call_user_func(function () {
+    if (!is_file($autoloadFile = __DIR__ . '/../vendor/autoload.php')) {
+        throw new \RuntimeException('Did not find vendor/autoload.php. Did you run "composer install --dev"?');
+    }
+
+    /** @noinspection PhpIncludeInspection */
+    require_once $autoloadFile;
+
+    ini_set('date.timezone', 'Europe/Berlin');
+});
+
+
+// Load the api key.
+$ini = parse_ini_file('ApiKey.ini');
+$myApiKey = $ini['api_key'];

+ 21 - 0
OpenWeatherMap/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013-2018 Christian Flach
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 87 - 0
OpenWeatherMap/README.md

@@ -0,0 +1,87 @@
+OpenWeatherMap PHP API
+======================
+A php API to retrieve and parse global weather data from 
+[OpenWeatherMap.org](http://www.OpenWeatherMap.org).
+This library aims to normalise the provided data and remove some inconsistencies.
+This library is neither maintained by OpenWeatherMap nor their official PHP API.
+
+[![Build Status](https://travis-ci.org/cmfcmf/OpenWeatherMap-PHP-Api.svg?branch=master)](https://travis-ci.org/cmfcmf/OpenWeatherMap-PHP-Api)
+[![license](https://img.shields.io/github/license/cmfcmf/OpenWeatherMap-PHP-Api.svg)](https://github.com/cmfcmf/OpenWeatherMap-PHP-Api/blob/master/LICENSE)
+[![release](https://img.shields.io/github/release/cmfcmf/OpenWeatherMap-PHP-Api.svg)](https://github.com/cmfcmf/OpenWeatherMap-PHP-Api/releases)
+[![codecov](https://codecov.io/gh/cmfcmf/OpenWeatherMap-PHP-Api/branch/master/graph/badge.svg)](https://codecov.io/gh/cmfcmf/OpenWeatherMap-PHP-Api)
+[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/cmfcmf/OpenWeatherMap-PHP-Api/badges/quality-score.png?s=f31ca08aa8896416cf162403d34362f0a5da0966)](https://scrutinizer-ci.com/g/cmfcmf/OpenWeatherMap-PHP-Api/)
+<br>
+[![SensioLabsInsight](https://insight.sensiolabs.com/projects/0addfb24-e2b4-4feb-848e-86b2078ca104/big.png)](https://insight.sensiolabs.com/projects/0addfb24-e2b4-4feb-848e-86b2078ca104)
+
+Installation
+============
+This library can be found on [Packagist](https://packagist.org/packages/cmfcmf/openweathermap-php-api).
+The recommended way to install and use it is through [Composer](http://getcomposer.org).
+
+    composer require "cmfcmf/openweathermap-php-api"
+
+
+Example call
+============
+```php
+<?php
+use Cmfcmf\OpenWeatherMap;
+use Cmfcmf\OpenWeatherMap\Exception as OWMException;
+
+// Must point to composer's autoload file.
+require 'vendor/autoload.php';
+
+// Language of data (try your own language here!):
+$lang = 'de';
+
+// Units (can be 'metric' or 'imperial' [default]):
+$units = 'metric';
+
+// Create OpenWeatherMap object. 
+// Don't use caching (take a look into Examples/Cache.php to see how it works).
+$owm = new OpenWeatherMap('YOUR-API-KEY');
+
+try {
+    $weather = $owm->getWeather('Berlin', $units, $lang);
+} catch(OWMException $e) {
+    echo 'OpenWeatherMap exception: ' . $e->getMessage() . ' (Code ' . $e->getCode() . ').';
+} catch(\Exception $e) {
+    echo 'General exception: ' . $e->getMessage() . ' (Code ' . $e->getCode() . ').';
+}
+
+echo $weather->temperature;
+```
+
+For more example code and instructions on how to use this library, please take 
+a look into  the `Examples` folder. Make sure to get an API Key from 
+http://home.openweathermap.org/ and put it into `Examples/ApiKey.ini`.
+- `CurrentWeather.php` Shows how to receive the current weather.
+- `WeatherForecast.php` Shows how to receive weather forecasts.
+- `WeatherHistory.php` Shows how to receive weather history.
+- `Cache.php` Shows how to implement and use a cache.
+
+Contributing
+============
+I'm happy about every **pull request** or **issue** you find and open to help 
+make this API **more awesome**.
+
+You can use [Vagrant](https://vagrantup.com) to kick-start your development.
+Simply run `vagrant up` and `vagrant ssh` to start a PHP VM with all 
+dependencies included.
+
+## Support me
+
+If you like my work, I'd really appreciate you buying me a coffee.
+Your donations help me put more time into Open-Source software development.
+
+<a href='https://ko-fi.com/cmfcmf' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
+
+License
+=======
+MIT — Please see the [LICENSE file](https://github.com/Cmfcmf/OpenWeatherMap-PHP-Api/blob/master/LICENSE)
+distributed with this source code for further information regarding copyright and licensing.
+
+**Please check out the following official links to read about the terms, pricing 
+and license of OpenWeatherMap before using the service:**
+- [OpenWeatherMap.org/terms](http://OpenWeatherMap.org/terms)
+- [OpenWeatherMap.org/appid](http://OpenWeatherMap.org/appid)

+ 15 - 0
OpenWeatherMap/Vagrantfile

@@ -0,0 +1,15 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant.configure(2) do |config|
+  config.vm.box = "ubuntu/xenial64"
+
+  config.vm.provision "shell", inline: <<-SHELL
+    apt-get -y -qq update
+    apt-get -y -qq install php-cli php-curl php-xml php-zip php-xdebug unzip git
+
+    curl --silent https://getcomposer.org/installer | php > /dev/null 2>&1
+    mv composer.phar /usr/local/bin/composer
+    echo "cd /vagrant" >> /home/ubuntu/.bashrc
+  SHELL
+end

+ 36 - 0
OpenWeatherMap/composer.json

@@ -0,0 +1,36 @@
+{
+    "name": "cmfcmf/openweathermap-php-api",
+    "description": "A php api to parse weather data from OpenWeatherMap.org. This api tries to normalise and abstract the data and remove inconsistencies.",
+    "keywords": ["weather", "OpenWeatherMap", "api", "owm", "free"],
+    "homepage": "https://github.com/cmfcmf/OpenWeatherMap-PHP-Api",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Christian Flach (cmfcmf)",
+            "email": "cmfcmf.flach@gmail.com",
+            "homepage": "http://cmfcmf.github.io",
+            "role": "Developer"
+        }
+    ],
+    "support": {
+        "issues": "https://github.com/cmfcmf/OpenWeatherMap-PHP-Api/issues",
+        "source": "https://github.com/cmfcmf/OpenWeatherMap-PHP-Api.git"
+    },
+    "require": {
+        "php": ">=5.3.0",
+        "ext-json": "*",
+        "ext-libxml": "*",
+        "ext-simplexml": "*"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^4.8 || ^5.0.5"
+    },
+    "suggest": {
+        "ext-curl": "Enables CURL usage for API requests."
+    },
+    "autoload": {
+        "psr-4": {
+            "Cmfcmf\\": "Cmfcmf"
+        }
+    }
+}

+ 1403 - 0
OpenWeatherMap/composer.lock

@@ -0,0 +1,1403 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "5a4c51cf3291fba46c9dd1f3a996f3bb",
+    "packages": [],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3,<8.0-DEV"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/doctrine/instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2015-06-14T21:17:01+00:00"
+        },
+        {
+            "name": "myclabs/deep-copy",
+            "version": "1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/DeepCopy.git",
+                "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+                "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6 || ^7.0"
+            },
+            "require-dev": {
+                "doctrine/collections": "^1.0",
+                "doctrine/common": "^2.6",
+                "phpunit/phpunit": "^4.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "DeepCopy\\": "src/DeepCopy/"
+                },
+                "files": [
+                    "src/DeepCopy/deep_copy.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Create deep copies (clones) of your objects",
+            "keywords": [
+                "clone",
+                "copy",
+                "duplicate",
+                "object",
+                "object graph"
+            ],
+            "time": "2017-10-19T19:58:43+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-common",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.6"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jaap van Otterdijk",
+                    "email": "opensource@ijaap.nl"
+                }
+            ],
+            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+            "homepage": "http://www.phpdoc.org",
+            "keywords": [
+                "FQSEN",
+                "phpDocumentor",
+                "phpdoc",
+                "reflection",
+                "static analysis"
+            ],
+            "time": "2017-09-11T18:02:19+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "4.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "94fd0001232e47129dd3504189fa1c7225010d08"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
+                "reference": "94fd0001232e47129dd3504189fa1c7225010d08",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0",
+                "phpdocumentor/reflection-common": "^1.0.0",
+                "phpdocumentor/type-resolver": "^0.4.0",
+                "webmozart/assert": "^1.0"
+            },
+            "require-dev": {
+                "doctrine/instantiator": "~1.0.5",
+                "mockery/mockery": "^1.0",
+                "phpunit/phpunit": "^6.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                }
+            ],
+            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+            "time": "2017-11-30T07:14:17+00:00"
+        },
+        {
+            "name": "phpdocumentor/type-resolver",
+            "version": "0.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/TypeResolver.git",
+                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
+                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5 || ^7.0",
+                "phpdocumentor/reflection-common": "^1.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^0.9.4",
+                "phpunit/phpunit": "^5.2||^4.8.24"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                }
+            ],
+            "time": "2017-07-14T14:27:02+00:00"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "1.8.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
+                "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": "^5.3|^7.0",
+                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
+                "sebastian/comparator": "^1.1|^2.0|^3.0",
+                "sebastian/recursion-context": "^1.0|^2.0|^3.0"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^2.5|^3.2",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.8.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Prophecy\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2018-08-05T17:53:17+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "4.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
+                "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-xmlwriter": "*",
+                "php": "^5.6 || ^7.0",
+                "phpunit/php-file-iterator": "^1.3",
+                "phpunit/php-text-template": "^1.2",
+                "phpunit/php-token-stream": "^1.4.2 || ^2.0",
+                "sebastian/code-unit-reverse-lookup": "^1.0",
+                "sebastian/environment": "^1.3.2 || ^2.0",
+                "sebastian/version": "^1.0 || ^2.0"
+            },
+            "require-dev": {
+                "ext-xdebug": "^2.1.4",
+                "phpunit/phpunit": "^5.7"
+            },
+            "suggest": {
+                "ext-xdebug": "^2.5.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "time": "2017-04-02T07:44:40+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "1.4.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
+                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "time": "2017-11-27T13:52:08+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2015-06-21T13:50:34+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "1.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "time": "2017-02-26T11:10:40+00:00"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "791198a2c6254db10131eecfe8c06670700904db"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db",
+                "reference": "791198a2c6254db10131eecfe8c06670700904db",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": "^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.2.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "keywords": [
+                "tokenizer"
+            ],
+            "time": "2017-11-27T05:48:46+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "5.5.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "3e6e88e56c912133de6e99b87728cca7ed70c5f5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6e88e56c912133de6e99b87728cca7ed70c5f5",
+                "reference": "3e6e88e56c912133de6e99b87728cca7ed70c5f5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "myclabs/deep-copy": "~1.3",
+                "php": "^5.6 || ^7.0",
+                "phpspec/prophecy": "^1.3.1",
+                "phpunit/php-code-coverage": "^4.0.1",
+                "phpunit/php-file-iterator": "~1.4",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": "^1.0.6",
+                "phpunit/phpunit-mock-objects": "^3.2",
+                "sebastian/comparator": "~1.1",
+                "sebastian/diff": "~1.2",
+                "sebastian/environment": "^1.3 || ^2.0",
+                "sebastian/exporter": "~1.2",
+                "sebastian/global-state": "~1.0",
+                "sebastian/object-enumerator": "~1.0",
+                "sebastian/resource-operations": "~1.0",
+                "sebastian/version": "~1.0|~2.0",
+                "symfony/yaml": "~2.1|~3.0"
+            },
+            "conflict": {
+                "phpdocumentor/reflection-docblock": "3.0.2"
+            },
+            "suggest": {
+                "phpunit/php-invoker": "~1.1"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.5.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "time": "2016-08-26T07:11:44+00:00"
+        },
+        {
+            "name": "phpunit/phpunit-mock-objects",
+            "version": "3.4.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+                "reference": "a23b761686d50a560cc56233b9ecf49597cc9118"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/a23b761686d50a560cc56233b9ecf49597cc9118",
+                "reference": "a23b761686d50a560cc56233b9ecf49597cc9118",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": "^5.6 || ^7.0",
+                "phpunit/php-text-template": "^1.2",
+                "sebastian/exporter": "^1.2 || ^2.0"
+            },
+            "conflict": {
+                "phpunit/phpunit": "<5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.4"
+            },
+            "suggest": {
+                "ext-soap": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Mock Object library for PHPUnit",
+            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+            "keywords": [
+                "mock",
+                "xunit"
+            ],
+            "time": "2017-06-30T09:13:00+00:00"
+        },
+        {
+            "name": "sebastian/code-unit-reverse-lookup",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+                "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+                "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.7 || ^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Looks up which function or method a line of code belongs to",
+            "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+            "time": "2017-03-04T06:30:41+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/diff": "~1.2",
+                "sebastian/exporter": "~1.2 || ~2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2017-01-29T09:50:25+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.4.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+                "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2017-05-22T07:24:03+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
+                "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2016-11-26T07:53:53+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "time": "2016-06-17T09:04:28+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "time": "2015-10-12T03:26:01+00:00"
+        },
+        {
+            "name": "sebastian/object-enumerator",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+                "reference": "d4ca2fb70344987502567bc50081c03e6192fb26"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26",
+                "reference": "d4ca2fb70344987502567bc50081c03e6192fb26",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+            "time": "2016-01-28T13:25:10+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "time": "2016-10-03T07:41:43+00:00"
+        },
+        {
+            "name": "sebastian/resource-operations",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/resource-operations.git",
+                "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+                "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides a list of PHP built-in functions that operate on resources",
+            "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+            "time": "2015-07-28T20:34:47+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "time": "2016-10-03T07:35:21+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.10.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
+                "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.9-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                },
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "time": "2018-08-06T14:22:27+00:00"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v3.4.18",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/yaml.git",
+                "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/640b6c27fed4066d64b64d5903a86043f4a4de7f",
+                "reference": "640b6c27fed4066d64b64d5903a86043f4a4de7f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5.9|>=7.0.8",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "conflict": {
+                "symfony/console": "<3.4"
+            },
+            "require-dev": {
+                "symfony/console": "~3.4|~4.0"
+            },
+            "suggest": {
+                "symfony/console": "For validating YAML files using the lint command"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.4-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Yaml\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Yaml Component",
+            "homepage": "https://symfony.com",
+            "time": "2018-10-02T16:33:53+00:00"
+        },
+        {
+            "name": "webmozart/assert",
+            "version": "1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webmozart/assert.git",
+                "reference": "0df1908962e7a3071564e857d86874dad1ef204a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a",
+                "reference": "0df1908962e7a3071564e857d86874dad1ef204a",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.6",
+                "sebastian/version": "^1.0.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Webmozart\\Assert\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                }
+            ],
+            "description": "Assertions to validate method input/output with nice error messages.",
+            "keywords": [
+                "assert",
+                "check",
+                "validate"
+            ],
+            "time": "2018-01-29T19:49:41+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.3.0",
+        "ext-json": "*",
+        "ext-libxml": "*",
+        "ext-simplexml": "*"
+    },
+    "platform-dev": []
+}

+ 26 - 0
OpenWeatherMap/phpunit.xml.dist

@@ -0,0 +1,26 @@
+<phpunit
+    backupGlobals="false"
+    backupStaticAttributes="false"
+    bootstrap="./tests/bootstrap.php"
+    colors="true"
+    convertErrorsToExceptions="true"
+    convertNoticesToExceptions="true"
+    convertWarningsToExceptions="true"
+    charset="UTF-8"
+    processIsolation="false">
+
+    <testsuites>
+        <testsuite name="Test Suite">
+            <directory suffix=".php">./tests/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory suffix=".php">./Cmfcmf/</directory>
+            <exclude>
+                <directory suffix=".php">./Examples/</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+</phpunit>

+ 92 - 0
OpenWeatherMap/tests/ExampleCacheTest.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\AbstractCache;
+
+require_once __DIR__ . '/bootstrap.php';
+
+/**
+ * Example cache implementation for doing unit testing.
+ *
+ * @ignore
+ */
+class ExampleCacheTest extends AbstractCache
+{
+    protected $seconds;
+    protected $tmp;
+
+    private function urlToPath($url)
+    {
+        $tmp = $this->tmp;
+        $dir = $tmp . DIRECTORY_SEPARATOR . "OpenWeatherMapPHPAPI";
+        if (!is_dir($dir)) {
+            mkdir($dir);
+        }
+
+        $path = $dir . DIRECTORY_SEPARATOR . md5($url);
+
+        return $path;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function isCached($url)
+    {
+        $path = $this->urlToPath($url);
+        if (!file_exists($path) || filectime($path) + $this->seconds < time()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function getCached($url)
+    {
+        return file_get_contents($this->urlToPath($url));
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setCached($url, $content)
+    {
+        file_put_contents($this->urlToPath($url), $content);
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setSeconds($seconds)
+    {
+        $this->seconds = $seconds;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function setTempPath($path)
+    {
+        if (!is_dir($path)) {
+            mkdir($path);
+        }
+        
+        $this->tmp = $path;
+    }
+}

+ 219 - 0
OpenWeatherMap/tests/Exceptions/OpenWeatherMapExceptionTest.php

@@ -0,0 +1,219 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\OpenWeatherMap;
+
+use \Cmfcmf\OpenWeatherMap;
+use Cmfcmf\OpenWeatherMap\Tests\TestFetcher;
+
+class OpenWeatherMapExceptionTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var string
+     */
+    protected $apiKey;
+
+    /**
+     * @var OpenWeatherMap
+     */
+    protected $owm;
+
+    protected function setUp()
+    {
+        $this->apiKey = 'unicorn-rainbow';
+        $this->owm = new OpenWeatherMap($this->apiKey, new TestFetcher(), false, 600);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testCacheException()
+    {
+        new OpenWeatherMap($this->apiKey, null, true, 600);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testSecondNotNumericException()
+    {
+        new OpenWeatherMap($this->apiKey, null, false, 'I am not numeric');
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetWeatherForecastException()
+    {
+        $days = 20;
+        $this->owm->getWeatherForecast('Berlin', 'imperial', 'en', '', $days);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetDailyWeatherForecastException()
+    {
+        $days = 20;
+        $this->owm->getDailyWeatherForecast('Berlin', 'imperial', 'en', '', $days);
+    }
+
+    /**
+     * @expectedException \Cmfcmf\OpenWeatherMap\Exception
+     */
+    public function testGetWeatherHistoryException()
+    {
+        $this->owm->getWeatherHistory('Berlin', new \DateTime('2015-11-01 00:00:00'), 1, 'hour', 'imperial', 'en', '');
+    }
+
+    /**
+     * @expectedException \Cmfcmf\OpenWeatherMap\Exception
+     */
+    public function testGetWeatherHistoryWithEndException()
+    {
+        $this->owm->getWeatherHistory('Berlin', new \DateTime('2015-11-01 00:00:00'), new \DateTime('now'), 'hour', 'imperial', 'en', '');
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetWeatherHistoryInvalidArgumentException()
+    {
+        $this->owm->getWeatherHistory('Berlin', new \DateTime('now'), 1, 'wrong-type', 'imperial', 'en', '');
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetRawDailyForecastDataInvalidArgumentException()
+    {
+        $this->owm->getRawDailyForecastData('Berlin', 'imperial', 'en', '', 'xml', 20);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetRawWeatherHistoryException()
+    {
+        $this->owm->getRawWeatherHistory('Berlin', new \DateTime('now'), 1, 'wrong-type', 'imperial', 'en', '');
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testGetRawWeatherHistoryWithEndDateException()
+    {
+        $this->owm->getRawWeatherHistory('Berlin', new \DateTime('now'), 'wrong-endOrCount', 'hour', 'imperial', 'en', '');
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     * @dataProvider      uvIndexExceptionDataProvider
+     */
+    public function testGetRawUVIndexWithQueryErrorException($lat, $lon, $dateTime, $precision)
+    {
+        $this->owm->getRawUVIndexData($lat, $lon, $dateTime, $precision);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     * @dataProvider      currentUVIndexExceptionDataProvider
+     */
+    public function testGetRawCurrentUVIndexWithQueryErrorException($lat, $lon)
+    {
+        $this->owm->getRawCurrentUVIndexData($lat, $lon);
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testGetRawUVIndexWithoutApiKey()
+    {
+        $this->owm->setApiKey(null);
+        $this->owm->getRawUVIndexData(1.1, 1.1, new \DateTime());
+    }
+
+    /**
+     * @expectedException \RuntimeException
+     */
+    public function testGetRawCurrentUVIndexWithoutApiKey()
+    {
+        $this->owm->setApiKey(null);
+        $this->owm->getRawCurrentUVIndexData(1.1, 1.1);
+    }
+
+    /**
+     * @expectedException \InvalidArgumentException
+     */
+    public function testBuildQueryUrlParameterException()
+    {
+        $this->owm->getWeather(true, 'imperial', 'en', '');
+    }
+
+    /**
+     * @expectedException \Cmfcmf\OpenWeatherMap\Exception
+     */
+    public function testParseXMLException()
+    {
+        $answer = 'I am not XML formatted data';
+        $method = new \ReflectionMethod($this->owm, 'parseXML');
+        $method->setAccessible(true);
+        
+        $method->invoke($this->owm, $answer);
+    }
+
+    /**
+     * @expectedException \Cmfcmf\OpenWeatherMap\Exception
+     */
+    public function testParseXMLWithIsJsonException()
+    {
+        $answer = array('message' => 'simple json data');
+        $answer = json_encode($answer);
+        $method = new \ReflectionMethod($this->owm, 'parseXML');
+        $method->setAccessible(true);
+
+        $method->invoke($this->owm, $answer);
+    }
+
+    /**
+     * @expectedException \Cmfcmf\OpenWeatherMap\Exception
+     */
+    public function testParseJsonException()
+    {
+        $answer = 'I am not a json format data';
+        $method = new \ReflectionMethod($this->owm, 'parseJson');
+        $method->setAccessible(true);
+        
+        $method->invoke($this->owm, $answer);
+    }
+
+    public function uvIndexExceptionDataProvider()
+    {
+        return array(
+            array('error-query-format', 'foo', new \DateTime(), 'year'),
+            array(5.4, 1.2, 'foo', 'month'),
+            array(5.4, 12, 'foo', 'day'),
+            array(5.4, 1.2, 'foo', 'bar'),
+        );
+    }
+
+    public function currentUVIndexExceptionDataProvider()
+    {
+        return array(
+            array('error-query-format', 'foo'),
+            array(5.4, 12),
+            array(5.4, '1.2'),
+        );
+    }
+}

+ 198 - 0
OpenWeatherMap/tests/FakeData.php

@@ -0,0 +1,198 @@
+<?php
+
+namespace Cmfcmf\OpenWeatherMap\Tests;
+
+class FakeData
+{
+    const WEATHER_GROUP_JSON = '{
+            "list":[{
+                "id":1851632,
+                "dt":1406106000,
+                "coord":{"lon":138.933334,"lat":34.966671},
+                "sys":{"type":3,"id":168940,"message":0.0297,"country":"US","sunrise":1427723751,"sunset":1427768967},
+                "name":"Shuzenji",
+                "main":{
+                    "temp":298.77,
+                    "temp_min":298.77,
+                    "temp_max":298.774,
+                    "pressure":1005.93,
+                    "sea_level":1018.18,
+                    "grnd_level":1005.93,
+                    "humidity":87
+                },
+                "weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],
+                "clouds":{"all":88},
+                "wind":{"speed":5.71,"deg":229.501},
+                "dt_txt":"2014-07-23 09:00:00"
+            },{
+                "id":1851632,
+                "dt":1406106000,
+                "coord":{"lon":138.933334,"lat":34.966671},
+                "sys":{"type":3,"id":168940,"message":0.0297,"country":"US","sunrise":1427723751,"sunset":1427768967},
+                "name":"Shuzenji",
+                "main":{
+                    "temp":298.77,
+                    "temp_min":298.77,
+                    "temp_max":298.774,
+                    "pressure":1005.93,
+                    "sea_level":1018.18,
+                    "grnd_level":1005.93,
+                    "humidity":87
+                },
+                "weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],
+                "clouds":{"all":88},
+                "wind":{"speed":5.71,"deg":229.501},
+                "dt_txt":"2014-07-23 09:00:00"
+            }]
+        }';
+
+    public static function forecastXML()
+    {
+        return '<weatherdata>
+        <location>
+            <name>Berlin</name>
+            <type></type>
+            <country>DE</country>
+            <timezone></timezone>
+            <location altitude="0" latitude="52.524368" longitude="13.41053" geobase="geonames" geobaseid="2950159"></location>
+        </location>
+        <credit></credit>
+        <meta>
+            <lastupdate></lastupdate>
+            <calctime>0.0215</calctime>
+            <nextupdate>
+            </nextupdate>
+        </meta>
+        <sun rise="2016-12-28T07:17:18" set="2016-12-28T14:59:55"></sun>
+        <forecast>
+            <time day="' . date('Y-m-d', time() + 0) . '">
+                <symbol number="500" name="light rain" var="10d"></symbol>
+                <precipitation value="0.25" type="rain"></precipitation>
+                <windDirection deg="315" code="NW" name="Northwest"></windDirection>
+                <windSpeed mps=" 4.38" name="Gentle Breeze"></windSpeed>
+                <temperature day="41" min="40.59" max="41" night="40.59" eve="41" morn="41"></temperature>
+                <pressure unit="hPa" value="1048.25"></pressure>
+                <humidity value="97" unit="%"></humidity>
+                <clouds value="overcast clouds" all="92" unit="%"></clouds>
+            </time>
+            <time day="' . date('Y-m-d', time() + 3600) . '">
+                <symbol number="500" name="light rain" var="10d"></symbol>
+                <precipitation value="0.24" type="rain"></precipitation>
+                <windDirection deg="253" code="WSW" name="West-southwest"></windDirection>
+                <windSpeed mps="6.2" name="Moderate breeze"></windSpeed>
+                <temperature day="40.14" min="28.96" max="40.14" night="28.96" eve="32.11" morn="39.06"></temperature>
+                    <pressure unit="hPa" value="1048.09"></pressure>
+                    <humidity value="97" unit="%"></humidity>
+                    <clouds value="clear sky" all="0" unit="%"></clouds>
+            </time>
+        </forecast>
+        </weatherdata>';
+    }
+
+    const WEATHER_HISTORY_JSON = '{
+            "cod":"200","calctime":"123456789","message":0.0032,"city_id":{"id":1851632,"name":"Shuzenji","coord":{"lon":138.933334,"lat":34.966671},"country":"JP"},
+            "cnt":10,
+            "list":[{
+                "dt":1406080800,
+                "temp":{
+                    "day":297.77,
+                    "min":293.52,
+                    "max":297.77,
+                    "night":293.52,
+                    "eve":297.77,
+                    "morn":297.77
+                },
+                "pressure":925.04,
+                "humidity":76,
+                "weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
+                "main":{"temp":306.15,"pressure":1013,"humidity":44,"temp_min":306,"temp_max":306},
+                "clouds":{"all":90},
+                "wind":{"speed":5.71,"deg":229.501}
+            },{
+                "dt":1406080800,
+                "temp":{
+                    "day":297.77,
+                    "min":293.52,
+                    "max":297.77,
+                    "night":293.52,
+                    "eve":297.77,
+                    "morn":297.77
+                },
+                "pressure":925.04,
+                "humidity":76,
+                "weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
+                "main":{"temp":306.15,"pressure":1013,"humidity":44,"temp_min":306,"temp_max":306},
+                "clouds":{"all":90},
+                "wind":{"speed":5.71,"deg":229.501}
+            }]
+        }';
+
+    const WEATHER_HISTORY_WITH_COUNTRY_JSON = '{
+            "cod":"200","calctime":"123456789","message":0.0032,"city_id":{"id":1851632,"name":"Shuzenji","coord":{"lon":138.933334,"lat":34.966671},"country":"JP"},
+            "cnt":1,
+            "list":[{
+                    "dt":1406080800,
+                    "temp":{
+                        "day":297.77,
+                        "min":293.52,
+                        "max":297.77,
+                        "night":293.52,
+                        "eve":297.77,
+                        "morn":297.77
+                    },
+                    "pressure":925.04,
+                    "humidity":76,
+                    "weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
+                    "main":{"temp":306.15,"pressure":1013,"humidity":44,"temp_min":306,"temp_max":306},
+                    "clouds":{"all":90},
+                    "wind":{"speed":5.71,"deg":229.501}
+            }]
+        }';
+
+
+    const WEATHER_HISTORY_WITH_RAIN_JSON = '{
+            "cod":"200","calctime":"123456789","message":0.0032,"city_id":{"id":1851632,"name":"Shuzenji","coord":{"lon":138.933334,"lat":34.966671},"country":"JP"},
+            "cnt":1,
+            "list":[{
+                    "dt":1406080800,
+                    "temp":{
+                        "day":297.77,
+                        "min":293.52,
+                        "max":297.77,
+                        "night":293.52,
+                        "eve":297.77,
+                        "morn":297.77
+                    },
+                    "pressure":925.04,
+                    "humidity":76,
+                    "weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
+                    "main":{"temp":306.15,"pressure":1013,"humidity":44,"temp_min":306,"temp_max":306},
+                    "clouds":{"all":90},
+                    "wind":{"speed":5.71,"deg":229.501},
+                    "rain":{"3h":3}
+            }]
+        }';
+
+    const CURRENT_WEATHER_XML = <<<XML
+<current>
+    <city id="2950159" name="Berlin">
+        <coord lon="13.41" lat="52.52"></coord>
+        <country>DE</country>
+        <sun rise="2017-01-02T07:16:51" set="2017-01-02T15:04:50"></sun>
+    </city>
+    <temperature value="36.48" min="35.6" max="37.4" unit="fahrenheit"></temperature>
+    <humidity value="86" unit="%"></humidity>
+    <pressure value="1014" unit="hPa"></pressure>
+    <wind>
+        <speed value="9.17" name="Fresh Breeze"></speed>
+        <gusts></gusts>
+        <direction value="300" code="WNW" name="West-northwest"></direction>
+    </wind>
+    <clouds value="75" name="broken clouds"></clouds>
+    <visibility value="8000"></visibility>
+    <precipitation mode="no"></precipitation>
+    <weather number="500" value="light rain" icon="10d"></weather>
+    <lastupdate value="2017-01-02T12:20:00"></lastupdate>
+</current>
+XML;
+}

+ 53 - 0
OpenWeatherMap/tests/Fetcher/CurlFetcherTest.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\Fetcher;
+
+use \Cmfcmf\OpenWeatherMap\Fetcher\CurlFetcher;
+
+/**
+ * @requires function curl_version
+ */
+class CurlFetcherTest extends \PHPUnit_Framework_TestCase
+{
+    public function testInvalidUrl()
+    {
+        $fetcher = new CurlFetcher();
+
+        $content = $fetcher->fetch('http://notexisting.example.com');
+
+        $this->assertSame(false, $content);
+    }
+
+    public function testEmptyUrl()
+    {
+        $fetcher = new CurlFetcher();
+
+        $content = $fetcher->fetch('');
+
+        $this->assertSame(false, $content);
+    }
+
+    public function testValidUrl()
+    {
+        $fetcher = new CurlFetcher();
+
+        $content = $fetcher->fetch('http://httpbin.org/html');
+
+        $this->assertContains('Herman Melville', $content);
+    }
+}

+ 59 - 0
OpenWeatherMap/tests/Fetcher/FileGetContentsFetcherTest.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\Fetcher;
+
+use \Cmfcmf\OpenWeatherMap\Fetcher\FileGetContentsFetcher;
+
+class FileGetContentsFetcherTest extends \PHPUnit_Framework_TestCase
+{
+    protected function setUp()
+    {
+        if (!ini_get('allow_url_fopen')) {
+            $this->markTestSkipped('"allow_url_fopen" is set to off.');
+        }
+    }
+
+    /**
+     * @expectedException \PHPUnit_Framework_Error_Warning
+     */
+    public function testInvalidUrl()
+    {
+        $fetcher = new FileGetContentsFetcher();
+
+        $fetcher->fetch('http://notexisting.example.com');
+    }
+
+    /**
+     * @expectedException \PHPUnit_Framework_Error_Warning
+     */
+    public function testEmptyUrl()
+    {
+        $fetcher = new FileGetContentsFetcher();
+
+        $fetcher->fetch('');
+    }
+
+    public function testValidUrl()
+    {
+        $fetcher = new FileGetContentsFetcher();
+
+        $content = $fetcher->fetch('http://httpbin.org/html');
+
+        $this->assertContains('Herman Melville', $content);
+    }
+}

+ 64 - 0
OpenWeatherMap/tests/OpenWeatherMap/CurrentWeatherGroupTest.php

@@ -0,0 +1,64 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+ 
+namespace Cmfcmf\OpenWeatherMap\Tests\OpenWeatherMap;
+
+use \Cmfcmf\OpenWeatherMap\CurrentWeatherGroup;
+use Cmfcmf\OpenWeatherMap\Tests\FakeData;
+
+class CurrentWeatherGroupTest extends \PHPUnit_Framework_TestCase
+{
+    protected $fakeJson;
+    protected $currentWeatherGroup;
+
+    public function setUp()
+    {
+        $this->fakeJson = json_decode(FakeData::WEATHER_GROUP_JSON);
+        $this->currentWeatherGroup = new CurrentWeatherGroup($this->fakeJson, 'metric');
+    }
+
+    public function testRewind()
+    {
+        $expectIndex = 1851632;
+        $this->currentWeatherGroup->rewind();
+        $position = $this->currentWeatherGroup->key();
+
+        $this->assertSame($expectIndex, $position);
+    }
+
+    public function testCurrent()
+    {
+        $this->currentWeatherGroup->rewind();
+        $current = $this->currentWeatherGroup->current();
+
+        $this->assertInternalType('object', $current);
+    }
+    public function testNext()
+    {
+        $expectIndex = 1851632;
+        $this->currentWeatherGroup->next();
+        $position = $this->currentWeatherGroup->key();
+
+        $this->assertSame($expectIndex, $position);
+    }
+
+    public function testValid()
+    {
+        $this->currentWeatherGroup->rewind();
+        $this->currentWeatherGroup->next();
+        $result = $this->currentWeatherGroup->valid();
+
+        $this->assertTrue($result);
+    }
+}

+ 66 - 0
OpenWeatherMap/tests/OpenWeatherMap/WeatherForecastTest.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+ 
+namespace Cmfcmf\OpenWeatherMap\Tests\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\Tests\FakeData;
+use \Cmfcmf\OpenWeatherMap\WeatherForecast;
+
+class WeatherForecastTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var string
+     */
+    protected $fakeXml;
+
+    /**
+     * @var WeatherForecast
+     */
+    protected $forecast;
+
+    protected function setUp()
+    {
+        $this->fakeXml = new \SimpleXMLElement(FakeData::forecastXML());
+        $this->forecast = new WeatherForecast($this->fakeXml, 'Berlin', 2);
+    }
+
+    public function testRewind()
+    {
+        $forecast = new WeatherForecast($this->fakeXml, 'metric', 2);
+        $expectIndex = 0;
+        $forecast->rewind();
+        $position = $forecast->key();
+
+        $this->assertSame($expectIndex, $position);
+    }
+
+    public function testCurrent()
+    {
+        $this->forecast->rewind();
+        $current = $this->forecast->current();
+
+        $this->assertInternalType('object', $current);
+    }
+
+    public function testNext()
+    {
+        $this->forecast->next();
+        $this->assertTrue($this->forecast->valid());
+    }
+
+    public function testValid()
+    {
+        $this->assertTrue($this->forecast->valid());
+    }
+}

+ 81 - 0
OpenWeatherMap/tests/OpenWeatherMap/WeatherHistoryTest.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\OpenWeatherMap;
+
+use Cmfcmf\OpenWeatherMap\Tests\FakeData;
+use \Cmfcmf\OpenWeatherMap\WeatherHistory;
+
+class WeatherHistoryTest extends \PHPUnit_Framework_TestCase
+{
+    protected $fakeJson;
+    protected $history;
+
+    protected function setUp()
+    {
+        $this->fakeJson = json_decode(FakeData::WEATHER_HISTORY_JSON, true);
+        $this->history = new WeatherHistory($this->fakeJson, 'Berlin');
+    }
+
+    public function testRewind()
+    {
+        $expectIndex = 0;
+        $this->history->rewind();
+        $position = $this->history->key();
+
+        $this->assertSame($expectIndex, $position);
+    }
+
+    public function testCurrent()
+    {
+        $this->history->rewind();
+        $current = $this->history->current();
+
+        $this->assertInternalType('object', $current);
+    }
+
+    public function testNext()
+    {
+        $expectIndex = 1;
+        $this->history->next();
+        $position = $this->history->key();
+
+        $this->assertSame($expectIndex, $position);
+    }
+
+    public function testValid()
+    {
+        $this->history->rewind();
+        $this->history->next();
+        $this->assertTrue($this->history->valid());
+    }
+
+    public function testJsonHasCountryAndPopulation()
+    {
+        $fakeJson = json_decode(FakeData::WEATHER_HISTORY_WITH_COUNTRY_JSON, true);
+        $history = new WeatherHistory($fakeJson, 'Berlin');
+
+        $history->rewind();
+        $this->assertTrue($history->valid());
+    }
+
+    public function testJsonWithRainKey()
+    {
+        $fakeJson = json_decode(FakeData::WEATHER_HISTORY_WITH_RAIN_JSON, true);
+        $history = new WeatherHistory($fakeJson, 'Berlin');
+
+        $history->rewind();
+        $this->assertTrue($history->valid());
+    }
+}

+ 205 - 0
OpenWeatherMap/tests/OpenWeatherMapTest.php

@@ -0,0 +1,205 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\OpenWeatherMap;
+
+use \Cmfcmf\OpenWeatherMap;
+use Cmfcmf\OpenWeatherMap\Exception;
+use Cmfcmf\OpenWeatherMap\Tests\TestFetcher;
+
+class OpenWeatherMapTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var string
+     */
+    protected $apiKey;
+
+    /**
+     * @var OpenWeatherMap
+     */
+    protected $owm;
+
+    /**
+     * @var OpenWeatherMap
+     */
+    protected $openWeather;
+
+    /**
+     * @var ExampleCacheTest
+     */
+    protected $cache;
+
+    protected function setUp()
+    {
+        $ini = parse_ini_file(__DIR__.'/../Examples/ApiKey.ini');
+        $myApiKey = $ini['api_key'];
+        $this->apiKey = $myApiKey;
+        $this->owm = new OpenWeatherMap($this->apiKey, new TestFetcher(), false, 600);
+        $this->openWeather = new OpenWeatherMap($this->apiKey, null, false, 600);
+        $this->cache = new ExampleCacheTest();
+    }
+
+    protected function tearDown()
+    {
+        $fileList = glob(__DIR__.'/temps/OpenWeatherMapPHPAPI/*');
+        foreach ($fileList as $fileName) {
+            @unlink($fileName);
+        }
+
+        @rmdir(__DIR__.'/temps/OpenWeatherMapPHPAPI');
+        @rmdir(__DIR__.'/temps');
+    }
+
+    public function testApiKeyIsEmpty()
+    {
+        $expectApiKey = '';
+        $weather = new OpenWeatherMap($expectApiKey, null, false, 600);
+        $apiKey = $weather->getApiKey();
+
+        $this->assertSame($expectApiKey, $apiKey);
+    }
+
+    public function testApiKeyNotNull()
+    {
+        $weather = $this->owm;
+        $apiKey = $weather->getApiKey();
+
+        $this->assertSame($this->apiKey, $apiKey);
+    }
+
+    public function testSecondIsZero()
+    {
+        $weather = new OpenWeatherMap($this->apiKey, null, false, 0);
+        $apiKey = $weather->getApiKey();
+
+        $this->assertSame($this->apiKey, $apiKey);
+    }
+
+    public function testSetApiKey()
+    {
+        $weather = $this->owm;
+        $weather->setApiKey($this->apiKey);
+        $apiKey = $weather->getApiKey();
+
+        $this->assertSame($this->apiKey, $apiKey);
+    }
+
+    public function testGetApiKey()
+    {
+        $weather = $this->owm;
+        $apiKey = $weather->getApiKey();
+
+        $this->assertSame($this->apiKey, $apiKey);
+    }
+
+    public function testGetWeather()
+    {
+        $currentWeather = $this->owm->getWeather('Berlin', 'imperial', 'en', '');
+
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\CurrentWeather', $currentWeather);
+    }
+
+    public function testGetWeatherGroup()
+    {
+        $currentWeather = $this->owm->getWeatherGroup(array('2950159'), 'imperial', 'en', '');
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\CurrentWeatherGroup', $currentWeather);
+
+        $currentWeather = $this->owm->getWeatherGroup('2950159', 'imperial', 'en', '');
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\CurrentWeatherGroup', $currentWeather);
+    }
+
+    public function testGetWeatherForecast()
+    {
+        $days = 1;
+        $defaultDay = $this->owm->getWeatherForecast('Berlin', 'imperial', 'en', '', $days);
+        
+        $days = 16;
+        $maxDay = $this->owm->getWeatherForecast('Berlin', 'imperial', 'en', '', $days);
+
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\WeatherForecast', $defaultDay);
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\WeatherForecast', $maxDay);
+    }
+
+    public function testGetCurrentUVIndex()
+    {
+        $owm = $this->openWeather;
+        $result = $owm->getCurrentUVIndex(40.7, -74.2);
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\UVIndex', $result);
+    }
+
+    public function testGetUVIndex()
+    {
+        $owm = $this->openWeather;
+        $precisions = array('year', 'month', 'day', 'hour', 'minute', 'second');
+        foreach ($precisions as $precision) {
+            try {
+                $result = $owm->getUVIndex(40.7, -74.2, new \DateTime(), $precision);
+            } catch (Exception $e) {
+                // OWM might not actually have data for the timespan.
+                $this->assertSame('An error occurred: not found', $e->getMessage());
+            }
+            $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\UVIndex', $result);
+        }
+    }
+
+    public function testGetDailyWeatherForecast()
+    {
+        $days = 16;
+        $dailyForecast = $this->owm->getDailyWeatherForecast('Berlin', 'imperial', 'en', '', $days);
+
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\WeatherForecast', $dailyForecast);
+    }
+
+    public function testGetWeatherHistory()
+    {
+        $this->markTestSkipped('This getWeatherHistory method ignored because the api key need to have a paid permission.');
+    }
+
+    public function testWasCached()
+    {
+        $weather = $this->owm;
+        $result = $weather->wasCached();
+
+        $this->assertFalse($result);
+    }
+
+    public function testCached()
+    {
+        $cache = $this->cache;
+        $cache->setTempPath(__DIR__.'/temps');
+        $weather = new OpenWeatherMap($this->apiKey, new TestFetcher(), $cache, 600);
+        $currWeatherData = $weather->getRawWeatherData('Berlin', 'imperial', 'en', $this->apiKey, 'xml');
+        $cachedWeatherData = $weather->getRawWeatherData('Berlin', 'imperial', 'en', $this->apiKey, 'xml');
+        
+        $this->assertInternalType('string', $currWeatherData);
+        $this->assertInternalType('string', $cachedWeatherData);
+    }
+
+    public function testBuildQueryUrlParameter()
+    {
+        $weather = $this->owm;
+        $queryWithNumbericArray = $weather->getWeather(array('2950159'), 'imperial', 'en', '');
+        $queryWithLatLonArray = $weather->getWeather(array('lat' => 52.524368, 'lon' => 13.410530), 'imperial', 'en', '');
+
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\CurrentWeather', $queryWithNumbericArray);
+        $this->assertInstanceOf('\Cmfcmf\OpenWeatherMap\CurrentWeather', $queryWithLatLonArray);
+    }
+
+    public function testAbstractCache()
+    {
+        /** @var OpenWeatherMap\AbstractCache $sut */
+        $sut = $this->getMockForAbstractClass('\Cmfcmf\OpenWeatherMap\AbstractCache');
+        $this->assertNull($sut->setSeconds(10));
+    }
+}

+ 65 - 0
OpenWeatherMap/tests/TestFetcher.php

@@ -0,0 +1,65 @@
+<?php
+/**
+ * OpenWeatherMap-PHP-API — A php api to parse weather data from http://www.OpenWeatherMap.org .
+ *
+ * @license MIT
+ *
+ * Please see the LICENSE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ *
+ * Please visit the following links to read about the usage policies and the license of
+ * OpenWeatherMap before using this class:
+ *
+ * @see http://www.OpenWeatherMap.org
+ * @see http://www.OpenWeatherMap.org/terms
+ * @see http://openweathermap.org/appid
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests;
+
+use Cmfcmf\OpenWeatherMap\Fetcher\FetcherInterface;
+
+class TestFetcher implements FetcherInterface
+{
+    /**
+     * Fetch contents from the specified url.
+     *
+     * @param string $url The url to be fetched.
+     *
+     * @return string The fetched content.
+     *
+     * @api
+     */
+    public function fetch($url)
+    {
+        $format = strpos($url, 'json') !== false ? 'json' : 'xml';
+        if (strpos($url, 'forecast') !== false) {
+            return $this->forecast($format);
+        } elseif (strpos($url, 'group') !== false) {
+            return $this->group($format);
+        } else {
+            return $this->currentWeather($format);
+        }
+    }
+
+    private function currentWeather($format)
+    {
+        if ($format == 'xml') {
+            return FakeData::CURRENT_WEATHER_XML;
+        }
+    }
+
+    private function forecast($format)
+    {
+        if ($format == 'xml') {
+            return FakeData::forecastXML();
+        }
+    }
+
+    private function group($format)
+    {
+        if ($format == 'json') {
+            return FakeData::WEATHER_GROUP_JSON;
+        }
+    }
+}

+ 61 - 0
OpenWeatherMap/tests/Util/SunTest.php

@@ -0,0 +1,61 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\Util;
+
+use Cmfcmf\OpenWeatherMap\Util\Sun;
+
+class SunTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Sun
+     */
+    private $sun;
+
+    public function testSunRise()
+    {
+        $rise = new \DateTime('2014-01-01 08:00:00');
+        $set = new \DateTime('2014-01-01 20:00:00');
+
+        $this->givenThereIsASunObject($rise, $set);
+
+        $this->assertSame($rise, $this->sun->rise);
+    }
+
+    public function testSunSet()
+    {
+        $rise = new \DateTime('2014-01-01 08:00:00');
+        $set = new \DateTime('2014-01-01 20:00:00');
+
+        $this->givenThereIsASunObject($rise, $set);
+
+        $this->assertSame($set, $this->sun->set);
+    }
+
+    /**
+     * @expectedException \LogicException
+     */
+    public function testSunSetBeforeSunRiseException()
+    {
+        $rise = new \DateTime('2014-01-01 08:00:00');
+        $set = new \DateTime('2014-01-01 7:00:00');
+
+        $this->givenThereIsASunObject($rise, $set);
+    }
+
+    private function givenThereIsASunObject($rise, $set)
+    {
+        $this->sun = new Sun($rise, $set);
+    }
+}

+ 100 - 0
OpenWeatherMap/tests/Util/TemperatureTest.php

@@ -0,0 +1,100 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\Util;
+
+use \Cmfcmf\OpenWeatherMap\Util\Unit;
+use \Cmfcmf\OpenWeatherMap\Util\Temperature;
+
+class TemperatureTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var string
+     */
+    protected $unit = 'Berlin';
+
+    /**
+     * @var Temperature
+     */
+    protected $temperature;
+
+    /**
+     * @var string
+     */
+    protected $nowTemp = '298.77';
+
+    /**
+     * @var string
+     */
+    protected $description = 'This is a description';
+
+    protected function setUp()
+    {
+        $units = 'Berlin';
+        $fakeTempNow = 298.77;
+        $fakeTempMin = 298.77;
+        $fakeTempMax = 298.774;
+        $tempNowUnit = new Unit($fakeTempNow, $units, $this->description);
+        $tempMinUnit = new Unit($fakeTempMin, $units, $this->description);
+        $tempMaxUnit = new Unit($fakeTempMax, $units, $this->description);
+        $this->temperature = new Temperature($tempNowUnit, $tempMinUnit, $tempMaxUnit);
+    }
+
+    public function test__toString()
+    {
+        $expectStr = $this->nowTemp;
+        $expectStr .= ' ' . $this->unit;
+        $str = $this->temperature->__toString();
+
+        $this->assertSame($expectStr, $str);
+        $this->assertInternalType('string', $str);
+    }
+
+    public function testGetUnit()
+    {
+        $expectUnit = $this->unit;
+        $unit = $this->temperature->getUnit();
+
+        $this->assertSame($expectUnit, $unit);
+        $this->assertInternalType('string', $unit);
+    }
+
+    public function testGetValue()
+    {
+        $expectValue = round($this->nowTemp, 2);
+        $value = $this->temperature->getValue();
+
+        $this->assertSame($expectValue, $value);
+        $this->assertInternalType('double', $value);
+    }
+
+    public function testGetDescription()
+    {
+        $expectDescription = $this->description;
+        $description = $this->temperature->getDescription();
+
+        $this->assertSame($expectDescription, $description);
+        $this->assertInternalType('string', $description);
+    }
+
+    public function testGetFormatted()
+    {
+        $expectFormattedString = $this->nowTemp;
+        $expectFormattedString .= ' ' . $this->unit;
+        $formattedString = $this->temperature->getFormatted();
+
+        $this->assertSame($expectFormattedString, $formattedString);
+        $this->assertInternalType('string', $formattedString);
+    }
+}

+ 61 - 0
OpenWeatherMap/tests/Util/TimeTest.php

@@ -0,0 +1,61 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\Util;
+
+use Cmfcmf\OpenWeatherMap\Util\Time;
+
+class TimeTest extends \PHPUnit_Framework_TestCase
+{
+    private function createDateTime($time)
+    {
+        return new \DateTime($time, new \DateTimeZone('UTC'));
+    }
+
+    public function testFromTo()
+    {
+        $fromS = '2014-01-01 08:00:00';
+        $toS = '2014-01-01 20:00:00';
+        $from = $this->createDateTime($fromS);
+        $to = $this->createDateTime($toS);
+        $day = $this->createDateTime('2014-01-01');
+
+        $time = new Time($from, $to);
+        $this->assertSame($from->format('c'), $time->from->format('c'));
+        $this->assertSame($to->format('c'), $time->to->format('c'));
+        $this->assertSame($day->format('c'), $time->day->format('c'));
+
+        $time = new Time($fromS, $toS);
+        $this->assertSame($from->format('c'), $time->from->format('c'));
+        $this->assertSame($to->format('c'), $time->to->format('c'));
+        $this->assertSame($day->format('c'), $time->day->format('c'));
+    }
+    public function testFrom()
+    {
+        $fromS = '2014-01-01 00:00:00';
+        $from = $this->createDateTime($fromS);
+        $day = $this->createDateTime('2014-01-01');
+        $to = $this->createDateTime('2014-01-01 23:59:59');
+
+        $time = new Time($from);
+        $this->assertSame($from->format('c'), $time->from->format('c'));
+        $this->assertSame($to->format('c'), $time->to->format('c'));
+        $this->assertSame($day->format('c'), $time->day->format('c'));
+
+        $time = new Time($fromS);
+        $this->assertSame($from->format('c'), $time->from->format('c'));
+        $this->assertSame($to->format('c'), $time->to->format('c'));
+        $this->assertSame($day->format('c'), $time->day->format('c'));
+    }
+}

+ 166 - 0
OpenWeatherMap/tests/Util/UnitTest.php

@@ -0,0 +1,166 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\Util;
+
+use \Cmfcmf\OpenWeatherMap\Util\Unit;
+
+class UnitTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Unit
+     */
+    private $unit;
+
+    const POSITIVE_INT_VALUE = 23;
+
+    const POSITIVE_FLOAT_VALUE = 48.23534;
+
+    const NEGATIVE_INT_VALUE = -30;
+
+    const NEGATIVE_FLOAT_VALUE = -93.45839;
+
+    const ZERO_INT_VALUE = 0;
+
+    const ZERO_FLOAT_VALUE = 0.0;
+
+    public function testGetValueWithPositiveIntValue()
+    {
+        $this->givenThereIsAUnitWithValue(self::POSITIVE_INT_VALUE);
+
+        $this->assertSame((float)self::POSITIVE_INT_VALUE, $this->unit->getValue());
+    }
+
+    public function testGetValueWithPositiveFloatValue()
+    {
+        $this->givenThereIsAUnitWithValue(self::POSITIVE_FLOAT_VALUE);
+
+        $this->assertSame(self::POSITIVE_FLOAT_VALUE, $this->unit->getValue());
+    }
+
+    public function testGetValueWithNegativeIntValue()
+    {
+        $this->givenThereIsAUnitWithValue(self::NEGATIVE_INT_VALUE);
+
+        $this->assertSame((float)self::NEGATIVE_INT_VALUE, $this->unit->getValue());
+    }
+
+    public function testGetValueWithNegativeFloatValue()
+    {
+        $this->givenThereIsAUnitWithValue(self::NEGATIVE_FLOAT_VALUE);
+
+        $this->assertSame(self::NEGATIVE_FLOAT_VALUE, $this->unit->getValue());
+    }
+
+    public function testGetValueWithZeroIntValue()
+    {
+        $this->givenThereIsAUnitWithValue(self::ZERO_INT_VALUE);
+
+        $this->assertSame((float)self::ZERO_INT_VALUE, $this->unit->getValue());
+    }
+
+    public function testGetValueWithZeroFloatValue()
+    {
+        $this->givenThereIsAUnitWithValue(self::ZERO_FLOAT_VALUE);
+
+        $this->assertSame(self::ZERO_FLOAT_VALUE, $this->unit->getValue());
+    }
+
+    private function givenThereIsAUnitWithValue($value, $unit = null)
+    {
+        $this->unit = $unit === null ? new Unit($value) : new Unit($value, $unit);
+    }
+
+    public function testGetUnitWithEmptyUnit()
+    {
+        $this->givenThereIsAUnitWithUnit("");
+
+        $this->assertSame("", $this->unit->getUnit());
+    }
+
+    public function testGetUnitWithStringAsUnit()
+    {
+        $this->givenThereIsAUnitWithUnit("Hey! I'm cmfcmf");
+
+        $this->assertSame("Hey! I'm cmfcmf", $this->unit->getUnit());
+    }
+
+    public function testCelsiusFix()
+    {
+        $this->givenThereIsAUnitWithUnit("celsius");
+
+        $this->assertSame("&deg;C", $this->unit->getUnit());
+    }
+
+    public function testMetricFix()
+    {
+        $this->givenThereIsAUnitWithUnit("metric");
+
+        $this->assertSame("&deg;C", $this->unit->getUnit());
+    }
+
+    public function testFahrenheitFix()
+    {
+        $this->givenThereIsAUnitWithUnit("fahrenheit");
+
+        $this->assertSame("F", $this->unit->getUnit());
+    }
+
+    private function givenThereIsAUnitWithUnit($unit)
+    {
+        $this->unit = new Unit(0, $unit);
+    }
+
+    public function testGetDescriptionWithEmptyDescription()
+    {
+        $this->givenThereIsAUnitWithDescription("");
+
+        $this->assertSame("", $this->unit->getDescription());
+    }
+
+    public function testGetDescriptionWithStringAsDescription()
+    {
+        $this->givenThereIsAUnitWithDescription("Hey! I'm cmfcmf");
+
+        $this->assertSame("Hey! I'm cmfcmf", $this->unit->getDescription());
+    }
+
+    private function givenThereIsAUnitWithDescription($description)
+    {
+        $this->unit = new Unit(0, "", $description);
+    }
+
+    public function testGetFormattedWithoutUnit()
+    {
+        $this->givenThereIsAUnitWithValue(self::POSITIVE_INT_VALUE);
+
+        $this->assertEquals(self::POSITIVE_INT_VALUE, $this->unit->getFormatted());
+        $this->assertEquals($this->unit->getValue(), $this->unit->getFormatted());
+    }
+
+    public function testGetFormattedWithUnit()
+    {
+        $this->givenThereIsAUnitWithValue(self::POSITIVE_INT_VALUE, 'K');
+
+        $this->assertEquals(self::POSITIVE_INT_VALUE . ' K', $this->unit->getFormatted());
+        $this->assertEquals($this->unit->getValue() . ' ' . $this->unit->getUnit(), $this->unit->getFormatted());
+    }
+
+    public function testToString()
+    {
+        $this->givenThereIsAUnitWithValue(self::POSITIVE_INT_VALUE, 'K');
+
+        $this->assertEquals($this->unit->getFormatted(), $this->unit);
+    }
+}

+ 67 - 0
OpenWeatherMap/tests/Util/WeatherTest.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * Copyright Zikula Foundation 2014 - Zikula Application Framework
+ *
+ * This work is contributed to the Zikula Foundation under one or more
+ * Contributor Agreements and licensed to You under the following license:
+ *
+ * @license GNU/LGPv3 (or at your option any later version).
+ * @package OpenWeatherMap-PHP-Api
+ *
+ * Please see the NOTICE file distributed with this source code for further
+ * information regarding copyright and licensing.
+ */
+
+namespace Cmfcmf\OpenWeatherMap\Tests\Util;
+
+use \Cmfcmf\OpenWeatherMap\Util\Weather;
+
+class WeatherTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var Weather
+     */
+    protected $weather;
+
+    /**
+     * @var string
+     */
+    protected $description = 'thunderstorm with light rain';
+
+    /**
+     * @var string
+     */
+    protected $iconName = '11d';
+
+    protected function setUp()
+    {
+        $this->weather = new Weather(200, $this->description, $this->iconName);
+    }
+
+    public function test__toString()
+    {
+        $expectDescription = $this->description;
+        $description = $this->weather->__toString();
+
+        $this->assertSame($expectDescription, $description);
+    }
+
+    public function testGetIconUrl()
+    {
+        $expectIconLink = '//openweathermap.org/img/w/11d.png';
+        $weather = $this->weather;
+        $iconLink = $weather->getIconUrl();
+
+        $this->assertSame($expectIconLink, $iconLink);
+    }
+
+    public function testSetIconUrlTemplate()
+    {
+        $expectIconLink = '//openweathermap.org';
+        $weather = $this->weather;
+        $weather::setIconUrlTemplate($expectIconLink);
+        $resultLink = $weather->getIconUrl();
+
+        $this->assertSame($expectIconLink, $resultLink);
+    }
+}

+ 17 - 0
OpenWeatherMap/tests/bootstrap.php

@@ -0,0 +1,17 @@
+<?php
+
+call_user_func(function () {
+    if (!is_file($autoloadFile = __DIR__ . '/../vendor/autoload.php')) {
+        throw new \RuntimeException('Did not find vendor/autoload.php. Did you run "composer install --dev"?');
+    }
+
+    if (!is_file($autoloadCacheFile = __DIR__ . '/ExampleCacheTest.php')) {
+        throw new \RuntimeException('Did not find CacheTest.php. Did you delete the "ExampleCacheTest.php"?');
+    }
+
+    /** @noinspection PhpIncludeInspection */
+    require_once $autoloadFile;
+    require_once $autoloadCacheFile;
+
+    ini_set('date.timezone', 'Europe/Berlin');
+});

+ 118 - 0
api/Rest.inc.php

@@ -0,0 +1,118 @@
+<?php
+    /* File : Rest.inc.php
+    */
+    class REST {
+         
+        public $_allow = array();
+        public $_content_type = "application/json";
+        public $_request = array();
+         
+        private $_method = "";      
+        private $_code = 200;
+         
+        public function __construct(){
+            $this->inputs();
+        }
+         
+        public function get_referer(){
+            return $_SERVER['HTTP_REFERER'];
+        }
+         
+        public function response($data,$status){
+            $this->_code = ($status)?$status:200;
+            $this->set_headers();
+            echo $data;
+            exit;
+        }
+         
+        private function get_status_message(){
+            $status = array(
+                        100 => 'Continue',  
+                        101 => 'Switching Protocols',  
+                        200 => 'OK',
+                        201 => 'Created',  
+                        202 => 'Accepted',  
+                        203 => 'Non-Authoritative Information',  
+                        204 => 'No Content',  
+                        205 => 'Reset Content',  
+                        206 => 'Partial Content',  
+                        300 => 'Multiple Choices',  
+                        301 => 'Moved Permanently',  
+                        302 => 'Found',  
+                        303 => 'See Other',  
+                        304 => 'Not Modified',  
+                        305 => 'Use Proxy',  
+                        306 => '(Unused)',  
+                        307 => 'Temporary Redirect',  
+                        400 => 'Bad Request',  
+                        401 => 'Unauthorized',  
+                        402 => 'Payment Required',  
+                        403 => 'Forbidden',  
+                        404 => 'Not Found',  
+                        405 => 'Method Not Allowed',  
+                        406 => 'Not Acceptable',  
+                        407 => 'Proxy Authentication Required',  
+                        408 => 'Request Timeout',  
+                        409 => 'Conflict',  
+                        410 => 'Gone',  
+                        411 => 'Length Required',  
+                        412 => 'Precondition Failed',  
+                        413 => 'Request Entity Too Large',  
+                        414 => 'Request-URI Too Long',  
+                        415 => 'Unsupported Media Type',  
+                        416 => 'Requested Range Not Satisfiable',  
+                        417 => 'Expectation Failed',  
+                        500 => 'Internal Server Error',  
+                        501 => 'Not Implemented',  
+                        502 => 'Bad Gateway',  
+                        503 => 'Service Unavailable',  
+                        504 => 'Gateway Timeout',  
+                        505 => 'HTTP Version Not Supported');
+            return ($status[$this->_code])?$status[$this->_code]:$status[500];
+        }
+         
+        public function get_request_method(){
+            return $_SERVER['REQUEST_METHOD'];
+        }
+         
+        private function inputs(){
+            switch($this->get_request_method()){
+                case "POST":
+                    $this->_request = $this->cleanInputs($_POST);
+                    break;
+                case "GET":
+                case "DELETE":
+                    $this->_request = $this->cleanInputs($_GET);
+                    break;
+                case "PUT":
+                    parse_str(file_get_contents("php://input"),$this->_request);
+                    $this->_request = $this->cleanInputs($this->_request);
+                    break;
+                default:
+                    $this->response('',406);
+                    break;
+            }
+        }       
+         
+        private function cleanInputs($data){
+            $clean_input = array();
+            if(is_array($data)){
+                foreach($data as $k => $v){
+                    $clean_input[$k] = $this->cleanInputs($v);
+                }
+            }else{
+                if(get_magic_quotes_gpc()){
+                    $data = trim(stripslashes($data));
+                }
+                $data = strip_tags($data);
+                $clean_input = trim($data);
+            }
+            return $clean_input;
+        }       
+         
+        private function set_headers(){
+            header("HTTP/1.1 ".$this->_code." ".$this->get_status_message());
+            header("Content-Type:".$this->_content_type);
+        }
+    }   
+?>

+ 71 - 0
api/api.php

@@ -0,0 +1,71 @@
+<?php
+     
+require_once("Rest.inc.php");
+     
+class API extends REST {
+     
+    public $data = "";
+    //Enter details of your database
+    const DB_SERVER = "localhost";
+    const DB_USER = "root";
+    const DB_PASSWORD = "R3M0T31";
+    const DB = "cropmonitor";
+     
+    private $db = NULL;
+ 
+    public function __construct(){
+        parent::__construct();              // Init parent contructor
+        $this->dbConnect();                 // Initiate Database connection
+}
+     
+private function dbConnect(){
+        $this->db = mysql_connect(self::DB_SERVER,self::DB_USER,self::DB_PASSWORD);
+        if($this->db)
+            mysql_select_db(self::DB,$this->db);
+}
+     
+    /*
+     * Public method for access api.
+     * This method dynmically call the method based on the query string
+     *
+     */
+public function processApi(){
+        $func = strtolower(trim(str_replace("/","",$_REQUEST['rquest'])));
+        if((int)method_exists($this,$func) > 0)
+            $this->$func();
+        else
+            $this->response('Error code 404, Page not found',404);   // If the method not exist with in this class, response would be "Page not found".
+}
+private function hello(){
+    echo str_replace("this","that","HELLO WORLD!!");
+ 
+}
+     
+ 
+private function test(){    
+    // Cross validation if the request method is GET else it will return "Not Acceptable" status
+    if($this->get_request_method() != "GET"){
+        $this->response('',406);
+    }
+    $myDatabase= $this->db;// variable to access your database
+    $param=$this->_request['var'];
+    // If success everythig is good send header as "OK" return param
+    $this->response($param, 200);    
+}
+ 
+     
+    /*
+     *  Encode array into JSON
+    */
+    private function json($data){
+        if(is_array($data)){
+            return json_encode($data);
+        }
+    }
+}
+ 
+    // Initiiate Library
+     
+    $api = new API;
+    $api->processApi();
+?>

+ 11 - 0
api/ht.htaccess

@@ -0,0 +1,11 @@
+    RewriteEngine On
+ 
+    RewriteCond %{REQUEST_FILENAME} !-d
+    RewriteCond %{REQUEST_FILENAME} !-s
+    RewriteRule ^(.*)$ api.php?rquest=$1 [QSA,NC,L]
+ 
+    RewriteCond %{REQUEST_FILENAME} -d
+    RewriteRule ^(.*)$ api.php [QSA,NC,L]
+ 
+    RewriteCond %{REQUEST_FILENAME} -s
+    RewriteRule ^(.*)$ api.php [QSA,NC,L]   

+ 151 - 0
api/updateweatherstation.php

@@ -0,0 +1,151 @@
+<?php
+$sql = null;
+
+$con = mysqli_connect("localhost", "root", "R3M0T31", "cropmonitor");
+
+$action=(isset($_GET["action"])) ? $_GET["action"] : "";
+$ID=(isset($_GET["ID"])) ? $_GET["ID"] : "";
+$PASSWORD=(isset($_GET["PASSWORD"])) ? $_GET["PASSWORD"] : "";
+$dateutc=(isset($_GET["dateutc"])) ? $_GET["dateutc"] : "";
+$winddir=(isset($_GET["winddir"])) ? $_GET["winddir"] : "";
+$windspeedmph=(isset($_GET["windspeedmph"])) ? $_GET["windspeedmph"] : "";
+$windgustmph=(isset($_GET["windgustmph"])) ? $_GET["windgustmph"] : "";
+$windgustdir=(isset($_GET["windgustdir"])) ? $_GET["windgustdir"] : "";
+$windspdmph_avg2m=(isset($_GET["windspdmph_avg2m"])) ? $_GET["windspdmph_avg2m"] : "";
+$winddir_avg2m=(isset($_GET["winddir_avg2m"])) ? $_GET["winddir_avg2m"] : "";
+$windgustmph_10m=(isset($_GET["windgustmph_10m"])) ? $_GET["windgustmph_10m"] : "";
+$windgustdir_10m=(isset($_GET["windgustdir_10m"])) ? $_GET["windgustdir_10m"] : "";
+$humidity=(isset($_GET["humidity"])) ? $_GET["humidity"] : "";
+$dewptf=(isset($_GET["dewptf"])) ? $_GET["dewptf"] : "";
+$tempf=(isset($_GET["tempf"])) ? $_GET["tempf"] : "";
+$temp2f=(isset($_GET["temp2f"])) ? $_GET["temp2f"] : "";
+$temp3f=(isset($_GET["temp3f"])) ? $_GET["temp3f"] : "";
+$temp4f=(isset($_GET["temp4f"])) ? $_GET["temp4f"] : "";
+$rainin=(isset($_GET["rainin"])) ? $_GET["rainin"] : "";
+$dailyrainin=(isset($_GET["dailyrainin"])) ? $_GET["dailyrainin"] : "";
+$baromin=(isset($_GET["baromin"])) ? $_GET["baromin"] : "";
+$weather=(isset($_GET["weather"])) ? $_GET["weather"] : "";
+$clouds=(isset($_GET["clouds"])) ? $_GET["clouds"] : "";
+$soiltempf=(isset($_GET["soiltempf"])) ? $_GET["soiltempf"] : "";
+$soiltemp2f=(isset($_GET["soiltemp2f"])) ? $_GET["soiltemp2f"] : "";
+$soiltemp3f=(isset($_GET["soiltemp3f"])) ? $_GET["soiltemp3f"] : "";
+$soiltemp4f=(isset($_GET["soiltemp4f"])) ? $_GET["soiltemp4f"] : "";
+$soilmoisture=(isset($_GET["soilmoisture"])) ? $_GET["soilmoisture"] : "";
+$soilmoisture2=(isset($_GET["soilmoisture2"])) ? $_GET["soilmoisture2"] : "";
+$soilmoisture3=(isset($_GET["soilmoisture3"])) ? $_GET["soilmoisture3"] : "";
+$soilmoisture4=(isset($_GET["soilmoisture4"])) ? $_GET["soilmoisture4"] : "";
+$leafwetness=(isset($_GET["leafwetness"])) ? $_GET["leafwetness"] : "";
+$leafwetness2=(isset($_GET["leafwetness2"])) ? $_GET["leafwetness2"] : "";
+$solarradiation=(isset($_GET["solarradiation"])) ? $_GET["solarradiation"] : "";
+$UV=(isset($_GET["UV"])) ? $_GET["UV"] : "";
+$visibility=(isset($_GET["visibility"])) ? $_GET["visibility"] : "";
+$indoortempf=(isset($_GET["indoortempf"])) ? $_GET["indoortempf"] : "";
+$indoorhumidity=(isset($_GET["indoorhumidity"])) ? $_GET["indoorhumidity"] : "";
+
+//add current datetime if not given.
+if ($dateutc === "now"){
+    $dateutc = gmdate("Y-m-d\TH%3Ai%3As\Z");
+} else {
+    $dateutc;
+}
+
+// Check connection
+	if (mysqli_connect_errno())
+		{
+		echo "Failed to connect to MySQL: " . mysqli_connect_error();
+		}
+		
+		
+$sql = mysqli_query($con, "INSERT INTO `weather_station`
+        (
+        action,
+        ID,
+        PASSWORD,
+        dateutc,
+        winddir,
+        windspeedmph,
+        windgustmph,
+        windgustdir,
+        windspdmph_avg2m,
+        winddir_avg2m,
+        windgustmph_10m,
+        windgustdir_10m,
+        humidity,
+        dewptf,
+        tempf,
+        temp2f,
+        temp3f,
+        temp4f,
+        rainin,
+        dailyrainin,
+        baromin,
+        weather,
+        clouds,
+        soiltempf,
+        soiltemp2f,
+        soiltemp3f,
+        soiltemp4f,
+        soilmoisture,
+        soilmoisture2,
+        soilmoisture3,
+        soilmoisture4,
+        leafwetness,
+        leafwetness2,
+        solarradiation,
+        UV,
+        visibility,
+        indoortempf,
+        indoorhumidity
+        ) 
+            VALUES
+        (
+        '" . $action . "',
+        '" . $ID . "',
+        '" . $PASSWORD . "',
+        '" . $dateutc . "',
+        '" . $winddir . "',
+        '" . $windspeedmph . "',
+        '" . $windgustmph . "',
+        '" . $windgustdir . "',
+        '" . $windspdmph_avg2m . "',
+        '" . $winddir_avg2m . "',
+        '" . $windgustmph_10m . "',
+        '" . $windgustdir_10m . "',
+        '" . $humidity . "',
+        '" . $dewptf . "',
+        '" . $tempf . "',
+        '" . $temp2f . "',
+        '" . $temp3f . "',
+        '" . $temp4f . "',
+        '" . $rainin . "',
+        '" . $dailyrainin . "',
+        '" . $baromin . "',
+        '" . $weather . "',
+        '" . $clouds . "',
+        '" . $soiltempf . "',
+        '" . $soiltemp2f . "',
+        '" . $soiltemp3f . "',
+        '" . $soiltemp4f . "',
+        '" . $soilmoisture . "',
+        '" . $soilmoisture2 . "',
+        '" . $soilmoisture3 . "',
+        '" . $soilmoisture4 . "',
+        '" . $leafwetness . "',
+        '" . $leafwetness2 . "',
+        '" . $solarradiation . "',
+        '" . $UV . "',
+        '" . $visibility . "',
+        '" . $indoortempf . "',
+        '" . $indoorhumidity . "'
+        )
+        ");
+
+if ($sql === TRUE)
+	{
+	echo "success"; //CHECKING
+    } else {
+    die(mysqli_error($con)); // TODO: better error handling
+    //echo "User Profile incorrect";
+}
+
+mysqli_close($con);

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ace.min.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-beautify.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-chromevox.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-elastic_tabstops_lite.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-emmet.js


+ 5 - 0
assets/components/ace/ace/ext-error_marker.js

@@ -0,0 +1,5 @@
+;
+                (function() {
+                    ace.require(["ace/ext/error_marker"], function() {});
+                })();
+            

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-keybinding_menu.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-language_tools.js


+ 5 - 0
assets/components/ace/ace/ext-linking.js

@@ -0,0 +1,5 @@
+ace.define("ace/ext/linking",["require","exports","module","ace/editor","ace/config"],function(e,t,n){function i(e){var t=e.editor,n=e.getAccelKey();if(n){var t=e.editor,r=e.getDocumentPosition(),i=t.session,s=i.getTokenAt(r.row,r.column);t._emit("linkHover",{position:r,token:s})}}function s(e){var t=e.getAccelKey(),n=e.getButton();if(n==0&&t){var r=e.editor,i=e.getDocumentPosition(),s=r.session,o=s.getTokenAt(i.row,i.column);r._emit("linkClick",{position:i,token:o})}}var r=e("ace/editor").Editor;e("../config").defineOptions(r.prototype,"editor",{enableLinking:{set:function(e){e?(this.on("click",s),this.on("mousemove",i)):(this.off("click",s),this.off("mousemove",i))},value:!1}})});
+                (function() {
+                    ace.require(["ace/ext/linking"], function() {});
+                })();
+            

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-modelist.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-old_ie.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-searchbox.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-settings_menu.js


+ 5 - 0
assets/components/ace/ace/ext-spellcheck.js

@@ -0,0 +1,5 @@
+ace.define("ace/ext/spellcheck",["require","exports","module","ace/lib/event","ace/editor","ace/config"],function(e,t,n){"use strict";var r=e("../lib/event");t.contextMenuHandler=function(e){var t=e.target,n=t.textInput.getElement();if(!t.selection.isEmpty())return;var i=t.getCursorPosition(),s=t.session.getWordRange(i.row,i.column),o=t.session.getTextRange(s);t.session.tokenRe.lastIndex=0;if(!t.session.tokenRe.test(o))return;var u="",a=o+" "+u;n.value=a,n.setSelectionRange(o.length,o.length+1),n.setSelectionRange(0,0),n.setSelectionRange(0,o.length);var f=!1;r.addListener(n,"keydown",function l(){r.removeListener(n,"keydown",l),f=!0}),t.textInput.setInputHandler(function(e){console.log(e,a,n.selectionStart,n.selectionEnd);if(e==a)return"";if(e.lastIndexOf(a,0)===0)return e.slice(a.length);if(e.substr(n.selectionEnd)==a)return e.slice(0,-a.length);if(e.slice(-2)==u){var r=e.slice(0,-2);if(r.slice(-1)==" ")return f?r.substring(0,n.selectionEnd):(r=r.slice(0,-1),t.session.replace(s,r),"")}return e})};var i=e("../editor").Editor;e("../config").defineOptions(i.prototype,"editor",{spellcheck:{set:function(e){var n=this.textInput.getElement();n.spellcheck=!!e,e?this.on("nativecontextmenu",t.contextMenuHandler):this.removeListener("nativecontextmenu",t.contextMenuHandler)},value:!0}})});
+                (function() {
+                    ace.require(["ace/ext/spellcheck"], function() {});
+                })();
+            

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-split.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-static_highlight.js


+ 5 - 0
assets/components/ace/ace/ext-statusbar.js

@@ -0,0 +1,5 @@
+ace.define("ace/ext/statusbar",["require","exports","module","ace/lib/dom","ace/lib/lang"],function(e,t,n){"use strict";var r=e("ace/lib/dom"),i=e("ace/lib/lang"),s=function(e,t){this.element=r.createElement("div"),this.element.className="ace_status-indicator",this.element.style.cssText="display: inline-block;",t.appendChild(this.element);var n=i.delayedCall(function(){this.updateStatus(e)}.bind(this));e.on("changeStatus",function(){n.schedule(100)}),e.on("changeSelection",function(){n.schedule(100)})};(function(){this.updateStatus=function(e){function n(e,n){e&&t.push(e,n||"|")}var t=[];n(e.keyBinding.getStatusText(e)),e.commands.recording&&n("REC");var r=e.selection.lead;n(r.row+":"+r.column," ");if(!e.selection.isEmpty()){var i=e.getSelectionRange();n("("+(i.end.row-i.start.row)+":"+(i.end.column-i.start.column)+")")}t.pop(),this.element.textContent=t.join("")}}).call(s.prototype),t.StatusBar=s});
+                (function() {
+                    ace.require(["ace/ext/statusbar"], function() {});
+                })();
+            

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-textarea.js


+ 5 - 0
assets/components/ace/ace/ext-themelist.js

@@ -0,0 +1,5 @@
+ace.define("ace/ext/themelist",["require","exports","module","ace/lib/fixoldbrowsers"],function(e,t,n){"use strict";e("ace/lib/fixoldbrowsers");var r=[["Chrome"],["Clouds"],["Crimson Editor"],["Dawn"],["Dreamweaver"],["Eclipse"],["GitHub"],["IPlastic"],["Solarized Light"],["TextMate"],["Tomorrow"],["XCode"],["Kuroir"],["KatzenMilch"],["SQL Server","sqlserver","light"],["Ambiance","ambiance","dark"],["Chaos","chaos","dark"],["Clouds Midnight","clouds_midnight","dark"],["Cobalt","cobalt","dark"],["idle Fingers","idle_fingers","dark"],["krTheme","kr_theme","dark"],["Merbivore","merbivore","dark"],["Merbivore Soft","merbivore_soft","dark"],["Mono Industrial","mono_industrial","dark"],["Monokai","monokai","dark"],["Pastel on dark","pastel_on_dark","dark"],["Solarized Dark","solarized_dark","dark"],["Terminal","terminal","dark"],["Tomorrow Night","tomorrow_night","dark"],["Tomorrow Night Blue","tomorrow_night_blue","dark"],["Tomorrow Night Bright","tomorrow_night_bright","dark"],["Tomorrow Night 80s","tomorrow_night_eighties","dark"],["Twilight","twilight","dark"],["Vibrant Ink","vibrant_ink","dark"]];t.themesByName={},t.themes=r.map(function(e){var n=e[1]||e[0].replace(/ /g,"_").toLowerCase(),r={caption:e[0],theme:"ace/theme/"+n,isDark:e[2]=="dark",name:n};return t.themesByName[n]=r,r})});
+                (function() {
+                    ace.require(["ace/ext/themelist"], function() {});
+                })();
+            

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/ext-whitespace.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/keybinding-emacs.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/keybinding-vim.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-css.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-html.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-javascript.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-json.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-less.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-markdown.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-php.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-scss.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-smarty.js


+ 1 - 0
assets/components/ace/ace/mode-sql.js

@@ -0,0 +1 @@
+ace.define("ace/mode/sql_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text_highlight_rules").TextHighlightRules,s=function(){var e="select|insert|update|delete|from|where|and|or|group|by|order|limit|offset|having|as|case|when|else|end|type|left|right|join|on|outer|desc|asc|union|create|table|primary|key|if|foreign|not|references|default|null|inner|cross|natural|database|drop|grant",t="true|false",n="avg|count|first|last|max|min|sum|ucase|lcase|mid|len|round|rank|now|format|coalesce|ifnull|isnull|nv|",r="int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|money|real|number|integer",i=this.createKeywordMapper({"support.function":n,keyword:e,"constant.language":t,"storage.type":r},"identifier",!0);this.$rules={start:[{token:"comment",regex:"--.*$"},{token:"comment",start:"/\\*",end:"\\*/"},{token:"string",regex:'".*?"'},{token:"string",regex:"'.*?'"},{token:"constant.numeric",regex:"[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"},{token:i,regex:"[a-zA-Z_$][a-zA-Z0-9_$]*\\b"},{token:"keyword.operator",regex:"\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|="},{token:"paren.lparen",regex:"[\\(]"},{token:"paren.rparen",regex:"[\\)]"},{token:"text",regex:"\\s+"}]},this.normalizeRules()};r.inherits(s,i),t.SqlHighlightRules=s}),ace.define("ace/mode/sql",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/sql_highlight_rules","ace/range"],function(e,t,n){"use strict";var r=e("../lib/oop"),i=e("./text").Mode,s=e("./sql_highlight_rules").SqlHighlightRules,o=e("../range").Range,u=function(){this.HighlightRules=s};r.inherits(u,i),function(){this.lineCommentStart="--",this.$id="ace/mode/sql"}.call(u.prototype),t.Mode=u})

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-svg.js


+ 0 - 0
assets/components/ace/ace/mode-text.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-twig.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/mode-xml.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/theme-ambiance.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/theme-chaos.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/theme-chrome.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/theme-clouds.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/theme-clouds_midnight.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/components/ace/ace/theme-cobalt.js


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff