Completed
Push — master ( c09f30...aef5a7 )
by Patrick
01:39
created

ShiftAPI   F

Complexity

Total Complexity 83

Size/Duplication

Total Lines 469
Duplicated Lines 7.04 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 33
loc 469
rs 2
c 0
b 0
f 0
wmc 83
lcom 1
cbo 11

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A canUpdate() 0 8 2
A canDelete() 0 4 1
A setup() 0 14 1
A canCreate() 0 5 1
A validateCreate() 0 12 3
A processEntry() 0 4 1
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 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/ForceShiftEmpty[/]', array($this, 'forceEmpty'));
24
    }
25
26
    protected function canCreate($request)
27
    {
28
        //Check is handled by validateCreate...
29
        return true;
30
    }
31
32
    protected function canUpdate($request, $entity)
33
    {
34
 	if($this->isVolunteerAdmin($request))
35
        {
36
            return true;
37
        }
38
        return $this->isUserDepartmentLead($entity['departmentID'], $this->user);
39
    }
40
41
    protected function canDelete($request, $entity)
42
    {
43
        return $this->canUpdate($request, $entity);
44
    }
45
46
    protected function validateCreate(&$obj, $request)
47
    {
48
        if($this->isVolunteerAdmin($request))
49
        {
50
            return true;
51
        }
52
        if(!isset($obj['departmentID']))
53
        {
54
             return false;
55
        }
56
        return $this->isUserDepartmentLead($obj['departmentID'], $this->user);
57
    }
58
59
    protected function processEntry($entry, $request)
60
    {
61
        return $this->processShift($entry, $request);
62
    }
63
64
    protected function genUUID()
65
    {
66
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
67
            // 32 bits for "time_low"
68
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
69
70
            // 16 bits for "time_mid"
71
            mt_rand(0, 0xffff),
72
73
            // 16 bits for "time_hi_and_version",
74
            // four most significant bits holds version number 4
75
            mt_rand(0, 0x0fff) | 0x4000,
76
77
            // 16 bits, 8 bits for "clk_seq_hi_res",
78
            // 8 bits for "clk_seq_low",
79
            // two most significant bits holds zero and one for variant DCE1.1
80
            mt_rand(0, 0x3fff) | 0x8000,
81
82
            // 48 bits for "node"
83
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
84
        );
85
    }
86
87
    public function createGroup($request, $response)
88
    {
89
        $array = $request->getParsedBody();
90
        $count = count($array);
91
        $entArray = array();
92
        $uuid = $this->genUUID();
93
        $dataTable = $this->getDataTable();
94
        //User must be able to edit all shifts
95
        for($i = 0; $i < $count; $i++)
96
        {
97
            $filter = $this->getFilterForPrimaryKey($array[$i]);
98
            $entity = $dataTable->read($filter);
99
            if($entity === false || !isset($entity[0]))
100
            {
101
                return $response->withStatus(404);
102
            }
103
            $entity = $entity[0];
104
            if(!$this->canUpdate($request, $entity))
105
            {
106
                return $response->withStatus(401);
107
            }
108
            $entity['groupID'] = $uuid;
109
            array_push($entArray, $entity);
110
        }
111
        //If we got here we can update them all
112
        $myRet = true;
113
        $errors = array();
114
        for($i = 0; $i < $count; $i++)
115
        {
116
            $filter = $this->getFilterForPrimaryKey($array[$i]);
117
            $ret = $dataTable->update($filter, $entArray[$i]);
118
            if($ret === false)
119
            {
120
               $myRet = false;
121
               array_push($errors, $array[$i]);
122
            }
123
        }
124
        if($myRet)
125
        {
126
            return $response->withJson($myRet);
127
        }
128
        else
129
        {
130
            return $response->withJson(array('res'=>$myRet, 'errors'=>$errors));
131
        }
132
    }
133
134
    public function newGroup($request, $response)
