Passed
Push — master ( b794fa...797527 )
by Christoph
82:28 queued 66:03
created
apps/weather_status/lib/Service/WeatherStatusService.php 2 patches
Indentation   +396 added lines, -396 removed lines patch added patch discarded remove patch
@@ -45,400 +45,400 @@
 block discarded – undo
45 45
  * @package OCA\WeatherStatus\Service
46 46
  */
47 47
 class WeatherStatusService {
48
-	public const MODE_BROWSER_LOCATION = 1;
49
-	public const MODE_MANUAL_LOCATION = 2;
50
-
51
-	/** @var IClientService */
52
-	private $clientService;
53
-
54
-	/** @var IClient */
55
-	private $client;
56
-
57
-	/** @var IConfig */
58
-	private $config;
59
-
60
-	/** @var IL10N */
61
-	private $l10n;
62
-
63
-	/** @var ILogger */
64
-	private $logger;
65
-
66
-	/** @var IAccountManager */
67
-	private $accountManager;
68
-
69
-	/** @var IUserManager */
70
-	private $userManager;
71
-
72
-	/** @var IAppManager */
73
-	private $appManager;
74
-
75
-	/** @var ICache */
76
-	private $cache;
77
-
78
-	/** @var string */
79
-	private $userId;
80
-
81
-	/** @var string */
82
-	private $version;
83
-
84
-	/**
85
-	 * WeatherStatusService constructor
86
-	 *
87
-	 * @param IClientService $clientService
88
-	 * @param IConfig $config
89
-	 * @param IL10N $l10n
90
-	 * @param ILogger $logger
91
-	 * @param IAccountManager $accountManager
92
-	 * @param IUserManager $userManager
93
-	 * @param IAppManager $appManager
94
-	 * @param ICacheFactory $cacheFactory
95
-	 * @param string $userId
96
-	 */
97
-	public function __construct(IClientService $clientService,
98
-								IConfig $config,
99
-								IL10N $l10n,
100
-								ILogger $logger,
101
-								IAccountManager $accountManager,
102
-								IUserManager $userManager,
103
-								IAppManager $appManager,
104
-								ICacheFactory $cacheFactory,
105
-								?string $userId) {
106
-		$this->config = $config;
107
-		$this->userId = $userId;
108
-		$this->l10n = $l10n;
109
-		$this->logger = $logger;
110
-		$this->accountManager = $accountManager;
111
-		$this->userManager = $userManager;
112
-		$this->appManager = $appManager;
113
-		$this->version = $appManager->getAppVersion(Application::APP_ID);
114
-		$this->clientService = $clientService;
115
-		$this->client = $clientService->newClient();
116
-		$this->cache = $cacheFactory->createDistributed('weatherstatus');
117
-	}
118
-
119
-	/**
120
-	 * Change the weather status mode. There are currently 2 modes:
121
-	 * - ask the browser
122
-	 * - use the user defined address
123
-	 * @param int $mode New mode
124
-	 * @return array success state
125
-	 */
126
-	public function setMode(int $mode): array {
127
-		$this->config->setUserValue($this->userId, Application::APP_ID, 'mode', strval($mode));
128
-		return ['success' => true];
129
-	}
130
-
131
-	/**
132
-	 * Get favorites list
133
-	 * @param array $favorites
134
-	 * @return array success state
135
-	 */
136
-	public function getFavorites(): array {
137
-		$favoritesJson = $this->config->getUserValue($this->userId, Application::APP_ID, 'favorites', '');
138
-		return json_decode($favoritesJson, true) ?: [];
139
-	}
140
-
141
-	/**
142
-	 * Set favorites list
143
-	 * @param array $favorites
144
-	 * @return array success state
145
-	 */
146
-	public function setFavorites(array $favorites): array {
147
-		$this->config->setUserValue($this->userId, Application::APP_ID, 'favorites', json_encode($favorites));
148
-		return ['success' => true];
149
-	}
150
-
151
-	/**
152
-	 * Try to use the address set in user personal settings as weather location
153
-	 *
154
-	 * @return array with success state and address information
155
-	 */
156
-	public function usePersonalAddress(): array {
157
-		$account = $this->accountManager->getAccount($this->userManager->get($this->userId));
158
-		try {
159
-			$address = $account->getProperty('address')->getValue();
160
-		} catch (PropertyDoesNotExistException $e) {
161
-			return ['success' => false];
162
-		}
163
-		if ($address === '') {
164
-			return ['success' => false];
165
-		}
166
-		return $this->setAddress($address);
167
-	}
168
-
169
-	/**
170
-	 * Set address and resolve it to get coordinates
171
-	 * or directly set coordinates and get address with reverse geocoding
172
-	 *
173
-	 * @param string|null $address Any approximative or exact address
174
-	 * @param float|null $lat Latitude in decimal degree format
175
-	 * @param float|null $lon Longitude in decimal degree format
176
-	 * @return array with success state and address information
177
-	 */
178
-	public function setLocation(?string $address, ?float $lat, ?float $lon): array {
179
-		if (!is_null($lat) && !is_null($lon)) {
180
-			// store coordinates
181
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'lat', strval($lat));
182
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'lon', strval($lon));
183
-			// resolve and store formatted address
184
-			$address = $this->resolveLocation($lat, $lon);
185
-			$address = $address ? $address : $this->l10n->t('Unknown address');
186
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'address', $address);
187
-			// get and store altitude
188
-			$altitude = $this->getAltitude($lat, $lon);
189
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'altitude', strval($altitude));
190
-			return [
191
-				'address' => $address,
192
-				'success' => true,
193
-			];
194
-		} elseif ($address) {
195
-			return $this->setAddress($address);
196
-		} else {
197
-			return ['success' => false];
198
-		}
199
-	}
200
-
201
-	/**
202
-	 * Provide address information from coordinates
203
-	 *
204
-	 * @param float $lat Latitude in decimal degree format
205
-	 * @param float $lon Longitude in decimal degree format
206
-	 */
207
-	private function resolveLocation(float $lat, float $lon): ?string {
208
-		$params = [
209
-			'lat' => number_format($lat, 2),
210
-			'lon' => number_format($lon, 2),
211
-			'addressdetails' => 1,
212
-			'format' => 'json',
213
-		];
214
-		$url = 'https://nominatim.openstreetmap.org/reverse';
215
-		$result = $this->requestJSON($url, $params);
216
-		return $this->formatOsmAddress($result);
217
-	}
218
-
219
-	/**
220
-	 * Get altitude from coordinates
221
-	 *
222
-	 * @param float $lat Latitude in decimal degree format
223
-	 * @param float $lon Longitude in decimal degree format
224
-	 * @return float altitude in meter
225
-	 */
226
-	private function getAltitude(float $lat, float $lon): float {
227
-		$params = [
228
-			'locations' => $lat . ',' . $lon,
229
-		];
230
-		$url = 'https://api.opentopodata.org/v1/srtm30m';
231
-		$result = $this->requestJSON($url, $params);
232
-		$altitude = 0;
233
-		if (isset($result['results']) && is_array($result['results']) && count($result['results']) > 0
234
-			&& is_array($result['results'][0]) && isset($result['results'][0]['elevation'])) {
235
-			$altitude = floatval($result['results'][0]['elevation']);
236
-		}
237
-		return $altitude;
238
-	}
239
-
240
-	/**
241
-	 * @return string Formatted address from JSON nominatim result
242
-	 */
243
-	private function formatOsmAddress(array $json): ?string {
244
-		if (isset($json['address']) && isset($json['display_name'])) {
245
-			$jsonAddr = $json['address'];
246
-			$cityAddress = '';
247
-			// priority : city, town, village, municipality
248
-			if (isset($jsonAddr['city'])) {
249
-				$cityAddress .= $jsonAddr['city'];
250
-			} elseif (isset($jsonAddr['town'])) {
251
-				$cityAddress .= $jsonAddr['town'];
252
-			} elseif (isset($jsonAddr['village'])) {
253
-				$cityAddress .= $jsonAddr['village'];
254
-			} elseif (isset($jsonAddr['municipality'])) {
255
-				$cityAddress .= $jsonAddr['municipality'];
256
-			} else {
257
-				return $json['display_name'];
258
-			}
259
-			// post code
260
-			if (isset($jsonAddr['postcode'])) {
261
-				$cityAddress .= ', ' . $jsonAddr['postcode'];
262
-			}
263
-			// country
264
-			if (isset($jsonAddr['country'])) {
265
-				$cityAddress .= ', ' . $jsonAddr['country'];
266
-				return $cityAddress;
267
-			} else {
268
-				return $json['display_name'];
269
-			}
270
-		} elseif (isset($json['display_name'])) {
271
-			return $json['display_name'];
272
-		}
273
-		return null;
274
-	}
275
-
276
-	/**
277
-	 * Set address and resolve it to get coordinates
278
-	 *
279
-	 * @param string $address Any approximative or exact address
280
-	 * @return array with success state and address information (coordinates and formatted address)
281
-	 */
282
-	public function setAddress(string $address): array {
283
-		$addressInfo = $this->searchForAddress($address);
284
-		if (isset($addressInfo['display_name']) && isset($addressInfo['lat']) && isset($addressInfo['lon'])) {
285
-			$formattedAddress = $this->formatOsmAddress($addressInfo);
286
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'address', $formattedAddress);
287
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'lat', strval($addressInfo['lat']));
288
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'lon', strval($addressInfo['lon']));
289
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'mode', strval(self::MODE_MANUAL_LOCATION));
290
-			// get and store altitude
291
-			$altitude = $this->getAltitude(floatval($addressInfo['lat']), floatval($addressInfo['lon']));
292
-			$this->config->setUserValue($this->userId, Application::APP_ID, 'altitude', strval($altitude));
293
-			return [
294
-				'lat' => $addressInfo['lat'],
295
-				'lon' => $addressInfo['lon'],
296
-				'address' => $formattedAddress,
297
-				'success' => true,
298
-			];
299
-		} else {
300
-			return ['success' => false];
301
-		}
302
-	}
303
-
304
-	/**
305
-	 * Ask nominatim information about an unformatted address
306
-	 *
307
-	 * @param string Unformatted address
308
-	 * @return array Full Nominatim result for the given address
309
-	 */
310
-	private function searchForAddress(string $address): array {
311
-		$params = [
312
-			'format' => 'json',
313
-			'addressdetails' => '1',
314
-			'extratags' => '1',
315
-			'namedetails' => '1',
316
-			'limit' => '1',
317
-		];
318
-		$url = 'https://nominatim.openstreetmap.org/search/' . $address;
319
-		$results = $this->requestJSON($url, $params);
320
-		if (count($results) > 0) {
321
-			return $results[0];
322
-		}
323
-		return ['error' => $this->l10n->t('No result.')];
324
-	}
325
-
326
-	/**
327
-	 * Get stored user location
328
-	 *
329
-	 * @return array which contains coordinates, formatted address and current weather status mode
330
-	 */
331
-	public function getLocation(): array {
332
-		$lat = $this->config->getUserValue($this->userId, Application::APP_ID, 'lat', '');
333
-		$lon = $this->config->getUserValue($this->userId, Application::APP_ID, 'lon', '');
334
-		$address = $this->config->getUserValue($this->userId, Application::APP_ID, 'address', '');
335
-		$mode = $this->config->getUserValue($this->userId, Application::APP_ID, 'mode', self::MODE_MANUAL_LOCATION);
336
-		return [
337
-			'lat' => $lat,
338
-			'lon' => $lon,
339
-			'address' => $address,
340
-			'mode' => intval($mode),
341
-		];
342
-	}
343
-
344
-	/**
345
-	 * Get forecast for current location
346
-	 *
347
-	 * @return array which contains success state and filtered forecast data
348
-	 */
349
-	public function getForecast(): array {
350
-		$lat = $this->config->getUserValue($this->userId, Application::APP_ID, 'lat', '');
351
-		$lon = $this->config->getUserValue($this->userId, Application::APP_ID, 'lon', '');
352
-		$alt = $this->config->getUserValue($this->userId, Application::APP_ID, 'altitude', '');
353
-		if (!is_numeric($alt)) {
354
-			$alt = 0;
355
-		}
356
-		if (is_numeric($lat) && is_numeric($lon)) {
357
-			return $this->forecastRequest(floatval($lat), floatval($lon), floatval($alt));
358
-		} else {
359
-			return ['success' => false];
360
-		}
361
-	}
362
-
363
-	/**
364
-	 * Actually make the request to the forecast service
365
-	 *
366
-	 * @param float $lat Latitude of requested forecast, in decimal degree format
367
-	 * @param float $lon Longitude of requested forecast, in decimal degree format
368
-	 * @param float $altitude Altitude of requested forecast, in meter
369
-	 * @param int $nbValues Number of forecast values (hours)
370
-	 * @return array Filtered forecast data
371
-	 */
372
-	private function forecastRequest(float $lat, float $lon, float $altitude, int $nbValues = 10): array {
373
-		$params = [
374
-			'lat' => number_format($lat, 2),
375
-			'lon' => number_format($lon, 2),
376
-			'altitude' => $altitude,
377
-		];
378
-		$url = 'https://api.met.no/weatherapi/locationforecast/2.0/compact';
379
-		$weather = $this->requestJSON($url, $params);
380
-		if (isset($weather['properties']) && isset($weather['properties']['timeseries']) && is_array($weather['properties']['timeseries'])) {
381
-			return array_slice($weather['properties']['timeseries'], 0, $nbValues);
382
-		}
383
-		return ['error' => $this->l10n->t('Malformed JSON data.')];
384
-	}
385
-
386
-	/**
387
-	 * Make a HTTP GET request and parse JSON result.
388
-	 * Request results are cached until the 'Expires' response header says so
389
-	 *
390
-	 * @param string $url Base URL to query
391
-	 * @param array $params GET parameters
392
-	 * @return array which contains the error message or the parsed JSON result
393
-	 */
394
-	private function requestJSON(string $url, array $params = []): array {
395
-		$cacheKey = $url . '|' . implode(',', $params) . '|' . implode(',', array_keys($params));
396
-		$cacheValue = $this->cache->get($cacheKey);
397
-		if ($cacheValue !== null) {
398
-			return $cacheValue;
399
-		}
400
-
401
-		try {
402
-			$options = [
403
-				'headers' => [
404
-					'User-Agent' => 'NextcloudWeatherStatus/' . $this->version . ' nextcloud.com'
405
-				],
406
-			];
407
-
408
-			$reqUrl = $url;
409
-			if (count($params) > 0) {
410
-				$paramsContent = http_build_query($params);
411
-				$reqUrl = $url . '?' . $paramsContent;
412
-			}
413
-
414
-			$response = $this->client->get($reqUrl, $options);
415
-			$body = $response->getBody();
416
-			$headers = $response->getHeaders();
417
-			$respCode = $response->getStatusCode();
418
-
419
-			if ($respCode >= 400) {
420
-				return ['error' => $this->l10n->t('Error')];
421
-			} else {
422
-				$json = json_decode($body, true);
423
-
424
-				// default cache duration is one hour
425
-				$cacheDuration = 60 * 60;
426
-				if (isset($headers['Expires']) && count($headers['Expires']) > 0) {
427
-					// if the Expires response header is set, use it to define cache duration
428
-					$expireTs = (new \Datetime($headers['Expires'][0]))->getTimestamp();
429
-					$nowTs = (new \Datetime())->getTimestamp();
430
-					$duration = $expireTs - $nowTs;
431
-					if ($duration > $cacheDuration) {
432
-						$cacheDuration = $duration;
433
-					}
434
-				}
435
-				$this->cache->set($cacheKey, $json, $cacheDuration);
436
-
437
-				return $json;
438
-			}
439
-		} catch (\Exception $e) {
440
-			$this->logger->warning($url . 'API error : ' . $e, ['app' => Application::APP_ID]);
441
-			return ['error' => $e->getMessage()];
442
-		}
443
-	}
48
+    public const MODE_BROWSER_LOCATION = 1;
49
+    public const MODE_MANUAL_LOCATION = 2;
50
+
51
+    /** @var IClientService */
52
+    private $clientService;
53
+
54
+    /** @var IClient */
55
+    private $client;
56
+
57
+    /** @var IConfig */
58
+    private $config;
59
+
60
+    /** @var IL10N */
61
+    private $l10n;
62
+
63
+    /** @var ILogger */
64
+    private $logger;
65
+
66
+    /** @var IAccountManager */
67
+    private $accountManager;
68
+
69
+    /** @var IUserManager */
70
+    private $userManager;
71
+
72
+    /** @var IAppManager */
73
+    private $appManager;
74
+
75
+    /** @var ICache */
76
+    private $cache;
77
+
78
+    /** @var string */
79
+    private $userId;
80
+
81
+    /** @var string */
82
+    private $version;
83
+
84
+    /**
85
+     * WeatherStatusService constructor
86
+     *
87
+     * @param IClientService $clientService
88
+     * @param IConfig $config
89
+     * @param IL10N $l10n
90
+     * @param ILogger $logger
91
+     * @param IAccountManager $accountManager
92
+     * @param IUserManager $userManager
93
+     * @param IAppManager $appManager
94
+     * @param ICacheFactory $cacheFactory
95
+     * @param string $userId
96
+     */
97
+    public function __construct(IClientService $clientService,
98
+                                IConfig $config,
99
+                                IL10N $l10n,
100
+                                ILogger $logger,
101
+                                IAccountManager $accountManager,
102
+                                IUserManager $userManager,
103
+                                IAppManager $appManager,
104
+                                ICacheFactory $cacheFactory,
105
+                                ?string $userId) {
106
+        $this->config = $config;
107
+        $this->userId = $userId;
108
+        $this->l10n = $l10n;
109
+        $this->logger = $logger;
110
+        $this->accountManager = $accountManager;
111
+        $this->userManager = $userManager;
112
+        $this->appManager = $appManager;
113
+        $this->version = $appManager->getAppVersion(Application::APP_ID);
114
+        $this->clientService = $clientService;
115
+        $this->client = $clientService->newClient();
116
+        $this->cache = $cacheFactory->createDistributed('weatherstatus');
117
+    }
118
+
119
+    /**
120
+     * Change the weather status mode. There are currently 2 modes:
121
+     * - ask the browser
122
+     * - use the user defined address
123
+     * @param int $mode New mode
124
+     * @return array success state
125
+     */
126
+    public function setMode(int $mode): array {
127
+        $this->config->setUserValue($this->userId, Application::APP_ID, 'mode', strval($mode));
128
+        return ['success' => true];
129
+    }
130
+
131
+    /**
132
+     * Get favorites list
133
+     * @param array $favorites
134
+     * @return array success state
135
+     */
136
+    public function getFavorites(): array {
137
+        $favoritesJson = $this->config->getUserValue($this->userId, Application::APP_ID, 'favorites', '');
138
+        return json_decode($favoritesJson, true) ?: [];
139
+    }
140
+
141
+    /**
142
+     * Set favorites list
143
+     * @param array $favorites
144
+     * @return array success state
145
+     */
146
+    public function setFavorites(array $favorites): array {
147
+        $this->config->setUserValue($this->userId, Application::APP_ID, 'favorites', json_encode($favorites));
148
+        return ['success' => true];
149
+    }
150
+
151
+    /**
152
+     * Try to use the address set in user personal settings as weather location
153
+     *
154
+     * @return array with success state and address information
155
+     */
156
+    public function usePersonalAddress(): array {
157
+        $account = $this->accountManager->getAccount($this->userManager->get($this->userId));
158
+        try {
159
+            $address = $account->getProperty('address')->getValue();
160
+        } catch (PropertyDoesNotExistException $e) {
161
+            return ['success' => false];
162
+        }
163
+        if ($address === '') {
164
+            return ['success' => false];
165
+        }
166
+        return $this->setAddress($address);
167
+    }
168
+
169
+    /**
170
+     * Set address and resolve it to get coordinates
171
+     * or directly set coordinates and get address with reverse geocoding
172
+     *
173
+     * @param string|null $address Any approximative or exact address
174
+     * @param float|null $lat Latitude in decimal degree format
175
+     * @param float|null $lon Longitude in decimal degree format
176
+     * @return array with success state and address information
177
+     */
178
+    public function setLocation(?string $address, ?float $lat, ?float $lon): array {
179
+        if (!is_null($lat) && !is_null($lon)) {
180
+            // store coordinates
181
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'lat', strval($lat));
182
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'lon', strval($lon));
183
+            // resolve and store formatted address
184
+            $address = $this->resolveLocation($lat, $lon);
185
+            $address = $address ? $address : $this->l10n->t('Unknown address');
186
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'address', $address);
187
+            // get and store altitude
188
+            $altitude = $this->getAltitude($lat, $lon);
189
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'altitude', strval($altitude));
190
+            return [
191
+                'address' => $address,
192
+                'success' => true,
193
+            ];
194
+        } elseif ($address) {
195
+            return $this->setAddress($address);
196
+        } else {
197
+            return ['success' => false];
198
+        }
199
+    }
200
+
201
+    /**
202
+     * Provide address information from coordinates
203
+     *
204
+     * @param float $lat Latitude in decimal degree format
205
+     * @param float $lon Longitude in decimal degree format
206
+     */
207
+    private function resolveLocation(float $lat, float $lon): ?string {
208
+        $params = [
209
+            'lat' => number_format($lat, 2),
210
+            'lon' => number_format($lon, 2),
211
+            'addressdetails' => 1,
212
+            'format' => 'json',
213
+        ];
214
+        $url = 'https://nominatim.openstreetmap.org/reverse';
215
+        $result = $this->requestJSON($url, $params);
216
+        return $this->formatOsmAddress($result);
217
+    }
218
+
219
+    /**
220
+     * Get altitude from coordinates
221
+     *
222
+     * @param float $lat Latitude in decimal degree format
223
+     * @param float $lon Longitude in decimal degree format
224
+     * @return float altitude in meter
225
+     */
226
+    private function getAltitude(float $lat, float $lon): float {
227
+        $params = [
228
+            'locations' => $lat . ',' . $lon,
229
+        ];
230
+        $url = 'https://api.opentopodata.org/v1/srtm30m';
231
+        $result = $this->requestJSON($url, $params);
232
+        $altitude = 0;
233
+        if (isset($result['results']) && is_array($result['results']) && count($result['results']) > 0
234
+            && is_array($result['results'][0]) && isset($result['results'][0]['elevation'])) {
235
+            $altitude = floatval($result['results'][0]['elevation']);
236
+        }
237
+        return $altitude;
238
+    }
239
+
240
+    /**
241
+     * @return string Formatted address from JSON nominatim result
242
+     */
243
+    private function formatOsmAddress(array $json): ?string {
244
+        if (isset($json['address']) && isset($json['display_name'])) {
245
+            $jsonAddr = $json['address'];
246
+            $cityAddress = '';
247
+            // priority : city, town, village, municipality
248
+            if (isset($jsonAddr['city'])) {
249
+                $cityAddress .= $jsonAddr['city'];
250
+            } elseif (isset($jsonAddr['town'])) {
251
+                $cityAddress .= $jsonAddr['town'];
252
+            } elseif (isset($jsonAddr['village'])) {
253
+                $cityAddress .= $jsonAddr['village'];
254
+            } elseif (isset($jsonAddr['municipality'])) {
255
+                $cityAddress .= $jsonAddr['municipality'];
256
+            } else {
257
+                return $json['display_name'];
258
+            }
259
+            // post code
260
+            if (isset($jsonAddr['postcode'])) {
261
+                $cityAddress .= ', ' . $jsonAddr['postcode'];
262
+            }
263
+            // country
264
+            if (isset($jsonAddr['country'])) {
265
+                $cityAddress .= ', ' . $jsonAddr['country'];
266
+                return $cityAddress;
267
+            } else {
268
+                return $json['display_name'];
269
+            }
270
+        } elseif (isset($json['display_name'])) {
271
+            return $json['display_name'];
272
+        }
273
+        return null;
274
+    }
275
+
276
+    /**
277
+     * Set address and resolve it to get coordinates
278
+     *
279
+     * @param string $address Any approximative or exact address
280
+     * @return array with success state and address information (coordinates and formatted address)
281
+     */
282
+    public function setAddress(string $address): array {
283
+        $addressInfo = $this->searchForAddress($address);
284
+        if (isset($addressInfo['display_name']) && isset($addressInfo['lat']) && isset($addressInfo['lon'])) {
285
+            $formattedAddress = $this->formatOsmAddress($addressInfo);
286
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'address', $formattedAddress);
287
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'lat', strval($addressInfo['lat']));
288
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'lon', strval($addressInfo['lon']));
289
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'mode', strval(self::MODE_MANUAL_LOCATION));
290
+            // get and store altitude
291
+            $altitude = $this->getAltitude(floatval($addressInfo['lat']), floatval($addressInfo['lon']));
292
+            $this->config->setUserValue($this->userId, Application::APP_ID, 'altitude', strval($altitude));
293
+            return [
294
+                'lat' => $addressInfo['lat'],
295
+                'lon' => $addressInfo['lon'],
296
+                'address' => $formattedAddress,
297
+                'success' => true,
298
+            ];
299
+        } else {
300
+            return ['success' => false];
301
+        }
302
+    }
303
+
304
+    /**
305
+     * Ask nominatim information about an unformatted address
306
+     *
307
+     * @param string Unformatted address
308
+     * @return array Full Nominatim result for the given address
309
+     */
310
+    private function searchForAddress(string $address): array {
311
+        $params = [
312
+            'format' => 'json',
313
+            'addressdetails' => '1',
314
+            'extratags' => '1',
315
+            'namedetails' => '1',
316
+            'limit' => '1',
317
+        ];
318
+        $url = 'https://nominatim.openstreetmap.org/search/' . $address;
319
+        $results = $this->requestJSON($url, $params);
320
+        if (count($results) > 0) {
321
+            return $results[0];
322
+        }
323
+        return ['error' => $this->l10n->t('No result.')];
324
+    }
325
+
326
+    /**
327
+     * Get stored user location
328
+     *
329
+     * @return array which contains coordinates, formatted address and current weather status mode
330
+     */
331
+    public function getLocation(): array {
332
+        $lat = $this->config->getUserValue($this->userId, Application::APP_ID, 'lat', '');
333
+        $lon = $this->config->getUserValue($this->userId, Application::APP_ID, 'lon', '');
334
+        $address = $this->config->getUserValue($this->userId, Application::APP_ID, 'address', '');
335
+        $mode = $this->config->getUserValue($this->userId, Application::APP_ID, 'mode', self::MODE_MANUAL_LOCATION);
336
+        return [
337
+            'lat' => $lat,
338
+            'lon' => $lon,
339
+            'address' => $address,
340
+            'mode' => intval($mode),
341
+        ];
342
+    }
343
+
344
+    /**
345
+     * Get forecast for current location
346
+     *
347
+     * @return array which contains success state and filtered forecast data
348
+     */
349
+    public function getForecast(): array {
350
+        $lat = $this->config->getUserValue($this->userId, Application::APP_ID, 'lat', '');
351
+        $lon = $this->config->getUserValue($this->userId, Application::APP_ID, 'lon', '');
352
+        $alt = $this->config->getUserValue($this->userId, Application::APP_ID, 'altitude', '');
353
+        if (!is_numeric($alt)) {
354
+            $alt = 0;
355
+        }
356
+        if (is_numeric($lat) && is_numeric($lon)) {
357
+            return $this->forecastRequest(floatval($lat), floatval($lon), floatval($alt));
358
+        } else {
359
+            return ['success' => false];
360
+        }
361
+    }
362
+
363
+    /**
364
+     * Actually make the request to the forecast service
365
+     *
366
+     * @param float $lat Latitude of requested forecast, in decimal degree format
367
+     * @param float $lon Longitude of requested forecast, in decimal degree format
368
+     * @param float $altitude Altitude of requested forecast, in meter
369
+     * @param int $nbValues Number of forecast values (hours)
370
+     * @return array Filtered forecast data
371
+     */
372
+    private function forecastRequest(float $lat, float $lon, float $altitude, int $nbValues = 10): array {
373
+        $params = [
374
+            'lat' => number_format($lat, 2),
375
+            'lon' => number_format($lon, 2),
376
+            'altitude' => $altitude,
377
+        ];
378
+        $url = 'https://api.met.no/weatherapi/locationforecast/2.0/compact';
379
+        $weather = $this->requestJSON($url, $params);
380
+        if (isset($weather['properties']) && isset($weather['properties']['timeseries']) && is_array($weather['properties']['timeseries'])) {
381
+            return array_slice($weather['properties']['timeseries'], 0, $nbValues);
382
+        }
383
+        return ['error' => $this->l10n->t('Malformed JSON data.')];
384
+    }
385
+
386
+    /**
387
+     * Make a HTTP GET request and parse JSON result.
388
+     * Request results are cached until the 'Expires' response header says so
389
+     *
390
+     * @param string $url Base URL to query
391
+     * @param array $params GET parameters
392
+     * @return array which contains the error message or the parsed JSON result
393
+     */
394
+    private function requestJSON(string $url, array $params = []): array {
395
+        $cacheKey = $url . '|' . implode(',', $params) . '|' . implode(',', array_keys($params));
396
+        $cacheValue = $this->cache->get($cacheKey);
397
+        if ($cacheValue !== null) {
398
+            return $cacheValue;
399
+        }
400
+
401
+        try {
402
+            $options = [
403
+                'headers' => [
404
+                    'User-Agent' => 'NextcloudWeatherStatus/' . $this->version . ' nextcloud.com'
405
+                ],
406
+            ];
407
+
408
+            $reqUrl = $url;
409
+            if (count($params) > 0) {
410
+                $paramsContent = http_build_query($params);
411
+                $reqUrl = $url . '?' . $paramsContent;
412
+            }
413
+
414
+            $response = $this->client->get($reqUrl, $options);
415
+            $body = $response->getBody();
416
+            $headers = $response->getHeaders();
417
+            $respCode = $response->getStatusCode();
418
+
419
+            if ($respCode >= 400) {
420
+                return ['error' => $this->l10n->t('Error')];
421
+            } else {
422
+                $json = json_decode($body, true);
423
+
424
+                // default cache duration is one hour
425
+                $cacheDuration = 60 * 60;
426
+                if (isset($headers['Expires']) && count($headers['Expires']) > 0) {
427
+                    // if the Expires response header is set, use it to define cache duration
428
+                    $expireTs = (new \Datetime($headers['Expires'][0]))->getTimestamp();
429
+                    $nowTs = (new \Datetime())->getTimestamp();
430
+                    $duration = $expireTs - $nowTs;
431
+                    if ($duration > $cacheDuration) {
432
+                        $cacheDuration = $duration;
433
+                    }
434
+                }
435
+                $this->cache->set($cacheKey, $json, $cacheDuration);
436
+
437
+                return $json;
438
+            }
439
+        } catch (\Exception $e) {
440
+            $this->logger->warning($url . 'API error : ' . $e, ['app' => Application::APP_ID]);
441
+            return ['error' => $e->getMessage()];
442
+        }
443
+    }
444 444
 }
