Passed
Push — master ( 3efdc5...e753ed )
by Patrick
06:19
created

ShiftAPI::emptyShift()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 20
c 2
b 0
f 0
nc 6
nop 3
dl 0
loc 30
rs 9.2888
1
<?php
2
class ShiftAPI extends VolunteerAPI
3
{
4
    use Processor;
0 ignored issues
show
introduced by
The trait Processor requires some properties which are not provided by ShiftAPI: $startTime, $certs, $endTime, $mail, $uid, $title
Loading history...
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
        if(isset($obj['unbounded']) && $obj['unbounded'])
58
        {
59
            if(!isset($obj['minShifts']) || $obj['minShifts'] === 0 || $obj['minShifts'] === '')
60
            {
61
                 $obj['minShifts'] = '1';
62
            }
63
        }
64
        return $this->isUserDepartmentLead($obj['departmentID'], $this->user);
65
    }
66
67
    protected function processEntry($entry, $request)
68
    {
69
        return $this->processShift($entry, $request);
70
    }
71
72
    protected function postUpdateAction($newObj, $request, $oldObj)
73
    {
74
        $oldShift = new \VolunteerShift(false, $oldObj);
75
        if($oldShift->isFilled() && ($oldObj['startTime'] != $newObj['startTime'] || $oldObj['endTime'] != $newObj['endTime']))
76
        {
77
            $email = new \Emails\ShiftEmail($oldShift, 'shiftChangedSource');
78
            $this->sendEmail($email);
79
        }
80
        return true;
81
    }
82
83
    protected function postDeleteAction($entry)
84
    {
85
        if(empty($entry))
86
        {
87
            return true;
88
        }
89
        $shift = new \VolunteerShift(false, $entry[0]);
90
        if($shift->isFilled())
91
        {
92
            $email = new \Emails\ShiftEmail($shift, 'shiftCanceledSource');
93
            $this->sendEmail($email);
94
        }
95
        return true;
96
    }
97
98
    protected function genUUID()
99
    {
100
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
101
            // 32 bits for "time_low"
102
            mt_rand(0, 0xffff), mt_rand(0, 0xffff),
103
104
            // 16 bits for "time_mid"
105
            mt_rand(0, 0xffff),
106
107
            // 16 bits for "time_hi_and_version",
108
            // four most significant bits holds version number 4
109
            mt_rand(0, 0x0fff) | 0x4000,
110
111
            // 16 bits, 8 bits for "clk_seq_hi_res",
112
            // 8 bits for "clk_seq_low",
113
            // two most significant bits holds zero and one for variant DCE1.1
114
            mt_rand(0, 0x3fff) | 0x8000,
115
116
            // 48 bits for "node"
117
            mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
118
        );
119
    }
120
121
    public function createGroup($request, $response)
122
    {
123
        $array = $request->getParsedBody();
124
        $count = count($array);
125
        $entArray = array();
126
        $uuid = $this->genUUID();
127
        $dataTable = $this->getDataTable();
128
        //User must be able to edit all shifts
129
        for($i = 0; $i < $count; $i++)
130
        {
131
            $filter = $this->getFilterForPrimaryKey($array[$i]);
132
            $entity = $dataTable->read($filter);
133
            if($entity === false || !isset($entity[0]))
134
            {
135
                return $response->withStatus(404);
136
            }
137
            $entity = $entity[0];
138
            if(!$this->canUpdate($request, $entity))
139
            {
140
                return $response->withStatus(401);
141
            }
142
            $entity['groupID'] = $uuid;
143
            array_push($entArray, $entity);
144
        }
145
        //If we got here we can update them all
146
        $myRet = true;
147
        $errors = array();
148
        for($i = 0; $i < $count; $i++)
149
        {
150
            $filter = $this->getFilterForPrimaryKey($array[$i]);
151
            $ret = $dataTable->update($filter, $entArray[$i]);
152
            if($ret === false)
153
            {
154
               $myRet = false;
155
               array_push($errors, $array[$i]);
156
            }
157
        }
158
        if($myRet)
0 ignored issues
show
introduced by
The condition $myRet is always true.
Loading history...
159
        {
160
            return $response->withJson($myRet);
161
        }
162
        else
163
        {
164
            return $response->withJson(array('res'=>$myRet, 'errors'=>$errors));
165
        }
166
    }
