From 09b9a9e3b237f051a9a56bd421837744181ea827 Mon Sep 17 00:00:00 2001 From: Hoja Mustaffa Abdul Latheef Date: Thu, 5 Dec 2024 09:56:01 +0100 Subject: [PATCH 1/7] [TASK] Removed static callback function wrapper --- Configuration/TCA/Overrides/tt_content.php | 42 +++++++++++----------- 1 file changed, 20 insertions(+), 22 deletions(-) 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', +); From f17874c256633bf790c6d34a815ad2ff3bf60cf9 Mon Sep 17 00:00:00 2001 From: Hoja Mustaffa Abdul Latheef Date: Thu, 5 Dec 2024 10:03:32 +0100 Subject: [PATCH 2/7] [TASK] Removed phpunit from composer.json --- composer.json | 1 - 1 file changed, 1 deletion(-) 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" From 0214daf15a31c55d2cb63ff2b5a2ba246a59bc5a Mon Sep 17 00:00:00 2001 From: Hoja Mustaffa Abdul Latheef Date: Thu, 5 Dec 2024 10:31:09 +0100 Subject: [PATCH 3/7] [TASK] Updated plugin wizard icons --- Configuration/Icons.php | 8 ++++++++ .../TSconfig/ContentElementWizard.tsconfig | 4 ++-- Resources/Public/Icons/plugin_alert.png | Bin 0 -> 2254 bytes Resources/Public/Icons/plugin_weather.png | Bin 0 -> 2452 bytes 4 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 Resources/Public/Icons/plugin_alert.png create mode 100644 Resources/Public/Icons/plugin_weather.png 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/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/Resources/Public/Icons/plugin_alert.png b/Resources/Public/Icons/plugin_alert.png new file mode 100644 index 0000000000000000000000000000000000000000..3567b014c77ee7bc5e0c5a0f3cfbdd367dc9c014 GIT binary patch literal 2254 zcmdT``#aN%1K!$ZWjRiYS#FWxu*hX}Qtr1QmTW$m%Lut_E^(--T$Z`aCA1OYP;(va zxSmyNE=vcaoY7502a!uiBJy>GCup|fslEOLI zxa}(YU+k6Ghhy6S(GF!;#*&aZr6I& zL@X({Bq%2S_E}ZXYs!IXONqDogL{^ywuT;fUQW2X@%r;=k@+hFX%Lqv?^a#;G;Hou zT+Si?3d5J3Lw1IAaD#CI81vz$cXxd2>KgGawB>HZ6YPZ5d7Gzi^0Q;v`6}4QN;>`S z8%jp*hZ_P+kH1@AR#i^Syc2J`dB7mLYt=*9q5sMz+`1PIk4Ps zNdkyv$wwVk(FEiazJ^B%ePljB!!BMqqS{;yoi@+m=jKRbI%U%A%!vFXo}WFAvTosp z(h(8q&TqevBuMM7junqnod(f%jY5cAQV@ZiE3dF|La>70v-(_ENfmfWQNIh^8DAU5%6P{$Bt z&;K`7H>uvlU$C`ao1GbNL30pDJ&nYY_~m=F)tWWSgTzZ-&$6ywCZE?z(jZYIDhI=! zO{{*aIft9J8kzgiE{VW66|p4h{fgCH4QE#| zypyw8J2H`__oiX&9)`CXaK}kak9R7VnCGN=k;;??N)=*jP|UEANw6H9;cWmYPWXe_ zt|ILl|Mqd)+3h>LuS-2T*CI6hZ25IFVy@Em^??vD!Oc6n?o_E{cA4@du1|a6?Jd+g4&wC@%T|{zOn8b zwmGjbnAo0NbnSbzDd9?R!H?LZojCtKKbj|x_XNW zPd8F{~oO#I^*lEY}^_Xo<@e~OQ37Drz7SG#gFWsLHORgo#x`gOUS%7-`s z`^0D)=I{4QNzO)?Ce*P{YcoCK&c9o&Turlc?8c;k&cm?>(*|{Y!|A;@e07$rOJ}pD`ZioP*LugWf^MXgnx(UL!5& z@d7gkIoy3=a6X+%4N%{>omF7;8d}v4puD;f$Add$3hSLConIH4#uG)N0S)t!IODiehs1mVxVef6U*pz3-0yOnO`VdV#BlIDhVA={MC%}g?%pziGn^eeHPCo6 z_Dd{b=e-XxJL_GcI%#{w*N`*|Yk(>o142elu6BFRb@5I2_4nM|rX8VY;6tAqzewf` z-t6S;@V~8`T^pJueP4rpZF`?IqJaEmP3MC4X<*a2Ml!;M_HEHr+~H}R_0G5Hy}|w$ zB1l5R(z&pNh?c1McA+1{eQM^!5!)W{67QM$R& zPksT(HIVaH$yEjcYF$sqgA;}3c526<|6k_g4wxvOueMKWZr(j8Ae^m>O@kFV`9F}f B69@nR literal 0 HcmV?d00001 diff --git a/Resources/Public/Icons/plugin_weather.png b/Resources/Public/Icons/plugin_weather.png new file mode 100644 index 0000000000000000000000000000000000000000..d1df2c58904c345204c7a1e2e597e560026713bc GIT binary patch literal 2452 zcmcJR`#;kS8^>puHD}qBq9mtni6Yrb&XvPZHgjIGFeEu-BB$JHZl}68IdxEGF?gZj4_>`rpev>DYwzNoHYw@0?~fOi#5W*tnu~&)l5)zb3N*!N&D~x~l)ByF*6ZA( z+>qv$?Nf#=0p3qqZi{EXn;#6C{kYH~d~?at=UvpBw&hC}YuG23Cq{?JUvB$Gf%n_) z7DCaI>|$-&>SnfRo8O>`7IC#B-1WbvqFI z3`p(CbSQ{H2TD)#QYBEzAT;WXU3s#!Bw-JTNa BxE6MTM)p$3NHa8n9X{UvQhQG%iMk!j_DJVl>2(3MqQpZ#I>e}GgPrsfAMv+2~ z(MF#RKbZ@-I5;=D(z32pKWg2vC)jcjH;_<-pOW$aEPJ449C5wwSu=|(a5)^Q-gimqV5e6bRIxS%%p;in?S zH*wxeribSKDNt!aVJJGx9{-Nv+xT9W@3WrsgnDBj!5lcNhm@-`oadu=}ik<7$ zNvpg-G`@R-8&HUlO&?zq_%kZY$(LivL$6xGVQFw}B(>aD&QUqNy)>o*7ZLt*_QkIr zi5IS~)WE27?zO55-+_Pm9a(5FuL2@&wJh(L=9PiO|YfRmo7)+}kD>M}?~uH|5AbqICJ* z_IA{nYmY+o#q=0rz2v(YcB~~`v#H?j`_{;HaQ5ol*RFRBsO5t94wU`AOJMYUr5>}3 zRE&ugesqXle4ILFb-%8yG#(~9x;IFe0ZLH{w(r61l(%HmoRBlBwk zud`hx+(R0NYy<}Sa1Q^TnVqPAANl!Bu5}^A^8h1sK+6pI@L~M#eQxV#yfSBc)#|L) zdo(s-4$3aF#q_ylq|Bus0-Eae&fYl42E33`z#1;iCupol`!t^c9*zj32N&VU;X)SrIr~b((qX zGsZ^`=cjr$zuc)M@{tCLUCLolU=KQb+Amq4qDd8c76`#NY~1#Ee&r_s%1~^RkR%m@b6~6rDC7cR12q#?hmZ$n%xD!v7A0QURMGvdaCH7=z}?@4%fU`K%lszp^uC}oU7y;_>iv9* zt`K*0f9ooMhjvaph6gO1eFN)T|3}H}ii$Y%Yry<;#Fwx{2d6iNYFJHw9Y5+&I{o*Rs7XRUp^=g+f`a`B|HhUb6)~NW_)S6ONy7+tT zagKTS&lwIwgw-#;T4skwUT<$NYvcic=f1$Yk@tNVxMv0gORKXuQ-uGzC?I+W`3$}g z=EMo_OtiVD2Jpu@caO!9bCM~Tde0zpnb03%J%+8~5E853R5mTrEQ(>A2HXdBbMQhI zONK`wHkPLPUYa>!p_Ow#pLIqwQg`xY2ISj?v+A#8EJ!0%b!lK;Lco4bW5z|GKi0<5 zcU80UcNoDZWDG`5D@V*6j={51)4#Mug;lKbmzWW`~XM zMPu{@JNj;^ygt)6-|^!IFmIsop@DwHp`3!Ix;9_t80kEEKF+tP#C7xwycQD!4UZan zxSC_({NByat|YzmTgb~*ESqLpP@dvF%DS*cwUfJJ(i6U7)Yh>~tdp}**EVyx@!BSx$SirnVI4 z4J08}sXuWp=~)VeN4INSFJP8W^=EvHmzEuJ6!FvL1aX0W9XIr>Iupn4k8~ZII@mYo zUw4W<?F8pL>lOzWBIZpKe=n6CJFkiv zF_o=&2eb~T^hrn(Y|~y-E0R06i>@t66P4u;2_J$HLG{|hY9u?8_r>KfVsT$~uzxbV oj}&zqNs6?`mL^L4|3lMBx_`E^Y<_C-H)|H?q#e$-+M1O5FLf`BCIA2c literal 0 HcmV?d00001 From 270d483ff695a148d09d24d8705f5728a3c1a911 Mon Sep 17 00:00:00 2001 From: Hoja Mustaffa Abdul Latheef Date: Thu, 5 Dec 2024 12:27:17 +0100 Subject: [PATCH 4/7] [TASK] Symfony command implementation --- .../Command/DeutscherWetterdienstCommand.php | 272 ++++++++++++++++ .../DeutscherWetterdienstWarnCellCommand.php | 90 +++++ Classes/Command/OpenWeatherMapCommand.php | 307 ++++++++++++++++++ Classes/Command/WeatherAbstractCommand.php | 69 ++++ Configuration/Services.yaml | 21 +- ext_localconf.php | 34 +- 6 files changed, 775 insertions(+), 18 deletions(-) create mode 100644 Classes/Command/DeutscherWetterdienstCommand.php create mode 100644 Classes/Command/DeutscherWetterdienstWarnCellCommand.php create mode 100644 Classes/Command/OpenWeatherMapCommand.php create mode 100644 Classes/Command/WeatherAbstractCommand.php diff --git a/Classes/Command/DeutscherWetterdienstCommand.php b/Classes/Command/DeutscherWetterdienstCommand.php new file mode 100644 index 0000000..cb708bd --- /dev/null +++ b/Classes/Command/DeutscherWetterdienstCommand.php @@ -0,0 +1,272 @@ + + */ + protected array $decodedResponse = []; + + /** + * Fetch only these warn cells + * + * @var array + */ + public array $selectedWarnCells = []; + + /** + * @var array + */ + protected array $keepRecords = []; + + /** + * @var array + */ + protected array $warnCellRecords = []; + + public int $recordStoragePage = 0; + + public string $clearCache = ''; + + protected PersistenceManager $persistenceManager; + + protected DwdWarnCellRepository $dwdWarnCellRepository; + + protected function configure(): void + { + $this->setHelp('Calls the Deutscher Wetterdienst api and saves response in weather2 format into database'); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $this->dwdWarnCellRepository = $this->getDwdWarnCellRepository(); + $response = $this->getRequestFactory()->request(self::API_URL); + if (!$this->checkResponse($response)) { + return Command::FAILURE; + } + + try { + $this->decodedResponse = $this->decodeResponse($response); + } catch (\Exception $e) { + $this->logger->log(LogLevel::ERROR, $e->getMessage()); + return Command::FAILURE; + } + + $this->handleResponse(); + + return Command::SUCCESS; + } + + /** + * 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/Command/DeutscherWetterdienstWarnCellCommand.php b/Classes/Command/DeutscherWetterdienstWarnCellCommand.php new file mode 100644 index 0000000..17a592e --- /dev/null +++ b/Classes/Command/DeutscherWetterdienstWarnCellCommand.php @@ -0,0 +1,90 @@ +setHelp('Calls the Deutscher Wetterdienst api and saves warn cells into database. Required before using DeutscherWetterdienstTask!'); + } + public function execute(InputInterface $input, OutputInterface $output): int + { + $response = $this->getRequestFactory()->request($this::API_URL); + if (!$this->checkResponse($response)) { + return Command::FAILURE; + } + $this->processResponse($response); + return Command::SUCCESS; + } + + 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/Command/OpenWeatherMapCommand.php b/Classes/Command/OpenWeatherMapCommand.php new file mode 100644 index 0000000..c51e9c0 --- /dev/null +++ b/Classes/Command/OpenWeatherMapCommand.php @@ -0,0 +1,307 @@ +setHelp('Calls the api of openweathermap.org and saves response into database'); + } + + /** + * This method is the heart of the scheduler task. It will be fired if the scheduler + * gets executed + */ + public function execute(InputInterface $input, OutputInterface $output): int + { + $logEntry = []; + $logEntry[] = '**************** [%s] ****************'; + $logEntry[] = 'Scheduler: "JWeiland\\weather2\\Task\\OpenWeatherMapCommand"'; + $logEntry[] = 'Scheduler settings: %s'; + $logEntry[] = 'Date format: "m.d.Y - H:i:s"'; + $this->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 Command::FAILURE; + } + 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($this->clearCache)) { + $cacheService = $this->getCacheService(); + $cacheService->clearPageCache(GeneralUtility::intExplode(',', $this->clearCache)); + } + + return Command::SUCCESS; + } + + /** + * 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/Command/WeatherAbstractCommand.php b/Classes/Command/WeatherAbstractCommand.php new file mode 100644 index 0000000..b1c9e6b --- /dev/null +++ b/Classes/Command/WeatherAbstractCommand.php @@ -0,0 +1,69 @@ + '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'][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'][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!', - ]; + //$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', From 207a8c72c1b193c465e23514af7f5d9b0271aef9 Mon Sep 17 00:00:00 2001 From: Hoja Mustaffa Abdul Latheef Date: Fri, 6 Dec 2024 14:43:50 +0100 Subject: [PATCH 5/7] [TASK] Removed all old scheduler tasks and replaced commands --- .../DeutscherWetterdienstWarnCellSearch.php | 56 - .../Command/DeutscherWetterdienstCommand.php | 202 ++-- .../DeutscherWetterdienstWarnCellCommand.php | 146 ++- Classes/Command/OpenWeatherMapCommand.php | 228 ++-- Classes/Command/WeatherAbstractCommand.php | 69 -- Classes/Task/DeutscherWetterdienstTask.php | 280 ----- ...etterdienstTaskAdditionalFieldProvider.php | 240 ----- .../DeutscherWetterdienstWarnCellTask.php | 89 -- Classes/Task/OpenWeatherMapTask.php | 335 ------ ...nWeatherMapTaskAdditionalFieldProvider.php | 353 ------- Classes/Task/WeatherAbstractTask.php | 69 -- .../DeutscherWetterdienstTaskModule.js | 34 - .../JavaScript/OpenWeatherMapTaskModule.js | 39 - .../Public/JavaScript/jquery.autocomplete.js | 974 ------------------ .../Task/OpenWeatherMapTaskTest.php | 167 --- ext_localconf.php | 20 - ext_tables.sql | 2 +- 17 files changed, 321 insertions(+), 2982 deletions(-) delete mode 100644 Classes/Ajax/DeutscherWetterdienstWarnCellSearch.php delete mode 100644 Classes/Command/WeatherAbstractCommand.php delete mode 100644 Classes/Task/DeutscherWetterdienstTask.php delete mode 100644 Classes/Task/DeutscherWetterdienstTaskAdditionalFieldProvider.php delete mode 100644 Classes/Task/DeutscherWetterdienstWarnCellTask.php delete mode 100644 Classes/Task/OpenWeatherMapTask.php delete mode 100644 Classes/Task/OpenWeatherMapTaskAdditionalFieldProvider.php delete mode 100644 Classes/Task/WeatherAbstractTask.php delete mode 100644 Resources/Public/JavaScript/DeutscherWetterdienstTaskModule.js delete mode 100644 Resources/Public/JavaScript/OpenWeatherMapTaskModule.js delete mode 100644 Resources/Public/JavaScript/jquery.autocomplete.js delete mode 100644 Tests/Functional/Task/OpenWeatherMapTaskTest.php 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 index cb708bd..0659e54 100644 --- a/Classes/Command/DeutscherWetterdienstCommand.php +++ b/Classes/Command/DeutscherWetterdienstCommand.php @@ -13,21 +13,24 @@ use Doctrine\DBAL\Exception; use JWeiland\Weather2\Domain\Model\DwdWarnCell; -use JWeiland\Weather2\Domain\Model\WeatherAlert; use JWeiland\Weather2\Domain\Repository\DwdWarnCellRepository; use JWeiland\Weather2\Utility\WeatherUtility; use Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; -use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Http\RequestFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager; +use TYPO3\CMS\Extbase\Service\CacheService; -#[AsCommand(name: 'weather2:fetch:deutscherWetterdienstAPI')] -final class DeutscherWetterdienstCommand extends WeatherAbstractCommand +final class DeutscherWetterdienstCommand extends Command { public const API_URL = 'https://www.dwd.de/DWD/warnungen/warnapp/json/warnings.json'; protected string $dbExtTable = 'tx_weather2_domain_model_weatheralert'; @@ -39,13 +42,6 @@ final class DeutscherWetterdienstCommand extends WeatherAbstractCommand */ protected array $decodedResponse = []; - /** - * Fetch only these warn cells - * - * @var array - */ - public array $selectedWarnCells = []; - /** * @var array */ @@ -56,37 +52,60 @@ final class DeutscherWetterdienstCommand extends WeatherAbstractCommand */ protected array $warnCellRecords = []; - public int $recordStoragePage = 0; - - public string $clearCache = ''; - - protected PersistenceManager $persistenceManager; - - protected DwdWarnCellRepository $dwdWarnCellRepository; + 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->setHelp('Calls the Deutscher Wetterdienst api and saves response in weather2 format into database'); + $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 { - $this->dwdWarnCellRepository = $this->getDwdWarnCellRepository(); - $response = $this->getRequestFactory()->request(self::API_URL); - if (!$this->checkResponse($response)) { - return Command::FAILURE; - } - + $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); - } catch (\Exception $e) { - $this->logger->log(LogLevel::ERROR, $e->getMessage()); + + $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; } - $this->handleResponse(); - - return Command::SUCCESS; } /** @@ -107,50 +126,78 @@ protected function decodeResponse(ResponseInterface $response): array 1485944083, ); } + return $decodedResponse; } /** * Checks the responseClass for alerts in selected regions */ - protected function handleResponse(): void + protected function handleResponse(InputInterface $input, OutputInterface $output): void { - $this->persistenceManager = $this->getPersistenceManager(); if (array_key_exists('warnings', $this->decodedResponse)) { - $this->processDwdItems($this->decodedResponse['warnings'], false); + $this->processDwdItems($this->decodedResponse['warnings'], false, $input, $output); } if (array_key_exists('vorabInformation', $this->decodedResponse)) { - $this->processDwdItems($this->decodedResponse['vorabInformation'], true); + $this->processDwdItems($this->decodedResponse['vorabInformation'], true, $input, $output); } $this->removeOldAlertsFromDb(); $this->persistenceManager->persistAll(); - if (!empty($this->clearCache)) { - $cacheService = $this->getCacheService(); - $cacheService->clearPageCache(GeneralUtility::intExplode(',', $this->clearCache)); + if (!empty($input->getArgument('clearCache'))) { + $this->cacheService->clearPageCache(GeneralUtility::intExplode(',', $input->getArgument('clearCache'))); } } /** * @param array $category */ - protected function processDwdItems(array $category, bool $isPreliminaryInformation): void + protected function processDwdItems(array $category, bool $isPreliminaryInformation, InputInterface $input, OutputInterface $output): 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)); + $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(''); } } + 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 */ @@ -166,16 +213,16 @@ protected function getComparisonHashForAlert(array $alert): string * @param array $alert * @throws Exception */ - protected function getUidOfAlert(array $alert): int + protected function getUidOfAlert(array $alert, int $recordStoragePid): int { - $connection = $this->getConnectionPool()->getConnectionForTable($this->dbExtTable); + $connection = $this->connectionPool->getConnectionForTable($this->dbExtTable); $identicalAlert = $connection ->select( ['uid'], $this->dbExtTable, [ 'comparison_hash' => $this->getComparisonHashForAlert($alert), - 'pid' => $this->recordStoragePage, + 'pid' => $recordStoragePid, ], ) ->fetchAssociative(); @@ -207,39 +254,39 @@ protected function getBackendUserAuthentication(): BackendUserAuthentication */ protected function getWeatherAlertInstanceForAlert( array $alert, - string $warnCellId, + int $warnCellId, bool $isPreliminaryInformation, - ): WeatherAlert { - $weatherAlert = new WeatherAlert(); - $weatherAlert->setPid($this->recordStoragePage); - $weatherAlert->setDwdWarnCell($this->getDwdWarnCell($warnCellId)); - $weatherAlert->setComparisonHash($this->getComparisonHashForAlert($alert)); - $weatherAlert->setPreliminaryInformation($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->setLevel($alert['level']); + $weatherAlert['level'] = $alert['level']; } if (isset($alert['type'])) { - $weatherAlert->setType($alert['type']); + $weatherAlert['type'] = $alert['type']; } if (isset($alert['headline'])) { - $weatherAlert->setTitle($alert['headline']); + $weatherAlert['title'] = $alert['headline']; } if (isset($alert['description'])) { - $weatherAlert->setDescription($alert['description']); + $weatherAlert['description'] = $alert['description']; } if (isset($alert['instruction'])) { - $weatherAlert->setInstruction($alert['instruction']); + $weatherAlert['instruction'] = $alert['instruction']; } if (isset($alert['start'])) { $startTime = new \DateTime(); $startTime->setTimestamp((int)substr((string)$alert['start'], 0, -3)); - $weatherAlert->setStartDate($startTime); + $weatherAlert['start_date'] = (int)$startTime->getTimestamp(); } if (isset($alert['end'])) { $endTime = new \DateTime(); $endTime->setTimestamp((int)substr((string)$alert['end'], 0, -3)); - $weatherAlert->setEndDate($endTime); + $weatherAlert['end_date'] = (int)$endTime->getTimestamp(); } return $weatherAlert; @@ -256,7 +303,7 @@ protected function getDwdWarnCell(string $warnCellId): DwdWarnCell protected function removeOldAlertsFromDb(): void { - $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable($this->dbExtTable); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($this->dbExtTable); $queryBuilder->delete($this->dbExtTable); if ($this->keepRecords) { @@ -269,4 +316,29 @@ protected function removeOldAlertsFromDb(): void $queryBuilder->executeStatement(); } + + 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 index 17a592e..9ce9ef2 100644 --- a/Classes/Command/DeutscherWetterdienstWarnCellCommand.php +++ b/Classes/Command/DeutscherWetterdienstWarnCellCommand.php @@ -13,78 +13,140 @@ use JWeiland\Weather2\Utility\WeatherUtility; use Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Http\RequestFactory; -final class DeutscherWetterdienstWarnCellCommand extends WeatherAbstractCommand +final class DeutscherWetterdienstWarnCellCommand extends Command { public const API_URL = 'https://www.dwd.de/DE/leistungen/opendata/help/warnungen/cap_warncellids_csv.csv?__blob=publicationFile&v=3'; + public function __construct( + protected readonly LoggerInterface $logger, + protected readonly RequestFactory $requestFactory, + protected readonly ConnectionPool $connectionPool + ) { + parent::__construct(); + } + protected function configure(): void { - $this->setHelp('Calls the Deutscher Wetterdienst api and saves warn cells into database. Required before using DeutscherWetterdienstTask!'); + $this->setHelp( + 'Calls the Deutscher Wetterdienst api and saves warn cells into database. Required before using DeutscherWetterdienstTask!' + ); } + public function execute(InputInterface $input, OutputInterface $output): int { - $response = $this->getRequestFactory()->request($this::API_URL); - if (!$this->checkResponse($response)) { + $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; } - $this->processResponse($response); - return Command::SUCCESS; } - protected function processResponse(ResponseInterface $response): void + protected function fetchWarnCellData(): ResponseInterface { - $connection = $this->getConnectionPool() - ->getConnectionForTable('tx_weather2_domain_model_dwdwarncell'); + 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.'); + } - $rawRows = explode(PHP_EOL, (string)$response->getBody()); - // remove header - array_shift($rawRows); + return $response; + } catch (\Exception $e) { + throw new \RuntimeException('Failed to fetch warn cell data: ' . $e->getMessage(), 0, $e); + } + } + + private function parseResponse(ResponseInterface $response): array + { + $rawRows = explode(PHP_EOL, trim((string)$response->getBody())); + array_shift($rawRows); // Remove header row - $data = []; - $i = 0; - foreach ($rawRows as $rawRow) { - if ($rawRow === '') { + $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] = 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, - ]; - } + [$warnCellId, $name, $nuts, $shortName, $sign] = $fields; + $rows[] = [ + 'warn_cell_id' => $warnCellId, + 'name' => $name, + 'nuts' => $nuts, + 'short_name' => $shortName, + 'sign' => $sign, + ]; } - $dataHandler = $this->getDataHandler(); - $dataHandler->start($data, []); - $dataHandler->process_datamap(); + return $rows; } - /** - * @param ResponseInterface $response - * @return bool Returns true if response is valid or false in case of an error - */ - private function checkResponse(ResponseInterface $response): bool + private function updateDatabase(array $rows, OutputInterface $output): void { - if ($response->getStatusCode() !== 200 || (string)$response->getBody() === '') { - $this->logger->log( - LogLevel::ERROR, - WeatherUtility::translate('message.api_response_null', 'deutscherwetterdienst'), - ); + $connection = $this->connectionPool->getConnectionForTable('tx_weather2_domain_model_dwdwarncell'); + + $progressBar = new ProgressBar($output, count($rows)); + $progressBar->start(); - return false; + foreach ($rows as $row) { + if (!$this->doesRecordExist($connection, $row['warn_cell_id'])) { + $this->insertRecord($connection, $row); + } + $progressBar->advance(); } - return true; + $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; + } + + 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 index c51e9c0..f8eefb6 100644 --- a/Classes/Command/OpenWeatherMapCommand.php +++ b/Classes/Command/OpenWeatherMapCommand.php @@ -13,122 +13,103 @@ use JWeiland\Weather2\Utility\WeatherUtility; use Psr\Http\Message\ResponseInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Http\RequestFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Core\Utility\MailUtility; +use TYPO3\CMS\Extbase\Service\CacheService; -final class OpenWeatherMapCommand extends WeatherAbstractCommand +final class OpenWeatherMapCommand extends Command { - /** - * Api request url - */ protected string $url = ''; - - /** - * Table name - */ protected string $dbExtTable = 'tx_weather2_domain_model_currentweather'; - - /** - * JSON response of openweathermap api - */ protected \stdClass $responseClass; - - /** - * Comma seperated list of page UIDs to clear cache - */ public string $clearCache = ''; - - /** - * Name of current record - */ public string $name = ''; - - /** - * Error notification on or off? - */ - public bool $errorNotification = false; - - /** - * E-Mail address of sender - */ - public string $emailSender = ''; - - /** - * Name of sender - */ - public string $emailSenderName = ''; - - /** - * E-Mail of receiver - */ - public string $emailReceiver = ''; - public string $country = ''; public string $city = ''; - public string $apiKey = ''; public int $recordStoragePage = 0; + public function __construct( + private readonly LoggerInterface $logger, + private readonly RequestFactory $requestFactory, + private readonly CacheService $cacheService, + private readonly ConnectionPool $connectionPool + ) { + parent::__construct(); + } + protected function configure(): void { - $this->setHelp('Calls the api of openweathermap.org and saves response into database'); + $this + ->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)'); } - /** - * This method is the heart of the scheduler task. It will be fired if the scheduler - * gets executed - */ public function execute(InputInterface $input, OutputInterface $output): int { - $logEntry = []; - $logEntry[] = '**************** [%s] ****************'; - $logEntry[] = 'Scheduler: "JWeiland\\weather2\\Task\\OpenWeatherMapCommand"'; - $logEntry[] = 'Scheduler settings: %s'; - $logEntry[] = 'Date format: "m.d.Y - H:i:s"'; - $this->logger->info(sprintf( - implode("\n", $logEntry), - date('m.d.Y - H:i:s', $this->getContextHandler()->getPropertyFromAspect('date', 'timestamp')), - json_encode($this), - )); - - $this->removeOldRecordsFromDb(); + $output->writeln('Starting OpenWeatherMap data fetch...'); - $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, + // 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'), ); - return Command::FAILURE; - } - 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))); + // Log request details + $this->logger->info('Requesting data from OpenWeatherMap API', ['url' => $this->url]); - // Changing the data save to query builder - $this->saveCurrentWeatherInstanceForResponseClass($this->responseClass); + // Make API request + $response = $this->requestFactory->request($this->url); - if (!empty($this->clearCache)) { - $cacheService = $this->getCacheService(); - $cacheService->clearPageCache(GeneralUtility::intExplode(',', $this->clearCache)); - } + 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); - return Command::SUCCESS; + $output->writeln('' . $e->getMessage() . ''); + return Command::FAILURE; + } } /** @@ -178,7 +159,10 @@ private function checkResponseCode(ResponseInterface $response): bool ); $this->sendMail( 'Error while requesting weather data', - sprintf(WeatherUtility::translate('messages.api_code_none', 'openweatherapi'), (string)$response->getBody()), + sprintf( + WeatherUtility::translate('messages.api_code_none', 'openweatherapi'), + (string)$response->getBody() + ), ); return false; } @@ -186,6 +170,7 @@ private function checkResponseCode(ResponseInterface $response): bool public function saveCurrentWeatherInstanceForResponseClass(\stdClass $responseClass): int { + $recordStoragePage = (int)$this->recordStoragePage; $weatherObjectArray = [ 'pid' => $this->recordStoragePage, 'name' => $this->name, @@ -233,75 +218,20 @@ public function saveCurrentWeatherInstanceForResponseClass(\stdClass $responseCl $weatherObjectArray['condition_code'] = $responseClass->weather[0]->id; } - $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable('tx_weather2_domain_model_currentweather'); + $queryBuilder = $this->connectionPool->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, - [ + $this->connectionPool + ->getConnectionForTable($this->dbExtTable) + ->delete($this->dbExtTable, [ 'pid' => $this->recordStoragePage, 'name' => $this->name, - ], - ); + ]); } } diff --git a/Classes/Command/WeatherAbstractCommand.php b/Classes/Command/WeatherAbstractCommand.php deleted file mode 100644 index b1c9e6b..0000000 --- a/Classes/Command/WeatherAbstractCommand.php +++ /dev/null @@ -1,69 +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 @@ -' + 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/ext_localconf.php b/ext_localconf.php index ad9dc67..2553b5b 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -14,26 +14,6 @@ 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 ); From 4edff8ef90d89b148210241483fcb00d336f7524 Mon Sep 17 00:00:00 2001 From: Hoja Mustaffa Abdul Latheef Date: Fri, 6 Dec 2024 15:01:58 +0100 Subject: [PATCH 6/7] [TASK] Documenation updates --- Configuration/Backend/AjaxRoutes.php | 8 --- .../AdministratorManual/Upgrade/Index.rst | 5 ++ Documentation/Introduction/Index.rst | 10 ++-- Documentation/Settings.cfg | 60 ------------------- Documentation/guides.xml | 14 +++++ 5 files changed, 25 insertions(+), 72 deletions(-) delete mode 100644 Configuration/Backend/AjaxRoutes.php delete mode 100644 Documentation/Settings.cfg create mode 100644 Documentation/guides.xml diff --git a/Configuration/Backend/AjaxRoutes.php b/Configuration/Backend/AjaxRoutes.php deleted file mode 100644 index 10f1feb..0000000 --- a/Configuration/Backend/AjaxRoutes.php +++ /dev/null @@ -1,8 +0,0 @@ - [ - 'path' => '/weather2/dwd/warn-cell-search', - 'target' => \JWeiland\Weather2\Ajax\DeutscherWetterdienstWarnCellSearch::class . '::renderWarnCells', - ], -]; 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 + + + + + + + + From 5e7bcb946af4fcff68cfb6621dbc78b7c840e259 Mon Sep 17 00:00:00 2001 From: Hoja Mustaffa Abdul Latheef Date: Fri, 6 Dec 2024 15:02:13 +0100 Subject: [PATCH 7/7] [TASK] Fixed php stan and CLI fixes --- .../Command/DeutscherWetterdienstCommand.php | 18 ++++++++++----- .../DeutscherWetterdienstWarnCellCommand.php | 15 ++++++++++--- Classes/Command/OpenWeatherMapCommand.php | 22 +------------------ ext_localconf.php | 5 ----- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/Classes/Command/DeutscherWetterdienstCommand.php b/Classes/Command/DeutscherWetterdienstCommand.php index 0659e54..3ed995e 100644 --- a/Classes/Command/DeutscherWetterdienstCommand.php +++ b/Classes/Command/DeutscherWetterdienstCommand.php @@ -158,7 +158,7 @@ protected function processDwdItems(array $category, bool $isPreliminaryInformati $recordStoragePid = (int)$input->getArgument('recordStoragePage'); foreach ($selectedWarnCells as $warnCellId) { $dwdWarnCells = $this->getDwdRecordsFindByName( - htmlspecialchars(strip_tags($warnCellId ?? '')), + htmlspecialchars(strip_tags($warnCellId)), ); $progressBar = new ProgressBar($output, count($dwdWarnCells)); $progressBar->start(); @@ -175,7 +175,7 @@ protected function processDwdItems(array $category, bool $isPreliminaryInformati $alert, $dwdWarnCell['uid'], $isPreliminaryInformation, - $recordStoragePid + $recordStoragePid, ); $this->insertRecord($row); $progressBar->advance(); @@ -188,6 +188,9 @@ protected function processDwdItems(array $category, bool $isPreliminaryInformati } } + /** + * @param array $row + */ private function insertRecord(array $row): void { $queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_weather2_domain_model_weatheralert'); @@ -251,12 +254,13 @@ protected function getBackendUserAuthentication(): BackendUserAuthentication * Returns filled WeatherAlert instance * * @param array $alert + * @return array */ protected function getWeatherAlertInstanceForAlert( array $alert, int $warnCellId, bool $isPreliminaryInformation, - int $recordStoragePid + int $recordStoragePid, ): array { $weatherAlert['pid'] = $recordStoragePid; $weatherAlert['dwd_warn_cell'] = $warnCellId; @@ -317,6 +321,10 @@ protected function removeOldAlertsFromDb(): void $queryBuilder->executeStatement(); } + /** + * @return array + * @throws Exception + */ protected function getDwdRecordsFindByName(string $name): array { $table = 'tx_weather2_domain_model_dwdwarncell'; @@ -330,8 +338,8 @@ protected function getDwdRecordsFindByName(string $name): array ->where( $queryBuilder->expr()->or( $queryBuilder->expr()->eq('name', $queryBuilder->createNamedParameter(trim($name))), - $queryBuilder->expr()->eq('warn_cell_id', $queryBuilder->createNamedParameter($name)) - ) + $queryBuilder->expr()->eq('warn_cell_id', $queryBuilder->createNamedParameter($name)), + ), ) ->orderBy('uid', 'ASC'); diff --git a/Classes/Command/DeutscherWetterdienstWarnCellCommand.php b/Classes/Command/DeutscherWetterdienstWarnCellCommand.php index 9ce9ef2..594203d 100644 --- a/Classes/Command/DeutscherWetterdienstWarnCellCommand.php +++ b/Classes/Command/DeutscherWetterdienstWarnCellCommand.php @@ -30,7 +30,7 @@ final class DeutscherWetterdienstWarnCellCommand extends Command public function __construct( protected readonly LoggerInterface $logger, protected readonly RequestFactory $requestFactory, - protected readonly ConnectionPool $connectionPool + protected readonly ConnectionPool $connectionPool, ) { parent::__construct(); } @@ -38,7 +38,7 @@ public function __construct( protected function configure(): void { $this->setHelp( - 'Calls the Deutscher Wetterdienst api and saves warn cells into database. Required before using DeutscherWetterdienstTask!' + 'Calls the Deutscher Wetterdienst api and saves warn cells into database. Required before using DeutscherWetterdienstTask!', ); } @@ -56,7 +56,7 @@ public function execute(InputInterface $input, OutputInterface $output): int } catch (\Throwable $e) { $this->logger->error( sprintf('Error while updating warn cells: %s', $e->getMessage()), - ['exception' => $e] + ['exception' => $e], ); $output->writeln($e->getMessage()); $output->writeln('An error occurred. Check the logs for details.'); @@ -82,6 +82,9 @@ protected function fetchWarnCellData(): ResponseInterface } } + /** + * @return array + */ private function parseResponse(ResponseInterface $response): array { $rawRows = explode(PHP_EOL, trim((string)$response->getBody())); @@ -108,6 +111,9 @@ private function parseResponse(ResponseInterface $response): array return $rows; } + /** + * @param array $rows + */ private function updateDatabase(array $rows, OutputInterface $output): void { $connection = $this->connectionPool->getConnectionForTable('tx_weather2_domain_model_dwdwarncell'); @@ -139,6 +145,9 @@ private function doesRecordExist(Connection $connection, string $warnCellId): bo return (int)$count > 0; } + /** + * @param array $row + */ private function insertRecord(Connection $connection, array $row): void { $connection->insert('tx_weather2_domain_model_dwdwarncell', [ diff --git a/Classes/Command/OpenWeatherMapCommand.php b/Classes/Command/OpenWeatherMapCommand.php index f8eefb6..b268ef3 100644 --- a/Classes/Command/OpenWeatherMapCommand.php +++ b/Classes/Command/OpenWeatherMapCommand.php @@ -18,7 +18,6 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Http\RequestFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -39,7 +38,7 @@ public function __construct( private readonly LoggerInterface $logger, private readonly RequestFactory $requestFactory, private readonly CacheService $cacheService, - private readonly ConnectionPool $connectionPool + private readonly ConnectionPool $connectionPool, ) { parent::__construct(); } @@ -122,18 +121,10 @@ 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; } @@ -145,10 +136,6 @@ private function checkResponseCode(ResponseInterface $response): bool 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( @@ -157,13 +144,6 @@ private function checkResponseCode(ResponseInterface $response): bool (string)$response->getBody(), ), ); - $this->sendMail( - 'Error while requesting weather data', - sprintf( - WeatherUtility::translate('messages.api_code_none', 'openweatherapi'), - (string)$response->getBody() - ), - ); return false; } } diff --git a/ext_localconf.php b/ext_localconf.php index 2553b5b..f930b45 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -6,11 +6,6 @@ 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 () {