135
    {
136
        if(!$this->canCreate($request))
137
        {
138
            return $response->withStatus(401);
139
        }
140
        $data = $request->getParsedBody();
141
        $shift = array();
142
        $shift['groupID'] = $this->genUUID();
143
        $shift['departmentID'] = $data['groupDepartmentID'];
144
        $shift['earlyLate'] = $data['groupEarlyLate'];
145
        $shift['enabled'] = $data['groupEnabled'];
146
        $shift['endTime'] = $data['groupEndTime'];
147
        $shift['eventID'] = $data['groupEvent'];
148
        $shift['name'] = $data['groupName'];
149
        $shift['startTime'] = $data['groupStartTime'];
150
        $dataTable = $this->getDataTable();
151
        $ret = true;
152
        foreach($data['roles'] as $role=>$count)
153
        {
154
            $count = intval($count);
155
            for($i = 0; $i < $count; $i++)
156
            {
157
                $shift['roleID'] = $role;
158
                if($dataTable->create($shift) === false)
159
                {
160
                    $ret = false;
161
                }
162
            }
163
        }
164
        return $response->withJSON($ret);
165
    }
166
167
    public function deleteGroup($request, $response)
168
    {
169
        $data = $request->getParsedBody();
170
        $dataTable = $this->getDataTable();
171
        $filter = new \Data\Filter('groupID eq '.$data['groupID']);
172
        $entities = $dataTable->read($filter);
173
        if(empty($entities))
174
        {
175
            return $response->withStatus(404);
176
        }
177
        if(!$this->canUpdate($request, $entities[0]))
178
        {
179
            return $response->withStatus(401);
180
        }
181
        $res = $dataTable->delete($filter);
182
        if($res)
183
        {
184
            return $response->withJSON($res);
185
        }
186
        return $response->withJSON($res, 500);
187
    }
188
189
    public function signup($request, $response, $args)
190
    {
191
        $this->validateLoggedIn($request);
192
        $shiftId = $args['shift'];
193
        $dataTable = $this->getDataTable();
194
        $filter = $this->getFilterForPrimaryKey($shiftId);
195
        $entity = $dataTable->read($filter);
196
        if(empty($entity))
197
        {
198
            return $response->withStatus(404);
199
        }
200
        $entity = $entity[0];
201 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...
202
        {
203
            return $response->withStatus(401);
204
        }
205
        $shift = new \VolunteerShift($shiftId, $entity);
206
        $entity = $this->processShift($entity, $request);
207
        if(isset($entity['minShifts']) && $entity['minShifts'] > 0)
208
        {
209
          $shift->makeCopy($dataTable);
210
        }
211
        if(isset($entity['overlap']) && $entity['overlap'])
212
        {
213
            $overlaps = $shift->findOverlaps($this->user->uid);
214
            $count = count($overlaps);
215
            $leads = array();
216
            for($i = 0; $i < $count; $i++)
217
            {
218
                $dept = new \VolunteerDepartment($overlaps[$i]->departmentID);
219
                $leads = array_merge($leads, $dept->getLeadEmails());
220
                $overlaps[$i]->status = 'pending';
221
                $tmp = new \Data\Filter('_id eq '.$overlaps[$i]->{'_id'});
222
                $res = $dataTable->update($tmp, $overlaps[$i]);
223
                if($res === false)
224
                {
225
                    return $response->withJSON(array('err'=>'Unable to update overlap with id '.$overlaps[$i]->{'_id'}));
226
                }
227
            }
228
            $dept = new \VolunteerDepartment($entity['departmentID']);
229
            $leads = array_merge($leads, $dept->getLeadEmails());
230
            $leads = array_unique($leads);
231
            $entity['participant'] = $this->user->uid;
232
            $entity['status'] = 'pending';
233
            $profile = new \VolunteerProfile($this->user->uid);
234
            $email = new \Emails\TwoShiftsAtOnceEmail($profile);
235
            $email->addLeads($leads);
236
            $emailProvider = \EmailProvider::getInstance();
237
            if($emailProvider->sendEmail($email) === false)
238
            {
239
                throw new \Exception('Unable to send duplicate email!');
240
            }
241
            return $response->withJSON($dataTable->update($filter, $entity));
242
        }
243 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...
244
        {
245
            $entity['participant'] = $this->user->uid;
246
            $entity['status'] = 'filled';
247
            return $response->withJSON($dataTable->update($filter, $entity));
248
        }
249 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...
250
        {
251
            $entity['participant'] = $this->user->uid;
252
            $entity['status'] = 'filled';
253
            return $response->withJSON($dataTable->update($filter, $entity));
254
        }
255
        print_r($entity); die();
256
    }
