1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Godbout\Alfred\Time\Services; |
4
|
|
|
|
5
|
|
|
use Carbon\Carbon; |
6
|
|
|
use Carbon\CarbonInterval; |
7
|
|
|
use Exception; |
8
|
|
|
use MorningTrain\TogglApi\TogglApi; |
9
|
|
|
|
10
|
|
|
class Toggl extends TimerService |
11
|
|
|
{ |
12
|
|
|
private $client; |
13
|
|
|
|
14
|
|
|
private $data = null; |
15
|
|
|
|
16
|
|
|
|
17
|
168 |
|
public function __construct($apiToken) |
18
|
|
|
{ |
19
|
168 |
|
$this->client = new TogglApi($apiToken); |
20
|
168 |
|
} |
21
|
|
|
|
22
|
36 |
|
public function projects() |
23
|
|
|
{ |
24
|
36 |
|
return $this->extractFromData('projects'); |
25
|
|
|
} |
26
|
|
|
|
27
|
14 |
|
public function tags() |
28
|
|
|
{ |
29
|
14 |
|
return $this->extractFromData('tags'); |
30
|
|
|
} |
31
|
|
|
|
32
|
11 |
|
public function pastTimers() |
33
|
|
|
{ |
34
|
|
|
try { |
35
|
11 |
|
$togglTimers = $this->client->getTimeEntriesInRange(Carbon::today(), Carbon::today()->subDays(30)); |
36
|
|
|
|
37
|
11 |
|
return $this->convertToPastTimers($togglTimers); |
38
|
|
|
} catch (Exception $e) { |
39
|
|
|
return []; |
40
|
|
|
} |
41
|
|
|
} |
42
|
|
|
|
43
|
121 |
|
public function startTimer() |
44
|
|
|
{ |
45
|
|
|
try { |
46
|
121 |
|
$timer = $this->client->startTimeEntry([ |
47
|
121 |
|
'description' => getenv('timer_description'), |
48
|
121 |
|
'pid' => getenv('timer_project_id'), |
49
|
121 |
|
'tags' => getenv('timer_tag') ? [getenv('timer_tag')] : '', |
50
|
121 |
|
'created_with' => 'Alfred Time' |
51
|
|
|
]); |
52
|
|
|
|
53
|
121 |
|
if (! isset($timer->id)) { |
54
|
121 |
|
return false; |
55
|
|
|
} |
56
|
|
|
} catch (Exception $e) { |
57
|
|
|
return false; |
58
|
|
|
} |
59
|
|
|
|
60
|
121 |
|
return $timer->id; |
61
|
|
|
} |
62
|
|
|
|
63
|
98 |
|
public function stopCurrentTimer() |
64
|
|
|
{ |
65
|
98 |
|
if ($timerId = $this->runningTimer()) { |
66
|
98 |
|
$response = $this->client->stopTimeEntry($timerId); |
67
|
|
|
|
68
|
98 |
|
if (! isset($response->id)) { |
69
|
|
|
throw new Exception("Can't stop current running timer.", 1); |
70
|
|
|
|
71
|
|
|
return false; |
|
|
|
|
72
|
|
|
} |
73
|
|
|
|
74
|
98 |
|
return true; |
75
|
|
|
} |
76
|
|
|
|
77
|
11 |
|
return false; |
78
|
|
|
} |
79
|
|
|
|
80
|
162 |
|
public function runningTimer() |
81
|
|
|
{ |
82
|
162 |
|
$timer = $this->client->getRunningTimeEntry(); |
83
|
|
|
|
84
|
162 |
|
return $timer->id ?? false; |
85
|
|
|
} |
86
|
|
|
|
87
|
22 |
|
public function continueTimer($timerId) |
88
|
|
|
{ |
89
|
|
|
/** |
90
|
|
|
* Timer attributes are stored in env variables |
91
|
|
|
* gathered in startTimer. |
92
|
|
|
*/ |
93
|
22 |
|
return $this->startTimer(); |
94
|
|
|
} |
95
|
|
|
|
96
|
120 |
|
public function deleteTimer($timerId) |
97
|
|
|
{ |
98
|
|
|
try { |
99
|
120 |
|
$this->client->deleteTimeEntry($timerId); |
100
|
|
|
} catch (Exception $e) { |
101
|
|
|
return false; |
102
|
|
|
} |
103
|
|
|
|
104
|
120 |
|
return true; |
105
|
|
|
} |
106
|
|
|
|
107
|
50 |
|
private function extractFromData($needle) |
108
|
|
|
{ |
109
|
50 |
|
$data = $this->getData(); |
110
|
|
|
|
111
|
50 |
|
if (! isset($data->$needle)) { |
112
|
6 |
|
return []; |
113
|
|
|
} |
114
|
|
|
|
115
|
44 |
|
$nonDeletedData = $this->filterOutServerwiseDeletedItemsFromData($data->$needle); |
116
|
|
|
|
117
|
44 |
|
return array_column($nonDeletedData, 'name', 'id'); |
118
|
|
|
} |
119
|
|
|
|
120
|
50 |
|
private function getData() |
121
|
|
|
{ |
122
|
50 |
|
if (is_null($this->data)) { |
123
|
50 |
|
return $this->client->getMe(true); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
return $this->data; |
127
|
|
|
} |
128
|
|
|
|
129
|
44 |
|
private function filterOutServerwiseDeletedItemsFromData($items = []) |
130
|
|
|
{ |
131
|
|
|
return array_filter($items, function ($item) { |
132
|
44 |
|
return ! isset($item->server_deleted_at); |
133
|
44 |
|
}); |
134
|
|
|
} |
135
|
|
|
|
136
|
11 |
|
protected function convertToPastTimers($togglTimers) |
137
|
|
|
{ |
138
|
11 |
|
$projects = $this->projects(); |
139
|
|
|
|
140
|
11 |
|
return array_reverse( |
141
|
|
|
array_map(function ($togglTimer) use ($projects) { |
142
|
11 |
|
return $this->buildPastTimerObject($togglTimer, $projects); |
143
|
11 |
|
}, $togglTimers) |
144
|
|
|
); |
145
|
|
|
} |
146
|
|
|
|
147
|
11 |
|
protected function buildPastTimerObject($togglTimer, $projects) |
148
|
|
|
{ |
149
|
11 |
|
$pastTimer['id'] = $togglTimer->id; |
|
|
|
|
150
|
11 |
|
$pastTimer['description'] = $togglTimer->description ?? ''; |
151
|
11 |
|
$pastTimer['duration'] = CarbonInterval::seconds($togglTimer->duration)->cascade()->format('%H:%I:%S'); |
152
|
|
|
|
153
|
11 |
|
if (isset($togglTimer->pid)) { |
154
|
11 |
|
$pastTimer['project_id'] = $togglTimer->pid; |
155
|
11 |
|
$pastTimer['project_name'] = $projects[$togglTimer->pid]; |
156
|
|
|
} |
157
|
|
|
|
158
|
11 |
|
if (isset($togglTimer->tags)) { |
159
|
11 |
|
$pastTimer['tags'] = implode(', ', (array) $togglTimer->tags); |
160
|
|
|
} |
161
|
|
|
|
162
|
11 |
|
return (object) $pastTimer; |
163
|
|
|
} |
164
|
|
|
} |
165
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.