Completed
Push — master ( 9147af...9e1d87 )
by Patrick
01:39
created

ShiftAPI   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 521
Duplicated Lines 9.79 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
dl 51
loc 521
rs 2
c 0
b 0
f 0
wmc 92
lcom 1
cbo 12

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setup() 0 15 1
A canCreate() 0 5 1
A canUpdate() 0 8 2
A canDelete() 0 4 1
A validateCreate() 0 12 3
A processEntry() 0 4 1
A postDeleteAction() 9 18 4
A genUUID() 0 22 1
B createGroup() 0 46 8
A newGroup() 0 32 5
A deleteGroup() 0 21 4
C signup() 16 68 15
A abandon() 0 20 4
A approvePending() 0 18 3
A disapprovePending() 0 26 4
B startGroupSignup() 8 46 9
C generateGroupLink() 9 77 17
A emptyShift() 9 31 5
A forceEmpty() 0 20 3

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 ShiftAPI 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 ShiftAPI, and based on these observations, apply Extract Interface, too.

1
<?php
2
class ShiftAPI extends VolunteerAPI
3
{
4
    use Processor;
5
6
    public function __construct()
7
    {
8
        parent::__construct('shifts');
9
    }
10
11
    public function setup($app)
12
    {
13
        parent::setup($app);
14
        $app->post('/Actions/CreateGroup', array($this, 'createGroup'));
15
        $app->post('/Actions/NewGroup', array($this, 'newGroup'));
16
        $app->post('/Actions/DeleteGroup', array($this, 'deleteGroup'));
17
        $app->post('/{shift}/Actions/Signup[/]', array($this, 'signup'));
18
        $app->post('/{shift}/Actions/Abandon[/]', array($this, 'abandon'));
19
        $app->post('/{shift}/Actions/Approve[/]', array($this, 'approvePending'));
20
        $app->post('/{shift}/Actions/Disapprove[/]', array($this, 'disapprovePending')); 
21
        $app->post('/{shift}/Actions/StartGroupSignup', array($this, 'startGroupSignup'));
22
        $app->post('/{shift}/Actions/GenerateGroupLink', array($this, 'generateGroupLink'));
23
        $app->post('/{shift}/Actions/EmptyShift[/]', array($this, 'emptyShift'));
24
        $app->post('/{shift}/Actions/ForceShiftEmpty[/]', array($this, 'forceEmpty'));
25
    }
26
27
    protected function canCreate($request)
28
    {
29
        //Check is handled by validateCreate...
30
        return true;
31
    }
32
33
    protected function canUpdate($request, $entity)
34
    {
35
 	if($this->isVolunteerAdmin($request))
36
        {
37
            return true;
38
        }
39
        return $this->isUserDepartmentLead($entity['departmentID'], $this->user);
40
    }
41
42
    protected function canDelete($request, $entity)
43
    {
44
        return $this->canUpdate($request, $entity);
45
    }
46
47
    protected function validateCreate(&$obj, $request)
48
    {
49
        if($this->isVolunteerAdmin($request))
50
        {
51
            return true;
52
        }
53
        if(!isset($obj['departmentID']))
54
        {
55
             return false;
56
        }
57
        return $this->isUserDepartmentLead($obj['departmentID'], $this->user);
58
    }
59
60
    protected function processEntry($entry, $request)
61
    {
62
        return $this->processShift($entry, $request);
63
    }
64
65
    protected function postDeleteAction($entry)
66
    {
67
        if(empty($entry))
68
        {
69
            return true;
70
        }
71
        $shift = new \VolunteerShift(false, $entry[0]);
72 View Code Duplication
        if($shift->isFilled())
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...
73
        {
74
            $email = new \Emails\ShiftEmail($shift, 'shiftCanceledSource');
75
            $emailProvider = \EmailProvider::getInstance();
76
            if($emailProvider->sendEmail($email) === false)
77
            {
78
                throw new \Exception('Unable to send email!');
79
            } 
80
        }
81
        return true;
82
    }
83
84
    protected function genUUID()
85
    {
86
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
87
            // 32 bits for "time_low"
88
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
89
90
            // 16 bits for "time_mid"
91
            mt_rand(0, 0xffff),
92
93
            // 16 bits for "time_hi_and_version",
94
            // four most significant bits holds version number 4
95
            mt_rand(0, 0x0fff) | 0x4000,
96
97
            // 16 bits, 8 bits for "clk_seq_hi_res",
98
            // 8 bits for "clk_seq_low",
99
            // two most significant bits holds zero and one for variant DCE1.1
100
            mt_rand(0, 0x3fff) | 0x8000,
101
102
            // 48 bits for "node"
103
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
104
        );
105
    }