257
258
    public function abandon($request, $response, $args)
259
    {
260
        $this->validateLoggedIn($request);
261
        $shiftId = $args['shift'];
262
        $dataTable = $this->getDataTable();
263
        $filter = $this->getFilterForPrimaryKey($shiftId);
264
        $entity = $dataTable->read($filter);
265
        if(empty($entity))
266
        {
267
            return $response->withStatus(404);
268
        }
269
        $entity = $entity[0];
270
        if(!isset($entity['participant']) || $entity['participant'] !== $this->user->uid)
271
        {
272
            return $response->withStatus(401);
273
        }
274
        $entity['participant'] = '';
275
        $entity['status'] = 'unfilled';
276
        return $response->withJSON($dataTable->update($filter, $entity));
277
    }
278
279
    public function approvePending($request, $response, $args)
280
    {
281
        if(!$this->canCreate($request))
282
        {
283
            return $response->withStatus(401);
284
        }
285
        $shiftId = $args['shift'];
286
        $dataTable = $this->getDataTable();
287
        $filter = $this->getFilterForPrimaryKey($shiftId);
288
        $entity = $dataTable->read($filter);
289
        if(empty($entity))
290
        {
291
            return $response->withStatus(404);
292
        }
293
        $entity = $entity[0];
294
        $entity['status'] = 'filled';
295
        return $response->withJSON($dataTable->update($filter, $entity));
296
    }
297
298
    public function disapprovePending($request, $response, $args)
299
    {
300
        if(!$this->canCreate($request))
301
        {
302
            return $response->withStatus(401);
303
        }
304
        $shiftId = $args['shift'];
305
        $dataTable = $this->getDataTable();
306
        $filter = $this->getFilterForPrimaryKey($shiftId);
307
        $entity = $dataTable->read($filter);
308
        if(empty($entity))
309
        {
310
            return $response->withStatus(404);
311
        }
312
        $entity['participant'] = '';
313
        $entity['status'] = 'unfilled';
314
        $profile = new \VolunteerProfile($this->user->uid);
315
        $email = new \Emails\PendingRejectedEmail($profile);
316
        $email->setShift($entity);
317
        $emailProvider = \EmailProvider::getInstance();
318
        if($emailProvider->sendEmail($email) === false)
319
        {
320
            throw new \Exception('Unable to send duplicate email!');
321
        }
322
        return $response->withJSON($dataTable->update($filter, $entity));
323
    }
324
325
    public function startGroupSignup($request, $response, $args)
326
    {
327
        $this->validateLoggedIn($request);
328
        $shiftId = $args['shift'];
329
        $dataTable = $this->getDataTable();
330
        $filter = $this->getFilterForPrimaryKey($shiftId);
331
        $entity = $dataTable->read($filter);
332
        if(empty($entity))
333
        {
334
            return $response->withStatus(404);
335
        }
336
        $entity = $entity[0];
337 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...
338
        {
339
            return $response->withStatus(401);
340
        }
341
        $filter = new \Data\Filter('groupID eq '.$entity['groupID'].' and enabled eq true');
342
        $entities = $dataTable->read($filter);
343
        $count = count($entities);
344
        $dept = new \VolunteerDepartment($entity['departmentID']);
345
        $res = array();
346
        $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...
347
        $res['earlyLate'] = $entity['earlyLate'];
348
        $res['endTime'] = $entity['endTime'];
349
        $res['eventID'] = $entity['eventID'];
350
        $res['name'] = $entity['name'];
351
        $res['startTime'] = $entity['startTime'];
352
        $res['groupID'] = $entity['groupID'];
353
        $res['shifts'] = array();
354
        $roles = array();
355
        for($i = 0; $i < $count; $i++)
356
        {
357 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...
358
            {
359
                continue;
360
            }
361
            if(!isset($roles[$entities[$i]['roleID']]))
362
            {
363
                $roles[$entities[$i]['roleID']] = new \VolunteerRole($entities[$i]['roleID']);
364
            }
365
            $role = $roles[$entities[$i]['roleID']];
366
            $entities[$i]['role'] = $role->display_name;
367
            array_push($res['shifts'], $entities[$i]);
368
        }
369
        return $response->withJSON($res);
370
    }