Please login to merge, or discard this patch.
Spacing   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -225,7 +225,7 @@  discard block
 block discarded – undo
225 225
 	 */
226 226
 	private function getAltitude(float $lat, float $lon): float {
227 227
 		$params = [
228
-			'locations' => $lat . ',' . $lon,
228
+			'locations' => $lat.','.$lon,
229 229
 		];
230 230
 		$url = 'https://api.opentopodata.org/v1/srtm30m';
231 231
 		$result = $this->requestJSON($url, $params);
@@ -258,11 +258,11 @@  discard block
 block discarded – undo
258 258
 			}
259 259
 			// post code
260 260
 			if (isset($jsonAddr['postcode'])) {
261
-				$cityAddress .= ', ' . $jsonAddr['postcode'];
261
+				$cityAddress .= ', '.$jsonAddr['postcode'];
262 262
 			}
263 263
 			// country
264 264
 			if (isset($jsonAddr['country'])) {
265
-				$cityAddress .= ', ' . $jsonAddr['country'];
265
+				$cityAddress .= ', '.$jsonAddr['country'];
266 266
 				return $cityAddress;
267 267
 			} else {
268 268
 				return $json['display_name'];
@@ -315,7 +315,7 @@  discard block
 block discarded – undo
315 315
 			'namedetails' => '1',
316 316
 			'limit' => '1',
317 317
 		];
