2015-01-12 21:39:25 +01:00
< ? php
2016-04-28 20:06:46 +02:00
/**
2015-01-12 21:39:25 +01:00
* Egroupware Weather widget
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ package home
* @ subpackage portlet
* @ link http :// www . egroupware . org
* @ author Nathan Gray
* @ version $Id $
*/
2016-04-28 20:06:46 +02:00
use EGroupware\Api ;
use EGroupware\Api\Framework ;
use EGroupware\Api\Etemplate ;
2015-01-12 21:39:25 +01:00
/**
* Widget displaying the weather
*
* This widget displays more or less data depending on the portlet size using
* a combination of the disabled attribute in the template , and unsetting
* things to fit . It also uses some CSS to make sure things fit according to
* the grid size .
*
* We use openweathermap . org as a data source .
*/
class home_weather_portlet extends home_portlet
{
const API_URL = " http://api.openweathermap.org/data/2.5/ " ;
const ICON_URL = 'http://openweathermap.org/img/w/' ;
2015-02-24 17:59:31 +01:00
const API_KEY = '45484f039c5caa14d31aefe7f5514292' ;
2015-01-12 21:39:25 +01:00
const CACHE_TIME = 3600 ; // Cache weather for an hour
/**
* Context for this portlet
*/
public function __construct ( Array & $context = array (), & $need_reload = false )
{
2016-04-28 20:06:46 +02:00
if ( false ) parent :: __construct ();
2015-01-12 21:39:25 +01:00
// City not set for new widgets created via context menu
if ( ! $context [ 'city' ] || $context [ 'height' ] < 2 )
{
// Set initial size to 3x2, default is too small
$context [ 'width' ] = 3 ;
$context [ 'height' ] = 2 ;
}
$need_reload = true ;
2016-04-28 20:06:46 +02:00
2015-01-12 21:39:25 +01:00
$this -> context = $context ;
}
2016-04-28 20:06:46 +02:00
public function exec ( $id = null , Etemplate & $etemplate = null )
2015-01-12 21:39:25 +01:00
{
// Allow to submit directly back here
if ( is_array ( $id ) && $id [ 'id' ])
{
$id = $id [ 'id' ];
}
$etemplate -> read ( 'home.weather' );
$etemplate -> set_dom_id ( $id );
$content = $this -> context ;
$request = array (
'units' => $this -> context [ 'units' ] ? $this -> context [ 'units' ] : 'metric' ,
'lang' => $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'lang' ],
// Always get (& cache) 10 days, we'll cut down later
'cnt' => 10
);
2015-04-14 01:02:45 +02:00
if ( $this -> context [ 'city_id' ])
{
$request [ 'id' ] = $this -> context [ 'city_id' ];
$content += $this -> get_weather ( $request );
}
elseif ( $this -> context [ 'city' ])
2015-01-12 21:39:25 +01:00
{
2015-04-14 01:02:45 +02:00
$request [ 'q' ] = $this -> context [ 'city' ];
2015-01-12 21:39:25 +01:00
$content += $this -> get_weather ( $request );
}
2015-04-14 01:02:45 +02:00
elseif ( $this -> context [ 'position' ])
{
list ( $request [ 'lat' ], $request [ 'lon' ]) = explode ( ',' , $this -> context [ 'position' ]);
$content += $this -> get_weather ( $request );
}
// Caching is best done by city ID, so store that
if ( $content [ 'city_id' ] && ( ! $this -> context [ 'city_id' ] || $content [ 'city_id' ] != $this -> context [ 'city_id' ]))
{
2016-04-28 20:06:46 +02:00
$arr = $GLOBALS [ 'egw' ] -> preferences -> read_repository ();
$portlets = $arr [ 'home' ];
// Save updated Api\Preferences
2015-04-14 01:02:45 +02:00
$portlets [ $id ][ 'city_id' ] = $content [ 'city_id' ];
$this -> context [ 'city' ] = $portlets [ $id ][ 'city' ] = $content [ 'settings' ][ 'city' ] =
$content [ 'settings' ][ 'title' ] = $content [ 'city' ] = is_array ( $content [ 'city' ]) ? $content [ 'city' ][ 'name' ] : $content [ 'city' ];
unset ( $portlets [ $id ][ 'position' ]);
$GLOBALS [ 'egw' ] -> preferences -> add ( 'home' , $id , $portlets [ $id ]);
$GLOBALS [ 'egw' ] -> preferences -> save_repository ( True );
}
2015-01-12 21:39:25 +01:00
// Adjust data to match portlet size
if ( $this -> context [ 'height' ] <= 2 && $this -> context [ 'width' ] <= 3 )
{
// Too small for the other days
unset ( $content [ 'list' ]);
}
else if ( $this -> context [ 'height' ] == 2 && $this -> context [ 'width' ] > 3 )
{
// Wider, but not taller
unset ( $content [ 'current' ]);
}
// Even too small for current high/low
if ( $this -> context [ 'width' ] < 3 )
{
$content [ 'current' ][ 'no_current_temp' ] = true ;
}
2016-04-28 20:06:46 +02:00
2015-01-12 21:39:25 +01:00
// Direct to full forecast page
$content [ 'attribution' ] = 'http://openweathermap.org/city/' . $content [ 'city_id' ];
2016-04-28 20:06:46 +02:00
2015-01-12 21:39:25 +01:00
$etemplate -> exec ( 'home.home_weather_portlet.exec' , $content , array (), array ( '__ALL__' => true ), array ( 'id' => $id ));
}
/**
* Fetch weather data from provider openweathermap . org
*
* @ see http :// openweathermap . org / api
* @ param array $query
*/
public function get_weather ( Array $query , $api_url = '' )
{
2015-02-24 18:01:25 +01:00
static $debug = false ;
2015-01-12 21:39:25 +01:00
if ( ! $api_url )
{
$api_url = self :: API_URL . '/weather?' ;
}
if ( self :: API_KEY )
{
$query [ 'APPID' ] = self :: API_KEY ;
}
2016-04-28 20:06:46 +02:00
$data = Api\Cache :: getTree ( 'home' , json_encode ( $query ), function ( $query )
{
2015-02-24 17:59:31 +01:00
$debug = false ;
2015-01-12 21:39:25 +01:00
if ( $debug ) error_log ( 'Fetching fresh data from ' . static :: API_URL );
$url = static :: API_URL . 'forecast/daily?' . http_build_query ( $query );
$forecast = file_get_contents ( $url );
$url = static :: API_URL . 'weather?' . http_build_query ( $query );
2015-02-24 17:59:31 +01:00
$current = file_get_contents ( $url );
2015-01-12 21:39:25 +01:00
if ( $debug ) error_log ( __METHOD__ . ' current: ' . $current );
return array_merge ( array ( 'current' => json_decode ( $current , true )), json_decode ( $forecast , true ));
}, array ( $query ), self :: CACHE_TIME );
// Some sample data, if you need to test
//error_log('Using hardcoded data instead of ' . $api_url . http_build_query($query));
//$weather = '{"coord":{"lon":-114.05,"lat":53.23},"sys":{"message":0.3098,"country":"Canada","sunrise":1420559329,"sunset":1420587344},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"base":"cmc stations","main":{"temp":-21.414,"temp_min":-21.414,"temp_max":-21.414,"pressure":947.79,"sea_level":1050.73,"grnd_level":947.79,"humidity":69},"wind":{"speed":3,"deg":273.5},"clouds":{"all":32},"dt":1420502430,"id":0,"name":"Thorsby","cod":200}';
//$weather = '{"cod":"200","message":0.1743,"city":{"id":"5978233","name":"Thorsby","coord":{"lon":-114.051,"lat":53.2285},"country":"Canada","population":0},"cnt":6,"list":[{"dt":1420743600,"temp":{"day":-17.49,"min":-27.86,"max":-16.38,"night":-27.86,"eve":-19.91,"morn":-16.77},"pressure":966.21,"humidity":66,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":6.91,"deg":312,"clouds":0,"snow":0.02},{"dt":1420830000,"temp":{"day":-24.86,"min":-29.71,"max":-17.98,"night":-18.31,"eve":-18.32,"morn":-29.51},"pressure":948.46,"humidity":54,"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"speed":3.21,"deg":166,"clouds":20},{"dt":1420916400,"temp":{"day":-18.51,"min":-25.57,"max":-17.86,"night":-23.83,"eve":-23.91,"morn":-19.28},"pressure":947.22,"humidity":74,"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03d"}],"speed":1.97,"deg":314,"clouds":48},{"dt":1421002800,"temp":{"day":-26.69,"min":-29.86,"max":-20.19,"night":-21.82,"eve":-24.66,"morn":-28.85},"pressure":951.93,"humidity":22,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"02d"}],"speed":1.36,"deg":196,"clouds":8},{"dt":1421089200,"temp":{"day":0.9,"min":-8.24,"max":0.9,"night":-4.99,"eve":-0.21,"morn":-8.24},"pressure":929.31,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":6.01,"deg":302,"clouds":5,"snow":0},{"dt":1421175600,"temp":{"day":-1.53,"min":-6.7,"max":2.23,"night":-3.65,"eve":2.23,"morn":-6.7},"pressure":934.51,"humidity":0,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"01d"}],"speed":3.9,"deg":201,"clouds":78}]}';
if ( $debug )
{
error_log ( __METHOD__ . ' weather info:' );
2016-04-28 20:06:46 +02:00
foreach ( array_keys ( $data ) as $key )
2015-01-12 21:39:25 +01:00
{
error_log ( $key . ': ' . array2string ( $data [ $key ]));
}
}
if ( is_string ( $data [ 'message' ]))
{
$desc = $this -> get_description ();
2016-04-28 20:06:46 +02:00
Framework :: message ( $desc [ 'displayName' ] . ': ' . $desc [ 'title' ] . " \n " . $data [ 'message' ], 'warning' );
2015-01-12 21:39:25 +01:00
return array ();
}
if ( array_key_exists ( 'city' , $data ))
{
$data [ 'city_id' ] = $data [ 'city' ][ 'id' ];
}
elseif ( $data [ 'city' ])
{
$data [ 'city_id' ] = $data [ 'id' ];
}
if ( $data [ 'list' ])
{
$massage =& $data [ 'list' ];
2016-04-28 20:06:46 +02:00
2015-01-12 21:39:25 +01:00
for ( $i = 0 ; $i < min ( count ( $massage ), $this -> context [ 'width' ]); $i ++ )
{
$forecast =& $massage [ $i ];
2016-04-28 20:06:46 +02:00
$forecast [ 'day' ] = Api\DateTime :: to ( $forecast [ 'dt' ], 'l' );
2015-01-12 21:39:25 +01:00
self :: format_forecast ( $forecast );
}
// Chop data to fit into portlet
for ( $i ; $i < count ( $massage ); $i ++ )
{
unset ( $massage [ $i ]);
}
}
2015-02-24 17:59:31 +01:00
if ( $data [ 'current' ] && is_array ( $data [ 'current' ]))
2015-01-12 21:39:25 +01:00
{
// Current weather
$data [ 'current' ][ 'temp' ] = $data [ 'current' ][ 'main' ];
self :: format_forecast ( $data [ 'current' ]);
}
if ( $data [ 'list' ])
{
$data [ 'current' ][ 'temp' ] = array_merge ( $data [ 'current' ][ 'temp' ], $data [ 'list' ][ 0 ][ 'temp' ]);
}
return $data ;
}
/**
* Format weather to our liking
*/
protected static function format_forecast ( & $data )
{
$weather =& $data [ 'weather' ] ? $data [ 'weather' ] : $data ;
$temp =& $data [ 'temp' ] ? $data [ 'temp' ] : $data ;
// Full URL for icon
if ( is_array ( $weather ))
{
foreach ( $weather as & $w )
{
$w [ 'icon' ] = static :: ICON_URL . $w [ 'icon' ] . '.png' ;
}
}
// Round
foreach ( array ( 'temp' , 'temp_min' , 'temp_max' , 'min' , 'max' ) as $temp_name )
{
if ( array_key_exists ( $temp_name , $temp ))
{
$temp [ $temp_name ] = '' . round ( $temp [ $temp_name ]);
}
}
}
public function get_actions ()
{
$actions = array (
);
return $actions ;
}
/**
* Return a list of settings to customize the portlet .
*
* Settings should be in the same style as for preferences . It is OK to return an empty array
* for no customizable settings .
*
* These should be already translated , no further translation will be done .
*
* @ see preferences / inc / class . preferences_settings . inc . php
* @ return Array of settings . Each setting should have the following keys :
* - name : Internal reference
* - type : Widget type for editing
* - label : Human name
* - help : Description of the setting , and what it does
* - default : Default value , for when it ' s not set yet
*/
public function get_properties ()
{
$properties = parent :: get_properties ();
$properties [] = array (
'name' => 'city' ,
'type' => 'textbox' ,
'label' => lang ( 'Location' ),
);
return $properties ;
}
public function get_description ()
{
return array (
'displayName' => lang ( 'Weather' ),
'title' => $this -> context [ 'city' ],
'description' => lang ( 'Weather' )
);
}
}