106
107
    public function createGroup($request, $response)
108
    {
109
        $array = $request->getParsedBody();
110
        $count = count($array);
111
        $entArray = array();
112
        $uuid = $this->genUUID();
113
        $dataTable = $this->getDataTable();
114
        //User must be able to edit all shifts
115
        for($i = 0; $i < $count; $i++)
116
        {
117
            $filter = $this->getFilterForPrimaryKey($array[$i]);
118
            $entity = $dataTable->read($filter);
119
            if($entity === false || !isset($entity[0]))
120
            {
121
                return $response->withStatus(404);
122
            }
123
            $entity = $entity[0];
124
            if(!$this->canUpdate($request, $entity))
125
            {
126
                return $response->withStatus(401);
127
            }
128
            $entity['groupID'] = $uuid;
129
            array_push($entArray, $entity);
130
        }
131
        //If we got here we can update them all
132
        $myRet = true;
133
        $errors = array();
134
        for($i = 0; $i < $count; $i++)
135
        {
136
            $filter = $this->getFilterForPrimaryKey($array[$i]);
137
            $ret = $dataTable->update($filter, $entArray[$i]);
138
            if($ret === false)
139
            {
140
               $myRet = false;
141
               array_push($errors, $array[$i]);
142
            }
143
        }
144
        if($myRet)
145
        {
146
            return $response->withJson($myRet);
147
        }
148
        else
149
        {
150
            return $response->withJson(array('res'=>$myRet, 'errors'=>$errors));
151
        }
152
    }
153
154
    public function newGroup($request, $response)
155
    {
156
        if(!$this->canCreate($request))
157
        {
158
            return $response->withStatus(401);
159
        }
160
        $data = $request->getParsedBody();
161
        $shift = array();
162
        $shift['groupID'] = $this->genUUID();
163
        $shift['departmentID'] = $data['groupDepartmentID'];
164
        $shift['earlyLate'] = $data['groupEarlyLate'];
165
        $shift['enabled'] = $data['groupEnabled'];
166
        $shift['endTime'] = $data['groupEndTime'];
167
        $shift['eventID'] = $data['groupEvent'];
168
        $shift['name'] = $data['groupName'];
169
        $shift['startTime'] = $data['groupStartTime'];
170
        $dataTable = $this->getDataTable();
171
        $ret = true;
172
        foreach($data['roles'] as $role=>$count)
173
        {
174
            $count = intval($count);
175
            for($i = 0; $i < $count; $i++)
176
            {
177
                $shift['roleID'] = $role;
178
                if($dataTable->create($shift) === false)
179
                {
180
                    $ret = false;
181
                }
182
            }
183
        }
184
        return $response->withJSON($ret);
185
    }
186
187
    public function deleteGroup($request, $response)
188
    {
189
        $data = $request->getParsedBody();
190
        $dataTable = $this->getDataTable();
191
        $filter = new \Data\Filter('groupID eq '.$data['groupID']);
192
        $entities = $dataTable->read($filter);
193
        if(empty($entities))
194
        {
195
            return $response->withStatus(404);
196
        }
197
        if(!$this->canUpdate($request, $entities[0]))
198
        {
199
            return $response->withStatus(401);
200
        }
201
        $res = $dataTable->delete($filter);
202
        if($res)
203
        {
204
            return $response->withJSON($res);
205
        }
206
        return $response->withJSON($res, 500);
207
    }
208
209
    public function signup($request, $response, $args)