318
-		$url = 'https://nominatim.openstreetmap.org/search/' . $address;
318
+		$url = 'https://nominatim.openstreetmap.org/search/'.$address;
319 319
 		$results = $this->requestJSON($url, $params);
320 320
 		if (count($results) > 0) {
321 321
 			return $results[0];
@@ -392,7 +392,7 @@  discard block
 block discarded – undo
392 392
 	 * @return array which contains the error message or the parsed JSON result
393 393
 	 */
394 394
 	private function requestJSON(string $url, array $params = []): array {
395
-		$cacheKey = $url . '|' . implode(',', $params) . '|' . implode(',', array_keys($params));
395
+		$cacheKey = $url.'|'.implode(',', $params).'|'.implode(',', array_keys($params));
396 396
 		$cacheValue = $this->cache->get($cacheKey);
397 397
 		if ($cacheValue !== null) {
398 398
 			return $cacheValue;
@@ -401,14 +401,14 @@  discard block
 block discarded – undo
401 401
 		try {
402 402
 			$options = [
403 403
 				'headers' => [
404
-					'User-Agent' => 'NextcloudWeatherStatus/' . $this->version . ' nextcloud.com'
404
+					'User-Agent' => 'NextcloudWeatherStatus/'.$this->version.' nextcloud.com'
405 405
 				],
406 406
 			];
407 407
 
408 408
 			$reqUrl = $url;
409 409
 			if (count($params) > 0) {
410 410
 				$paramsContent = http_build_query($params);
411
-				$reqUrl = $url . '?' . $paramsContent;
411
+				$reqUrl = $url.'?'.$paramsContent;
412 412
 			}
413 413
 
414 414
 			$response = $this->client->get($reqUrl, $options);
@@ -437,7 +437,7 @@  discard block
 block discarded – undo
437 437
 				return $json;
438 438
 			}
439 439
 		} catch (\Exception $e) {
440
-			$this->logger->warning($url . 'API error : ' . $e, ['app' => Application::APP_ID]);
440
+			$this->logger->warning($url.'API error : '.$e, ['app' => Application::APP_ID]);
441 441
 			return ['error' => $e->getMessage()];
442 442
 		}
443 443
 	}
Please login to merge, or discard this patch.