167
168
    public function newGroup($request, $response)
169
    {
170
        if(!$this->canCreate($request))
171
        {
172
            return $response->withStatus(401);
173
        }
174
        $data = $request->getParsedBody();
175
        $shift = array();
176
        $shift['groupID'] = $this->genUUID();
177
        $shift['departmentID'] = $data['groupDepartmentID'];
178
        $shift['earlyLate'] = $data['groupEarlyLate'];
179
        $shift['enabled'] = $data['groupEnabled'];
180
        $shift['endTime'] = $data['groupEndTime'];
181
        $shift['eventID'] = $data['groupEvent'];
182
        $shift['name'] = $data['groupName'];
183
        $shift['startTime'] = $data['groupStartTime'];
184
        $dataTable = $this->getDataTable();
185
        $ret = true;
186
        foreach($data['roles'] as $role=>$count)
187
        {
188
            $count = intval($count);
189
            for($i = 0; $i < $count; $i++)
190
            {
191
                $shift['roleID'] = $role;
192
                if($dataTable->create($shift) === false)
193
                {
194
                    $ret = false;
195
                }
196
            }
197
        }
198
        return $response->withJSON($ret);
199
    }
200
201
    public function deleteGroup($request, $response)
202
    {
203
        $data = $request->getParsedBody();
204
        $dataTable = $this->getDataTable();
205
        $filter = new \Data\Filter('groupID eq '.$data['groupID']);
206
        $entities = $dataTable->read($filter);
207
        if(empty($entities))
208
        {
209
            return $response->withStatus(404);
210
        }
211
        if(!$this->canUpdate($request, $entities[0]))
212
        {
213
            return $response->withStatus(401);
214
        }
215
        $res = $dataTable->delete($filter);
216
        if($res)
217
        {
218
            return $response->withJSON($res);
219
        }
220
        return $response->withJSON($res, 500);
221
    }
222
223
    protected function doSignup($uid, $status, $entity, $filter, $dataTable)
224
    {
225
        if(isset($entity['earlyLate']) && $entity['earlyLate'] !== '-1')
226
        {
227
            $event = new \VolunteerEvent($entity['eventID']);
228
            if(!$event->hasVolOnEEList($uid, intval($entity['earlyLate'])))
229
            {
230
                $status = 'pending';
231
                $entity['needEEApproval'] = true;
232
                $event->addToEEList($uid, intval($entity['earlyLate']));
233
            }
234
        }
235
        $entity['participant'] = $uid;
236
        $entity['status'] = $status;
237
        return $dataTable->update($filter, $entity);
238
    }
239
240
    public function signup($request, $response, $args)
