aero_weather-1.0.3/src/Service/AeroWeatherApi.php
src/Service/AeroWeatherApi.php
<?php
namespace Drupal\aero_weather\Service;
use GuzzleHttp\Exception\ConnectException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Http\ClientFactory;
use Drupal\Core\Cache\CacheBackendInterface;
use GuzzleHttp\Exception\RequestException;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
/**
* Service for fetching and caching exchange rate data.
*/
class AeroWeatherApi {
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
private $configFactory;
/**
* The HTTP client factory.
*
* @var \Drupal\Core\Http\ClientFactory
*/
protected $httpClientFactory;
/**
* The cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* The logger channel.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The module handler service.
*
* Used to invoke hook_alter() and check module existence.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The string translation service.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $stringTranslation;
/**
* The file URL generator service.
*
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected $fileUrlGenerator;
/**
* The file entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $fileStorage;
/**
* AeroWeatherApi constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Core\Http\ClientFactory $http_client_factory
* The HTTP client factory.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* The cache backend.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
* The logger channel factory.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation service, used to translate human-readable text
* in the service, like country names, into different languages.
* @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator
* The file URL generator service for generating URLs for weather
* icons or files.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager service.
*/
public function __construct(ConfigFactoryInterface $config_factory, ClientFactory $http_client_factory, CacheBackendInterface $cache_backend, LoggerChannelFactoryInterface $logger_factory, ModuleHandlerInterface $module_handler, TranslationInterface $string_translation, FileUrlGeneratorInterface $file_url_generator, EntityTypeManagerInterface $entity_type_manager) {
$this->configFactory = $config_factory;
$this->httpClientFactory = $http_client_factory;
$this->cache = $cache_backend;
$this->logger = $logger_factory->get('aero_weather');
$this->moduleHandler = $module_handler;
$this->stringTranslation = $string_translation;
$this->fileUrlGenerator = $file_url_generator;
$this->fileStorage = $entity_type_manager->getStorage('file');
}
/**
* Fetches aero weather from the API, using cache if available.
*
* @return array
* An array of aero weather or an empty array on failure.
*/
public function getWeatherData($location_name, $forecast_days = NULL) {
$config = $this->configFactory->get('aero_weather.settings');
$apiKey = $config->get('api_key');
$cacheEnabled = $config->get('cache_enabled');
$forecast_days = $forecast_days ?? 5;
$location_hash = $this->generateHashFromText($location_name);
if (!$apiKey || $location_name == '') {
return [];
}
$cache_id = 'aero_weather_' . $location_hash;
$cache_entry = $this->cache->get($cache_id);
// If cache exists, check date validity.
if ($cacheEnabled && $cache_entry) {
$cached_data = $cache_entry->data;
// Validate if the cached date matches today.
if (isset($cached_data['date']) && $cached_data['date'] === date('Y-m-d')) {
return $cached_data['location_data'];
}
}
$exchangeRateUrl = "https://api.weatherapi.com/v1/forecast.json?key={$apiKey}&q={$location_name}&days={$forecast_days}";
$httpClient = $this->httpClientFactory->fromOptions();
try {
$response = $httpClient->get($exchangeRateUrl);
$location_data = json_decode($response->getBody(), TRUE);
if (isset($location_data['location'])) {
// Store location_data + cache date.
$cache_data = [
'date' => date('Y-m-d'),
'location_data' => $location_data,
];
// Determine cache expiration (e.g., until tomorrow midnight)
$expire = 0;
if ($cacheEnabled) {
$expire = $this->getCacheTimestamp();
}
$this->cache->set($cache_id, $cache_data, $expire);
return $location_data;
}
}
catch (ConnectException $e) {
$this->logger->error('Connection error: @message', ['@message' => $e->getMessage()]);
}
catch (RequestException $e) {
$this->logger->error('API request failed: @message', ['@message' => $e->getMessage()]);
}
return [];
}
/**
* Retrieves the cache enabled configuration value.
*
* @return bool
* TRUE if cache is enabled, FALSE otherwise.
*/
public function isCacheEnabled() {
return $this->configFactory->get('aero_weather.settings')->get('cache_enabled');
}
/**
* Retrieves the cache duration timestamp with unit conversion.
*
* @return int
* The cache timestamp in seconds.
*/
public function getCacheTimestamp() {
$config = $this->configFactory->get('aero_weather.settings');
// Retrieve the cache time and unit settings.
$cache_time = $config->get('cache_time') ?: 1;
$cache_unit = $config->get('cache_unit') ?: 'hours';
// Convert the time based on the unit.
switch ($cache_unit) {
case 'minutes':
// Convert minutes to seconds.
$cache_duration_seconds = $cache_time * 60;
break;
case 'hours':
// Convert hours to seconds.
$cache_duration_seconds = $cache_time * 60 * 60;
break;
default:
// Default to hours if an unknown unit is found.
$cache_duration_seconds = $cache_time * 60 * 60;
break;
}
// Return the timestamp (current time + cache duration)
return time() + $cache_duration_seconds;
}
/**
* Generates a hash for a given string.
*
* @param string $text
* The string to be hashed.
*
* @return string
* The generated hash.
*/
public function generateHashFromText($text) {
$algorithm = 'sha256';
$hash = hash($algorithm, $text);
return $hash;
}
/**
* Fetches all Aero Weather settings including icon file URLs.
*
* @return array
* An associative array of all Aero Weather settings including:
* - api_key: string
* - cache_enabled: bool
* - cache_time: int
* - cache_unit: string
* - icon_style: string
* - icons: array
* - humidity, pressure, wind, uv_index, precipitation, clouds,
* visibility, sunrise, sunset
* Each element contains:
* - upload_url: string|NULL (full URL to uploaded file)
* - url: string (URL if set manually)
* - font: string (font icon class)
*/
public function getWeatherIconSettings(): array {
$config = $this->configFactory->get('aero_weather.settings');
$icon_elements = [
'humidity', 'pressure', 'wind', 'uv_index', 'precipitation',
'clouds', 'visibility', 'sunrise', 'sunset',
];
$icons = [];
$icon_style = $config->get('icon_style') ?? 'default';
foreach ($icon_elements as $element) {
$upload_url = NULL;
$icon_markup = '';
// Load uploaded file URL.
$fids = $config->get("{$element}_upload") ?? [];
if (!empty($fids)) {
$file = $this->fileStorage->load(reset($fids));
if ($file) {
$upload_url = $this->fileUrlGenerator->generateAbsoluteString($file->getFileUri());
}
}
$url = $config->get("{$element}_url") ?? '';
$font = $config->get("{$element}_font") ?? '';
switch ($icon_style) {
case 'upload_url':
if (!empty($upload_url)) {
$icon_markup = '<img src="' . Html::escape($upload_url) . '" alt="' . Html::escape($element) . '" class="w-5 h-5 mlr-2"/>';
}
break;
case 'url':
if (!empty($url)) {
$icon_markup = '<img src="' . Html::escape($url) . '" alt="' . Html::escape($element) . '" class="w-5 h-5 mlr-2"/>';
}
break;
case 'font':
if (!empty($font)) {
$safe_html = Xss::filter($font, ['i', 'span']);
// Add extra classes.
if (strpos($safe_html, 'class="') !== FALSE) {
$icon_markup = preg_replace('/class="([^"]+)"/', 'class="$1 w-5 h-5 mlr-2"', $safe_html);
}
else {
$icon_markup = str_replace('<i', '<i class="w-5 h-5 mlr-2"', $safe_html);
}
}
break;
}
$icons[$element] = !empty($icon_markup) ? Markup::create($icon_markup) : '';
}
return $icons;
}
}