210
    {
211
        $this->validateLoggedIn($request);
212
        $shiftId = $args['shift'];
213
        $dataTable = $this->getDataTable();
214
        $filter = $this->getFilterForPrimaryKey($shiftId);
215
        $entity = $dataTable->read($filter);
216
        if(empty($entity))
217
        {
218
            return $response->withStatus(404);
219
        }
220
        $entity = $entity[0];
221 View Code Duplication
        if(isset($entity['participant']) && strlen($entity['participant']) > 0)
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...
222
        {
223
            return $response->withStatus(401);
224
        }
225
        $shift = new \VolunteerShift($shiftId, $entity);
226
        $entity = $this->processShift($entity, $request);
227
        if(isset($entity['minShifts']) && $entity['minShifts'] > 0)
228
        {
229
          $shift->makeCopy($dataTable);
230
        }
231
        if(isset($entity['overlap']) && $entity['overlap'])
232
        {
233
            $overlaps = $shift->findOverlaps($this->user->uid);
234
            $count = count($overlaps);
235
            $leads = array();
236
            for($i = 0; $i < $count; $i++)
237
            {
238
                $dept = new \VolunteerDepartment($overlaps[$i]->departmentID);
239
                $leads = array_merge($leads, $dept->getLeadEmails());
240
                $overlaps[$i]->status = 'pending';
241
                $tmp = new \Data\Filter('_id eq '.$overlaps[$i]->{'_id'});
242
                $res = $dataTable->update($tmp, $overlaps[$i]);
243
                if($res === false)
244
                {
245
                    return $response->withJSON(array('err'=>'Unable to update overlap with id '.$overlaps[$i]->{'_id'}));
246
                }
247
            }
248
            $dept = new \VolunteerDepartment($entity['departmentID']);
249
            $leads = array_merge($leads, $dept->getLeadEmails());
250
            $leads = array_unique($leads);
251
            $entity['participant'] = $this->user->uid;
252
            $entity['status'] = 'pending';
253
            $profile = new \VolunteerProfile($this->user->uid);
254
            $email = new \Emails\TwoShiftsAtOnceEmail($profile);
255
            $email->addLeads($leads);
256
            $emailProvider = \EmailProvider::getInstance();
257
            if($emailProvider->sendEmail($email) === false)
258
            {
259
                throw new \Exception('Unable to send duplicate email!');
260
            }
261
            return $response->withJSON($dataTable->update($filter, $entity));
262
        }
263 View Code Duplication
        if(isset($entity['available']) && $entity['available'])
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...
264
        {
265
            $entity['participant'] = $this->user->uid;
266
            $entity['status'] = 'filled';
267
            return $response->withJSON($dataTable->update($filter, $entity));
268
        }
269 View Code Duplication
        if(isset($entity['status']) && $entity['status'] === 'groupPending')
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...
270
        {
271
            $entity['participant'] = $this->user->uid;
272
            $entity['status'] = 'filled';
273
            return $response->withJSON($dataTable->update($filter, $entity));
274
        }
275
        print_r($entity); die();
276
    }
277
278
    public function abandon($request, $response, $args)
279
    {
280
        $this->validateLoggedIn($request);
281
        $shiftId = $args['shift'];
282
        $dataTable = $this->getDataTable();
283
        $filter = $this->getFilterForPrimaryKey($shiftId);
284
        $entity = $dataTable->read($filter);
285
        if(empty($entity))
286
        {
287
            return $response->withStatus(404);
288
        }
289
        $entity = $entity[0];
290
        if(!isset($entity['participant']) || $entity['participant'] !== $this->user->uid)
291
        {
292
            return $response->withStatus(401);
293
        }
294
        $entity['participant'] = '';
295
        $entity['status'] = 'unfilled';
296
        return $response->withJSON($dataTable->update($filter, $entity));
297
    }
298
299
    public function approvePending($request, $response, $args)
300
    {
301
        if(!$this->canCreate($request))
302
        {
303
            return $response->withStatus(401);
304
        }
305
        $shiftId = $args['shift'];
306
        $dataTable = $this->getDataTable();
307
        $filter = $this->getFilterForPrimaryKey($shiftId);
308
        $entity = $dataTable->read($filter);
309
        if(empty($entity))
310
        {
311
            return $response->withStatus(404);
312
        }
313
        $entity = $entity[0];
314
        $entity['status'] = 'filled';
315
        return $response->withJSON($dataTable->update($filter, $entity));
316
    }
