View source
<?php
namespace Drupal\weather\Service;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\weather\Entity\WeatherForecastInformationInterface;
use Drupal\weather\Entity\WeatherPlaceInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class ParserService {
use StringTranslationTrait;
protected $entityTypeManager;
protected $weatherHelper;
protected $httpClient;
protected $logger;
protected $currentUser;
protected $messenger;
protected $weatherForecastInfoStorage;
protected $weatherForecastStorage;
protected $weatherPlaceStorage;
public function __construct(EntityTypeManagerInterface $entity_type_manager, HelperService $weatherHelper, Client $httpClient, LoggerChannelFactoryInterface $loggerFactory, AccountProxyInterface $current_user, MessengerInterface $messenger) {
$this->entityTypeManager = $entity_type_manager;
$this->weatherHelper = $weatherHelper;
$this->httpClient = $httpClient;
$this->logger = $loggerFactory;
$this->currentUser = $current_user;
$this->messenger = $messenger;
$this->weatherForecastInfoStorage = $entity_type_manager
->getStorage('weather_forecast_information');
$this->weatherForecastStorage = $entity_type_manager
->getStorage('weather_forecast');
$this->weatherPlaceStorage = $entity_type_manager
->getStorage('weather_place');
}
public function downloadForecast(string $geoid = '', string $url = '') {
$timeout = 10;
if ($geoid) {
$url = $this->weatherHelper
->getLinkForGeoid($geoid, 'yr');
}
$client = $this->httpClient;
try {
$response = $client
->get($url, [
'timeout' => $timeout,
]);
return $this
->parseForecast($response
->getBody(), $geoid);
} catch (RequestException $e) {
$this->logger
->get('weather')
->error($this
->t('Download of forecast failed: @error', [
'@error' => $e
->getMessage(),
]));
if ($this->currentUser
->hasPermission('administer site configuration')) {
$this->messenger
->addError($this
->t('Download of forecast failed: @error', [
'@error' => $e
->getMessage(),
]));
}
}
}
protected function parseForecast($xml, $geoid = '') {
$use_errors = libxml_use_internal_errors(TRUE);
$fc = simplexml_load_string($xml);
libxml_use_internal_errors($use_errors);
if ($fc === FALSE) {
return FALSE;
}
$this
->updatePlaces($fc);
if ($geoid == '') {
$geoid = $fc->location->location['geobase'] . "_" . $fc->location->location['geobaseid'];
}
$meta['geoid'] = $geoid;
$meta['utc_offset'] = (int) $fc->location->timezone['utcoffsetMinutes'];
$utctime = strtotime((string) $fc->meta->lastupdate . ' UTC') - 60 * $meta['utc_offset'];
$meta['last_update'] = gmdate('Y-m-d H:i:s', $utctime);
$utctime = strtotime((string) $fc->meta->nextupdate . ' UTC') - 60 * $meta['utc_offset'];
$meta['next_update'] = gmdate('Y-m-d H:i:s', $utctime);
$meta['next_download_attempt'] = $meta['next_update'];
$forecastInfo = $this->weatherForecastInfoStorage
->load($meta['geoid']);
if ($forecastInfo instanceof WeatherForecastInformationInterface) {
foreach ($meta as $field => $value) {
$forecastInfo->{$field} = $value;
}
$forecastInfo
->save();
}
else {
$this->weatherForecastInfoStorage
->create($meta)
->save();
}
$outdated = $this->weatherForecastStorage
->loadByProperties([
'geoid' => $meta['geoid'],
]);
$this->weatherForecastStorage
->delete($outdated);
foreach ($fc->forecast->tabular->time as $time) {
$forecast = [];
$forecast['geoid'] = $meta['geoid'];
$forecast['time_from'] = str_replace('T', ' ', (string) $time['from']);
$forecast['time_to'] = str_replace('T', ' ', (string) $time['to']);
$forecast['period'] = (string) $time['period'];
$forecast['symbol'] = (string) $time->symbol['var'];
if (strlen($forecast['symbol']) > 3) {
$forecast['symbol'] = substr($forecast['symbol'], 3, 3);
}
$forecast['precipitation'] = (double) $time->precipitation['value'];
$forecast['wind_direction'] = (int) $time->windDirection['deg'];
$forecast['wind_speed'] = (double) $time->windSpeed['mps'];
$forecast['temperature'] = (int) $time->temperature['value'];
$forecast['pressure'] = (int) $time->pressure['value'];
$fcExists = $this->weatherForecastStorage
->loadByProperties([
'geoid' => $meta['geoid'],
'time_from' => $forecast['time_from'],
]);
if (!$fcExists) {
$this->weatherForecastStorage
->create($forecast)
->save();
}
}
return TRUE;
}
protected function updatePlaces($fc) {
$place['geoid'] = $fc->location->location['geobase'] . "_" . $fc->location->location['geobaseid'];
$place['latitude'] = (string) $fc->location->location['latitude'];
$place['latitude'] = round($place['latitude'], 5);
$place['longitude'] = (string) $fc->location->location['longitude'];
$place['longitude'] = round($place['longitude'], 5);
$place['country'] = (string) $fc->location->country;
$place['name'] = (string) $fc->location->name;
$url = (string) $fc->credit->link['url'];
list($country, $link) = $this->weatherHelper
->parsePlaceUrl($url);
$place['link'] = $link;
$existingPlace = $this->weatherPlaceStorage
->load($place['geoid']);
if (!$existingPlace) {
$place['status'] = 'added';
$this->weatherPlaceStorage
->create($place)
->save();
}
else {
$modified = FALSE;
foreach ($place as $field => $value) {
$existingValue = $existingPlace->{$field}->value;
if ($existingPlace
->hasField($field) && $existingValue != $value) {
$existingPlace->{$field} = $value;
$modified = TRUE;
}
}
if ($modified) {
$existingPlace->status = WeatherPlaceInterface::STATUS_MODIFIED;
$existingPlace
->save();
}
}
}
public function getForecastsFromDatabase($geoid, $utc_offset, $days, $detailed, $time) {
$current_local_time = gmdate('Y-m-d H:i:s', $time + $utc_offset * 60);
$first_forecast = $this->weatherForecastStorage
->getQuery()
->condition('geoid', $geoid)
->condition('time_to', $current_local_time, '>=')
->sort('time_from', 'ASC')
->range(0, 1)
->execute();
if (!$first_forecast) {
return [];
}
$first_forecast = $this->weatherForecastStorage
->load(reset($first_forecast));
$weather = $this
->createWeatherArray([
$first_forecast,
]);
$first_forecast_day = explode('-', key($weather));
$tomorrow_local_time = gmdate('Y-m-d H:i:s', gmmktime(0, 0, 0, $first_forecast_day[1], $first_forecast_day[2] + 1, $first_forecast_day[0]));
$forecasts_until_local_time = gmdate('Y-m-d 23:59:59', gmmktime(23, 59, 59, $first_forecast_day[1], $first_forecast_day[2] + $days - 1, $first_forecast_day[0]));
$query = $this->weatherForecastStorage
->getQuery()
->condition('geoid', $geoid);
if ($detailed) {
$query
->condition('time_to', $current_local_time, '>=');
if ($days > 0) {
$query
->condition('time_from', $forecasts_until_local_time, '<=');
}
$query
->sort('time_from', 'ASC');
$forecasts = $query
->execute();
$forecasts = $this->weatherForecastStorage
->loadMultiple($forecasts);
$weather = $this
->createWeatherArray($forecasts);
}
elseif ($days > 1 || $days == 0) {
$query
->condition('time_from', $tomorrow_local_time, '>=');
$query
->condition('period', '2');
if ($days > 1) {
$query
->condition('time_from', $forecasts_until_local_time, '<=');
}
$query
->sort('time_from', 'ASC');
$forecasts = $query
->execute();
$forecasts = $this->weatherForecastStorage
->loadMultiple($forecasts);
$weather = array_merge($weather, $this
->createWeatherArray($forecasts));
}
return $weather;
}
protected function createWeatherArray(array $forecasts) {
$weather = [];
foreach ($forecasts as $forecast) {
list($day_from, $time_from) = explode(' ', $forecast->time_from->value);
$time_range = substr($time_from, 0, 5);
list($day_to, $time_to) = explode(' ', $forecast->time_to->value);
$time_range .= '-' . substr($time_to, 0, 5);
if ($day_to === $day_from) {
unset($day_to);
}
$weather[$day_from][$time_range] = [
'period' => $forecast->period->value,
'symbol' => $forecast->symbol->value,
'precipitation' => $forecast->precipitation->value,
'wind_direction' => $forecast->wind_direction->value,
'wind_speed' => $forecast->wind_speed->value,
'temperature' => $forecast->temperature->value,
'pressure' => $forecast->pressure->value,
];
}
return $weather;
}
public function getWeather($geoid, $days = 0, $detailed = TRUE) {
$time = time();
$result = $this
->downloadWeather($geoid, $time);
$weather['forecasts'] = $this
->getForecastsFromDatabase($geoid, $result['utc_offset'], $days, $detailed, $time);
$weather['utc_offset'] = $result['utc_offset'];
return $weather;
}
protected function downloadWeather(string $geoid, int $time) : array {
$currentUtcTime = gmdate('Y-m-d H:i:s');
$meta = [
'geoid' => $geoid,
'last_update' => $currentUtcTime,
'next_update' => $currentUtcTime,
'next_download_attempt' => $currentUtcTime,
'utc_offset' => 0,
];
$forecastInfo = $this->weatherForecastInfoStorage
->load($geoid);
if ($forecastInfo instanceof WeatherForecastInformationInterface) {
foreach ($meta as $key => $value) {
if ($key != 'geoid' && $forecastInfo
->hasField($key) && !$forecastInfo->{$key}
->isEmpty()) {
$meta[$key] = $forecastInfo->{$key}->value;
}
}
}
if ($currentUtcTime >= $meta['next_download_attempt']) {
$result = $this
->downloadForecast($geoid);
if (!$result) {
$this
->setNextAttempt($meta, $time);
}
else {
$forecastInfo = $this->weatherForecastInfoStorage
->load($geoid);
$newNextUpdate = $forecastInfo->next_update->value;
if ($currentUtcTime >= $newNextUpdate) {
foreach ($meta as $key => $value) {
if ($key != 'geoid' && $forecastInfo
->hasField($key) && !$forecastInfo->{$key}
->isEmpty()) {
$meta[$key] = $forecastInfo->{$key}->value;
}
}
$this
->setNextAttempt($meta, $time);
}
}
}
return $meta;
}
protected function setNextAttempt($meta, $time) {
$next_update = strtotime($meta['next_update'] . ' UTC');
$seconds_to_retry = 675;
while ($next_update + $seconds_to_retry <= $time) {
if ($seconds_to_retry < 86400) {
$seconds_to_retry = $seconds_to_retry * 2;
}
else {
$seconds_to_retry = $seconds_to_retry + 86400;
}
}
$meta['next_download_attempt'] = gmdate('Y-m-d H:i:s', $next_update + $seconds_to_retry);
$forecastInfo = $this->weatherForecastInfoStorage
->load($meta['geoid']);
if ($forecastInfo) {
$this->weatherForecastInfoStorage
->delete([
$forecastInfo,
]);
}
$this->weatherForecastInfoStorage
->create($meta)
->save();
}
}