1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
require 'Toggl.class.php'; |
4
|
|
|
require 'Harvest.class.php'; |
5
|
|
|
|
6
|
|
|
class AlfredTime |
7
|
|
|
{ |
8
|
|
|
private $config; |
9
|
|
|
private $message; |
10
|
|
|
private $currentImplementation = [ |
11
|
|
|
'start' => ['toggl'], |
12
|
|
|
'start_default' => ['toggl', 'harvest'], |
13
|
|
|
'stop' => ['toggl', 'harvest'], |
14
|
|
|
'delete' => ['toggl'], |
15
|
|
|
]; |
16
|
|
|
private $toggl; |
17
|
|
|
private $harvest; |
18
|
|
|
|
19
|
|
|
public function __construct() |
20
|
|
|
{ |
21
|
|
|
$this->config = $this->loadConfiguration(); |
22
|
|
|
$this->message = ''; |
23
|
|
|
$this->toggl = new Toggl($this->config['toggl']['api_token']); |
24
|
|
|
$this->harvest = new Harvest($this->config['harvest']['domain'], $this->config['harvest']['api_token']); |
25
|
|
|
} |
26
|
|
|
|
27
|
|
|
public function isConfigured() |
28
|
|
|
{ |
29
|
|
|
return $this->config === null ? false : true; |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
public function hasTimerRunning() |
33
|
|
|
{ |
34
|
|
|
return $this->config['workflow']['is_timer_running'] === false ? false : true; |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
public function getTimerDescription() |
38
|
|
|
{ |
39
|
|
|
return $this->config['workflow']['timer_description']; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
public function startTimer($description = '', $projectsDefault = null, $tagsDefault = null, $startDefault = false) |
43
|
|
|
{ |
44
|
|
|
$message = ''; |
45
|
|
|
$startType = $startDefault === true ? 'start_default' : 'start'; |
46
|
|
|
$atLeastOneServiceStarted = false; |
47
|
|
|
$implementedServices = $this->implementedServicesForFeature($startType); |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* When starting a new timer, all the services timer IDs have to be put to null |
51
|
|
|
* so that when the user uses the UNDO feature, it doesn't delete old previous |
52
|
|
|
* other services timers. The timer IDs are used for the UNDO feature and |
53
|
|
|
* should then contain the IDs of the last starts through the workflow, not |
54
|
|
|
* through each individual service |
55
|
|
|
*/ |
56
|
|
|
if (empty($implementedServices) === false) { |
57
|
|
|
foreach ($this->activatedServices() as $service) { |
58
|
|
|
$this->config['workflow']['timer_' . $service . '_id'] = null; |
59
|
|
|
$this->saveConfiguration(); |
60
|
|
|
} |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
foreach ($implementedServices as $service) { |
64
|
|
|
$defaultProjectId = isset($projectsDefault[$service]) ? $projectsDefault[$service] : null; |
65
|
|
|
$defaultTags = isset($tagsDefault[$service]) ? $tagsDefault[$service] : null; |
66
|
|
|
|
67
|
|
|
$functionName = 'start' . ucfirst($service) . 'Timer'; |
68
|
|
|
$timerId = call_user_func_array(['AlfredTime', $functionName], [$description, $defaultProjectId, $defaultTags]); |
69
|
|
|
$this->config['workflow']['timer_' . $service . '_id'] = $timerId; |
70
|
|
|
if ($timerId !== null) { |
71
|
|
|
$atLeastOneServiceStarted = true; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
$message .= $this->getLastMessage() . "\r\n"; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
if ($atLeastOneServiceStarted === true) { |
78
|
|
|
$this->config['workflow']['timer_description'] = $description; |
79
|
|
|
$this->config['workflow']['is_timer_running'] = true; |
80
|
|
|
$this->saveConfiguration(); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
return $message; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
public function startTimerWithDefaultOptions($description) |
87
|
|
|
{ |
88
|
|
|
$projectsDefault = [ |
89
|
|
|
'toggl' => $this->config['toggl']['default_project_id'], |
90
|
|
|
'harvest' => $this->config['harvest']['default_project_id'], |
91
|
|
|
]; |
92
|
|
|
|
93
|
|
|
$tagsDefault = [ |
94
|
|
|
'toggl' => $this->config['toggl']['default_tags'], |
95
|
|
|
'harvest' => $this->config['harvest']['default_task_id'], |
96
|
|
|
]; |
97
|
|
|
|
98
|
|
|
return $this->startTimer($description, $projectsDefault, $tagsDefault, true); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
public function stopRunningTimer() |
102
|
|
|
{ |
103
|
|
|
$message = ''; |
104
|
|
|
$atLeastOneServiceStopped = false; |
105
|
|
|
|
106
|
|
|
foreach ($this->activatedServices() as $service) { |
107
|
|
|
$functionName = 'stop' . ucfirst($service) . 'Timer'; |
108
|
|
|
if (call_user_func(['AlfredTime', $functionName]) === true) { |
109
|
|
|
$atLeastOneServiceStopped = true; |
110
|
|
|
} |
111
|
|
|
$message .= $this->getLastMessage() . "\r\n"; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
View Code Duplication |
if ($atLeastOneServiceStopped === true) { |
|
|
|
|
115
|
|
|
$this->config['workflow']['is_timer_running'] = false; |
116
|
|
|
$this->saveConfiguration(); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
return $message; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
public function generateDefaultConfigurationFile() |
123
|
|
|
{ |
124
|
|
|
$this->config = [ |
125
|
|
|
'workflow' => [ |
126
|
|
|
'is_timer_running' => false, |
127
|
|
|
'timer_toggl_id' => null, |
128
|
|
|
'timer_harvest_id' => null, |
129
|
|
|
'timer_description' => '', |
130
|
|
|
], |
131
|
|
|
'toggl' => [ |
132
|
|
|
'is_active' => true, |
133
|
|
|
'api_token' => '', |
134
|
|
|
'default_project_id' => '', |
135
|
|
|
'default_tags' => '', |
136
|
|
|
], |
137
|
|
|
'harvest' => [ |
138
|
|
|
'is_active' => true, |
139
|
|
|
'domain' => '', |
140
|
|
|
'api_token' => '', |
141
|
|
|
'default_project_id' => '', |
142
|
|
|
'default_task_id' => '', |
143
|
|
|
], |
144
|
|
|
]; |
145
|
|
|
|
146
|
|
|
$this->saveConfiguration(); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
public function activatedServices() |
150
|
|
|
{ |
151
|
|
|
$services = []; |
152
|
|
|
|
153
|
|
|
if ($this->isTogglActive() === true) { |
154
|
|
|
array_push($services, 'toggl'); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
if ($this->isHarvestActive() === true) { |
158
|
|
|
array_push($services, 'harvest'); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
return $services; |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
public function servicesToUndo() |
165
|
|
|
{ |
166
|
|
|
$services = []; |
167
|
|
|
|
168
|
|
|
foreach ($this->activatedServices() as $service) { |
169
|
|
|
if ($this->config['workflow']['timer_' . $service . '_id'] !== null) { |
170
|
|
|
array_push($services, $service); |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
return $services; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
public function syncOnlineDataToLocalCache() |
178
|
|
|
{ |
179
|
|
|
$message = ''; |
180
|
|
|
|
181
|
|
|
if ($this->isTogglActive() === true) { |
182
|
|
|
$message .= $this->syncTogglOnlineDataToLocalCache(); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return $message; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
public function getProjects() |
189
|
|
|
{ |
190
|
|
|
$projects = []; |
191
|
|
|
|
192
|
|
|
if ($this->isTogglActive() === true) { |
193
|
|
|
$projects = array_merge($projects, $this->getTogglProjects()); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
return $projects; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
public function getTags() |
200
|
|
|
{ |
201
|
|
|
$tags = []; |
202
|
|
|
|
203
|
|
|
if ($this->isTogglActive() === true) { |
204
|
|
|
$tags = array_merge($tags, $this->getTogglTags()); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
return $tags; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
public function UndoTimer() |
211
|
|
|
{ |
212
|
|
|
$message = ''; |
213
|
|
|
|
214
|
|
|
if ($this->hasTimerRunning() === true) { |
215
|
|
|
$this->stopRunningTimer(); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
$atLeastOneTimerDeleted = false; |
219
|
|
|
foreach ($this->servicesToUndo() as $service) { |
220
|
|
|
$functionName = 'delete' . ucfirst($service) . 'Timer'; |
221
|
|
|
if (call_user_func_array(['AlfredTime', $functionName], [$this->config['workflow']['timer_' . $service . '_id']]) === true) { |
222
|
|
|
$this->config['workflow']['timer_' . $service . '_id'] = null; |
223
|
|
|
$atLeastOneTimerDeleted = true; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
$message .= $this->getLastMessage() . "\r\n"; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
if ($atLeastOneTimerDeleted === true) { |
230
|
|
|
$this->saveConfiguration(); |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
return $message; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
public function getRecentTimers() |
237
|
|
|
{ |
238
|
|
|
$timers = []; |
239
|
|
|
|
240
|
|
|
if ($this->isTogglActive() === true) { |
241
|
|
|
$timers = array_merge($timers, $this->getRecentTogglTimers()); |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
return $timers; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
public function deleteTimer($timerId) |
248
|
|
|
{ |
249
|
|
|
$message = ''; |
250
|
|
|
|
251
|
|
|
$atLeastOneTimerDeleted = false; |
252
|
|
|
foreach ($this->implementedServicesForFeature('delete') as $service) { |
253
|
|
|
$functionName = 'delete' . ucfirst($service) . 'Timer'; |
254
|
|
|
if (call_user_func_array(['AlfredTime', $functionName], [$timerId]) === true) { |
255
|
|
|
$this->config['workflow']['timer_' . $service . '_id'] = null; |
256
|
|
|
$atLeastOneTimerDeleted = true; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
$message .= $this->getLastMessage() . "\r\n"; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
View Code Duplication |
if ($atLeastOneTimerDeleted === true) { |
|
|
|
|
263
|
|
|
$this->config['workflow']['is_timer_running'] = false; |
264
|
|
|
$this->saveConfiguration(); |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
return $message; |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
public function implementedServicesForFeature($feature = null) |
271
|
|
|
{ |
272
|
|
|
$services = []; |
273
|
|
|
|
274
|
|
|
if (isset($this->currentImplementation[$feature]) === true) { |
275
|
|
|
$services = $this->currentImplementation[$feature]; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
return $services; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
public function getProjectName($projectId) |
282
|
|
|
{ |
283
|
|
|
$projectName = ''; |
284
|
|
|
|
285
|
|
|
$projects = $this->getProjects(); |
286
|
|
|
foreach ($projects as $project) { |
287
|
|
|
if ($project['id'] === $projectId) { |
288
|
|
|
$projectName = $project['name']; |
289
|
|
|
break; |
290
|
|
|
} |
291
|
|
|
} |
292
|
|
|
|
293
|
|
|
return $projectName; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
private function getRecentTogglTimers() |
297
|
|
|
{ |
298
|
|
|
return $this->toggl->getRecentTimers(); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
private function getLastMessage() |
302
|
|
|
{ |
303
|
|
|
return $this->message; |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
private function deleteTogglTimer($togglId) |
307
|
|
|
{ |
308
|
|
|
$res = $this->toggl->deleteTimer($togglId); |
309
|
|
|
$this->message = $this->toggl->getLastMessage(); |
310
|
|
|
|
311
|
|
|
return $res; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
private function deleteHarvestTimer($harvestId) |
315
|
|
|
{ |
316
|
|
|
$res = false; |
317
|
|
|
|
318
|
|
|
$domain = $this->config['harvest']['domain']; |
319
|
|
|
|
320
|
|
|
$url = 'https://' . $domain . '.harvestapp.com/daily/delete/' . $harvestId; |
321
|
|
|
|
322
|
|
|
$base64Token = $this->config['harvest']['api_token']; |
323
|
|
|
|
324
|
|
|
$headers = [ |
325
|
|
|
"Content-type: application/json", |
326
|
|
|
"Accept: application/json", |
327
|
|
|
'Authorization: Basic ' . $base64Token, |
328
|
|
|
]; |
329
|
|
|
|
330
|
|
|
$ch = curl_init($url); |
331
|
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); |
332
|
|
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); |
333
|
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
334
|
|
|
$response = curl_exec($ch); |
335
|
|
|
$lastHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); |
336
|
|
|
curl_close($ch); |
337
|
|
|
|
338
|
|
|
if ($response === false || $lastHttpCode !== 200) { |
339
|
|
|
$this->message = '- Could not delete Harvest timer!'; |
340
|
|
|
} else { |
341
|
|
|
$this->message = '- Harvest timer deleted'; |
342
|
|
|
$res = true; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
return $res; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
private function syncTogglOnlineDataToLocalCache() |
349
|
|
|
{ |
350
|
|
|
$data = $this->toggl->getOnlineData(); |
351
|
|
|
$this->message = $this->toggl->getLastMessage(); |
352
|
|
|
|
353
|
|
|
if (empty($data) === false) { |
354
|
|
|
$this->saveTogglDataCache($data); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
return $this->message; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
private function saveTogglDataCache($data) |
361
|
|
|
{ |
362
|
|
|
$cacheFile = getenv('alfred_workflow_data') . '/toggl_cache.json'; |
363
|
|
|
file_put_contents($cacheFile, json_encode($data)); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
private function loadConfiguration() |
367
|
|
|
{ |
368
|
|
|
$config = null; |
369
|
|
|
$configFile = getenv('alfred_workflow_data') . '/config.json'; |
370
|
|
|
|
371
|
|
|
if (file_exists($configFile)) { |
372
|
|
|
$config = json_decode(file_get_contents($configFile), true); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
return $config; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
private function saveConfiguration() |
379
|
|
|
{ |
380
|
|
|
$workflowDir = getenv('alfred_workflow_data'); |
381
|
|
|
$configFile = $workflowDir . '/config.json'; |
382
|
|
|
|
383
|
|
|
if (file_exists($workflowDir) === false) { |
384
|
|
|
mkdir($workflowDir); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
file_put_contents($configFile, json_encode($this->config, JSON_PRETTY_PRINT)); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
private function startTogglTimer($description, $projectId, $tagNames) |
391
|
|
|
{ |
392
|
|
|
$togglId = $this->toggl->startTimer($description, $projectId, $tagNames); |
393
|
|
|
$this->message = $this->toggl->getLastMessage(); |
394
|
|
|
|
395
|
|
|
return $togglId; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
View Code Duplication |
private function stopTogglTimer() |
|
|
|
|
399
|
|
|
{ |
400
|
|
|
$togglId = $this->config['workflow']['timer_toggl_id']; |
401
|
|
|
|
402
|
|
|
$res = $this->toggl->stopTimer($togglId); |
403
|
|
|
$this->message = $this->toggl->getLastMessage(); |
404
|
|
|
|
405
|
|
|
return $res; |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
private function startHarvestTimer($description, $projectId, $taskId) |
409
|
|
|
{ |
410
|
|
|
$harvestId = $this->harvest->startTimer($description, $projectId, $taskId); |
411
|
|
|
$this->message = $this->harvest->getLastMessage(); |
412
|
|
|
|
413
|
|
|
return $harvestId; |
414
|
|
|
} |
415
|
|
|
|
416
|
|
View Code Duplication |
private function stopHarvestTimer() |
|
|
|
|
417
|
|
|
{ |
418
|
|
|
$harvestId = $this->config['workflow']['timer_harvest_id']; |
419
|
|
|
|
420
|
|
|
$res = $this->harvest->stopTimer($harvestId); |
421
|
|
|
$this->message = $this->harvest->getLastMessage(); |
422
|
|
|
|
423
|
|
|
return $res; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
private function getTogglProjects() |
427
|
|
|
{ |
428
|
|
|
$cacheData = []; |
429
|
|
|
$cacheFile = getenv('alfred_workflow_data') . '/toggl_cache.json'; |
430
|
|
|
|
431
|
|
|
if (file_exists($cacheFile)) { |
432
|
|
|
$cacheData = json_decode(file_get_contents($cacheFile), true); |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* To only show projects that are currently active |
437
|
|
|
* The Toggl API is slightly weird on that |
438
|
|
|
*/ |
439
|
|
|
foreach ($cacheData['data']['projects'] as $key => $project) { |
440
|
|
|
if (isset($project['server_deleted_at']) === true) { |
441
|
|
|
unset($cacheData['data']['projects'][$key]); |
442
|
|
|
} |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
return $cacheData['data']['projects']; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
private function getTogglTags() |
449
|
|
|
{ |
450
|
|
|
$cacheFile = getenv('alfred_workflow_data') . '/toggl_cache.json'; |
451
|
|
|
$cacheData = []; |
452
|
|
|
|
453
|
|
|
if (file_exists($cacheFile)) { |
454
|
|
|
$cacheData = json_decode(file_get_contents($cacheFile), true); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
return $cacheData['data']['tags']; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
private function isTogglActive() |
461
|
|
|
{ |
462
|
|
|
return $this->config['toggl']['is_active']; |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
private function isHarvestActive() |
466
|
|
|
{ |
467
|
|
|
return $this->config['harvest']['is_active']; |
468
|
|
|
} |
469
|
|
|
} |
470
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.