317
318
    public function disapprovePending($request, $response, $args)
319
    {
320
        if(!$this->canCreate($request))
321
        {
322
            return $response->withStatus(401);
323
        }
324
        $shiftId = $args['shift'];
325
        $dataTable = $this->getDataTable();
326
        $filter = $this->getFilterForPrimaryKey($shiftId);
327
        $entity = $dataTable->read($filter);
328
        if(empty($entity))
329
        {
330
            return $response->withStatus(404);
331
        }
332
        $entity['participant'] = '';
333
        $entity['status'] = 'unfilled';
334
        $profile = new \VolunteerProfile($this->user->uid);
335
        $email = new \Emails\PendingRejectedEmail($profile);
336
        $email->setShift($entity);
337
        $emailProvider = \EmailProvider::getInstance();
338
        if($emailProvider->sendEmail($email) === false)
339
        {
340
            throw new \Exception('Unable to send duplicate email!');
341
        }
342
        return $response->withJSON($dataTable->update($filter, $entity));
343
    }
344
345
    public function startGroupSignup($request, $response, $args)
346
    {
347
        $this->validateLoggedIn($request);
348
        $shiftId = $args['shift'];
349
        $dataTable = $this->getDataTable();
350
        $filter = $this->getFilterForPrimaryKey($shiftId);
351
        $entity = $dataTable->read($filter);
352
        if(empty($entity))
353
        {
354
            return $response->withStatus(404);
355
        }
356
        $entity = $entity[0];
357 View Code Duplication
        if(isset($entity['participant']) && strlen($entity['participant']) > 0)
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...
358
        {
359
            return $response->withStatus(401);
360
        }
361
        $filter = new \Data\Filter('groupID eq '.$entity['groupID'].' and enabled eq true');
362
        $entities = $dataTable->read($filter);
363
        $count = count($entities);
364
        $dept = new \VolunteerDepartment($entity['departmentID']);
365
        $res = array();
366
        $res['department'] = $dept->departmentName;
0 ignored issues
show
Bug introduced by
The property departmentName does not seem to exist in VolunteerDepartment.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
367
        $res['earlyLate'] = $entity['earlyLate'];
368
        $res['endTime'] = $entity['endTime'];
369
        $res['eventID'] = $entity['eventID'];
370
        $res['name'] = $entity['name'];
371
        $res['startTime'] = $entity['startTime'];
372
        $res['groupID'] = $entity['groupID'];
373
        $res['shifts'] = array();
374
        $roles = array();
375
        for($i = 0; $i < $count; $i++)
376
        {
377 View Code Duplication
            if(isset($entities[$i]['status']) && ($entities[$i]['status'] === 'filled' || $entities[$i]['status'] === 'pending'))
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...
378
            {
379
                continue;
380
            }
381
            if(!isset($roles[$entities[$i]['roleID']]))
382
            {
383
                $roles[$entities[$i]['roleID']] = new \VolunteerRole($entities[$i]['roleID']);
384
            }
385
            $role = $roles[$entities[$i]['roleID']];
386
            $entities[$i]['role'] = $role->display_name;
387
            array_push($res['shifts'], $entities[$i]);
388
        }
389
        return $response->withJSON($res);
390
    }
391
392
    public function generateGroupLink($request, $response, $args)
