|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace rdx\fuelly; |
|
4
|
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
|
6
|
|
|
use rdx\fuelly\FuelUp; |
|
7
|
|
|
use rdx\fuelly\UnitConversion; |
|
8
|
|
|
use rdx\fuelly\Vehicle; |
|
9
|
|
|
use rdx\fuelly\WebAuth; |
|
10
|
|
|
use rdx\http\HTTP; |
|
11
|
|
|
|
|
12
|
|
|
class Client { |
|
13
|
|
|
|
|
14
|
|
|
public $base = 'http://www.fuelly.com/'; |
|
15
|
|
|
public $loginBase = 'https://m.fuelly.com/'; |
|
16
|
|
|
|
|
17
|
|
|
public $dateFormat = 'd/m/Y'; |
|
18
|
|
|
public $timeFormat = 'g:i a'; |
|
19
|
|
|
|
|
20
|
|
|
public $auth; // rdx\fuelly\WebAuth |
|
21
|
|
|
public $input; // rdx\fuelly\InputConversion |
|
22
|
|
|
public $username = ''; |
|
23
|
|
|
public $vehicles = array(); |
|
24
|
|
|
|
|
25
|
|
|
public $log = array(); |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* Dependency constructor |
|
29
|
|
|
*/ |
|
30
|
|
|
public function __construct( WebAuth $auth, InputConversion $input ) { |
|
31
|
|
|
$this->auth = $auth; |
|
32
|
|
|
$this->input = $input; |
|
33
|
|
|
} |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* |
|
37
|
|
|
*/ |
|
38
|
|
|
public function createTrendInputConversion() { |
|
39
|
|
|
// Trend is always in real numbers, and only its natives are reliable so use those |
|
40
|
|
|
return new InputConversion('ml', 'usg', $this->input->mileage, '.', ','); |
|
41
|
|
|
} |
|
42
|
|
|
|
|
43
|
|
|
/** |
|
44
|
|
|
* |
|
45
|
|
|
*/ |
|
46
|
|
|
public function getFuelUp( $id ) { |
|
47
|
|
|
$fuelup = compact('id'); |
|
48
|
|
|
|
|
49
|
|
|
$response = $this->_get('fuelups/' . $id . '/edit'); |
|
50
|
|
|
|
|
51
|
|
|
preg_match_all('#<input[\s\S]+?name="([^"]+)"[\s\S]+?>#', $response->body, $matches, PREG_SET_ORDER); |
|
52
|
|
|
foreach ( $matches as $match ) { |
|
|
|
|
|
|
53
|
|
|
if ( in_array($match[1], array('_token', 'miles_last_fuelup', 'price_per_unit', 'amount', 'fuelup_date')) ) { |
|
54
|
|
|
preg_match('#value="([^"]+)"#', $match[0], $match2); |
|
55
|
|
|
$fuelup[ $match[1] ] = $match2[1]; |
|
56
|
|
|
} |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
preg_match('#<textarea[^>]+name="note"[^>]*>([^>]+)</textarea>#', $response->body, $match); |
|
60
|
|
|
$fuelup['note'] = trim(@$match[1]); |
|
61
|
|
|
|
|
62
|
|
|
return $fuelup; |
|
63
|
|
|
} |
|
64
|
|
|
|
|
65
|
|
|
/** |
|
66
|
|
|
* |
|
67
|
|
|
*/ |
|
68
|
|
|
public function updateFuelUp( $id, $data ) { |
|
69
|
|
|
$data = $this->_validateFuelUpData($data); |
|
70
|
|
|
unset($data['id']); |
|
71
|
|
|
|
|
72
|
|
|
if ( !isset($data['_token']) ) { |
|
73
|
|
|
$response = $this->_get('fuelups/' . $id . '/edit'); |
|
74
|
|
|
$data['_token'] = $this->extractFormToken($response->body); |
|
75
|
|
|
} |
|
76
|
|
|
|
|
77
|
|
|
$data['_method'] = 'PUT'; |
|
78
|
|
|
$response = $this->_post('fuelups/' . $id, array( |
|
79
|
|
|
'data' => $data, |
|
80
|
|
|
)); |
|
81
|
|
|
return $response->code == 302; |
|
82
|
|
|
} |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* |
|
86
|
|
|
*/ |
|
87
|
|
|
public function _validateFuelUpData( $data ) { |
|
88
|
|
|
$data += array( |
|
89
|
|
|
'errorlevel' => 2, |
|
90
|
|
|
'price_per_unit' => '', |
|
91
|
|
|
'cost' => '', |
|
92
|
|
|
'city_pct' => '0', |
|
93
|
|
|
'fueltype_id' => '', |
|
94
|
|
|
'fuelup_date' => date($this->dateFormat), |
|
95
|
|
|
'time' => date($this->timeFormat), |
|
96
|
|
|
'paymenttype_id' => '', |
|
97
|
|
|
'fuelbrand' => '', |
|
98
|
|
|
'tirepsi' => '', |
|
99
|
|
|
'note' => '', |
|
100
|
|
|
); |
|
101
|
|
|
|
|
102
|
|
|
$required = array('usercar_id', 'miles_last_fuelup', 'amount'); |
|
103
|
|
|
$missing = array_diff($required, array_keys(array_filter($data))); |
|
104
|
|
|
if ( $missing ) { |
|
|
|
|
|
|
105
|
|
|
throw new InvalidArgumentException('Missing params: ' . implode(', ', $missing)); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
return $data; |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* |
|
113
|
|
|
*/ |
|
114
|
|
|
public function getFuelUpsWithIds( Vehicle $vehicle, $limit = 15 ) { |
|
115
|
|
|
$query = http_build_query(array( |
|
116
|
|
|
'iDisplayStart' => 0, |
|
117
|
|
|
'iDisplayLength' => $limit, |
|
118
|
|
|
'sSortDir_0' => 'desc', |
|
119
|
|
|
'usercar_id' => $vehicle->id, |
|
120
|
|
|
)); |
|
121
|
|
|
$response = $this->_get('ajax/fuelup-log?' . $query); |
|
122
|
|
|
if ( $response->code == 200 ) { |
|
123
|
|
|
if ( $response->response ) { |
|
124
|
|
|
$fuelups = array(); |
|
125
|
|
|
foreach ( $response->response['aaData'] as $fuelup ) { |
|
126
|
|
|
if ( preg_match('#fuelups/(\d+)/edit#', $fuelup[0], $match) ) { |
|
127
|
|
|
$fuelup = array( |
|
128
|
|
|
'id' => $match[1], |
|
129
|
|
|
'usercar_id' => $vehicle->id, |
|
130
|
|
|
'fuelup_date' => $fuelup[2][0], |
|
131
|
|
|
'miles_last_fuelup' => $fuelup[3][0], |
|
132
|
|
|
'amount' => $fuelup[4][0], |
|
133
|
|
|
); |
|
134
|
|
|
|
|
135
|
|
|
$fuelups[ $fuelup['id'] ] = FuelUp::createFromDetail($vehicle, $fuelup); |
|
136
|
|
|
} |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
// Sort by date DESC |
|
140
|
|
|
uasort($fuelups, array(FuelUp::class, 'dateCmp')); |
|
141
|
|
|
|
|
142
|
|
|
return $fuelups; |
|
143
|
|
|
} |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
return array(); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* |
|
151
|
|
|
*/ |
|
152
|
|
|
public function getAllFuelups( Vehicle $vehicle ) { |
|
153
|
|
|
$response = $this->_get('car/make/model/2001/username/' . $vehicle->id . '/export'); |
|
154
|
|
|
if ( $token = $this->extractFormToken($response->body) ) { |
|
155
|
|
|
$response = $this->_post('exportfuelups', array( |
|
156
|
|
|
'data' => array( |
|
157
|
|
|
'_token' => $token, |
|
158
|
|
|
'usercar_id' => $vehicle->id, |
|
159
|
|
|
), |
|
160
|
|
|
)); |
|
161
|
|
|
if ( $response->code == 200 ) { |
|
162
|
|
|
$lines = array_filter(preg_split('#[\r\n]+#', $response->body)); |
|
163
|
|
|
$header = $rows = array(); |
|
164
|
|
|
foreach ( $lines as $line ) { |
|
165
|
|
|
$row = array_map('trim', str_getcsv($line)); |
|
166
|
|
|
if ( !$header ) { |
|
|
|
|
|
|
167
|
|
|
$header = $row; |
|
168
|
|
|
} |
|
169
|
|
|
else { |
|
170
|
|
|
$rows[] = array_combine($header, $row); |
|
171
|
|
|
} |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
return $rows; |
|
175
|
|
|
} |
|
176
|
|
|
} |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* |
|
181
|
|
|
*/ |
|
182
|
|
|
public function addFuelUp( $data ) { |
|
183
|
|
|
$data = $this->_validateFuelUpData($data); |
|
184
|
|
|
|
|
185
|
|
|
// GET /fuelups/create |
|
186
|
|
|
$response = $this->_get('fuelups/create'); |
|
187
|
|
|
|
|
188
|
|
|
if ( $token = $this->extractFormToken($response->body) ) { |
|
189
|
|
|
$data['_token'] = $token; |
|
190
|
|
|
|
|
191
|
|
|
// POST /fuelups/create |
|
192
|
|
|
$response = $this->_post('fuelups', array( |
|
193
|
|
|
'data' => $data, |
|
194
|
|
|
)); |
|
195
|
|
|
if ( $response->code == 302 ) { |
|
196
|
|
|
$response = $this->_get($response->headers['location'][0]); |
|
197
|
|
|
|
|
198
|
|
|
// Take new fuelup ID from response and add it |
|
199
|
|
|
if ( $response->code == 200 ) { |
|
200
|
|
|
$regex = '#' . preg_quote($this->base, '#') . 'fuelups/(\d+)/edit#'; |
|
201
|
|
|
if ( preg_match($regex, $response->body, $match) ) { |
|
202
|
|
|
$response->fuelup_id = $match[1]; |
|
203
|
|
|
} |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
return $response; |
|
207
|
|
|
} |
|
208
|
|
|
} |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
/** |
|
212
|
|
|
* |
|
213
|
|
|
*/ |
|
214
|
|
|
public function getVehicle( $id ) { |
|
215
|
|
|
$vehicles = $this->getVehicles(); |
|
216
|
|
|
return @$vehicles[$id]; |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
/** |
|
220
|
|
|
* |
|
221
|
|
|
*/ |
|
222
|
|
|
public function getVehicles() { |
|
223
|
|
|
// Must exist, because session must be valid, so we did a `GET /dashboard` |
|
224
|
|
|
return $this->vehicles; |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
/** |
|
228
|
|
|
* |
|
229
|
|
|
*/ |
|
230
|
|
|
protected function extractVehicles( $html ) { |
|
231
|
|
|
$regex = '#<ul class="dashboard-vehicle" data-clickable="([^"]+)">[\w\W]+?</ul>#'; |
|
232
|
|
|
$vehicles = array(); |
|
233
|
|
|
if ( preg_match_all($regex, $html, $matches) ) { |
|
234
|
|
|
foreach ( $matches[0] as $i => $html ) { |
|
235
|
|
|
$url = $matches[1][$i]; |
|
236
|
|
|
|
|
237
|
|
|
preg_match('#/(\d+)$#', $url, $match); |
|
238
|
|
|
$id = $match[1]; |
|
239
|
|
|
|
|
240
|
|
|
preg_match('#<h3[^>]*>(.+?)</h3>#', $html, $match); |
|
241
|
|
|
$name = htmlspecialchars_decode($match[1]); |
|
242
|
|
|
|
|
243
|
|
|
preg_match("#:\s*url\('/([^']+)'\)#", $html, $match); |
|
244
|
|
|
$image = $this->base . $match[1]; |
|
245
|
|
|
|
|
246
|
|
|
preg_match("#data-trend='([^']+)'#", $html, $match); |
|
247
|
|
|
$trend = @json_decode($match[1], true) ?: false; |
|
248
|
|
|
|
|
249
|
|
|
$vehicles[$id] = new Vehicle($this, compact('url', 'id', 'name', 'image', 'trend')); |
|
250
|
|
|
} |
|
251
|
|
|
} |
|
252
|
|
|
|
|
253
|
|
|
return $vehicles; |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
/** |
|
257
|
|
|
* |
|
258
|
|
|
*/ |
|
259
|
|
|
public function logIn() { |
|
260
|
|
|
if ( !$this->auth->mail || !$this->auth->pass ) { |
|
261
|
|
|
return false; |
|
262
|
|
|
} |
|
263
|
|
|
|
|
264
|
|
|
// GET /login |
|
265
|
|
|
$response = $this->_get('login', array('login' => true)); |
|
266
|
|
|
|
|
267
|
|
|
if ( $token = $this->extractFormToken($response->body) ) { |
|
268
|
|
|
// POST /login |
|
269
|
|
|
$response = $this->_post('login', array( |
|
270
|
|
|
'login' => true, |
|
271
|
|
|
'cookies' => $response->cookies, |
|
272
|
|
|
'data' => array( |
|
273
|
|
|
'_token' => $token, |
|
274
|
|
|
'email' => $this->auth->mail, |
|
275
|
|
|
'password' => $this->auth->pass, |
|
276
|
|
|
), |
|
277
|
|
|
)); |
|
278
|
|
|
$this->auth->session = $response->cookies_by_name['fuelly_session'][0]; |
|
279
|
|
|
return $this->checkSession(); |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
|
|
return false; |
|
283
|
|
|
} |
|
284
|
|
|
|
|
285
|
|
|
/** |
|
286
|
|
|
* |
|
287
|
|
|
*/ |
|
288
|
|
|
public function checkSession() { |
|
289
|
|
|
if ( !$this->auth->session ) { |
|
290
|
|
|
return false; |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
$response = $this->_get('dashboard'); |
|
294
|
|
|
if ( $response->code == 200 ) { |
|
295
|
|
|
$regex = '#<a href="' . preg_quote($this->base, '#') . 'driver/([\w\d]+)/edit">Settings</a>#'; |
|
296
|
|
|
if ( preg_match($regex, $response->body, $match) ) { |
|
297
|
|
|
$this->username = $match[1]; |
|
298
|
|
|
|
|
299
|
|
|
// Since we're downloading /dashboard anyway, let's extract our vehicles from it |
|
300
|
|
|
$this->vehicles = $this->extractVehicles($response->body); |
|
301
|
|
|
|
|
302
|
|
|
return true; |
|
303
|
|
|
} |
|
304
|
|
|
} |
|
305
|
|
|
|
|
306
|
|
|
return false; |
|
307
|
|
|
} |
|
308
|
|
|
|
|
309
|
|
|
/** |
|
310
|
|
|
* |
|
311
|
|
|
*/ |
|
312
|
|
|
public function ensureSession() { |
|
313
|
|
|
if ( !$this->checkSession() ) { |
|
314
|
|
|
return $this->logIn(); |
|
315
|
|
|
} |
|
316
|
|
|
|
|
317
|
|
|
return true; |
|
318
|
|
|
} |
|
319
|
|
|
|
|
320
|
|
|
/** |
|
321
|
|
|
* |
|
322
|
|
|
*/ |
|
323
|
|
|
protected function extractFormToken( $html ) { |
|
324
|
|
|
if ( preg_match('#<input.+?name="_token".+?>#i', $html, $match) ) { |
|
325
|
|
|
if ( preg_match('#value="([^"]+)"#', $match[0], $match) ) { |
|
326
|
|
|
return $match[1]; |
|
327
|
|
|
} |
|
328
|
|
|
} |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
|
|
|
|
332
|
|
|
|
|
333
|
|
|
/** |
|
334
|
|
|
* HTTP GET |
|
335
|
|
|
*/ |
|
336
|
|
|
public function _get( $uri, $options = array() ) { |
|
337
|
|
|
return $this->_http($uri, $options + array('method' => 'GET')); |
|
338
|
|
|
} |
|
339
|
|
|
|
|
340
|
|
|
/** |
|
341
|
|
|
* HTTP POST |
|
342
|
|
|
*/ |
|
343
|
|
|
public function _post( $uri, $options = array() ) { |
|
344
|
|
|
return $this->_http($uri, $options + array('method' => 'POST')); |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
/** |
|
348
|
|
|
* HTTP URL |
|
349
|
|
|
*/ |
|
350
|
|
|
public function _url( $uri, $options = array() ) { |
|
351
|
|
|
$base = !empty($options['login']) ? $this->loginBase : $this->base; |
|
352
|
|
|
$url = strpos($uri, '://') ? $uri : $base . $uri; |
|
353
|
|
|
return $url; |
|
354
|
|
|
} |
|
355
|
|
|
|
|
356
|
|
|
/** |
|
357
|
|
|
* HTTP REQUEST |
|
358
|
|
|
*/ |
|
359
|
|
|
public function _http( $uri, $options = array() ) { |
|
360
|
|
|
if ( $this->auth->session ) { |
|
361
|
|
|
$options['cookies'][] = array('fuelly_session', $this->auth->session); |
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
$url = $this->_url($uri, $options); |
|
365
|
|
|
|
|
366
|
|
|
$log = array(); |
|
367
|
|
|
$log['req'] = $options['method'] . ' ' . $url; |
|
368
|
|
|
$this->log[] = &$log; |
|
369
|
|
|
|
|
370
|
|
|
$_start = microtime(1); |
|
371
|
|
|
$request = HTTP::create($url, $options); |
|
372
|
|
|
|
|
373
|
|
|
$response = $request->request(); |
|
374
|
|
|
$_time = microtime(1) - $_start; |
|
375
|
|
|
|
|
376
|
|
|
$log['rsp'] = $response->code . ' ' . $response->status; |
|
377
|
|
|
$log['time'] = $_time; |
|
378
|
|
|
|
|
379
|
|
|
return $response; |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
} |
|
383
|
|
|
|
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.