diff --git a/Classes/Ajax/DeutscherWetterdienstWarnCellSearch.php b/Classes/Ajax/DeutscherWetterdienstWarnCellSearch.php deleted file mode 100644 index c8b9a3b..0000000 --- a/Classes/Ajax/DeutscherWetterdienstWarnCellSearch.php +++ /dev/null @@ -1,56 +0,0 @@ -dwdWarnCellRepository = $dwdWarnCellRepository; - } - - public function renderWarnCells(ServerRequestInterface $request): Response - { - $dwdWarnCells = $this->dwdWarnCellRepository->findByName( - htmlspecialchars(strip_tags($request->getQueryParams()['query'] ?? '')), - ); - - $suggestions = []; - foreach ($dwdWarnCells as $dwdWarnCell) { - $suggestions[] = [ - 'data' => $dwdWarnCell->getWarnCellId(), - 'value' => sprintf( - '%s (%s)', - $dwdWarnCell->getName(), - $dwdWarnCell->getWarnCellId(), - ), - ]; - } - - return new JsonResponse( - ['suggestions' => $suggestions], - ); - } -} diff --git a/Classes/Command/DeutscherWetterdienstCommand.php b/Classes/Command/DeutscherWetterdienstCommand.php new file mode 100644 index 0000000..3ed995e --- /dev/null +++ b/Classes/Command/DeutscherWetterdienstCommand.php @@ -0,0 +1,352 @@ + + */ + protected array $decodedResponse = []; + + /** + * @var array + */ + protected array $keepRecords = []; + + /** + * @var array + */ + protected array $warnCellRecords = []; + + public function __construct( + protected readonly PersistenceManager $persistenceManager, + protected readonly DwdWarnCellRepository $dwdWarnCellRepository, + protected readonly RequestFactory $requestFactory, + protected readonly LoggerInterface $logger, + protected readonly ConnectionPool $connectionPool, + protected readonly CacheService $cacheService, + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setDescription('Fetch and process weather alerts from Deutscher Wetterdienst') + ->setHelp('Calls the Deutscher Wetterdienst api and saves response in weather2 format into database') + ->addArgument( + 'selectedWarnCells', + InputArgument::OPTIONAL, + 'Fetch alerts for selected cities (e.g. Pforzheim)', + ) + ->addArgument( + 'recordStoragePage', + InputArgument::OPTIONAL, + 'Record storage page (optional)', + ) + ->addArgument( + 'clearCache', + InputArgument::OPTIONAL, + 'Clear cache for pages (comma separated list with IDs)', + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Starting to fetch warn cell data...'); + try { + $response = $this->requestFactory->request(self::API_URL); + if (!$this->checkResponse($response)) { + return Command::FAILURE; + } + + $this->decodedResponse = $this->decodeResponse($response); + + $this->handleResponse($input, $output); + + $output->writeln('Warn cell data has been successfully updated.'); + return Command::SUCCESS; + } catch (\Throwable $exception) { + $this->logger->error($exception->getMessage(), ['exception' => $exception]); + $output->writeln('Failed to process weather alerts: ' . $exception->getMessage() . ''); + return Command::FAILURE; + } + + } + + /** + * Decodes the response string + * You cannot use json_decode for that only, because dwd adds JavaScript code into + * the json file... + * + * @return array + * @throws \UnexpectedValueException + */ + protected function decodeResponse(ResponseInterface $response): array + { + $pattern = '/^warnWetter\.loadWarnings\(|\);$/'; + $decodedResponse = json_decode(preg_replace($pattern, '', (string)$response->getBody()), true); + if ($decodedResponse === null) { + throw new \UnexpectedValueException( + 'Response can not be decoded because it is an invalid string', + 1485944083, + ); + } + + return $decodedResponse; + } + + /** + * Checks the responseClass for alerts in selected regions + */ + protected function handleResponse(InputInterface $input, OutputInterface $output): void + { + if (array_key_exists('warnings', $this->decodedResponse)) { + $this->processDwdItems($this->decodedResponse['warnings'], false, $input, $output); + } + if (array_key_exists('vorabInformation', $this->decodedResponse)) { + $this->processDwdItems($this->decodedResponse['vorabInformation'], true, $input, $output); + } + $this->removeOldAlertsFromDb(); + $this->persistenceManager->persistAll(); + + if (!empty($input->getArgument('clearCache'))) { + $this->cacheService->clearPageCache(GeneralUtility::intExplode(',', $input->getArgument('clearCache'))); + } + } + + /** + * @param array $category + */ + protected function processDwdItems(array $category, bool $isPreliminaryInformation, InputInterface $input, OutputInterface $output): void + { + $selectedWarnCells = explode(',', $input->getArgument('selectedWarnCells')); + $recordStoragePid = (int)$input->getArgument('recordStoragePage'); + foreach ($selectedWarnCells as $warnCellId) { + $dwdWarnCells = $this->getDwdRecordsFindByName( + htmlspecialchars(strip_tags($warnCellId)), + ); + $progressBar = new ProgressBar($output, count($dwdWarnCells)); + $progressBar->start(); + foreach ($dwdWarnCells as $dwdWarnCell) { + $suggestion = $dwdWarnCell['warn_cell_id']; + if (array_key_exists($suggestion, $category) && is_array($category[$suggestion])) { + foreach ($category[$suggestion] as $alert) { + if ($alertUid = $this->getUidOfAlert($alert, $recordStoragePid)) { + // alert does already exist as record + $this->keepRecords[] = $alertUid; + } else { + // create a new alert record + $row = $this->getWeatherAlertInstanceForAlert( + $alert, + $dwdWarnCell['uid'], + $isPreliminaryInformation, + $recordStoragePid, + ); + $this->insertRecord($row); + $progressBar->advance(); + } + } + } + } + $progressBar->finish(); + $output->writeln(''); + } + } + + /** + * @param array $row + */ + private function insertRecord(array $row): void + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_weather2_domain_model_weatheralert'); + $affectedRows = $queryBuilder + ->insert('tx_weather2_domain_model_weatheralert') + ->values($row) + ->executeStatement(); + $this->keepRecords[] = $queryBuilder->getConnection()->lastInsertId(); + } + + /** + * @param array $alert + */ + protected function getComparisonHashForAlert(array $alert): string + { + return md5(serialize($alert)); + } + + /** + * Either returns the uid of a record that equals $alert + * OR returns zero if there is no record for that $alert + * + * @param array $alert + * @throws Exception + */ + protected function getUidOfAlert(array $alert, int $recordStoragePid): int + { + $connection = $this->connectionPool->getConnectionForTable($this->dbExtTable); + $identicalAlert = $connection + ->select( + ['uid'], + $this->dbExtTable, + [ + 'comparison_hash' => $this->getComparisonHashForAlert($alert), + 'pid' => $recordStoragePid, + ], + ) + ->fetchAssociative(); + + return $identicalAlert['uid'] ?? 0; + } + + protected function checkResponse(ResponseInterface $response): bool + { + if ($response->getStatusCode() !== 200 || (string)$response->getBody() === '') { + $this->logger->log( + LogLevel::ERROR, + WeatherUtility::translate('message.api_response_null', 'deutscherwetterdienst'), + ); + return false; + } + return true; + } + + protected function getBackendUserAuthentication(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } + + /** + * Returns filled WeatherAlert instance + * + * @param array $alert + * @return array + */ + protected function getWeatherAlertInstanceForAlert( + array $alert, + int $warnCellId, + bool $isPreliminaryInformation, + int $recordStoragePid, + ): array { + $weatherAlert['pid'] = $recordStoragePid; + $weatherAlert['dwd_warn_cell'] = $warnCellId; + $weatherAlert['comparison_hash'] = $alert; + $weatherAlert['preliminary_information'] = (int)$isPreliminaryInformation; + + if (isset($alert['level'])) { + $weatherAlert['level'] = $alert['level']; + } + if (isset($alert['type'])) { + $weatherAlert['type'] = $alert['type']; + } + if (isset($alert['headline'])) { + $weatherAlert['title'] = $alert['headline']; + } + if (isset($alert['description'])) { + $weatherAlert['description'] = $alert['description']; + } + if (isset($alert['instruction'])) { + $weatherAlert['instruction'] = $alert['instruction']; + } + if (isset($alert['start'])) { + $startTime = new \DateTime(); + $startTime->setTimestamp((int)substr((string)$alert['start'], 0, -3)); + $weatherAlert['start_date'] = (int)$startTime->getTimestamp(); + } + if (isset($alert['end'])) { + $endTime = new \DateTime(); + $endTime->setTimestamp((int)substr((string)$alert['end'], 0, -3)); + $weatherAlert['end_date'] = (int)$endTime->getTimestamp(); + } + + return $weatherAlert; + } + + protected function getDwdWarnCell(string $warnCellId): DwdWarnCell + { + if (!array_key_exists($warnCellId, $this->warnCellRecords)) { + $this->warnCellRecords[$warnCellId] = $this->dwdWarnCellRepository + ->findOneByWarnCellId($warnCellId); + } + return $this->warnCellRecords[$warnCellId]; + } + + protected function removeOldAlertsFromDb(): void + { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($this->dbExtTable); + $queryBuilder->delete($this->dbExtTable); + + if ($this->keepRecords) { + $queryBuilder->where( + $queryBuilder + ->expr() + ->notIn('uid', $this->keepRecords), + ); + } + + $queryBuilder->executeStatement(); + } + + /** + * @return array + * @throws Exception + */ + protected function getDwdRecordsFindByName(string $name): array + { + $table = 'tx_weather2_domain_model_dwdwarncell'; + $connection = $this->connectionPool->getConnectionForTable($table); + $queryBuilder = $connection->createQueryBuilder(); + + try { + // Build the query + $queryBuilder->select('*') + ->from($table) // Replace 'your_table_name' with your actual table name + ->where( + $queryBuilder->expr()->or( + $queryBuilder->expr()->eq('name', $queryBuilder->createNamedParameter(trim($name))), + $queryBuilder->expr()->eq('warn_cell_id', $queryBuilder->createNamedParameter($name)), + ), + ) + ->orderBy('uid', 'ASC'); + + return $queryBuilder->executeQuery()->fetchAllAssociative(); + } catch (\Exception $e) { + // Handle exception if needed + return []; + } + } +} diff --git a/Classes/Command/DeutscherWetterdienstWarnCellCommand.php b/Classes/Command/DeutscherWetterdienstWarnCellCommand.php new file mode 100644 index 0000000..594203d --- /dev/null +++ b/Classes/Command/DeutscherWetterdienstWarnCellCommand.php @@ -0,0 +1,161 @@ +setHelp( + 'Calls the Deutscher Wetterdienst api and saves warn cells into database. Required before using DeutscherWetterdienstTask!', + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Starting to fetch warn cell data...'); + + try { + $response = $this->fetchWarnCellData(); + $rows = $this->parseResponse($response); + $this->updateDatabase($rows, $output); + + $output->writeln('Warn cell data has been successfully updated.'); + return Command::SUCCESS; + } catch (\Throwable $e) { + $this->logger->error( + sprintf('Error while updating warn cells: %s', $e->getMessage()), + ['exception' => $e], + ); + $output->writeln($e->getMessage()); + $output->writeln('An error occurred. Check the logs for details.'); + return Command::FAILURE; + } + } + + protected function fetchWarnCellData(): ResponseInterface + { + try { + $response = $this->requestFactory->request($this::API_URL); + if ($response->getStatusCode() !== 200 || (string)$response->getBody() === '') { + $this->logger->log( + LogLevel::ERROR, + WeatherUtility::translate('message.api_response_null', 'deutscherwetterdienst'), + ); + throw new \RuntimeException('Invalid response from API.'); + } + + return $response; + } catch (\Exception $e) { + throw new \RuntimeException('Failed to fetch warn cell data: ' . $e->getMessage(), 0, $e); + } + } + + /** + * @return array + */ + private function parseResponse(ResponseInterface $response): array + { + $rawRows = explode(PHP_EOL, trim((string)$response->getBody())); + array_shift($rawRows); // Remove header row + + $rows = []; + foreach ($rawRows as $index => $rawRow) { + $fields = str_getcsv($rawRow, ';'); + if (count($fields) !== 5) { + $this->logger->warning(sprintf('Malformed row at line %d: %s', $index + 2, $rawRow)); + continue; + } + + [$warnCellId, $name, $nuts, $shortName, $sign] = $fields; + $rows[] = [ + 'warn_cell_id' => $warnCellId, + 'name' => $name, + 'nuts' => $nuts, + 'short_name' => $shortName, + 'sign' => $sign, + ]; + } + + return $rows; + } + + /** + * @param array $rows + */ + private function updateDatabase(array $rows, OutputInterface $output): void + { + $connection = $this->connectionPool->getConnectionForTable('tx_weather2_domain_model_dwdwarncell'); + + $progressBar = new ProgressBar($output, count($rows)); + $progressBar->start(); + + foreach ($rows as $row) { + if (!$this->doesRecordExist($connection, $row['warn_cell_id'])) { + $this->insertRecord($connection, $row); + } + $progressBar->advance(); + } + + $progressBar->finish(); + $output->writeln(''); + } + + private function doesRecordExist(Connection $connection, string $warnCellId): bool + { + $queryBuilder = $connection->createQueryBuilder(); + $count = $queryBuilder + ->count('uid') + ->from('tx_weather2_domain_model_dwdwarncell') + ->where($queryBuilder->expr()->eq('warn_cell_id', $queryBuilder->createNamedParameter($warnCellId))) + ->executeQuery() + ->fetchOne(); + + return (int)$count > 0; + } + + /** + * @param array $row + */ + private function insertRecord(Connection $connection, array $row): void + { + $connection->insert('tx_weather2_domain_model_dwdwarncell', [ + 'pid' => 0, + 'warn_cell_id' => $row['warn_cell_id'], + 'name' => $row['name'], + 'short_name' => $row['short_name'], + 'sign' => $row['sign'], + ]); + } +} diff --git a/Classes/Command/OpenWeatherMapCommand.php b/Classes/Command/OpenWeatherMapCommand.php new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/Classes/Command/OpenWeatherMapCommand.php @@ -0,0 +1,217 @@ +setHelp('Calls the api of openweathermap.org and saves response into database') + ->addArgument('name', InputArgument::REQUIRED, 'Name') + ->addArgument('city', InputArgument::REQUIRED, 'City name (e.g. Munich)') + ->addArgument('country', InputArgument::REQUIRED, 'Country Code (e.g. DE)') + ->addArgument('apiKey', InputArgument::REQUIRED, 'API-Key') + ->addArgument( + 'clearCache', + InputArgument::OPTIONAL, + 'Clear cache for pages (comma separated list with IDs)', + ) + ->addArgument('recordStoragePage', InputArgument::OPTIONAL, 'Record storage page (optional)'); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Starting OpenWeatherMap data fetch...'); + + try { + // Assign arguments + $this->name = ($input->getArgument('name')) ?? ''; + $this->city = ($input->getArgument('recordStoragePage')) ?? ''; + $this->country = ($input->getArgument('recordStoragePage')) ?? ''; + $this->recordStoragePage = (int)$input->getArgument('recordStoragePage'); + + $this->removeOldRecordsFromDb(); + $this->url = sprintf( + 'https://api.openweathermap.org/data/2.5/weather?q=%s,%s&units=%s&APPID=%s', + urlencode($input->getArgument('city')), + urlencode($input->getArgument('country')), + 'metric', + $input->getArgument('apiKey'), + ); + + // Log request details + $this->logger->info('Requesting data from OpenWeatherMap API', ['url' => $this->url]); + + // Make API request + $response = $this->requestFactory->request($this->url); + + if (!($this->checkResponseCode($response))) { + return Command::FAILURE; + } + + $this->responseClass = json_decode((string)$response->getBody()); + $this->logger->info(sprintf('Response class: %s', json_encode($this->responseClass))); + + // Changing the data save to query builder + $this->saveCurrentWeatherInstanceForResponseClass($this->responseClass); + + if (!empty($input->getArgument('clearCache'))) { + $cacheService = $this->cacheService; + $cacheService->clearPageCache(GeneralUtility::intExplode(',', $this->clearCache)); + } + + $output->writeln('Open Weather Map data successfully updated!'); + return Command::SUCCESS; + } catch (\Throwable $e) { + $errorMessage = 'Exception while fetching data from API: ' . $e->getMessage(); + $this->logger->error($errorMessage); + + $output->writeln('' . $e->getMessage() . ''); + return Command::FAILURE; + } + } + + /** + * Checks the JSON response + * + * @param ResponseInterface $response + * @return bool Returns true if given data is valid or false in case of an error + */ + private function checkResponseCode(ResponseInterface $response): bool + { + if ($response->getStatusCode() === 401) { + $this->logger->error(WeatherUtility::translate('message.api_response_401', 'openweatherapi')); + return false; + } + if ($response->getStatusCode() !== 200) { + $this->logger->error(WeatherUtility::translate('message.api_response_null', 'openweatherapi')); + return false; + } + + /** @var \stdClass $responseClass */ + $responseClass = json_decode((string)$response->getBody(), false); + + switch ($responseClass->cod) { + case '200': + return true; + case '404': + $this->logger->error(WeatherUtility::translate('messages.api_code_404', 'openweatherapi')); + return false; + default: + $this->logger->error( + sprintf( + WeatherUtility::translate('messages.api_code_none', 'openweatherapi'), + (string)$response->getBody(), + ), + ); + return false; + } + } + + public function saveCurrentWeatherInstanceForResponseClass(\stdClass $responseClass): int + { + $recordStoragePage = (int)$this->recordStoragePage; + $weatherObjectArray = [ + 'pid' => $this->recordStoragePage, + 'name' => $this->name, + ]; + + if (isset($responseClass->main->temp)) { + $weatherObjectArray['temperature_c'] = (float)$responseClass->main->temp; + } + if (isset($responseClass->main->pressure)) { + $weatherObjectArray['pressure_hpa'] = (float)$responseClass->main->pressure; + } + if (isset($responseClass->main->humidity)) { + $weatherObjectArray['humidity_percentage'] = $responseClass->main->humidity; + } + if (isset($responseClass->main->temp_min)) { + $weatherObjectArray['min_temp_c'] = $responseClass->main->temp_min; + } + if (isset($responseClass->main->temp_max)) { + $weatherObjectArray['max_temp_c'] = $responseClass->main->temp_max; + } + if (isset($responseClass->wind->speed)) { + $weatherObjectArray['wind_speed_m_p_s'] = $responseClass->wind->speed; + } + if (isset($responseClass->wind->deg)) { + $weatherObjectArray['wind_speed_m_p_s'] = $responseClass->wind->deg; + } + if (isset($responseClass->rain)) { + $rain = (array)$responseClass->rain; + $weatherObjectArray['rain_volume'] = (float)($rain['1h'] ?? 0.0); + } + if (isset($responseClass->snow)) { + $snow = (array)$responseClass->snow; + $weatherObjectArray['snow_volume'] = (float)($snow['1h'] ?? 0.0); + } + if (isset($responseClass->clouds->all)) { + $weatherObjectArray['clouds_percentage'] = $responseClass->clouds->all; + } + if (isset($responseClass->dt)) { + $weatherObjectArray['measure_timestamp'] = $responseClass->dt; + } + if (isset($responseClass->weather[0]->icon)) { + $weatherObjectArray['icon'] = $responseClass->weather[0]->icon; + } + if (isset($responseClass->weather[0]->id)) { + $weatherObjectArray['condition_code'] = $responseClass->weather[0]->id; + } + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_weather2_domain_model_currentweather'); + return $queryBuilder + ->insert('tx_weather2_domain_model_currentweather') + ->values($weatherObjectArray) + ->executeStatement(); + } + + protected function removeOldRecordsFromDb(): void + { + $this->connectionPool + ->getConnectionForTable($this->dbExtTable) + ->delete($this->dbExtTable, [ + 'pid' => $this->recordStoragePage, + 'name' => $this->name, + ]); + } +} diff --git a/Classes/Task/DeutscherWetterdienstTask.php b/Classes/Task/DeutscherWetterdienstTask.php deleted file mode 100644 index 65d8efe..0000000 --- a/Classes/Task/DeutscherWetterdienstTask.php +++ /dev/null @@ -1,280 +0,0 @@ - - */ - protected array $decodedResponse = []; - - /** - * Fetch only these warn cells - * - * @var array - */ - public array $selectedWarnCells = []; - - /** - * @var int - */ - public int $recordStoragePage = 0; - - /** - * @var string - */ - public string $clearCache = ''; - - /** - * @var array - */ - protected array $keepRecords = []; - - /** - * @var PersistenceManager - */ - protected PersistenceManager $persistenceManager; - - /** - * @var array - */ - protected array $warnCellRecords = []; - - /** - * @var DwdWarnCellRepository - */ - protected DwdWarnCellRepository $dwdWarnCellRepository; - - /** - * @return bool - */ - public function execute(): bool - { - $this->dwdWarnCellRepository = $this->getDwdWarnCellRepository(); - $response = $this->getRequestFactory()->request(self::API_URL); - if (!$this->checkResponse($response)) { - return false; - } - - try { - $this->decodedResponse = $this->decodeResponse($response); - } catch (\Exception $e) { - $this->logger->log(LogLevel::ERROR, $e->getMessage()); - return false; - } - - $this->handleResponse(); - - return true; - } - - /** - * Decodes the response string - * You cannot use json_decode for that only, because dwd adds JavaScript code into - * the json file... - * - * @return array - * @throws \UnexpectedValueException - */ - protected function decodeResponse(ResponseInterface $response): array - { - $pattern = '/^warnWetter\.loadWarnings\(|\);$/'; - $decodedResponse = json_decode(preg_replace($pattern, '', (string)$response->getBody()), true); - if ($decodedResponse === null) { - throw new \UnexpectedValueException( - 'Response can not be decoded because it is an invalid string', - 1485944083, - ); - } - return $decodedResponse; - } - - /** - * Checks the responseClass for alerts in selected regions - */ - protected function handleResponse(): void - { - $this->persistenceManager = $this->getPersistenceManager(); - if (array_key_exists('warnings', $this->decodedResponse)) { - $this->processDwdItems($this->decodedResponse['warnings'], false); - } - if (array_key_exists('vorabInformation', $this->decodedResponse)) { - $this->processDwdItems($this->decodedResponse['vorabInformation'], true); - } - $this->removeOldAlertsFromDb(); - $this->persistenceManager->persistAll(); - - if (!empty($this->clearCache)) { - $cacheService = $this->getCacheService(); - $cacheService->clearPageCache(GeneralUtility::intExplode(',', $this->clearCache)); - } - } - - /** - * @param array $category - */ - protected function processDwdItems(array $category, bool $isPreliminaryInformation): void - { - foreach ($this->selectedWarnCells as $warnCellId) { - if (array_key_exists($warnCellId, $category) && is_array($category[$warnCellId])) { - foreach ($category[$warnCellId] as $alert) { - if ($alertUid = $this->getUidOfAlert($alert)) { - // alert does already exist as record - $this->keepRecords[] = $alertUid; - } else { - // create a new alert record - $this->persistenceManager->add($this->getWeatherAlertInstanceForAlert($alert, $warnCellId, $isPreliminaryInformation)); - } - } - } - } - } - - /** - * @param array $alert - */ - protected function getComparisonHashForAlert(array $alert): string - { - return md5(serialize($alert)); - } - - /** - * Either returns the uid of a record that equals $alert - * OR returns zero if there is no record for that $alert - * - * @param array $alert - * @throws Exception - */ - protected function getUidOfAlert(array $alert): int - { - $connection = $this->getConnectionPool()->getConnectionForTable($this->dbExtTable); - $identicalAlert = $connection - ->select( - ['uid'], - $this->dbExtTable, - [ - 'comparison_hash' => $this->getComparisonHashForAlert($alert), - 'pid' => $this->recordStoragePage, - ], - ) - ->fetchAssociative(); - - return $identicalAlert['uid'] ?? 0; - } - - protected function checkResponse(ResponseInterface $response): bool - { - if ($response->getStatusCode() !== 200 || (string)$response->getBody() === '') { - $this->logger->log( - LogLevel::ERROR, - WeatherUtility::translate('message.api_response_null', 'deutscherwetterdienst'), - ); - return false; - } - return true; - } - - protected function getBackendUserAuthentication(): BackendUserAuthentication - { - return $GLOBALS['BE_USER']; - } - - /** - * Returns filled WeatherAlert instance - * - * @param array $alert - */ - protected function getWeatherAlertInstanceForAlert( - array $alert, - string $warnCellId, - bool $isPreliminaryInformation, - ): WeatherAlert { - $weatherAlert = new WeatherAlert(); - $weatherAlert->setPid($this->recordStoragePage); - $weatherAlert->setDwdWarnCell($this->getDwdWarnCell($warnCellId)); - $weatherAlert->setComparisonHash($this->getComparisonHashForAlert($alert)); - $weatherAlert->setPreliminaryInformation($isPreliminaryInformation); - - if (isset($alert['level'])) { - $weatherAlert->setLevel($alert['level']); - } - if (isset($alert['type'])) { - $weatherAlert->setType($alert['type']); - } - if (isset($alert['headline'])) { - $weatherAlert->setTitle($alert['headline']); - } - if (isset($alert['description'])) { - $weatherAlert->setDescription($alert['description']); - } - if (isset($alert['instruction'])) { - $weatherAlert->setInstruction($alert['instruction']); - } - if (isset($alert['start'])) { - $startTime = new \DateTime(); - $startTime->setTimestamp((int)substr((string)$alert['start'], 0, -3)); - $weatherAlert->setStartDate($startTime); - } - if (isset($alert['end'])) { - $endTime = new \DateTime(); - $endTime->setTimestamp((int)substr((string)$alert['end'], 0, -3)); - $weatherAlert->setEndDate($endTime); - } - - return $weatherAlert; - } - - protected function getDwdWarnCell(string $warnCellId): DwdWarnCell - { - if (!array_key_exists($warnCellId, $this->warnCellRecords)) { - $this->warnCellRecords[$warnCellId] = $this->dwdWarnCellRepository - ->findOneByWarnCellId($warnCellId); - } - return $this->warnCellRecords[$warnCellId]; - } - - protected function removeOldAlertsFromDb(): void - { - $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable($this->dbExtTable); - $queryBuilder->delete($this->dbExtTable); - - if ($this->keepRecords) { - $queryBuilder->where( - $queryBuilder - ->expr() - ->notIn('uid', $this->keepRecords), - ); - } - - $queryBuilder->executeStatement(); - } -} diff --git a/Classes/Task/DeutscherWetterdienstTaskAdditionalFieldProvider.php b/Classes/Task/DeutscherWetterdienstTaskAdditionalFieldProvider.php deleted file mode 100644 index 3dee465..0000000 --- a/Classes/Task/DeutscherWetterdienstTaskAdditionalFieldProvider.php +++ /dev/null @@ -1,240 +0,0 @@ - - */ - protected array $requiredFields = [ - 'dwd_regionSelection', - ]; - - /** - * Fields to insert from task if empty - * - * @var array - */ - protected $insertFields = [ - 'dwd_selectedWarnCells', - 'dwd_recordStoragePage', - 'dwd_clearCache', - ]; - - public function __construct( - DwdWarnCellRepository $dwdWarnCellRepository, - UriBuilder $uriBuilder, - PageRenderer $pageRenderer, - ) { - $this->dwdWarnCellRepository = $dwdWarnCellRepository; - $this->uriBuilder = $uriBuilder; - $this->pageRenderer = $pageRenderer; - } - - /** - * @param array $taskInfo - * @param DeutscherWetterdienstTask $task - * - * @return array - */ - public function getAdditionalFields( - array &$taskInfo, - $task, - SchedulerModuleController $schedulerModule, - ): array { - $this->initialize(); - foreach ($this->insertFields as $fieldID) { - if (empty($taskInfo[$fieldID])) { - $propertyName = str_replace('dwd_', '', $fieldID); - if ($task instanceof DeutscherWetterdienstTask) { - $taskInfo[$fieldID] = $task->$propertyName; - } else { - $taskInfo[$fieldID] = ''; - } - } - } - - $additionalFields = []; - - $fieldID = 'dwd_selectedWarnCells'; - if ($this->areRegionsAvailable()) { - $fieldCode = '
' . $this->getHtmlForSelectedRegions($taskInfo); - } else { - $flashMessage = GeneralUtility::makeInstance( - FlashMessage::class, - WeatherUtility::translate('message.noDwdWarnCellsFound', 'deutscherwetterdienst'), - '', - ContextualFeedbackSeverity::WARNING, - ); - $fieldCode = $this->addFlashMessage($flashMessage, true); - } - - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_deutscherwetterdienst.xlf:warnCells', - ]; - - $fieldID = 'dwd_recordStoragePage'; - $fieldCode = '
- -
'; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_deutscherwetterdienst.xlf:recordStoragePage', - ]; - - $fieldID = 'dwd_clearCache'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_deutscherwetterdienst.xlf:clear_cache', - ]; - - return $additionalFields; - } - - protected function initialize(): void - { - $this->pageRenderer->addInlineLanguageLabelFile( - 'EXT:weather2/Resources/Private/Language/locallang_scheduler_javascript_deutscherwetterdienst.xlf', - ); - $this->pageRenderer->addCssFile('EXT:weather2/Resources/Public/Css/dwdScheduler.css'); - $this->pageRenderer->loadJavaScriptModule('TYPO3/CMS/Backend/FormEngineValidation'); - $this->pageRenderer->loadJavaScriptModule('TYPO3/CMS/Weather2/jquery.autocomplete'); - $this->pageRenderer->loadJavaScriptModule('TYPO3/CMS/Weather2/DeutscherWetterdienstTaskModule'); - $popupSettings = [ - 'PopupWindow' => [ - 'width' => '800px', - 'height' => '550px', - ], - ]; - $this->pageRenderer->addInlineSettingArray('Popup', $popupSettings); - $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('record_edit')); - $this->pageRenderer->addInlineSetting('FormEngine', 'formName', 'tx_scheduler_form'); - $this->pageRenderer->addInlineSetting('FormEngine', 'backPath', ''); - $this->pageRenderer->loadJavaScriptModule( - 'TYPO3/CMS/Backend/FormEngine', - ); - } - - protected function areRegionsAvailable(): bool - { - return $this->dwdWarnCellRepository->countAll() > 0; - } - - /** - * @param array $taskInfo - */ - public function getHtmlForSelectedRegions(array $taskInfo): string - { - $ulItems = ''; - if (is_array($taskInfo['dwd_selectedWarnCells'])) { - foreach ($taskInfo['dwd_selectedWarnCells'] as $warnCellId) { - $dwdWarnCell = $this->dwdWarnCellRepository->findOneByWarnCellId($warnCellId); - if ($dwdWarnCell instanceof DwdWarnCell) { - $label = sprintf('%s (%s)', $dwdWarnCell->getName(), $dwdWarnCell->getWarnCellId()); - $ulItems .= '
  • ' . - '' . - WeatherUtility::translate('removeItem', 'deutscherwetterdienstJs') . '' . - $label . - '
  • '; - } - } - } - - return '
      ' . $ulItems . '
    '; - } - - /** - * @param array $submittedData - */ - public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule): bool - { - $isValid = true; - - if ($submittedData['dwd_recordStoragePage']) { - $submittedData['dwd_recordStoragePage'] = preg_replace( - '/\D/', - '', - $submittedData['dwd_recordStoragePage'], - ); - } else { - $submittedData['dwd_recordStoragePage'] = 0; - } - - foreach ($submittedData as $fieldName => $field) { - $value = is_string($field) ? trim($field) : $field; - - if (empty($value) && in_array($fieldName, $this->requiredFields, true)) { - $isValid = false; - $this->addMessage('Field: ' . $fieldName . ' must not be empty', ContextualFeedbackSeverity::ERROR); - } else { - $submittedData[$fieldName] = $value; - } - } - - return $isValid; - } - - /** - * @param array $submittedData - */ - public function saveAdditionalFields(array $submittedData, AbstractTask $task): void - { - /** @var DeutscherWetterdienstTask $task */ - $task->selectedWarnCells = $submittedData['dwd_selectedWarnCells'] ?: []; - $task->recordStoragePage = (int)$submittedData['dwd_recordStoragePage']; - $task->clearCache = $submittedData['dwd_clearCache'] ?? ''; - } - - protected function addFlashMessage(FlashMessage $flashMessage, bool $returnRenderedFlashMessage = false): string - { - $messageQueue = $this->getFlashMessageService()->getMessageQueueByIdentifier(); - $messageQueue->addMessage($flashMessage); - - if ($returnRenderedFlashMessage) { - return $messageQueue->renderFlashMessages(); - } - - return ''; - } - - protected function getFlashMessageService(): FlashMessageService - { - return GeneralUtility::makeInstance(FlashMessageService::class); - } -} diff --git a/Classes/Task/DeutscherWetterdienstWarnCellTask.php b/Classes/Task/DeutscherWetterdienstWarnCellTask.php deleted file mode 100644 index 0779cc3..0000000 --- a/Classes/Task/DeutscherWetterdienstWarnCellTask.php +++ /dev/null @@ -1,89 +0,0 @@ -getRequestFactory()->request($this::API_URL); - if (!$this->checkResponse($response)) { - return false; - } - $this->processResponse($response); - return true; - } - - protected function processResponse(ResponseInterface $response): void - { - $connection = $this->getConnectionPool() - ->getConnectionForTable('tx_weather2_domain_model_dwdwarncell'); - - $rawRows = explode(PHP_EOL, (string)$response->getBody()); - // remove header - array_shift($rawRows); - - $data = []; - $i = 0; - foreach ($rawRows as $rawRow) { - if ($rawRow === '') { - continue; - } - - [$warnCellId, $name, $nuts, $shortName, $sign] = str_getcsv($rawRow, ';'); - // check if a record for this id already exists - if ($connection->count('uid', 'tx_weather2_domain_model_dwdwarncell', ['warn_cell_id' => $warnCellId]) === 0) { - $data['tx_weather2_domain_model_dwdwarncell']['NEW' . $i++] = [ - 'pid' => 0, - 'warn_cell_id' => $warnCellId, - 'name' => $name, - 'short_name' => $shortName, - 'sign' => $sign, - ]; - } - } - - $dataHandler = $this->getDataHandler(); - $dataHandler->start($data, []); - $dataHandler->process_datamap(); - } - - /** - * @param ResponseInterface $response - * @return bool Returns true if response is valid or false in case of an error - */ - private function checkResponse(ResponseInterface $response): bool - { - if ($response->getStatusCode() !== 200 || (string)$response->getBody() === '') { - $this->logger->log( - LogLevel::ERROR, - WeatherUtility::translate('message.api_response_null', 'deutscherwetterdienst'), - ); - - return false; - } - - return true; - } -} diff --git a/Classes/Task/OpenWeatherMapTask.php b/Classes/Task/OpenWeatherMapTask.php deleted file mode 100644 index 13e4fb2..0000000 --- a/Classes/Task/OpenWeatherMapTask.php +++ /dev/null @@ -1,335 +0,0 @@ -logger->info(sprintf( - implode("\n", $logEntry), - date('m.d.Y - H:i:s', $this->getContextHandler()->getPropertyFromAspect('date', 'timestamp')), - json_encode($this), - )); - - $this->removeOldRecordsFromDb(); - - $this->url = sprintf( - 'https://api.openweathermap.org/data/2.5/weather?q=%s,%s&units=%s&APPID=%s', - urlencode($this->city), - urlencode($this->country), - 'metric', - $this->apiKey, - ); - try { - $response = $this->getRequestFactory()->request($this->url); - } catch (\Throwable $exception) { - $errorMessage = 'Exception while fetching data from API: ' . $exception->getMessage(); - $this->logger->error($errorMessage); - $this->sendMail( - 'Error while requesting weather data', - $errorMessage, - ); - return false; - } - if (!($this->checkResponseCode($response))) { - return false; - } - - $this->responseClass = json_decode((string)$response->getBody()); - $this->logger->info(sprintf('Response class: %s', json_encode($this->responseClass))); - - // Changing the data save to query builder - $this->saveCurrentWeatherInstanceForResponseClass($this->responseClass); - - if (!empty($this->clearCache)) { - $cacheService = $this->getCacheService(); - $cacheService->clearPageCache(GeneralUtility::intExplode(',', $this->clearCache)); - } - - return true; - } - - /** - * Checks the JSON response - * - * @param ResponseInterface $response - * @return bool Returns true if given data is valid or false in case of an error - */ - private function checkResponseCode(ResponseInterface $response): bool - { - if ($response->getStatusCode() === 401) { - $this->logger->error(WeatherUtility::translate('message.api_response_401', 'openweatherapi')); - $this->sendMail( - 'Error while requesting weather data', - WeatherUtility::translate('message.api_response_401', 'openweatherapi'), - ); - return false; - } - if ($response->getStatusCode() !== 200) { - $this->logger->error(WeatherUtility::translate('message.api_response_null', 'openweatherapi')); - $this->sendMail( - 'Error while requesting weather data', - WeatherUtility::translate('message.api_response_null', 'openweatherapi'), - ); - return false; - } - - /** @var \stdClass $responseClass */ - $responseClass = json_decode((string)$response->getBody(), false); - - switch ($responseClass->cod) { - case '200': - return true; - case '404': - $this->logger->error(WeatherUtility::translate('messages.api_code_404', 'openweatherapi')); - $this->sendMail( - 'Error while requesting weather data', - WeatherUtility::translate('messages.api_code_404', 'openweatherapi'), - ); - return false; - default: - $this->logger->error( - sprintf( - WeatherUtility::translate('messages.api_code_none', 'openweatherapi'), - (string)$response->getBody(), - ), - ); - $this->sendMail( - 'Error while requesting weather data', - sprintf(WeatherUtility::translate('messages.api_code_none', 'openweatherapi'), (string)$response->getBody()), - ); - return false; - } - } - - public function saveCurrentWeatherInstanceForResponseClass(\stdClass $responseClass): int - { - $weatherObjectArray = [ - 'pid' => $this->recordStoragePage, - 'name' => $this->name, - ]; - - if (isset($responseClass->main->temp)) { - $weatherObjectArray['temperature_c'] = (float)$responseClass->main->temp; - } - if (isset($responseClass->main->pressure)) { - $weatherObjectArray['pressure_hpa'] = (float)$responseClass->main->pressure; - } - if (isset($responseClass->main->humidity)) { - $weatherObjectArray['humidity_percentage'] = $responseClass->main->humidity; - } - if (isset($responseClass->main->temp_min)) { - $weatherObjectArray['min_temp_c'] = $responseClass->main->temp_min; - } - if (isset($responseClass->main->temp_max)) { - $weatherObjectArray['max_temp_c'] = $responseClass->main->temp_max; - } - if (isset($responseClass->wind->speed)) { - $weatherObjectArray['wind_speed_m_p_s'] = $responseClass->wind->speed; - } - if (isset($responseClass->wind->deg)) { - $weatherObjectArray['wind_speed_m_p_s'] = $responseClass->wind->deg; - } - if (isset($responseClass->rain)) { - $rain = (array)$responseClass->rain; - $weatherObjectArray['rain_volume'] = (float)($rain['1h'] ?? 0.0); - } - if (isset($responseClass->snow)) { - $snow = (array)$responseClass->snow; - $weatherObjectArray['snow_volume'] = (float)($snow['1h'] ?? 0.0); - } - if (isset($responseClass->clouds->all)) { - $weatherObjectArray['clouds_percentage'] = $responseClass->clouds->all; - } - if (isset($responseClass->dt)) { - $weatherObjectArray['measure_timestamp'] = $responseClass->dt; - } - if (isset($responseClass->weather[0]->icon)) { - $weatherObjectArray['icon'] = $responseClass->weather[0]->icon; - } - if (isset($responseClass->weather[0]->id)) { - $weatherObjectArray['condition_code'] = $responseClass->weather[0]->id; - } - - $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable('tx_weather2_domain_model_currentweather'); - return $queryBuilder - ->insert('tx_weather2_domain_model_currentweather') - ->values($weatherObjectArray) - ->executeStatement(); - } - - /** - * Sends a mail with $subject and $body to in task selected mail receiver. - */ - private function sendMail(string $subject, string $body): void - { - // only continue if notifications are enabled - if (!$this->errorNotification) { - return; - } - - $mail = $this->getMailMessageHandler(); - $fromAddress = ''; - $fromName = ''; - if (MailUtility::getSystemFromAddress()) { - $fromAddress = MailUtility::getSystemFromAddress(); - } - if (MailUtility::getSystemFrom()) { - $fromName = MailUtility::getSystemFromName(); - } - if ($this->emailSender) { - $fromAddress = $this->emailSender; - } - if ($this->emailSenderName) { - $fromName = $this->emailSenderName; - } - - if ($fromAddress && $fromName && $this->emailReceiver) { - $from = [$fromAddress => $fromName]; - } else { - $this->logger->error( - ($this->emailReceiver === '' ? 'E-Mail receiver address is missing ' : '') . - ($fromAddress === '' ? 'E-Mail sender address ' : '') . - ($fromName === '' ? 'E-Mail sender name is missing' : ''), - ); - - return; - } - - $mail->setSubject($subject)->setFrom($from)->setTo([$this->emailReceiver]); - $mail->text($body); - $mail->send(); - - if ($mail->isSent()) { - $this->logger->notice('Notification mail sent!'); - - return; - } - - $this->logger->error('Notification mail not sent because of an error!'); - } - - protected function removeOldRecordsFromDb(): void - { - $connection = $this->getConnectionPool() - ->getConnectionForTable($this->dbExtTable); - - $connection->delete( - $this->dbExtTable, - [ - 'pid' => $this->recordStoragePage, - 'name' => $this->name, - ], - ); - } -} diff --git a/Classes/Task/OpenWeatherMapTaskAdditionalFieldProvider.php b/Classes/Task/OpenWeatherMapTaskAdditionalFieldProvider.php deleted file mode 100644 index a67af4a..0000000 --- a/Classes/Task/OpenWeatherMapTaskAdditionalFieldProvider.php +++ /dev/null @@ -1,353 +0,0 @@ - $requiredFields - */ - protected array $requiredFields = [ - 'name', - 'city', - 'country', - 'apiKey', - ]; - - /** - * Fields to insert from task if empty - * - * @var array $insertFields - */ - protected array $insertFields = [ - 'name', - 'city', - 'country', - 'apiKey', - 'clearCache', - 'errorNotification', - 'emailSenderName', - 'emailSender', - 'emailReceiver', - 'recordStoragePage', - ]; - - public function __construct( - CountryProvider $countryProvider, - UriBuilder $uriBuilder, - PageRenderer $pageRenderer, - ) { - $this->countryProvider = $countryProvider; - $this->uriBuilder = $uriBuilder; - $this->pageRenderer = $pageRenderer; - } - - /** - * @param OpenWeatherMapTask|null $task - * @param array $taskInfo - * - * @return array - * @throws RouteNotFoundException - */ - public function getAdditionalFields( - array &$taskInfo, - $task, - SchedulerModuleController $schedulerModule, - ): array { - $this->pageRenderer->loadJavaScriptModule('TYPO3/CMS/Backend/FormEngineValidation'); - $this->pageRenderer->loadJavaScriptModule('TYPO3/CMS/Weather2/OpenWeatherMapTaskModule'); - $popupSettings = [ - 'PopupWindow' => [ - 'width' => '800px', - 'height' => '550px', - ], - ]; - $this->pageRenderer->addInlineSettingArray('Popup', $popupSettings); - $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', (string)$this->uriBuilder->buildUriFromRoute('record_edit')); - $this->pageRenderer->addInlineSetting('FormEngine', 'formName', 'tx_scheduler_form'); - $this->pageRenderer->addInlineSetting('FormEngine', 'backPath', ''); - $this->pageRenderer->loadJavaScriptModule( - 'TYPO3/CMS/Backend/FormEngine', - ); - - foreach ($this->insertFields as $fieldID) { - if (empty($taskInfo[$fieldID])) { - if ($task instanceof OpenWeatherMapTask) { - $taskInfo[$fieldID] = $task->$fieldID; - } else { - $taskInfo[$fieldID] = ''; - } - } - } - - $additionalFields = []; - - $fieldID = 'name'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:name', - ]; - - $fieldID = 'recordStoragePage'; - $fieldCode = '
    '; - - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:record_storage_page', - ]; - - // todo: Add second task to import regions with id from OpenWeatherMap-Servers like DeutschWetterDienstTask - $fieldID = 'city'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:city', - ]; - - $fieldID = 'country'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:country', - ]; - - $fieldID = 'apiKey'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:api_key', - ]; - - $fieldID = 'clearCache'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:clear_cache', - ]; - - $fieldID = 'errorNotification'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:error_notification', - ]; - - $fieldID = 'mailConfig'; - $fieldCode = $this->checkMailConfiguration(); - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:mail_config', - ]; - - $fieldID = 'emailSenderName'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:email_sendername', - ]; - - $fieldID = 'emailSender'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:email_sender', - ]; - - $fieldID = 'emailReceiver'; - $fieldCode = ''; - $additionalFields[$fieldID] = [ - 'code' => $fieldCode, - 'label' => 'LLL:EXT:weather2/Resources/Private/Language/locallang_scheduler_openweatherapi.xlf:email_receiver', - ]; - - return $additionalFields; - } - - /** - * @param array $submittedData - */ - public function validateAdditionalFields( - array &$submittedData, - SchedulerModuleController $schedulerModule, - ): bool { - $isValid = true; - - if ($submittedData['recordStoragePage']) { - $submittedData['recordStoragePage'] = preg_replace('/\D/', '', $submittedData['recordStoragePage']); - } else { - $submittedData['recordStoragePage'] = 0; - } - - foreach ($submittedData as $fieldName => $field) { - $value = is_string($field) ? trim($field) : $field; - if (empty($value) && in_array($fieldName, $this->requiredFields, true)) { - $isValid = false; - $this->addMessage('Field: ' . $fieldName . ' can not be empty', ContextualFeedbackSeverity::ERROR); - } else { - $submittedData[$fieldName] = $value; - } - } - - $isValidResponseCode = $this->isValidResponseCode( - $submittedData['city'], - $submittedData['country'], - $submittedData['apiKey'], - ); - - if (!$isValidResponseCode) { - return false; - } - - return $isValid; - } - - private function isValidResponseCode( - string $city, - string $country, - string $apiKey, - ): bool { - $url = sprintf( - 'https://api.openweathermap.org/data/2.5/weather?q=%s,%s&units=%s&APPID=%s', - urlencode($city), - urlencode($country), - 'metric', - $apiKey, - ); - - $response = GeneralUtility::makeInstance(RequestFactory::class)->request($url); - if ($response->getStatusCode() === 401) { - $this->addMessage( - WeatherUtility::translate('message.api_response_401', 'openweatherapi'), - ContextualFeedbackSeverity::ERROR, - ); - return false; - } - if ($response->getStatusCode() === 404) { - $this->addMessage( - WeatherUtility::translate('message.api_code_404', 'openweatherapi'), - ContextualFeedbackSeverity::ERROR, - ); - return false; - } - if ($response->getStatusCode() !== 200) { - $this->addMessage( - WeatherUtility::translate('message.api_response_null', 'openweatherapi'), - ContextualFeedbackSeverity::ERROR, - ); - return false; - } - - $responseClass = json_decode((string)$response->getBody(), false); - switch ($responseClass->cod) { - case '200': - $this->addMessage(sprintf( - WeatherUtility::translate('message.api_code_200', 'openweatherapi'), - $responseClass->name, - $responseClass->sys->country, - ), ContextualFeedbackSeverity::INFO); - return true; - case '404': - $this->addMessage( - WeatherUtility::translate('message.api_code_404', 'openweatherapi'), - ContextualFeedbackSeverity::ERROR, - ); - return false; - default: - $this->addMessage(sprintf( - WeatherUtility::translate('message.api_code_none', 'openweatherapi'), - json_encode($responseClass), - ), ContextualFeedbackSeverity::ERROR); - return false; - } - } - - /** - * @param array $submittedData - */ - public function saveAdditionalFields(array $submittedData, AbstractTask $task): void - { - /** @var OpenWeatherMapTask $task */ - $task->name = $submittedData['name'] ?? ''; - $task->city = $submittedData['city'] ?? ''; - $task->recordStoragePage = (int)($submittedData['recordStoragePage'] ?? 0); - $task->country = $submittedData['country'] ?? ''; - $task->apiKey = $submittedData['apiKey'] ?? ''; - $task->clearCache = $submittedData['clearCache'] ?? '0'; - $task->errorNotification = $submittedData['errorNotification'] ?? ''; - $task->emailSenderName = $submittedData['emailSenderName'] ?? ''; - $task->emailSender = $submittedData['emailSender'] ?? ''; - $task->emailReceiver = $submittedData['emailReceiver'] ?? ''; - } - - private function checkMailConfiguration(): string - { - $text = ''; - $mailConfiguration = $GLOBALS['TYPO3_CONF_VARS']['MAIL']; - - $text .= ''; - $text .= '

    Transport: ' . $mailConfiguration['transport'] . '

    '; - if ($mailConfiguration['transport'] === 'smtp') { - $text .= '

    SMTP Server: ' . $mailConfiguration['transport_smtp_server'] . '

    SMTP Encryption: ' . $mailConfiguration['transport_smtp_encrypt'] . '

    SMTP Username: ' . $mailConfiguration['transport_smtp_username'] . '

    '; - } - - return $text; - } - - /** - * Returns an array with country codes and corresponding names - */ - private function getCountryCodesOptionsHtml(string $selected = ''): string - { - $languageService = GeneralUtility::makeInstance(LanguageServiceFactory::class) - ->createFromUserPreferences($GLOBALS['BE_USER']); - /** @var Country[] $countries */ - $countries = $this->countryProvider->getAll(); - $options = []; - foreach ($countries as $country) { - $options[] = sprintf( - '%s (%s)', - // check 2 and 3 digit country code for compatibility reasons - $selected === $country->getAlpha2IsoCode() || $selected === $country->getAlpha3IsoCode() ? ' selected' : '', - $country->getAlpha2IsoCode(), - $languageService->sL($country->getLocalizedNameLabel()), - $country->getAlpha2IsoCode(), - ); - } - - return implode('', $options); - } -} diff --git a/Classes/Task/WeatherAbstractTask.php b/Classes/Task/WeatherAbstractTask.php deleted file mode 100644 index 9e2e3d2..0000000 --- a/Classes/Task/WeatherAbstractTask.php +++ /dev/null @@ -1,69 +0,0 @@ - [ - 'path' => '/weather2/dwd/warn-cell-search', - 'target' => \JWeiland\Weather2\Ajax\DeutscherWetterdienstWarnCellSearch::class . '::renderWarnCells', - ], -]; diff --git a/Configuration/Icons.php b/Configuration/Icons.php index 77795ad..d1b8e72 100644 --- a/Configuration/Icons.php +++ b/Configuration/Icons.php @@ -8,6 +8,14 @@ 'provider' => SvgIconProvider::class, 'source' => 'EXT:weather2/Resources/Public/Icons/Extension.svg', ], + 'plugin-current-weather' => [ + 'provider' => BitmapIconProvider::class, + 'source' => 'EXT:weather2/Resources/Public/Icons/plugin_weather.png', + ], + 'plugin-weather-alert' => [ + 'provider' => BitmapIconProvider::class, + 'source' => 'EXT:weather2/Resources/Public/Icons/plugin_alert.png', + ], 'ext-weather2-table-currentweather' => [ 'provider' => BitmapIconProvider::class, 'source' => 'EXT:weather2/Resources/Public/Icons/tx_weather2_domain_model_currentweather.gif', diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index bb96ce2..6601b10 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -1,5 +1,5 @@ imports: - - { resource: Backend/DashboardWidgets.yaml } + - {resource: Backend/DashboardWidgets.yaml} services: _defaults: @@ -21,3 +21,22 @@ services: # Will be called by GeneralUtility in ScheduleModuleController JWeiland\Weather2\Task\OpenWeatherMapTaskAdditionalFieldProvider: public: true + + # Symfony Commands + JWeiland\Weather2\Command\DeutscherWetterdienstCommand: + tags: + - name: console.command + command: 'weather2:fetch:deutscherWetterdienstAPI' + description: 'Calls the Deutscher Wetterdienst api and saves response in weather2 format into database' + + JWeiland\Weather2\Command\DeutscherWetterdienstWarnCellCommand: + tags: + - name: console.command + command: 'weather2:fetch:warnCellsFromDeutscherWetterdienstAPI' + description: 'Calls the Deutscher Wetterdienst api and saves warn cells into database. Required before using DeutscherWetterdienstTask!' + + JWeiland\Weather2\Command\OpenWeatherMapCommand: + tags: + - name: console.command + command: 'weather2:fetch:fromOpenWeatherAPI' + description: 'Calls the api of openweathermap.org and saves response into database.' diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php index 0139f9f..3c2b4da 100644 --- a/Configuration/TCA/Overrides/tt_content.php +++ b/Configuration/TCA/Overrides/tt_content.php @@ -7,28 +7,26 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Extbase\Utility\ExtensionUtility; -(static function (): void { - ExtensionUtility::registerPlugin( - 'Weather2', - 'Currentweather', - 'LLL:EXT:weather2/Resources/Private/Language/locallang_db.xlf:plugin.currentweather.title', - ); +ExtensionUtility::registerPlugin( + 'Weather2', + 'Currentweather', + 'LLL:EXT:weather2/Resources/Private/Language/locallang_db.xlf:plugin.currentweather.title', +); - ExtensionUtility::registerPlugin( - 'Weather2', - 'Weatheralert', - 'LLL:EXT:weather2/Resources/Private/Language/locallang_db.xlf:plugin.weatheralert.title', - ); +ExtensionUtility::registerPlugin( + 'Weather2', + 'Weatheralert', + 'LLL:EXT:weather2/Resources/Private/Language/locallang_db.xlf:plugin.weatheralert.title', +); - $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['weather2_currentweather'] = 'pi_flexform'; - ExtensionManagementUtility::addPiFlexFormValue( - 'weather2_currentweather', - 'FILE:EXT:weather2/Configuration/FlexForms/flexform_currentweather.xml', - ); +$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['weather2_currentweather'] = 'pi_flexform'; +ExtensionManagementUtility::addPiFlexFormValue( + 'weather2_currentweather', + 'FILE:EXT:weather2/Configuration/FlexForms/flexform_currentweather.xml', +); - $GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['weather2_weatheralert'] = 'pi_flexform'; - ExtensionManagementUtility::addPiFlexFormValue( - 'weather2_weatheralert', - 'FILE:EXT:weather2/Configuration/FlexForms/flexform_weatheralert.xml', - ); -})(); +$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_addlist']['weather2_weatheralert'] = 'pi_flexform'; +ExtensionManagementUtility::addPiFlexFormValue( + 'weather2_weatheralert', + 'FILE:EXT:weather2/Configuration/FlexForms/flexform_weatheralert.xml', +); diff --git a/Configuration/TSconfig/ContentElementWizard.tsconfig b/Configuration/TSconfig/ContentElementWizard.tsconfig index 66ced8a..0763b2f 100644 --- a/Configuration/TSconfig/ContentElementWizard.tsconfig +++ b/Configuration/TSconfig/ContentElementWizard.tsconfig @@ -1,7 +1,7 @@ mod.wizards.newContentElement.wizardItems.plugins { elements { weather2_current { - iconIdentifier = ext-weather2 + iconIdentifier = plugin-current-weather title = LLL:EXT:weather2/Resources/Private/Language/locallang_db.xlf:plugin.currentweather.title description = LLL:EXT:weather2/Resources/Private/Language/locallang_db.xlf:plugin.currentweather.descriptiom tt_content_defValues { @@ -10,7 +10,7 @@ mod.wizards.newContentElement.wizardItems.plugins { } } weather2_alert { - iconIdentifier = ext-weather2 + iconIdentifier = plugin-weather-alert title = LLL:EXT:weather2/Resources/Private/Language/locallang_db.xlf:plugin.weatheralert.title description = LLL:EXT:weather2/Resources/Private/Language/locallang_db.xlf:plugin.weatheralert.descriptiom tt_content_defValues { diff --git a/Documentation/AdministratorManual/Upgrade/Index.rst b/Documentation/AdministratorManual/Upgrade/Index.rst index 8e5c761..cffa026 100644 --- a/Documentation/AdministratorManual/Upgrade/Index.rst +++ b/Documentation/AdministratorManual/Upgrade/Index.rst @@ -7,6 +7,11 @@ Upgrade If you upgrade EXT:weather2 to a newer version, please read this section carefully! +Update from 5.x to 6.0 +====================== +Added TYPO3 13 Compatibility +Removed all schedulers and replaced with Symfony Console Commands. + Update from 4.x to 5.0 ====================== diff --git a/Documentation/Introduction/Index.rst b/Documentation/Introduction/Index.rst index acd8316..b77792f 100644 --- a/Documentation/Introduction/Index.rst +++ b/Documentation/Introduction/Index.rst @@ -17,10 +17,12 @@ Currently the source code is available at `Github + + + + + + + + diff --git a/Resources/Public/Icons/plugin_alert.png b/Resources/Public/Icons/plugin_alert.png new file mode 100644 index 0000000..3567b01 Binary files /dev/null and b/Resources/Public/Icons/plugin_alert.png differ diff --git a/Resources/Public/Icons/plugin_weather.png b/Resources/Public/Icons/plugin_weather.png new file mode 100644 index 0000000..d1df2c5 Binary files /dev/null and b/Resources/Public/Icons/plugin_weather.png differ diff --git a/Resources/Public/JavaScript/DeutscherWetterdienstTaskModule.js b/Resources/Public/JavaScript/DeutscherWetterdienstTaskModule.js deleted file mode 100644 index ea4d4cf..0000000 --- a/Resources/Public/JavaScript/DeutscherWetterdienstTaskModule.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Module: TYPO3/CMS/Weather2/DeutscherWetterdienstTaskModule - * Object that replace pages_ID with ID - */ -define('TYPO3/CMS/Weather2/DeutscherWetterdienstTaskModule', ['jquery'], function ($) { - $(document).ready(function () { - $('#dwd_recordStoragePage').change(function () { - $(this).val($(this).val().replace(/[^0-9]/g, '')); - }); - - $('#dwd_warn_cell_search').autocomplete({ - serviceUrl: TYPO3.settings.ajaxUrls['weather2_dwd_warn-cell-search'], - dataType: 'json', - minChars: 3, - onSelect: function (suggestion) { - if (!$('#dwd_warnCellItem_' + suggestion.data).length) { - $('#dwd_selected_warn_cells_ul').append('
  • ' + TYPO3.lang.removeItem + '' + suggestion.value + '
  • '); - $('#dwd_warnCellItem_' + suggestion.data + ' .dwd_removeItem').click(function () { - $(this).parent('li').remove(); - }); - } - } - }).keypress(function (e) { - let code = (e.keyCode ? e.keyCode : e.which); - if (code == 13) { - return false; - } - }); - - $('.dwd_removeItem').click(function () { - $(this).parent('li').remove(); - }); - }); -}); diff --git a/Resources/Public/JavaScript/OpenWeatherMapTaskModule.js b/Resources/Public/JavaScript/OpenWeatherMapTaskModule.js deleted file mode 100644 index 684c52e..0000000 --- a/Resources/Public/JavaScript/OpenWeatherMapTaskModule.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Module: TYPO3/CMS/Weather2/OpenWeatherMapTaskModule - * JavaScript to show/hide fields in scheduler - */ -define('TYPO3/CMS/Weather2/OpenWeatherMapTaskModule', ['jquery'], function ($) { - $(document).ready(function () { - $('#recordStoragePage').change(function () { - $(this).val($(this).val().replace(/[^0-9]/g, '')); - }); - - let errorNotificationFields = ['mailConfig_row', 'emailSenderName_row', 'emailSender_row', 'emailReceiver_row']; - - $('#errorNotification').click(function () { - toggleErrorNotificationFields(); - }); - - toggleErrorNotificationFields(); - - function toggleErrorNotificationFields () { - if ($('#errorNotification').is(':checked')) { - setDisplayAttributeOfElements('', errorNotificationFields); - } else { - setDisplayAttributeOfElements('none', errorNotificationFields); - } - } - - /** - * Sets the display property for each element in array elements - * - * @param display display property value from css (e.g. block or none) - * @param elements array of all elements ['first_element', 'second_element'] - */ - function setDisplayAttributeOfElements (display, elements) { - $(elements).each(function (index, value) { - $('#' + value).css('display', display); - }); - } - }); -}); diff --git a/Resources/Public/JavaScript/jquery.autocomplete.js b/Resources/Public/JavaScript/jquery.autocomplete.js deleted file mode 100644 index a7fc72d..0000000 --- a/Resources/Public/JavaScript/jquery.autocomplete.js +++ /dev/null @@ -1,974 +0,0 @@ -/** - * Ajax Autocomplete for jQuery, version %version% - * (c) 2015 Tomas Kirda - * - * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. - * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete - */ -// Expose plugin as an AMD module if AMD loader is present: -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // AMD. Register as an anonymous module. - define(['jquery'], factory); - } else if (typeof exports === 'object' && typeof require === 'function') { - // Browserify - factory(require('jquery')); - } else { - // Browser globals - factory(jQuery); - } -}(function ($) { - 'use strict'; - - let - utils = (function () { - return { - escapeRegExChars: function (value) { - return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - }, - createNode: function (containerClass) { - let div = document.createElement('div'); - div.className = containerClass; - div.style.position = 'absolute'; - div.style.display = 'none'; - return div; - } - }; - }()), - - keys = { - ESC: 27, - TAB: 9, - RETURN: 13, - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40 - }; - - function Autocomplete (el, options) { - let noop = function () { }, - that = this, - defaults = { - ajaxSettings: {}, - autoSelectFirst: false, - appendTo: document.body, - serviceUrl: null, - lookup: null, - onSelect: null, - width: 'auto', - minChars: 1, - maxHeight: 300, - deferRequestBy: 0, - params: {}, - formatResult: Autocomplete.formatResult, - delimiter: null, - zIndex: 9999, - type: 'GET', - noCache: false, - onSearchStart: noop, - onSearchComplete: noop, - onSearchError: noop, - preserveInput: false, - containerClass: 'autocomplete-suggestions', - tabDisabled: false, - dataType: 'text', - currentRequest: null, - triggerSelectOnValidInput: true, - preventBadQueries: true, - lookupFilter: function (suggestion, originalQuery, queryLowerCase) { - return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; - }, - paramName: 'query', - transformResult: function (response) { - return typeof response === 'string' ? $.parseJSON(response) : response; - }, - showNoSuggestionNotice: false, - noSuggestionNotice: 'No results', - orientation: 'bottom', - forceFixPosition: false - }; - - // Shared variables: - that.element = el; - that.el = $(el); - that.suggestions = []; - that.badQueries = []; - that.selectedIndex = -1; - that.currentValue = that.element.value; - that.intervalId = 0; - that.cachedResponse = {}; - that.onChangeInterval = null; - that.onChange = null; - that.isLocal = false; - that.suggestionsContainer = null; - that.noSuggestionsContainer = null; - that.options = $.extend({}, defaults, options); - that.classes = { - selected: 'autocomplete-selected', - suggestion: 'autocomplete-suggestion' - }; - that.hint = null; - that.hintValue = ''; - that.selection = null; - - // Initialize and set options: - that.initialize(); - that.setOptions(options); - } - - Autocomplete.utils = utils; - - $.Autocomplete = Autocomplete; - - Autocomplete.formatResult = function (suggestion, currentValue) { - let pattern = '(' + utils.escapeRegExChars(currentValue) + ')'; - - return suggestion.value - .replace(new RegExp(pattern, 'gi'), '$1<\/strong>') - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/<(\/?strong)>/g, '<$1>'); - }; - - Autocomplete.prototype = { - - killerFn: null, - - initialize: function () { - let that = this, - suggestionSelector = '.' + that.classes.suggestion, - selected = that.classes.selected, - options = that.options, - container; - - // Remove autocomplete attribute to prevent native suggestions: - that.element.setAttribute('autocomplete', 'off'); - - that.killerFn = function (e) { - if ($(e.target).closest('.' + that.options.containerClass).length === 0) { - that.killSuggestions(); - that.disableKillerFn(); - } - }; - - // html() deals with many types: htmlString or Element or Array or jQuery - that.noSuggestionsContainer = $('
    ') - .html(this.options.noSuggestionNotice).get(0); - - that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass); - - container = $(that.suggestionsContainer); - - container.appendTo(options.appendTo); - - // Only set width if it was provided: - if (options.width !== 'auto') { - container.width(options.width); - } - - // Listen for mouse over event on suggestions list: - container.on('mouseover.autocomplete', suggestionSelector, function () { - that.activate($(this).data('index')); - }); - - // Deselect active element when mouse leaves suggestions container: - container.on('mouseout.autocomplete', function () { - that.selectedIndex = -1; - container.children('.' + selected).removeClass(selected); - }); - - // Listen for click event on suggestions list: - container.on('click.autocomplete', suggestionSelector, function () { - that.select($(this).data('index')); - }); - - that.fixPositionCapture = function () { - if (that.visible) { - that.fixPosition(); - } - }; - - $(window).on('resize.autocomplete', that.fixPositionCapture); - - that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); }); - that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); }); - that.el.on('blur.autocomplete', function () { that.onBlur(); }); - that.el.on('focus.autocomplete', function () { that.onFocus(); }); - that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); }); - that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); }); - }, - - onFocus: function () { - let that = this; - that.fixPosition(); - if (that.options.minChars === 0 && that.el.val().length === 0) { - that.onValueChange(); - } - }, - - onBlur: function () { - this.enableKillerFn(); - }, - - abortAjax: function () { - let that = this; - if (that.currentRequest) { - that.currentRequest.abort(); - that.currentRequest = null; - } - }, - - setOptions: function (suppliedOptions) { - let that = this, - options = that.options; - - $.extend(options, suppliedOptions); - - that.isLocal = $.isArray(options.lookup); - - if (that.isLocal) { - options.lookup = that.verifySuggestionsFormat(options.lookup); - } - - options.orientation = that.validateOrientation(options.orientation, 'bottom'); - - // Adjust height, width and z-index: - $(that.suggestionsContainer).css({ - 'max-height': options.maxHeight + 'px', - 'width': options.width + 'px', - 'z-index': options.zIndex - }); - }, - - clearCache: function () { - this.cachedResponse = {}; - this.badQueries = []; - }, - - clear: function () { - this.clearCache(); - this.currentValue = ''; - this.suggestions = []; - }, - - disable: function () { - let that = this; - that.disabled = true; - clearInterval(that.onChangeInterval); - that.abortAjax(); - }, - - enable: function () { - this.disabled = false; - }, - - fixPosition: function () { - // Use only when container has already its content - - let that = this, - $container = $(that.suggestionsContainer), - containerParent = $container.parent().get(0); - // Fix position automatically when appended to body. - // In other cases force parameter must be given. - if (containerParent !== document.body && !that.options.forceFixPosition) { - return; - } - - // Choose orientation - let orientation = that.options.orientation, - containerHeight = $container.outerHeight(), - height = that.el.outerHeight(), - offset = that.el.offset(), - styles = { 'top': offset.top, 'left': offset.left }; - - if (orientation === 'auto') { - let viewPortHeight = $(window).height(), - scrollTop = $(window).scrollTop(), - topOverflow = -scrollTop + offset.top - containerHeight, - bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight); - - orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom'; - } - - if (orientation === 'top') { - styles.top += -containerHeight; - } else { - styles.top += height; - } - - // If container is not positioned to body, - // correct its position using offset parent offset - if (containerParent !== document.body) { - let opacity = $container.css('opacity'), - parentOffsetDiff; - - if (!that.visible) { - $container.css('opacity', 0).show(); - } - - parentOffsetDiff = $container.offsetParent().offset(); - styles.top -= parentOffsetDiff.top; - styles.left -= parentOffsetDiff.left; - - if (!that.visible) { - $container.css('opacity', opacity).hide(); - } - } - - // -2px to account for suggestions border. - if (that.options.width === 'auto') { - styles.width = (that.el.outerWidth() - 2) + 'px'; - } - - $container.css(styles); - }, - - enableKillerFn: function () { - let that = this; - $(document).on('click.autocomplete', that.killerFn); - }, - - disableKillerFn: function () { - let that = this; - $(document).off('click.autocomplete', that.killerFn); - }, - - killSuggestions: function () { - let that = this; - that.stopKillSuggestions(); - that.intervalId = window.setInterval(function () { - if (that.visible) { - that.el.val(that.currentValue); - that.hide(); - } - - that.stopKillSuggestions(); - }, 50); - }, - - stopKillSuggestions: function () { - window.clearInterval(this.intervalId); - }, - - isCursorAtEnd: function () { - let that = this, - valLength = that.el.val().length, - selectionStart = that.element.selectionStart, - range; - - if (typeof selectionStart === 'number') { - return selectionStart === valLength; - } - if (document.selection) { - range = document.selection.createRange(); - range.moveStart('character', -valLength); - return valLength === range.text.length; - } - return true; - }, - - onKeyPress: function (e) { - let that = this; - - // If suggestions are hidden and user presses arrow down, display suggestions: - if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) { - that.suggest(); - return; - } - - if (that.disabled || !that.visible) { - return; - } - - switch (e.which) { - case keys.ESC: - that.el.val(that.currentValue); - that.hide(); - break; - case keys.RIGHT: - if (that.hint && that.options.onHint && that.isCursorAtEnd()) { - that.selectHint(); - break; - } - return; - case keys.TAB: - if (that.hint && that.options.onHint) { - that.selectHint(); - return; - } - if (that.selectedIndex === -1) { - that.hide(); - return; - } - that.select(that.selectedIndex); - if (that.options.tabDisabled === false) { - return; - } - break; - case keys.RETURN: - if (that.selectedIndex === -1) { - that.hide(); - return; - } - that.select(that.selectedIndex); - break; - case keys.UP: - that.moveUp(); - break; - case keys.DOWN: - that.moveDown(); - break; - default: - return; - } - - // Cancel event if function did not return: - e.stopImmediatePropagation(); - e.preventDefault(); - }, - - onKeyUp: function (e) { - let that = this; - - if (that.disabled) { - return; - } - - switch (e.which) { - case keys.UP: - case keys.DOWN: - return; - } - - clearInterval(that.onChangeInterval); - - if (that.currentValue !== that.el.val()) { - that.findBestHint(); - if (that.options.deferRequestBy > 0) { - // Defer lookup in case when value changes very quickly: - that.onChangeInterval = setInterval(function () { - that.onValueChange(); - }, that.options.deferRequestBy); - } else { - that.onValueChange(); - } - } - }, - - onValueChange: function () { - let that = this, - options = that.options, - value = that.el.val(), - query = that.getQuery(value); - - if (that.selection && that.currentValue !== query) { - that.selection = null; - (options.onInvalidateSelection || $.noop).call(that.element); - } - - clearInterval(that.onChangeInterval); - that.currentValue = value; - that.selectedIndex = -1; - - // Check existing suggestion for the match before proceeding: - if (options.triggerSelectOnValidInput && that.isExactMatch(query)) { - that.select(0); - return; - } - - if (query.length < options.minChars) { - that.hide(); - } else { - that.getSuggestions(query); - } - }, - - isExactMatch: function (query) { - let suggestions = this.suggestions; - - return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase()); - }, - - getQuery: function (value) { - let delimiter = this.options.delimiter, - parts; - - if (!delimiter) { - return value; - } - parts = value.split(delimiter); - return $.trim(parts[parts.length - 1]); - }, - - getSuggestionsLocal: function (query) { - let that = this, - options = that.options, - queryLowerCase = query.toLowerCase(), - filter = options.lookupFilter, - limit = parseInt(options.lookupLimit, 10), - data; - - data = { - suggestions: $.grep(options.lookup, function (suggestion) { - return filter(suggestion, query, queryLowerCase); - }) - }; - - if (limit && data.suggestions.length > limit) { - data.suggestions = data.suggestions.slice(0, limit); - } - - return data; - }, - - getSuggestions: function (q) { - let response, - that = this, - options = that.options, - serviceUrl = options.serviceUrl, - params, - cacheKey, - ajaxSettings; - - options.params[options.paramName] = q; - params = options.ignoreParams ? null : options.params; - - if (options.onSearchStart.call(that.element, options.params) === false) { - return; - } - - if ($.isFunction(options.lookup)) { - options.lookup(q, function (data) { - that.suggestions = data.suggestions; - that.suggest(); - options.onSearchComplete.call(that.element, q, data.suggestions); - }); - return; - } - - if (that.isLocal) { - response = that.getSuggestionsLocal(q); - } else { - if ($.isFunction(serviceUrl)) { - serviceUrl = serviceUrl.call(that.element, q); - } - cacheKey = serviceUrl + '?' + $.param(params || {}); - response = that.cachedResponse[cacheKey]; - } - - if (response && $.isArray(response.suggestions)) { - that.suggestions = response.suggestions; - that.suggest(); - options.onSearchComplete.call(that.element, q, response.suggestions); - } else if (!that.isBadQuery(q)) { - that.abortAjax(); - - ajaxSettings = { - url: serviceUrl, - data: params, - type: options.type, - dataType: options.dataType - }; - - $.extend(ajaxSettings, options.ajaxSettings); - - that.currentRequest = $.ajax(ajaxSettings).done(function (data) { - let result; - that.currentRequest = null; - result = options.transformResult(data, q); - that.processResponse(result, q, cacheKey); - options.onSearchComplete.call(that.element, q, result.suggestions); - }).fail(function (jqXHR, textStatus, errorThrown) { - options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown); - }); - } else { - options.onSearchComplete.call(that.element, q, []); - } - }, - - isBadQuery: function (q) { - if (!this.options.preventBadQueries) { - return false; - } - - let badQueries = this.badQueries, - i = badQueries.length; - - while (i--) { - if (q.indexOf(badQueries[i]) === 0) { - return true; - } - } - - return false; - }, - - hide: function () { - let that = this, - container = $(that.suggestionsContainer); - - if ($.isFunction(that.options.onHide) && that.visible) { - that.options.onHide.call(that.element, container); - } - - that.visible = false; - that.selectedIndex = -1; - clearInterval(that.onChangeInterval); - $(that.suggestionsContainer).hide(); - that.signalHint(null); - }, - - suggest: function () { - if (this.suggestions.length === 0) { - if (this.options.showNoSuggestionNotice) { - this.noSuggestions(); - } else { - this.hide(); - } - return; - } - - let that = this, - options = that.options, - groupBy = options.groupBy, - formatResult = options.formatResult, - value = that.getQuery(that.currentValue), - className = that.classes.suggestion, - classSelected = that.classes.selected, - container = $(that.suggestionsContainer), - noSuggestionsContainer = $(that.noSuggestionsContainer), - beforeRender = options.beforeRender, - html = '', - category, - formatGroup = typeof options.formatGroup == 'function' ? options.formatGroup : function (suggestion, index) { - let currentCategory = suggestion.data[groupBy]; - - if (category === currentCategory) { - return ''; - } - - category = currentCategory; - - return '
    ' + category + '
    '; - }; - - if (options.triggerSelectOnValidInput && that.isExactMatch(value)) { - that.select(0); - return; - } - - // Build suggestions inner HTML: - $.each(that.suggestions, function (i, suggestion) { - if (groupBy) { - html += formatGroup(suggestion, value, i); - } - - html += '
    ' + formatResult(suggestion, value) + '
    '; - }); - - this.adjustContainerWidth(); - - noSuggestionsContainer.detach(); - container.html(html); - - if ($.isFunction(beforeRender)) { - beforeRender.call(that.element, container); - } - - that.fixPosition(); - container.show(); - - // Select first value by default: - if (options.autoSelectFirst) { - that.selectedIndex = 0; - container.scrollTop(0); - container.children('.' + className).first().addClass(classSelected); - } - - that.visible = true; - that.findBestHint(); - }, - - noSuggestions: function () { - let that = this, - container = $(that.suggestionsContainer), - noSuggestionsContainer = $(that.noSuggestionsContainer); - - this.adjustContainerWidth(); - - // Some explicit steps. Be careful here as it easy to get - // noSuggestionsContainer removed from DOM if not detached properly. - noSuggestionsContainer.detach(); - container.empty(); // clean suggestions if any - container.append(noSuggestionsContainer); - - that.fixPosition(); - - container.show(); - that.visible = true; - }, - - adjustContainerWidth: function () { - let that = this, - options = that.options, - width, - container = $(that.suggestionsContainer); - - // If width is auto, adjust width before displaying suggestions, - // because if instance was created before input had width, it will be zero. - // Also it adjusts if input width has changed. - // -2px to account for suggestions border. - if (options.width === 'auto') { - width = that.el.outerWidth() - 2; - container.width(width > 0 ? width : 300); - } - }, - - findBestHint: function () { - let that = this, - value = that.el.val().toLowerCase(), - bestMatch = null; - - if (!value) { - return; - } - - $.each(that.suggestions, function (i, suggestion) { - let foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0; - if (foundMatch) { - bestMatch = suggestion; - } - return !foundMatch; - }); - - that.signalHint(bestMatch); - }, - - signalHint: function (suggestion) { - let hintValue = '', - that = this; - if (suggestion) { - hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length); - } - if (that.hintValue !== hintValue) { - that.hintValue = hintValue; - that.hint = suggestion; - (this.options.onHint || $.noop)(hintValue); - } - }, - - verifySuggestionsFormat: function (suggestions) { - // If suggestions is string array, convert them to supported format: - if (suggestions.length && typeof suggestions[0] === 'string') { - return $.map(suggestions, function (value) { - return { value: value, data: null }; - }); - } - - return suggestions; - }, - - validateOrientation: function (orientation, fallback) { - orientation = $.trim(orientation || '').toLowerCase(); - - if ($.inArray(orientation, ['auto', 'bottom', 'top']) === -1) { - orientation = fallback; - } - - return orientation; - }, - - processResponse: function (result, originalQuery, cacheKey) { - let that = this, - options = that.options; - - result.suggestions = that.verifySuggestionsFormat(result.suggestions); - - // Cache results if cache is not disabled: - if (!options.noCache) { - that.cachedResponse[cacheKey] = result; - if (options.preventBadQueries && result.suggestions.length === 0) { - that.badQueries.push(originalQuery); - } - } - - // Return if originalQuery is not matching current query: - if (originalQuery !== that.getQuery(that.currentValue)) { - return; - } - - that.suggestions = result.suggestions; - that.suggest(); - }, - - activate: function (index) { - let that = this, - activeItem, - selected = that.classes.selected, - container = $(that.suggestionsContainer), - children = container.find('.' + that.classes.suggestion); - - container.find('.' + selected).removeClass(selected); - - that.selectedIndex = index; - - if (that.selectedIndex !== -1 && children.length > that.selectedIndex) { - activeItem = children.get(that.selectedIndex); - $(activeItem).addClass(selected); - return activeItem; - } - - return null; - }, - - selectHint: function () { - let that = this, - i = $.inArray(that.hint, that.suggestions); - - that.select(i); - }, - - select: function (i) { - let that = this; - that.hide(); - that.onSelect(i); - }, - - moveUp: function () { - let that = this; - - if (that.selectedIndex === -1) { - return; - } - - if (that.selectedIndex === 0) { - $(that.suggestionsContainer).children().first().removeClass(that.classes.selected); - that.selectedIndex = -1; - that.el.val(that.currentValue); - that.findBestHint(); - return; - } - - that.adjustScroll(that.selectedIndex - 1); - }, - - moveDown: function () { - let that = this; - - if (that.selectedIndex === (that.suggestions.length - 1)) { - return; - } - - that.adjustScroll(that.selectedIndex + 1); - }, - - adjustScroll: function (index) { - let that = this, - activeItem = that.activate(index); - - if (!activeItem) { - return; - } - - let offsetTop, - upperBound, - lowerBound, - heightDelta = $(activeItem).outerHeight(); - - offsetTop = activeItem.offsetTop; - upperBound = $(that.suggestionsContainer).scrollTop(); - lowerBound = upperBound + that.options.maxHeight - heightDelta; - - if (offsetTop < upperBound) { - $(that.suggestionsContainer).scrollTop(offsetTop); - } else if (offsetTop > lowerBound) { - $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta); - } - - if (!that.options.preserveInput) { - that.el.val(that.getValue(that.suggestions[index].value)); - } - that.signalHint(null); - }, - - onSelect: function (index) { - let that = this, - onSelectCallback = that.options.onSelect, - suggestion = that.suggestions[index]; - - that.currentValue = that.getValue(suggestion.value); - - if (that.currentValue !== that.el.val() && !that.options.preserveInput) { - that.el.val(that.currentValue); - } - - that.signalHint(null); - that.suggestions = []; - that.selection = suggestion; - - if ($.isFunction(onSelectCallback)) { - onSelectCallback.call(that.element, suggestion); - } - }, - - getValue: function (value) { - let that = this, - delimiter = that.options.delimiter, - currentValue, - parts; - - if (!delimiter) { - return value; - } - - currentValue = that.currentValue; - parts = currentValue.split(delimiter); - - if (parts.length === 1) { - return value; - } - - return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value; - }, - - dispose: function () { - let that = this; - that.el.off('.autocomplete').removeData('autocomplete'); - that.disableKillerFn(); - $(window).off('resize.autocomplete', that.fixPositionCapture); - $(that.suggestionsContainer).remove(); - } - }; - - // Create chainable jQuery plugin: - $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) { - let dataKey = 'autocomplete'; - // If function invoked without argument return - // instance of the first matched element: - if (arguments.length === 0) { - return this.first().data(dataKey); - } - - return this.each(function () { - let inputElement = $(this), - instance = inputElement.data(dataKey); - - if (typeof options === 'string') { - if (instance && typeof instance[options] === 'function') { - instance[options](args); - } - } else { - // If instance already exists, destroy it: - if (instance && instance.dispose) { - instance.dispose(); - } - instance = new Autocomplete(this, options); - inputElement.data(dataKey, instance); - } - }); - }; -})); diff --git a/Tests/Functional/Task/OpenWeatherMapTaskTest.php b/Tests/Functional/Task/OpenWeatherMapTaskTest.php deleted file mode 100644 index 739c78c..0000000 --- a/Tests/Functional/Task/OpenWeatherMapTaskTest.php +++ /dev/null @@ -1,167 +0,0 @@ -stream = new Stream('php://temp', 'rw'); - - $this->responseMock = $this->createMock(Response::class); - $this->responseMock - ->expects(self::atLeastOnce()) - ->method('getBody') - ->willReturn($this->stream); - - $this->requestFactoryMock = $this->createMock(RequestFactory::class); - $this->requestFactoryMock - ->expects(self::once()) - ->method('request') - ->with(self::isType('string')) - ->willReturn($this->responseMock); - - GeneralUtility::addInstance(RequestFactory::class, $this->requestFactoryMock); - - // We have to use GM:makeInstance because of LoggerAwareInterface - $this->subject = $this->getAccessibleMock(OpenWeatherMapTask::class, null, [], '', false); - $this->subject->city = 'Filderstadt'; - $this->subject->apiKey = 'IHaveForgottenToAddOne'; - $this->subject->clearCache = ''; - $this->subject->country = 'Germany'; - $this->subject->recordStoragePage = 1; - $this->subject->name = 'Filderstadt'; - - $loggerMock = $this->createMock(Logger::class); - $this->subject->setLogger($loggerMock); - } - - protected function tearDown(): void - { - unset( - $this->subject, - $this->requestFactoryMock, - $this->responseMock, - $this->stream, - ); - - parent::tearDown(); - } - - /** - * @test - * @throws \JsonException - */ - public function execute(): void - { - $time = time(); - $this->stream->write( - json_encode([ - 'cod' => true, - 'dt' => $time, - 'main' => [ - 'temp' => 14.6, - 'pressure' => 8.2, - 'humidity' => 12, - 'temp_min' => 13.2, - 'temp_max' => 16.4, - ], - 'wind' => [ - 'speed' => 3.7, - 'deg' => 25, - ], - 'snow' => [ - '1h' => 4.0, - '3h' => 11.0, - ], - 'rain' => [ - '1h' => 6.0, - '3h' => 15.0, - ], - 'clouds' => [ - 'all' => 11, - ], - 'weather' => [ - 0 => [ - 'id' => 1256, - 'main' => 'rain', - 'icon' => '[ICON]', - ], - ], - ], JSON_THROW_ON_ERROR), - ); - - $this->responseMock - ->expects(self::atLeastOnce()) - ->method('getStatusCode') - ->willReturn(200); - - self::assertTrue( - $this->subject->execute(), - ); - - $row = $this->getConnectionPool() - ->getConnectionForTable('tx_weather2_domain_model_currentweather') - ->select(['humidity_percentage', 'measure_timestamp'], 'tx_weather2_domain_model_currentweather') - ->fetchAssociative(); - - $expected = [ - 'humidity_percentage' => 12, - 'measure_timestamp' => $time, - ]; - - self::assertSame($expected, $row); - } -} diff --git a/composer.json b/composer.json index 23ba688..18f13ff 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,6 @@ "ergebnis/composer-normalize": "^2.44", "friendsofphp/php-cs-fixer": "^3.14", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^11.2.5", "roave/security-advisories": "dev-latest", "typo3/coding-standards": "^0.8", "typo3/testing-framework": "^9.0.1" diff --git a/ext_localconf.php b/ext_localconf.php index b854740..f930b45 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -6,34 +6,9 @@ use JWeiland\Weather2\Controller\CurrentWeatherController; use JWeiland\Weather2\Controller\WeatherAlertController; -use JWeiland\Weather2\Task\DeutscherWetterdienstTask; -use JWeiland\Weather2\Task\DeutscherWetterdienstTaskAdditionalFieldProvider; -use JWeiland\Weather2\Task\DeutscherWetterdienstWarnCellTask; -use JWeiland\Weather2\Task\OpenWeatherMapTask; -use JWeiland\Weather2\Task\OpenWeatherMapTaskAdditionalFieldProvider; use TYPO3\CMS\Extbase\Utility\ExtensionUtility; call_user_func(static function () { - $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][OpenWeatherMapTask::class] = [ - 'extension' => 'weather2', - 'title' => 'Call openweathermap.org api', - 'description' => 'Calls the api of openweathermap.org and saves response into database', - 'additionalFields' => OpenWeatherMapTaskAdditionalFieldProvider::class, - ]; - - $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][DeutscherWetterdienstTask::class] = [ - 'extension' => 'weather2', - 'title' => 'Get weather alerts from Deutscher Wetterdienst', - 'description' => 'Calls the Deutscher Wetterdienst api and saves response in weather2 format into database', - 'additionalFields' => DeutscherWetterdienstTaskAdditionalFieldProvider::class, - ]; - - $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][DeutscherWetterdienstWarnCellTask::class] = [ - 'extension' => 'weather2', - 'title' => 'Get warn cell records from Deutscher Wetterdienst', - 'description' => 'Calls the Deutscher Wetterdienst api and saves warn cells into database. Required before using DeutscherWetterdienstTask!', - ]; - ExtensionUtility::configurePlugin( 'Weather2', 'Currentweather', diff --git a/ext_tables.sql b/ext_tables.sql index b12ae5a..8f035b5 100644 --- a/ext_tables.sql +++ b/ext_tables.sql @@ -41,7 +41,7 @@ CREATE TABLE tx_weather2_domain_model_weatheralert ( # CREATE TABLE tx_weather2_domain_model_dwdwarncell ( warn_cell_id varchar(30) DEFAULT '' NOT NULL, - name varchar(60) DEFAULT '' NOT NULL, + name varchar(100) DEFAULT '' NOT NULL, short_name varchar(30) DEFAULT '' NOT NULL, sign varchar(10) DEFAULT '' NOT NULL );