241
    {
242
        $this->validateLoggedIn($request);
243
        $shiftId = $args['shift'];
244
        $dataTable = $this->getDataTable();
245
        $filter = $this->getFilterForPrimaryKey($shiftId);
246
        $entity = $dataTable->read($filter);
247
        if(empty($entity))
248
        {
249
            return $response->withStatus(404);
250
        }
251
        $entity = $entity[0];
252
        if(isset($entity['participant']) && strlen($entity['participant']) > 0)
253
        {
254
            return $response->withStatus(401);
255
        }
256
        $shift = new \VolunteerShift($shiftId, $entity);
257
        $entity = $this->processShift($entity, $request);
258
        if(isset($entity['minShifts']) && $entity['minShifts'] > 0)
259
        {
260
          $shift->makeCopy($dataTable);
261
        }
262
        if(isset($entity['overlap']) && $entity['overlap'])
263
        {
264
            $overlaps = $shift->findOverlaps($this->user->uid);
265
            $count = count($overlaps);
0 ignored issues
show
Bug introduced by
It seems like $overlaps can also be of type true; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

265
            $count = count(/** @scrutinizer ignore-type */ $overlaps);
Loading history...
266
            $leads = array();
267
            for($i = 0; $i < $count; $i++)
268
            {
269
                $dept = new \VolunteerDepartment($overlaps[$i]->departmentID);
270
                $leads = array_merge($leads, $dept->getLeadEmails());
271
                $overlaps[$i]->status = 'pending';
272
                $tmp = new \Data\Filter('_id eq '.$overlaps[$i]->{'_id'});
273
                $res = $dataTable->update($tmp, $overlaps[$i]);
274
                if($res === false)
275
                {
276
                    return $response->withJSON(array('err'=>'Unable to update overlap with id '.$overlaps[$i]->{'_id'}));
277
                }
278
            }
279
            $dept = new \VolunteerDepartment($entity['departmentID']);
280
            $leads = array_merge($leads, $dept->getLeadEmails());
281
            $leads = array_unique($leads);
282
            $ret = $this->doSignup($this->user->uid, 'pending', $entity, $filter, $dataTable);
283
            $profile = new \VolunteerProfile($this->user->uid);
284
            $email = new \Emails\TwoShiftsAtOnceEmail($profile);
285
            $email->addLeads($leads);
286
            $emailProvider = \EmailProvider::getInstance();
287
            if($emailProvider->sendEmail($email) === false)
288
            {
289
                throw new \Exception('Unable to send duplicate email!');
290
            }
291
            return $response->withJSON($ret);
292
        }
293
        if(isset($entity['available']) && $entity['available'])
294
        {
295
            $ret = $this->doSignup($this->user->uid, 'filled', $entity, $filter, $dataTable);
296
            return $response->withJSON($ret);
297
        }
298
        if(isset($entity['status']) && $entity['status'] === 'groupPending')
299
        {
300
            $ret = $this->doSignup($this->user->uid, 'filled', $entity, $filter, $dataTable);
301
            return $response->withJSON($ret);
302
        }
303
        print_r($entity); die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
304
    }
305
306
    public function abandon($request, $response, $args)
307
    {
308
        $this->validateLoggedIn($request);
309
        $shiftId = $args['shift'];
310
        $dataTable = $this->getDataTable();
311
        $filter = $this->getFilterForPrimaryKey($shiftId);
312
        $entity = $dataTable->read($filter);
313
        if(empty($entity))
314
        {
315
            return $response->withStatus(404);
316
        }
317
        $entity = $entity[0];
318
        if(!isset($entity['participant']) || $entity['participant'] !== $this->user->uid)
319
        {
320
            return $response->withStatus(401);
321
        }
322
        $entity['participant'] = '';
323
        $entity['status'] = 'unfilled';
324
        if(isset($entity['needEEApproval']))
325
        {
326
          unset($entity['needEEApproval']);
327
        }
328
        return $response->withJSON($dataTable->update($filter, $entity));
329
    }
330
331
    public function approvePending($request, $response, $args)
332
    {
333
        if(!$this->canCreate($request))
334
        {
335
            return $response->withStatus(401);
336
        }
337
        $shiftId = $args['shift'];
338
        $dataTable = $this->getDataTable();
339
        $filter = $this->getFilterForPrimaryKey($shiftId);
340
        $entity = $dataTable->read($filter);
341
        if(empty($entity))
342
        {
343
            return $response->withStatus(404);
344
        }
345
        $entity = $entity[0];
346
        $entity['status'] = 'filled';
347
        return $response->withJSON($dataTable->update($filter, $entity));
348
    }
349
350
    public function disapprovePending($request, $response, $args)
