Completed
Push — master ( fb6de7...e6e877 )
by Guillaume
05:24
created

Time   C

Complexity

Total Complexity 63

Size/Duplication

Total Lines 448
Duplicated Lines 2.23 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 63
c 5
b 0
f 0
lcom 1
cbo 3
dl 10
loc 448
rs 5.8893

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A activatedServices() 0 12 3
A deleteServiceTimer() 6 13 3
A deleteTimer() 0 19 4
A generateDefaultConfigurationFile() 0 4 1
A getProjectName() 0 15 3
A getProjects() 0 10 2
A getRecentTimers() 0 10 2
A getTags() 0 10 2
A getTimerDescription() 0 4 1
A hasTimerRunning() 0 4 2
A implementedServicesForFeature() 0 10 2
A isConfigured() 0 4 2
A isServiceActive() 0 4 1
A servicesToUndo() 0 12 3
D startTimer() 0 41 9
A startTimerWithDefaultOptions() 0 14 1
A stopRunningTimer() 0 21 4
A syncOnlineDataToLocalCache() 0 10 2
B undoTimer() 4 25 5
A getRecentTogglTimers() 0 4 1
A getTogglProjects() 0 21 4
A getTogglTags() 0 11 2
A saveTogglDataCache() 0 5 1
A syncTogglOnlineDataToLocalCache() 0 12 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Time often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Time, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace AlfredTime;
4
5
use AlfredTime\Toggl;
6
use AlfredTime\Config;
7
use AlfredTime\Harvest;
8
9
class Time
10
{
11
    /**
12
     * @var mixed
13
     */
14
    private $config;
15
16
    /**
17
     * @var array
18
     */
19
    private $currentImplementation = [
20
        'start'         => ['toggl'],
21
        'start_default' => ['toggl', 'harvest'],
22
        'stop'          => ['toggl', 'harvest'],
23
        'delete'        => ['toggl'],
24
    ];
25
26
    /**
27
     * @var mixed
28
     */
29
    private $harvest;
30
31
    /**
32
     * @var mixed
33
     */
34
    private $message;
35
36
    /**
37
     * @var array
38
     */
39
    private $services = [
40
        'toggl',
41
        'harvest',
42
    ];
43
44
    /**
45
     * @var mixed
46
     */
47
    private $toggl;
48
49
    public function __construct()
50
    {
51
        $this->config = new Config(getenv('alfred_workflow_data') . '/config.json');
52
53
        $this->harvest = new Harvest($this->config->get('harvest', 'domain'), $this->config->get('harvest', 'api_token'));
54
        $this->toggl = new Toggl($this->config->get('toggl', 'api_token'));
55
        $this->message = '';
56
    }
57
58
    /**
59
     * @return mixed
60
     */
61
    public function activatedServices()
62
    {
63
        $activatedServices = [];
64
65
        foreach ($this->services as $service) {
66
            if ($this->isServiceActive($service) === true) {
67
                array_push($activatedServices, $service);
68
            }
69
        }
70
71
        return $activatedServices;
72
    }
73
74
    /**
75
     * @param  $service
76
     * @param  $timerId
77
     * @return mixed
78
     */
79
    public function deleteServiceTimer($service, $timerId)
80
    {
81
        $res = false;
82
83 View Code Duplication
        if ($this->$service->deleteTimer($timerId) === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
84
            if ($timerId === $this->config->get('workflow', 'timer_' . $service . '_id')) {
85
                $this->config->update('workflow', 'timer_' . $service . '_id', null);
86
                $res = true;
87
            }
88
        }
89
90
        return $res;
91
    }
92
93
    /**
94
     * @param  $timerId
95
     * @return string
96
     */
97
    public function deleteTimer($timerId)
98
    {
99
        $message = '';
100
        $atLeastOneTimerDeleted = false;
101
102
        foreach ($this->implementedServicesForFeature('delete') as $service) {
103
            if ($this->deleteServiceTimer($service, $timerId) === true) {
104
                $atLeastOneTimerDeleted = true;
105
            }
106
107
            $message .= $this->$service->getLastMessage() . "\r\n";
108
        }
109
110
        if ($atLeastOneTimerDeleted === true) {
111
            $this->config->update('workflow', 'is_timer_running', false);
112
        }
113
114
        return $message;
115
    }
116
117
    public function generateDefaultConfigurationFile()
118
    {
119
        $this->config->generateDefaultConfigurationFile();
120
    }
121
122
    /**
123
     * @param  $projectId
124
     * @return mixed
125
     */
126
    public function getProjectName($projectId)
127
    {
128
        $projectName = '';
129
130
        $projects = $this->getProjects();
131
132
        foreach ($projects as $project) {
133
            if ($project['id'] === $projectId) {
134
                $projectName = $project['name'];
135
                break;
136
            }
137
        }
138
139
        return $projectName;
140
    }
141
142
    /**
143
     * @return mixed
144
     */
145
    public function getProjects()
146
    {
147
        $projects = [];
148
149
        if ($this->isServiceActive('toggl') === true) {
150
            $projects = array_merge($projects, $this->getTogglProjects());
151
        }
152
153
        return $projects;
154
    }
155
156
    /**
157
     * @return mixed
158
     */
159
    public function getRecentTimers()
160
    {
161
        $timers = [];
162
163
        if ($this->isServiceActive('toggl') === true) {
164
            $timers = array_merge($timers, $this->getRecentTogglTimers());
165
        }
166
167
        return $timers;
168
    }
169
170
    /**
171
     * @return mixed
172
     */
173
    public function getTags()
174
    {
175
        $tags = [];
176
177
        if ($this->isServiceActive('toggl') === true) {
178
            $tags = array_merge($tags, $this->getTogglTags());
179
        }
180
181
        return $tags;
182
    }
183
184
    /**
185
     * @return mixed
186
     */
187
    public function getTimerDescription()
188
    {
189
        return $this->config->get('workflow', 'timer_description');
190
    }
191
192
    /**
193
     * @return mixed
194
     */
195
    public function hasTimerRunning()
196
    {
197
        return $this->config->get('workflow', 'is_timer_running') === false ? false : true;
198
    }
199
200
    /**
201
     * @param  $feature
202
     * @return mixed
203
     */
204
    public function implementedServicesForFeature($feature = null)
205
    {
206
        $services = [];
207
208
        if (isset($this->currentImplementation[$feature]) === true) {
209
            $services = $this->currentImplementation[$feature];
210
        }
211
212
        return $services;
213
    }
214
215
    /**
216
     * @return mixed
217
     */
218
    public function isConfigured()
219
    {
220
        return $this->config === null ? false : true;
221
    }
222
223
    /**
224
     * @param  $service
225
     * @return mixed
226
     */
227
    public function isServiceActive($service)
228
    {
229
        return $this->config->get($service, 'is_active');
230
    }
231
232
    /**
233
     * @return mixed
234
     */
235
    public function servicesToUndo()
236
    {
237
        $services = [];
238
239
        foreach ($this->activatedServices() as $service) {
240
            if ($this->config->get('workflow', 'timer_' . $service . '_id') !== null) {
241
                array_push($services, $service);
242
            }
243
        }
244
245
        return $services;
246
    }
247
248
    /**
249
     * @param  $description
250
     * @param  $projectsDefault
251
     * @param  null               $tagsDefault
252
     * @param  boolean            $startDefault
253
     * @return mixed
254
     */
255
    public function startTimer($description = '', $projectsDefault = null, $tagsDefault = null, $startDefault = false)
256
    {
257
        $message = '';
258
        $startType = $startDefault === true ? 'start_default' : 'start';
259
        $atLeastOneServiceStarted = false;
260
        $implementedServices = $this->implementedServicesForFeature($startType);
261
262
/*
263
 * When starting a new timer, all the services timer IDs have to be put to null
264
 * so that when the user uses the UNDO feature, it doesn't delete old previous
265
 * other services timers. The timer IDs are used for the UNDO feature and
266
 * should then contain the IDs of the last starts through the workflow, not
267
 * through each individual sefrvice
268
 */
269
        if (empty($implementedServices) === false) {
270
            foreach ($this->activatedServices() as $service) {
271
                $this->config->update('workflow', 'timer_' . $service . '_id', null);
272
            }
273
        }
274
275
        foreach ($implementedServices as $service) {
276
            $defaultProjectId = isset($projectsDefault[$service]) ? $projectsDefault[$service] : null;
277
            $defaultTags = isset($tagsDefault[$service]) ? $tagsDefault[$service] : null;
278
279
            $timerId = $this->$service->startTimer($description, $defaultProjectId, $defaultTags);
280
            $this->config->update('workflow', 'timer_' . $service . '_id', $timerId);
281
282
            if ($timerId !== null) {
283
                $atLeastOneServiceStarted = true;
284
            }
285
286
            $message .= $this->$service->getLastMessage() . "\r\n";
287
        }
288
289
        if ($atLeastOneServiceStarted === true) {
290
            $this->config->update('workflow', 'timer_description', $description);
291
            $this->config->update('workflow', 'is_timer_running', true);
292
        }
293
294
        return $message;
295
    }
296
297
    /**
298
     * @param  $description
299
     * @return mixed
300
     */
301
    public function startTimerWithDefaultOptions($description)
302
    {
303
        $projectsDefault = [
304
            'toggl'   => $this->config->get('toggl', 'default_project_id'),
305
            'harvest' => $this->config->get('harvest', 'default_project_id'),
306
        ];
307
308
        $tagsDefault = [
309
            'toggl'   => $this->config->get('toggl', 'default_tags'),
310
            'harvest' => $this->config->get('harvest', 'default_task_id'),
311
        ];
312
313
        return $this->startTimer($description, $projectsDefault, $tagsDefault, true);
314
    }
315
316
    /**
317
     * @return mixed
318
     */
319
    public function stopRunningTimer()
320
    {
321
        $message = '';
322
        $atLeastOneServiceStopped = false;
323
324
        foreach ($this->activatedServices() as $service) {
325
            $timerId = $this->config->get('workflow', 'timer_' . $service . '_id');
326
327
            if ($this->$service->stopTimer($timerId) === true) {
328
                $atLeastOneServiceStopped = true;
329
            }
330
331
            $message .= $this->$service->getLastMessage() . "\r\n";
332
        }
333
334
        if ($atLeastOneServiceStopped === true) {
335
            $this->config->update('workflow', 'is_timer_running', false);
336
        }
337
338
        return $message;
339
    }
340
341
    /**
342
     * @return mixed
343
     */
344
    public function syncOnlineDataToLocalCache()
345
    {
346
        $message = '';
347
348
        if ($this->isServiceActive('toggl') === true) {
349
            $message .= $this->syncTogglOnlineDataToLocalCache();
350
        }
351
352
        return $message;
353
    }
354
355
    /**
356
     * @return mixed
357
     */
358
    public function undoTimer()
359
    {
360
        $message = '';
361
362
        if ($this->hasTimerRunning() === true) {
363
            $this->stopRunningTimer();
364
        }
365
366
        $atLeastOneTimerDeleted = false;
367
368
        foreach ($this->servicesToUndo() as $service) {
369 View Code Duplication
            if ($this->$service->deleteTimer($this->config->get('workflow', 'timer_' . $service . '_id')) === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
370
                $this->config->update('workflow', 'timer_' . $service . '_id', null);
371
                $atLeastOneTimerDeleted = true;
372
            }
373
374
            $message .= $this->$service->getLastMessage() . "\r\n";
375
        }
376
377
        if ($atLeastOneTimerDeleted === true) {
378
            $this->config->update('workflow', 'is_timer_running', false);
379
        }
380
381
        return $message;
382
    }
383
384
    /**
385
     * @return mixed
386
     */
387
    private function getRecentTogglTimers()
388
    {
389
        return $this->toggl->getRecentTimers();
390
    }
391
392
    /**
393
     * @return mixed
394
     */
395
    private function getTogglProjects()
396
    {
397
        $cacheData = [];
398
        $cacheFile = getenv('alfred_workflow_data') . '/toggl_cache.json';
399
400
        if (file_exists($cacheFile)) {
401
            $cacheData = json_decode(file_get_contents($cacheFile), true);
402
        }
403
404
/*
405
 * To only show projects that are currently active
406
 * The Toggl API is slightly weird on that
407
 */
408
        foreach ($cacheData['data']['projects'] as $key => $project) {
409
            if (isset($project['server_deleted_at']) === true) {
410
                unset($cacheData['data']['projects'][$key]);
411
            }
412
        }
413
414
        return $cacheData['data']['projects'];
415
    }
416
417
    /**
418
     * @return mixed
419
     */
420
    private function getTogglTags()
421
    {
422
        $cacheFile = getenv('alfred_workflow_data') . '/toggl_cache.json';
423
        $cacheData = [];
424
425
        if (file_exists($cacheFile)) {
426
            $cacheData = json_decode(file_get_contents($cacheFile), true);
427
        }
428
429
        return $cacheData['data']['tags'];
430
    }
431
432
    /**
433
     * @param $data
434
     */
435
    private function saveTogglDataCache($data)
436
    {
437
        $cacheFile = getenv('alfred_workflow_data') . '/toggl_cache.json';
438
        file_put_contents($cacheFile, json_encode($data));
439
    }
440
441
    /**
442
     * @return mixed
443
     */
444
    private function syncTogglOnlineDataToLocalCache()
445
    {
446
        $data = $this->toggl->getOnlineData();
447
448
        $this->message = $this->toggl->getLastMessage();
449
450
        if (empty($data) === false) {
451
            $this->saveTogglDataCache($data);
452
        }
453
454
        return $this->message;
455
    }
456
}
457