393
    {
394
        $this->validateLoggedIn($request);
395
        $shiftId = $args['shift'];
396
        $dataTable = $this->getDataTable();
397
        $filter = $this->getFilterForPrimaryKey($shiftId);
398
        $entity = $dataTable->read($filter);
399
        if(empty($entity))
400
        {
401
            return $response->withStatus(404);
402
        }
403
        $entity = $entity[0];
404 View Code Duplication
        if(isset($entity['participant']) && strlen($entity['participant']) > 0)
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...
405
        {
406
            return $response->withStatus(401);
407
        }
408
        $data = $request->getParsedBody();
409
        $myShift = $data['myshift'];
410
        $roles = array();
411
        foreach($data as $key => $value)
412
        {
413
            if(substr($key, 0, 6) === "roles.")
414
            {
415
                $roles[substr($key, 6)] = $value;
416
            }
417
        }
418
        $filter = new \Data\Filter('groupID eq '.$entity['groupID'].' and enabled eq true');
419
        $entities = $dataTable->read($filter);
420
        $count = count($entities);
421
        $uuid = $this->genUUID();
422
        for($i = 0; $i < $count; $i++)
423
        {
424 View Code Duplication
            if(isset($entities[$i]['status']) && ($entities[$i]['status'] === 'filled' || $entities[$i]['status'] === 'pending'))
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...
425
            {
426
                $entities[$i] = false;
427
                continue;
428
            }
429
            if((string)$entities[$i]['_id'] === (string)new \MongoDB\BSON\ObjectId($myShift))
430
            {
431
                $entities[$i]['participant'] = $this->user->uid;
432
                $entities[$i]['status'] = 'filled';
433
                $entities[$i]['signupLink'] = $uuid;
434
            }
435
            else if(isset($roles[$entities[$i]['roleID']]))
436
            {
437
                $entities[$i]['status'] = 'groupPending';
438
                $entities[$i]['signupLink'] = $uuid;
439
                $roles[$entities[$i]['roleID']]--;
440
                if($roles[$entities[$i]['roleID']] === 0)
441
                {
442
                    unset($roles[$entities[$i]['roleID']]);
443
                }
444
            }
445
            else
446
            {
447
                $entities[$i] = false;
448
            }
449
        }
450
        if(count($roles) !== 0)
451
        {
452
            throw new \Exception('Not enough shifts to fullfill requests');
453
        }
454
        for($i = 0; $i < $count; $i++)
455
        {
456
            if($entities[$i] === false)
457
            {
458
                continue;
459
            }
460
            $filter = new \Data\Filter('_id eq '.$entities[$i]['_id']);
461
            $res = $dataTable->update($filter, $entities[$i]);
462
            if($res === false)
463
            {
464
                throw new \Exception('Not able to save shift '.$entities[$i]['_id']);
465
            }
466
        }
467
        return $response->withJSON(array('uuid' => $uuid));
468
    }
469
470
    function emptyShift($request, $response, $args)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
471
    {
472
        $this->validateLoggedIn($request);
473
        $shiftId = $args['shift'];
474
        $dataTable = $this->getDataTable();
475
        $filter = $this->getFilterForPrimaryKey($shiftId);
476
        $entity = $dataTable->read($filter);
477
        if(empty($entity))
478
        {
479
            return $response->withStatus(404);
480
        }
481
        $entity = $entity[0];
482
        if(!$this->canUpdate($request, $entity))
483
        {
484
            return $response->withStatus(401);
485
        }
486
        $shift = new \VolunteerShift(false, $entity);
487
        $entity['participant'] = '';
488
        $entity['status'] = 'unfilled';
489
        $ret = $dataTable->update($filter, $entity);
490 View Code Duplication
        if($ret)
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...
491
        {
492
            $email = new \Emails\ShiftEmail($shift, 'shiftEmptiedSource');
493
            $emailProvider = \EmailProvider::getInstance();
494
            if($emailProvider->sendEmail($email) === false)
495
            {
496
                throw new \Exception('Unable to send email!');
497
            }
498
        }
499
        return $response->withJSON($ret);
500
    }
501
502
    function forceEmpty($request, $response, $args)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
503
    {
504
        $this->validateLoggedIn($request);
505
        $shiftId = $args['shift'];
506
        $dataTable = $this->getDataTable();
507
        $filter = $this->getFilterForPrimaryKey($shiftId);
508
        $entity = $dataTable->read($filter);
509
        if(empty($entity))
510
        {
511
            return $response->withStatus(404);
512
        }
513
        $entity = $entity[0];
514
        if(!$this->canUpdate($request, $entity))
515
        {
516
            return $response->withStatus(401);
517
        }
518
        $entity['participant'] = '';
519
        $entity['status'] = 'unfilled';
520
        return $response->withJSON($dataTable->update($filter, $entity));
521
    }
522
}
523