351
    {
352
        if(!$this->canCreate($request))
353
        {
354
            return $response->withStatus(401);
355
        }
356
        $shiftId = $args['shift'];
357
        $dataTable = $this->getDataTable();
358
        $filter = $this->getFilterForPrimaryKey($shiftId);
359
        $entity = $dataTable->read($filter);
360
        if(empty($entity))
361
        {
362
            return $response->withStatus(404);
363
        }
364
        $entity['participant'] = '';
365
        $entity['status'] = 'unfilled';
366
        $profile = new \VolunteerProfile($this->user->uid);
367
        $email = new \Emails\PendingRejectedEmail($profile);
368
        $email->setShift($entity);
369
        $this->sendEmail($email);
370
        return $response->withJSON($dataTable->update($filter, $entity));
371
    }
372
373
    public function startGroupSignup($request, $response, $args)
374
    {
375
        $this->validateLoggedIn($request);
376
        $shiftId = $args['shift'];
377
        $dataTable = $this->getDataTable();
378
        $filter = $this->getFilterForPrimaryKey($shiftId);
379
        $entity = $dataTable->read($filter);
380
        if(empty($entity))
381
        {
382
            return $response->withStatus(404);
383
        }
384
        $entity = $entity[0];
385
        if(isset($entity['participant']) && strlen($entity['participant']) > 0)
386
        {
387
            return $response->withStatus(401);
388
        }
389
        $filter = new \Data\Filter('groupID eq '.$entity['groupID'].' and enabled eq true');
390
        $entities = $dataTable->read($filter);
391
        $count = count($entities);
0 ignored issues
show
Bug introduced by
It seems like $entities can also be of type boolean; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

391
        $count = count(/** @scrutinizer ignore-type */ $entities);
Loading history...
392
        $dept = new \VolunteerDepartment($entity['departmentID']);
393
        $res = array();
394
        $res['department'] = $dept->departmentName;
0 ignored issues
show
Bug Best Practice introduced by
The property departmentName does not exist on VolunteerDepartment. Since you implemented __get, consider adding a @property annotation.
Loading history...
395
        $res['earlyLate'] = $entity['earlyLate'];
396
        $res['endTime'] = $entity['endTime'];
397
        $res['eventID'] = $entity['eventID'];
398
        $res['name'] = $entity['name'];
399
        $res['startTime'] = $entity['startTime'];
400
        $res['groupID'] = $entity['groupID'];
401
        $res['shifts'] = array();
402
        $roles = array();
403
        for($i = 0; $i < $count; $i++)
404
        {
405
            if(isset($entities[$i]['status']) && ($entities[$i]['status'] === 'filled' || $entities[$i]['status'] === 'pending'))
406
            {
407
                continue;
408
            }
409
            if(!isset($roles[$entities[$i]['roleID']]))
410
            {
411
                $roles[$entities[$i]['roleID']] = new \VolunteerRole($entities[$i]['roleID']);
412
            }
413
            $role = $roles[$entities[$i]['roleID']];
414
            $entities[$i]['role'] = $role->display_name;
415
            array_push($res['shifts'], $entities[$i]);
416
        }
417
        return $response->withJSON($res);
418
    }
419
420
    public function generateGroupLink($request, $response, $args)
