Completed
Push — master ( af7e3b...7eaee9 )
by Guillaume
02:18
created

Time   C

Complexity

Total Complexity 67

Size/Duplication

Total Lines 463
Duplicated Lines 15.55 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 11
Bugs 0 Features 0
Metric Value
wmc 67
c 11
b 0
f 0
lcom 1
cbo 3
dl 72
loc 463
rs 5.7097

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A activatedServices() 0 12 3
A deleteTimer() 0 19 4
A generateDefaultConfigurationFile() 0 4 1
A getProjectName() 0 15 3
A deleteServiceTimer() 0 13 3
A getProjects() 17 17 3
A getRecentTimers() 13 13 3
A getServiceProjects() 0 21 4
A getTags() 13 13 3
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() 9 21 4
A syncOnlineDataToLocalCache() 13 13 3
B undoTimer() 7 24 5
A getRecentServiceTimers() 0 4 1
A getServiceTags() 0 12 2
A saveServiceDataCache() 0 5 1
A syncServiceOnlineDataToLocalCache() 0 11 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 boolean
78
     */
79
    public function deleteServiceTimer($service, $timerId)
80
    {
81
        $res = false;
82
83
        if ($this->$service->deleteTimer($timerId) === true) {
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 View Code Duplication
    public function getProjects()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
146
    {
147
        $tempServices = ['toggl'];
148
        $projects = [];
149
150
/*
151
 * Temporary, only get the projects of Toggl
152
 * Later, we will get Harvest ones too
153
 */
154
        foreach ($tempServices as $service) {
155
            if ($this->isServiceActive($service) === true) {
156
                $projects = $this->getServiceProjects($service);
157
            }
158
        }
159
160
        return $projects;
161
    }
162
163
    /**
164
     * @return mixed
165
     */
166 View Code Duplication
    public function getRecentTimers()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
167
    {
168
        $tempServices = ['toggl'];
169
        $timers = [];
170
171
        foreach ($tempServices as $service) {
172
            if ($this->isServiceActive($service) === true) {
173
                $timers = array_merge($timers, $this->getRecentServiceTimers($service));
174
            }
175
        }
176
177
        return $timers;
178
    }
179
180
    /**
181
     * @param $service
182
     */
183
    public function getServiceProjects($service)
184
    {
185
        $projects = [];
186
        $cacheFile = getenv('alfred_workflow_data') . '/' . $service . '_cache.json';
187
188
        if (file_exists($cacheFile)) {
189
            $projects = json_decode(file_get_contents($cacheFile), true)['data']['projects'];
190
191
/*
192
 * To only show projects that are currently active
193
 * The Toggl API is slightly weird on that
194
 */
195
            foreach ($projects as $key => $project) {
196
                if (isset($project['server_deleted_at']) === true) {
197
                    unset($projects[$key]);
198
                }
199
            }
200
        }
201
202
        return $projects;
203
    }
204
205
    /**
206
     * @return mixed
207
     */
208 View Code Duplication
    public function getTags()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
209
    {
210
        $tempServices = ['toggl'];
211
        $tags = [];
212
213
        foreach ($tempServices as $service) {
214
            if ($this->isServiceActive($service) === true) {
215
                $tags = array_merge($tags, $this->getServiceTags($service));
216
            }
217
        }
218
219
        return $tags;
220
    }
221
222
    /**
223
     * @return mixed
224
     */
225
    public function getTimerDescription()
226
    {
227
        return $this->config->get('workflow', 'timer_description');
228
    }
229
230
    /**
231
     * @return boolean
232
     */
233
    public function hasTimerRunning()
234
    {
235
        return $this->config->get('workflow', 'is_timer_running') === false ? false : true;
236
    }
237
238
    /**
239
     * @param  string  $feature
240
     * @return mixed
241
     */
242
    public function implementedServicesForFeature($feature = null)
243
    {
244
        $services = [];
245
246
        if (isset($this->currentImplementation[$feature]) === true) {
247
            $services = $this->currentImplementation[$feature];
248
        }
249
250
        return $services;
251
    }
252
253
    /**
254
     * @return boolean
255
     */
256
    public function isConfigured()
257
    {
258
        return $this->config === null ? false : true;
259
    }
260
261
    /**
262
     * @param  $service
263
     * @return mixed
264
     */
265
    public function isServiceActive($service)
266
    {
267
        return $this->config->get($service, 'is_active');
268
    }
269
270
    /**
271
     * @return mixed
272
     */
273
    public function servicesToUndo()
274
    {
275
        $services = [];
276
277
        foreach ($this->activatedServices() as $service) {
278
            if ($this->config->get('workflow', 'timer_' . $service . '_id') !== null) {
279
                array_push($services, $service);
280
            }
281
        }
282
283
        return $services;
284
    }
285
286
    /**
287
     * @param  $description
288
     * @param  $projectsDefault
289
     * @param  null               $tagsDefault
290
     * @param  boolean            $startDefault
291
     * @return string
292
     */
293
    public function startTimer($description = '', $projectsDefault = null, $tagsDefault = null, $startDefault = false)
294
    {
295
        $message = '';
296
        $startType = $startDefault === true ? 'start_default' : 'start';
297
        $atLeastOneServiceStarted = false;
298
        $implementedServices = $this->implementedServicesForFeature($startType);
299
300
/*
301
 * When starting a new timer, all the services timer IDs have to be put to null
302
 * so that when the user uses the UNDO feature, it doesn't delete old previous
303
 * other services timers. The timer IDs are used for the UNDO feature and
304
 * should then contain the IDs of the last starts through the workflow, not
305
 * through each individual sefrvice
306
 */
307
        if (empty($implementedServices) === false) {
308
            foreach ($this->activatedServices() as $service) {
309
                $this->config->update('workflow', 'timer_' . $service . '_id', null);
310
            }
311
312
            foreach ($implementedServices as $service) {
313
                $defaultProjectId = isset($projectsDefault[$service]) ? $projectsDefault[$service] : null;
314
                $defaultTags = isset($tagsDefault[$service]) ? $tagsDefault[$service] : null;
315
316
                $timerId = $this->$service->startTimer($description, $defaultProjectId, $defaultTags);
317
                $this->config->update('workflow', 'timer_' . $service . '_id', $timerId);
318
319
                if ($timerId !== null) {
320
                    $atLeastOneServiceStarted = true;
321
                }
322
323
                $message .= $this->$service->getLastMessage() . "\r\n";
324
            }
325
        }
326
327
        if ($atLeastOneServiceStarted === true) {
328
            $this->config->update('workflow', 'timer_description', $description);
329
            $this->config->update('workflow', 'is_timer_running', true);
330
        }
331
332
        return $message;
333
    }
334
335
    /**
336
     * @param  $description
337
     * @return string
338
     */
339
    public function startTimerWithDefaultOptions($description)
340
    {
341
        $projectsDefault = [
342
            'toggl'   => $this->config->get('toggl', 'default_project_id'),
343
            'harvest' => $this->config->get('harvest', 'default_project_id'),
344
        ];
345
346
        $tagsDefault = [
347
            'toggl'   => $this->config->get('toggl', 'default_tags'),
348
            'harvest' => $this->config->get('harvest', 'default_task_id'),
349
        ];
350
351
        return $this->startTimer($description, $projectsDefault, $tagsDefault, true);
352
    }
353
354
    /**
355
     * @return string
356
     */
357
    public function stopRunningTimer()
358
    {
359
        $message = '';
360
        $atLeastOneServiceStopped = false;
361
362 View Code Duplication
        foreach ($this->activatedServices() as $service) {
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...
363
            $timerId = $this->config->get('workflow', 'timer_' . $service . '_id');
364
365
            if ($this->$service->stopTimer($timerId) === true) {
366
                $atLeastOneServiceStopped = true;
367
            }
368
369
            $message .= $this->$service->getLastMessage() . "\r\n";
370
        }
371
372
        if ($atLeastOneServiceStopped === true) {
373
            $this->config->update('workflow', 'is_timer_running', false);
374
        }
375
376
        return $message;
377
    }
378
379
    /**
380
     * @return string
381
     */
382 View Code Duplication
    public function syncOnlineDataToLocalCache()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
383
    {
384
        $tempServices = ['toggl'];
385
        $message = '';
386
387
        foreach ($tempServices as $service) {
388
            if ($this->isServiceActive($service) === true) {
389
                $message .= $this->syncServiceOnlineDataToLocalCache($service);
390
            }
391
        }
392
393
        return $message;
394
    }
395
396
    /**
397
     * @return string
398
     */
399
    public function undoTimer()
400
    {
401
        $message = '';
402
403
        if ($this->hasTimerRunning() === true) {
404
            $this->stopRunningTimer();
405
        }
406
407
        $atLeastOneTimerDeleted = false;
408
409 View Code Duplication
        foreach ($this->servicesToUndo() as $service) {
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...
410
            if ($this->deleteServiceTimer($service, $this->config->get('workflow', 'timer_' . $service . '_id')) === true) {
411
                $atLeastOneTimerDeleted = true;
412
            }
413
414
            $message .= $this->$service->getLastMessage() . "\r\n";
415
        }
416
417
        if ($atLeastOneTimerDeleted === true) {
418
            $this->config->update('workflow', 'is_timer_running', false);
419
        }
420
421
        return $message;
422
    }
423
424
    /**
425
     * @return mixed
426
     */
427
    private function getRecentServiceTimers($service)
428
    {
429
        return $this->$service->getRecentTimers();
430
    }
431
432
    /**
433
     * @return mixed
434
     */
435
    private function getServiceTags($service)
436
    {
437
        $tags = [];
438
439
        $cacheFile = getenv('alfred_workflow_data') . '/' . $service . '_cache.json';
440
441
        if (file_exists($cacheFile)) {
442
            $tags = json_decode(file_get_contents($cacheFile), true)['data']['tags'];
443
        }
444
445
        return $tags;
446
    }
447
448
    /**
449
     * @param $data
450
     */
451
    private function saveServiceDataCache($service, $data)
452
    {
453
        $cacheFile = getenv('alfred_workflow_data') . '/' . $service . '_cache.json';
454
        file_put_contents($cacheFile, json_encode($data));
455
    }
456
457
    /**
458
     * @return mixed
459
     */
460
    private function syncServiceOnlineDataToLocalCache($service)
461
    {
462
        $data = $this->$service->getOnlineData();
463
        $this->message .= $this->$service->getLastMessage();
464
465
        if (empty($data) === false) {
466
            $this->saveServiceDataCache($service, $data);
467
        }
468
469
        return $this->message;
470
    }
471
}
472