371
372
    public function generateGroupLink($request, $response, $args)
373
    {
374
        $this->validateLoggedIn($request);
375
        $shiftId = $args['shift'];
376
        $dataTable = $this->getDataTable();
377
        $filter = $this->getFilterForPrimaryKey($shiftId);
378
        $entity = $dataTable->read($filter);
379
        if(empty($entity))
380
        {
381
            return $response->withStatus(404);
382
        }
383
        $entity = $entity[0];
384 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...
385
        {
386
            return $response->withStatus(401);
387
        }
388
        $data = $request->getParsedBody();
389
        $myShift = $data['myshift'];
390
        $roles = array();
391
        foreach($data as $key => $value)
392
        {
393
            if(substr($key, 0, 6) === "roles.")
394
            {
395
                $roles[substr($key, 6)] = $value;
396
            }
397
        }
398
        $filter = new \Data\Filter('groupID eq '.$entity['groupID'].' and enabled eq true');
399
        $entities = $dataTable->read($filter);
400
        $count = count($entities);
401
        $uuid = $this->genUUID();
402
        for($i = 0; $i < $count; $i++)
403
        {
404 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...
405
            {
406
                $entities[$i] = false;
407
                continue;
408
            }
409
            if((string)$entities[$i]['_id'] === (string)new \MongoDB\BSON\ObjectId($myShift))
410
            {
411
                $entities[$i]['participant'] = $this->user->uid;
412
                $entities[$i]['status'] = 'filled';
413
                $entities[$i]['signupLink'] = $uuid;
414
            }
415
            else if(isset($roles[$entities[$i]['roleID']]))
416
            {
417
                $entities[$i]['status'] = 'groupPending';
418
                $entities[$i]['signupLink'] = $uuid;
419
                $roles[$entities[$i]['roleID']]--;
420
                if($roles[$entities[$i]['roleID']] === 0)
421
                {
422
                    unset($roles[$entities[$i]['roleID']]);
423
                }
424
            }
425
            else
426
            {
427
                $entities[$i] = false;
428
            }
429
        }
430
        if(count($roles) !== 0)
431
        {
432
            throw new \Exception('Not enough shifts to fullfill requests');
433
        }
434
        for($i = 0; $i < $count; $i++)
435
        {
436
            if($entities[$i] === false)
437
            {
438
                continue;
439
            }
440
            $filter = new \Data\Filter('_id eq '.$entities[$i]['_id']);
441
            $res = $dataTable->update($filter, $entities[$i]);
442
            if($res === false)
443
            {
444
                throw new \Exception('Not able to save shift '.$entities[$i]['_id']);
445
            }
446
        }
447
        return $response->withJSON(array('uuid' => $uuid));
448
    }
449
450
    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...
451
    {
452
        $this->validateLoggedIn($request);
453
        $shiftId = $args['shift'];
454
        $dataTable = $this->getDataTable();
455
        $filter = $this->getFilterForPrimaryKey($shiftId);
456
        $entity = $dataTable->read($filter);
457
        if(empty($entity))
458
        {
459
            return $response->withStatus(404);
460
        }
461
        $entity = $entity[0];
462
        if(!$this->canUpdate($request, $entity))
463
        {
464
            return $response->withStatus(401);
465
        }
466
        $entity['participant'] = '';
467
        $entity['status'] = 'unfilled';
468
        return $response->withJSON($dataTable->update($filter, $entity));
469
    }
470
}
471