421
    {
422
        $this->validateLoggedIn($request);
423
        $shiftId = $args['shift'];
424
        $dataTable = $this->getDataTable();
425
        $filter = $this->getFilterForPrimaryKey($shiftId);
426
        $entity = $dataTable->read($filter);
427
        if(empty($entity))
428
        {
429
            return $response->withStatus(404);
430
        }
431
        $entity = $entity[0];
432
        if(isset($entity['participant']) && strlen($entity['participant']) > 0)
433
        {
434
            return $response->withStatus(401);
435
        }
436
        $data = $request->getParsedBody();
437
        $myShift = $data['myshift'];
438
        $roles = array();
439
        foreach($data as $key => $value)
440
        {
441
            if(substr($key, 0, 6) === "roles.")
442
            {
443
                $roles[substr($key, 6)] = $value;
444
            }
445
        }
446
        $filter = new \Data\Filter('groupID eq '.$entity['groupID'].' and enabled eq true');
447
        $entities = $dataTable->read($filter);
448
        $count = count($entities);
0 ignored issues
show
Bug introduced by
It seems like $entities can also be of type boolean; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

448
        $count = count(/** @scrutinizer ignore-type */ $entities);
Loading history...
449
        $uuid = $this->genUUID();
450
        for($i = 0; $i < $count; $i++)
451
        {
452
            if(isset($entities[$i]['status']) && ($entities[$i]['status'] === 'filled' || $entities[$i]['status'] === 'pending'))
453
            {
454
                $entities[$i] = false;
455
                continue;
456
            }
457
            if((string)$entities[$i]['_id'] === (string)new \MongoDB\BSON\ObjectId($myShift))
458
            {
459
                $entities[$i]['participant'] = $this->user->uid;
460
                $entities[$i]['status'] = 'filled';
461
                $entities[$i]['signupLink'] = $uuid;
462
            }
463
            else if(isset($roles[$entities[$i]['roleID']]))
464
            {
465
                $entities[$i]['status'] = 'groupPending';
466
                $entities[$i]['signupLink'] = $uuid;
467
                $roles[$entities[$i]['roleID']]--;
468
                if($roles[$entities[$i]['roleID']] === 0)
469
                {
470
                    unset($roles[$entities[$i]['roleID']]);
471
                }
472
            }
473
            else
474
            {
475
                $entities[$i] = false;
476
            }
477
        }
478
        if(count($roles) !== 0)
479
        {
480
            throw new \Exception('Not enough shifts to fullfill requests');
481
        }
482
        for($i = 0; $i < $count; $i++)
483
        {
484
            if($entities[$i] === false)
485
            {
486
                continue;
487
            }
488
            $filter = new \Data\Filter('_id eq '.$entities[$i]['_id']);
489
            $res = $dataTable->update($filter, $entities[$i]);
490
            if($res === false)
491
            {
492
                throw new \Exception('Not able to save shift '.$entities[$i]['_id']);
493
            }
494
        }
495
        return $response->withJSON(array('uuid' => $uuid));
496
    }
497
498
    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...
499
    {
500
        $this->validateLoggedIn($request);
501
        $shiftId = $args['shift'];
502
        $dataTable = $this->getDataTable();
503
        $filter = $this->getFilterForPrimaryKey($shiftId);
504
        $entity = $dataTable->read($filter);
505
        if(empty($entity))
506
        {
507
            return $response->withStatus(404);
508
        }
509
        $entity = $entity[0];
510
        if(!$this->canUpdate($request, $entity))
511
        {
512
            return $response->withStatus(401);
513
        }
514
        $shift = new \VolunteerShift(false, $entity);
515
        $entity['participant'] = '';
516
        $entity['status'] = 'unfilled';
517
        if(isset($entity['needEEApproval']))
518
        {
519
          unset($entity['needEEApproval']);
520
        }
521
        $ret = $dataTable->update($filter, $entity);
522
        if($ret)
523
        {
524
            $email = new \Emails\ShiftEmail($shift, 'shiftEmptiedSource');
525
            $this->sendEmail($email);
526
        }
527
        return $response->withJSON($ret);
528
    }
529
530
    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...
531
    {
532
        $this->validateLoggedIn($request);
533
        $shiftId = $args['shift'];
534
        $dataTable = $this->getDataTable();
535
        $filter = $this->getFilterForPrimaryKey($shiftId);
536
        $entity = $dataTable->read($filter);
537
        if(empty($entity))
538
        {
539
            return $response->withStatus(404);
540
        }
541
        $entity = $entity[0];
542
        if(!$this->canUpdate($request, $entity))
543
        {
544
            return $response->withStatus(401);
545
        }
546
        $entity['participant'] = '';
547
        $entity['status'] = 'unfilled';
548
        if(isset($entity['needEEApproval']))
549
        {
550
          unset($entity['needEEApproval']);
551
        }
552
        return $response->withJSON($dataTable->update($filter, $entity));
553
    }
554
}
555
/* vim: set tabstop=4 shiftwidth=4 expandtab: */
556