1010 * of the License, or any later version.
1111 */
1212
13- use TYPO3 \CMS \Core \Cache \CacheManager ;
1413use TYPO3 \CMS \Core \Cache \Frontend \FrontendInterface ;
1514use TYPO3 \CMS \Core \Configuration \ExtensionConfiguration ;
1615use TYPO3 \CMS \Core \Database \ConnectionPool ;
1716use TYPO3 \CMS \Core \Database \Query \QueryHelper ;
1817use TYPO3 \CMS \Core \Database \Query \Restriction \DeletedRestriction ;
18+ use TYPO3 \CMS \Core \Http \RequestFactory ;
19+ use TYPO3 \CMS \Core \SingletonInterface ;
1920use TYPO3 \CMS \Core \Utility \GeneralUtility ;
2021
2122/**
2223 * Calculate the geo coordinates of an address, using the google geocoding
2324 * API, an API key is needed, as this is a server-side process.
2425 */
25- class GeoService
26+ class GeoService implements SingletonInterface
2627{
27- /**
28- * @var int
29- */
30- protected $ cacheTime = 7776000 ; // 90 days
31-
32- /**
33- * @var int
34- */
35- protected $ maxRetries = 0 ;
36-
37- /**
38- * base URL to fetch the Coordinates (Latitude, Longitude of a Address String.
39- * @var string
40- */
41- protected $ geocodingUrl = 'https://maps.googleapis.com/maps/api/geocode/json?language=de&sensor=false ' ;
42-
43- /**
44- * constructor method.
45- *
46- * sets the google code API key
47- *
48- * @param string $apiKey (optional) the API key from google, if empty, the default from the configuration is taken
49- */
50- public function __construct ($ apiKey = null )
51- {
52- if (class_exists (ExtensionConfiguration::class)) {
53- $ geoCodingConfig = GeneralUtility::makeInstance (ExtensionConfiguration::class)->get ('geocoding ' );
54- } else {
55- $ geoCodingConfig = $ GLOBALS ['TYPO3_CONF_VARS ' ]['EXTENSIONS ' ]['geocoding ' ] ?: unserialize ($ GLOBALS ['TYPO3_CONF_VARS ' ]['EXT ' ]['extConf ' ]['geocoding ' ], ['allowed_classes ' => false ]);
56- }
28+ /** base URL to fetch the Coordinates (Latitude, Longitude of a Address String.*/
29+ protected string $ geocodingUrl = 'https://maps.googleapis.com/maps/api/geocode/json?language=de&sensor=false ' ;
30+ protected int $ cacheTime = 7776000 ; // 90 days
31+ protected int $ maxRetries = 0 ;
32+
33+ public function __construct (
34+ protected readonly FrontendInterface $ cache ,
35+ protected readonly ExtensionConfiguration $ extensionConfiguration ,
36+ protected readonly RequestFactory $ requestFactory
37+ ) {
38+ $ geoCodingConfig = $ extensionConfiguration ->get ('geocoding ' );
5739 // load from extension configuration
58- if ($ apiKey === null ) {
59- $ apiKey = $ geoCodingConfig ['googleApiKey ' ] ?: '' ;
60- }
40+ $ apiKey = $ geoCodingConfig ['googleApiKey ' ] ?? '' ;
6141 if (!empty ($ apiKey )) {
6242 $ this ->geocodingUrl .= '&key= ' . $ apiKey ;
6343 }
64- $ this ->maxRetries = (int )$ geoCodingConfig ['maxRetries ' ];
44+ $ this ->maxRetries = (int )( $ geoCodingConfig ['maxRetries ' ] ?? 0 ) ;
6545 }
6646
6747 /**
6848 * core functionality: asks google for the coordinates of an address
6949 * stores known addresses in a local cache.
7050 *
71- * @param $street
72- * @param $zip
73- * @param $city
74- * @param $country
75- *
7651 * @return array an array with latitude and longitude
7752 */
78- public function getCoordinatesForAddress ($ street = null , $ zip = null , $ city = null , $ country = 'Germany ' ): array
53+ public function getCoordinatesForAddress (? string $ street = null , ? string $ zip = null , ? string $ city = null , ? string $ country = 'Germany ' ): array
7954 {
8055 $ addressParts = [];
8156 foreach ([$ street , $ zip . ' ' . $ city , $ country ] as $ addressPart ) {
57+ if ($ addressPart === null ) {
58+ continue ;
59+ }
8260 if (strlen (trim ($ addressPart )) <= 0 ) {
8361 continue ;
8462 }
8563 $ addressParts [] = trim ($ addressPart );
8664 }
8765
66+ if ($ addressParts === []) {
67+ return [];
68+ }
69+
8870 $ address = ltrim (implode (', ' , $ addressParts ), ', ' );
8971 if (empty ($ address )) {
9072 return [];
9173 }
9274
93- $ cacheObject = $ this ->initializeCache ();
9475 $ cacheKey = 'geocode- ' . strtolower (str_replace (' ' , '- ' , preg_replace ('/[^0-9a-zA-Z ]/m ' , '' , $ address )));
9576
9677 // Found in cache? Return it.
97- if ($ cacheObject ->has ($ cacheKey )) {
98- return $ cacheObject ->get ($ cacheKey );
78+ if ($ this -> cache ->has ($ cacheKey )) {
79+ return $ this -> cache ->get ($ cacheKey );
9980 }
10081
10182 $ result = $ this ->getApiCallResult (
@@ -112,7 +93,7 @@ public function getCoordinatesForAddress($street = null, $zip = null, $city = nu
11293 'longitude ' => $ geometry ['location ' ]['lng ' ],
11394 ];
11495 // Now store the $result in cache and return
115- $ cacheObject ->set ($ cacheKey , $ result , [], $ this ->cacheTime );
96+ $ this -> cache ->set ($ cacheKey , $ result , [], $ this ->cacheTime );
11697 return $ result ;
11798 }
11899
@@ -122,27 +103,17 @@ public function getCoordinatesForAddress($street = null, $zip = null, $city = nu
122103 *
123104 * only works if your DB table has the necessary fields
124105 * helpful when calculating a batch of addresses and save the latitude/longitude automatically
125- * @param string $tableName
126- * @param string $latitudeField
127- * @param string $longitudeField
128- * @param string $streetField
129- * @param string $zipField
130- * @param string $cityField
131- * @param string $countryField
132- * @param string $addWhereClause
133- * @return int
134106 */
135107 public function calculateCoordinatesForAllRecordsInTable (
136- $ tableName ,
137- $ latitudeField = 'latitude ' ,
138- $ longitudeField = 'longitude ' ,
139- $ streetField = 'street ' ,
140- $ zipField = 'zip ' ,
141- $ cityField = 'city ' ,
142- $ countryField = 'country ' ,
143- $ addWhereClause = ''
144- ): int
145- {
108+ string $ tableName ,
109+ string $ latitudeField = 'latitude ' ,
110+ string $ longitudeField = 'longitude ' ,
111+ string $ streetField = 'street ' ,
112+ string $ zipField = 'zip ' ,
113+ string $ cityField = 'city ' ,
114+ string $ countryField = 'country ' ,
115+ string $ addWhereClause = ''
116+ ): int {
146117 // Fetch all records without latitude/longitude
147118 $ connection = GeneralUtility::makeInstance (ConnectionPool::class)->getConnectionForTable ($ tableName );
148119 $ queryBuilder = $ connection ->createQueryBuilder ();
@@ -168,82 +139,61 @@ public function calculateCoordinatesForAllRecordsInTable(
168139 $ queryBuilder ->andWhere (QueryHelper::stripLogicalOperatorPrefix ($ addWhereClause ));
169140 }
170141
171- $ records = $ queryBuilder ->execute ()->fetchAll ();
172-
173- if (count ($ records ) > 0 ) {
174- foreach ($ records as $ record ) {
175- $ country = $ record [$ countryField ];
176- // resolve the label for the country
177- if ($ GLOBALS ['TCA ' ][$ tableName ]['columns ' ][$ countryField ]['config ' ]['type ' ] == 'select ' ) {
178- foreach ($ GLOBALS ['TCA ' ][$ tableName ]['columns ' ][$ countryField ]['config ' ]['items ' ] as $ itm ) {
179- if ($ itm [1 ] == $ country ) {
180- if (is_object ($ GLOBALS ['TSFE ' ])) {
181- $ country = $ GLOBALS ['TSFE ' ]->sL ($ itm [0 ]);
182- } else {
183- $ country = $ GLOBALS ['LANG ' ]->sL ($ itm [0 ]);
184- }
142+ $ records = $ queryBuilder ->executeQuery ()->fetchAllAssociative ();
143+
144+ foreach ($ records as $ record ) {
145+ $ country = $ record [$ countryField ] ?? '' ;
146+ // resolve the label for the country
147+ if (($ GLOBALS ['TCA ' ][$ tableName ]['columns ' ][$ countryField ]['config ' ]['type ' ] ?? '' ) === 'select ' ) {
148+ foreach ($ GLOBALS ['TCA ' ][$ tableName ]['columns ' ][$ countryField ]['config ' ]['items ' ] ?? [] as $ itm ) {
149+ if (($ itm [1 ] ?? null ) === $ country ) {
150+ if (is_object ($ GLOBALS ['TSFE ' ])) {
151+ $ country = $ GLOBALS ['TSFE ' ]->sL ($ itm [0 ]);
152+ } else {
153+ $ country = $ GLOBALS ['LANG ' ]->sL ($ itm [0 ]);
185154 }
186155 }
187156 }
188- // do the geocoding
189- if (! empty ( $ record [ $ zipField ]) || ! empty ( $ record [ $ cityField ])) {
190- $ coords = $ this -> getCoordinatesForAddress ( $ record [$ streetField ], $ record [ $ zipField ], $ record [$ cityField ], $ country );
191- if ( $ coords ) {
192- // Update the record to fill in the latitude and longitude values in the DB
193- $ connection -> update (
194- $ tableName ,
195- [
196- $ latitudeField => $ coords [ ' latitude ' ],
197- $ longitudeField => $ coords ['longitude ' ],
198- ],
199- [
200- ' uid ' => $ record [ ' uid ' ]
201- ]
202- );
203- }
157+ }
158+ // do the geocoding
159+ if (! empty ( $ record [$ zipField ]) || ! empty ( $ record [$ cityField ])) {
160+ $ coords = $ this -> getCoordinatesForAddress ( $ record [ $ streetField ] ?? null , $ record [ $ zipField ] ?? null , $ record [ $ cityField ] ?? null , $ country );
161+ if ( $ coords ) {
162+ // Update the record to fill in the latitude and longitude values in the DB
163+ $ connection -> update (
164+ $ tableName ,
165+ [
166+ $ latitudeField => $ coords ['latitude ' ],
167+ $ longitudeField => $ coords [ ' longitude ' ],
168+ ],
169+ [
170+ ' uid ' => $ record [ ' uid ' ],
171+ ]
172+ );
204173 }
205174 }
206175 }
207176
208177 return count ($ records );
209178 }
210179
211- /**
212- * Initializes the cache for the DB requests.
213- *
214- * @return FrontendInterface Cache Object
215- */
216- protected function initializeCache (): FrontendInterface
217- {
218- try {
219- $ cacheManager = GeneralUtility::makeInstance (CacheManager::class);
220- return $ cacheManager ->getCache ('geocoding ' );
221- } catch (\TYPO3 \CMS \Core \Cache \Exception \NoSuchCacheException $ e ) {
222- throw new \RuntimeException ('Unable to load Cache! ' , 1487138924 );
223- }
224- }
225-
226- /**
227- * @param string $url
228- * @param int $remainingTries
229- * @return array
230- */
231180 protected function getApiCallResult (string $ url , int $ remainingTries = 10 ): array
232181 {
233- $ response = GeneralUtility:: getUrl ($ url );
234- $ result = json_decode ( $ response , true );
235-
236- if ( $ result ['status ' ] !== ' OK ') {
182+ $ response = $ this -> requestFactory -> request ($ url );
183+
184+ $ result = json_decode ( $ response -> getBody ()-> getContents (), true ) ?? [];
185+ if (! in_array (( $ result ['status ' ] ?? '' ), [ ' OK ', ' OVER_QUERY_LIMIT ' ], true )) {
237186 throw new \RuntimeException (
238187 sprintf (
239- 'Request to Google Maps API returned status "%s". Got following error message: "%s" ' ,
240- $ result ['status ' ],
241- $ result ['error_message ' ]),
188+ 'Request to Google Maps API returned status "%s". Got following error message: "%s" ' ,
189+ $ result ['status ' ] ?? 0 ,
190+ $ result ['error_message ' ] ?? ''
191+ ),
242192 1621512170
243193 );
244194 }
245-
246- if ($ result ['status ' ] ! == 'OVER_QUERY_LIMIT ' || $ remainingTries <= 0 ) {
195+
196+ if (( $ result ['status ' ] ?? '' ) = == 'OVER_QUERY_LIMIT ' || $ remainingTries <= 0 ) {
247197 return $ result ;
248198 }
249199 return $ this ->getApiCallResult ($ url , $ remainingTries - 1 );
0 commit comments