Passed
Push — feature/default-questions-for-... ( d343de...513462 )
by Chris
10:44 queued 04:09
created

JobController::populateDefaultQuestions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 35
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 3.0213

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 35
ccs 13
cts 15
cp 0.8667
rs 9.7
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 3.0213
1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
namespace App\Http\Controllers;
4
5
use Illuminate\Support\Facades\Lang;
6
use Illuminate\Support\Facades\Auth;
7
use Illuminate\Support\Facades\Log;
8
use Illuminate\Http\RedirectResponse;
9
use Illuminate\Http\Request;
10
use Illuminate\Http\Response;
11
use Illuminate\View\View;
12
use App\Http\Controllers\Controller;
13
14
use Carbon\Carbon;
15
16
use App\Models\JobPoster;
17
use App\Models\JobPosterQuestion;
18
use App\Models\Lookup\JobTerm;
19
use App\Models\Lookup\Province;
20
use App\Models\Lookup\SecurityClearance;
21
use App\Models\Lookup\LanguageRequirement;
22
use App\Models\Lookup\CitizenshipDeclaration;
23
use App\Models\Lookup\Department;
24
use App\Models\Lookup\SkillLevel;
25
use App\Models\Lookup\CriteriaType;
26
use App\Models\Lookup\VeteranStatus;
27
use App\Models\JobApplication;
28
use App\Models\Criteria;
29
use App\Models\Skill;
30
use App\Models\JobPosterKeyTask;
31
32
use App\Services\Validation\JobPosterValidator;
33
use Jenssegers\Date\Date;
34
35
class JobController extends Controller
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class JobController
Loading history...
36
{
37
    /**
38
     * Display a listing of JobPosters.
39
     *
40
     * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
41
     */
42
    public function index()
43
    {
44
        $now = Carbon::now();
45
46
        //Find published jobs that are currently open for applications
47
        $jobs = JobPoster::where('open_date_time', '<=', $now)
48
            ->where('close_date_time', '>=', $now)
49
            ->where('published', true)
50
            ->get();
51
        $jobs->load('manager.work_environment');
52
53
        return view('applicant/job_index', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
54
            'job_index' => Lang::get('applicant/job_index'),
55
            'jobs' => $jobs
56
        ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
57
    }
58
59
    /**
60
     * Display a listing of a manager's JobPosters.
61
     *
62
     * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
63
     */
64 4
    public function managerIndex()
65
    {
66 4
        $manager = Auth::user()->manager;
0 ignored issues
show
Bug introduced by
Accessing manager on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
67
68 4
        $veteran_applications = [];
69 4
        $citizen_applications = [];
70 4
        $other_applications = [];
71
72 4
        foreach ($manager->job_posters as $job) {
73 4
            $job->submitted_applications->load(['veteran_status', 'citizenship_declaration']);
74
            $veteran_applications[$job->id] = $job->submitted_applications->filter(function ($application) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
75
                return $application->veteran_status->name !== "none" &&
76
                    $application->citizenship_declaration->name === "citizen";
77 4
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
78
            $citizen_applications[$job->id] = $job->submitted_applications->filter(function ($application) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
79
                return $application->veteran_status->name === "none" &&
80
                    $application->citizenship_declaration->name === "citizen";
81 4
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
82
            $other_applications[$job->id] = $job->submitted_applications->filter(function ($application) {
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
83
                return $application->citizenship_declaration->name !== "citizen";
84 4
            });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
85
        }
86
87 4
        return view('manager/job_index', [
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
88 4
            "manager_job_index" => [
89
                "title" => "My Job Posts"
90
            ],
91 4
            'jobs' => $manager->job_posters,
92 4
            'veteran_applications' => $veteran_applications,
93 4
            'citizen_applications' => $citizen_applications,
94 4
            'other_applications' => $other_applications,
95
        ]);
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
96
    }
97
98
    /**
99
     * Display the specified job poster.
100
     *
101
     * @param \Illuminate\Http\Request $request   Incoming request object.
102
     * @param \App\Models\JobPoster    $jobPoster Job Poster object.
103
     *
104
     * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
105
     */
106
    public function show(Request $request, JobPoster $jobPoster)
107
    {
108
        //TODO: Improve workplace photos, and reference them in template direction from WorkEnvironment model
109
        $workplacePhotos = [];
110
        foreach ($jobPoster->manager->work_environment->workplace_photo_captions as $photoCaption) {
111
            $workplacePhotos[] = [
112
                'description' => $photoCaption->description,
113
                'url' => '/images/user.png'
114
            ];
115
        }
116
117
        //TODO: replace route('manager.show',manager.id) in templates with link using slug
118
119
        $criteria = [
120
            'essential' => $jobPoster->criteria->filter(
121
                function ($value, $key) {
122
                    return $value->criteria_type->name == 'essential';
123
                }
124
            ),
125
            'asset' => $jobPoster->criteria->filter(
126
                function ($value, $key) {
127
                    return $value->criteria_type->name == 'asset';
128
                }
129
            ),
130
        ];
131
132
        return view(
133
            'applicant/job_post',
134
            [
135
                'job_post' => Lang::get('applicant/job_post'),
136
                'manager' => $jobPoster->manager,
137
                'manager_profile_photo_url' => '/images/user.png', //TODO get real photo
138
                'team_culture' => $jobPoster->manager->team_culture,
139
                'work_environment' => $jobPoster->manager->work_environment,
140
                'workplace_photos' => $workplacePhotos,
141
                'job' => $jobPoster,
142
                'criteria' => $criteria,
143
                'skill_template' => Lang::get('common/skills'),
144
            ]
145
        );
146
    }
147
148
    /**
149
     * Display the form for creating a new Job Poster
150
     *
151
     * @param \Illuminate\Http\Request $request Incoming request object.
152
     *
153
     * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory Job Create view
154
     */
155 2
    public function create(Request $request)
156
    {
157 2
        return $this->populateCreateView($request);
158
    }
159
160
    /**
161
     * Display the form for editing an existing Job Poster
162
     *
163
     * @param \Illuminate\Http\Request $request   Incoming request object.
164
     * @param \App\Models\JobPoster    $jobPoster Job Poster object.
165
     *
166
     * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory Job Create view
167
     */
168 2
    public function edit(Request $request, JobPoster $jobPoster)
169
    {
170 2
        return $this->populateCreateView($request, $jobPoster);
171
    }
172
173
    /**
174
     * Get the manager from the request object and check if creating or editing
175
     *
176
     * @param \Illuminate\Http\Request $request   Incoming request object.
177
     * @param \App\Models\JobPoster    $jobPoster Optional Job Poster object.
178
     *
179
     * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory Job Create view
180
     */
181 4
    public function populateCreateView(Request $request, JobPoster $jobPoster = null)
182
    {
183 4
        $manager = $request->user() ? $request->user()->manager : null;
184 4
        if (isset($jobPoster)) {
185 2
            $job = $jobPoster;
186 2
            $route = ['manager.jobs.store', $jobPoster];
187 2
            $jobHeading = 'manager/job_edit';
188
        } else {
189 2
            $job = [];
190 2
            $defaultQuestions = $this->populateDefaultQuestions();
191 2
            if (!empty($defaultQuestions)) {
192 2
                $job['job_poster_questions'] = $defaultQuestions;
193
            }
194 2
            $route = ['manager.jobs.store'];
195 2
            $jobHeading = 'manager/job_create';
196
        }
197
198 4
        $skillLangs = Lang::get('common/skills');
199
200 4
        $softSkills = Skill::whereHas(
201 4
            'skill_type',
202
            function ($query) : void {
203 4
                $query->where('name', '=', 'soft');
204 4
            }
205 4
        )->get()
206 4
            ->mapWithKeys(
207
                function ($skill) use ($skillLangs) {
208
                    return [
209 4
                        $skill->id => $skillLangs['skills'][$skill->name]['name']
210
                    ];
211 4
                }
212
            )
213 4
            ->all();
214
215 4
        $hardSkills = Skill::whereHas(
216 4
            'skill_type',
217
            function ($query) : void {
218 4
                $query->where('name', '=', 'hard');
219 4
            }
220 4
        )->get()
221 4
            ->mapWithKeys(
222
                function ($skill) use ($skillLangs) {
223
                    return [
224 4
                        $skill->id => $skillLangs['skills'][$skill->name]['name']
225
                    ];
226 4
                }
227
            )
228 4
            ->all();
229
230 4
        asort($softSkills, SORT_LOCALE_STRING);
231 4
        asort($hardSkills, SORT_LOCALE_STRING);
232
233
        $skills = [
234
            'essential' => [
235 4
                'hard' => $hardSkills,
236 4
                'soft' => $softSkills
237
            ],
238
            'asset' => [
239 4
                'hard' => $hardSkills,
240 4
                'soft' => $softSkills
241
            ]
242
        ];
243
244 4
        $skillLevelCollection = SkillLevel::all();
245
246 4
        $skillLevels = array();
247
248 4
        $skillLevels['hard'] = $skillLevelCollection->mapWithKeys(
249
            function ($skillLevel) use ($skillLangs) {
250 4
                return [$skillLevel->id => $skillLangs['skill_levels']['hard'][$skillLevel->name]];
251 4
            }
252 4
        )->all();
253
254 4
        $skillLevels['soft'] = $skillLevelCollection->mapWithKeys(
255
            function ($skillLevel) use ($skillLangs) {
256 4
                return [$skillLevel->id => $skillLangs['skill_levels']['soft'][$skillLevel->name]];
257 4
            }
258 4
        )->all();
259
260 4
        return view(
261 4
            'manager/job_create',
262
            [
263 4
                'job_heading' => Lang::get($jobHeading),
264 4
                'manager' => $manager,
265 4
                'provinces' => Province::all(),
266 4
                'departments' => Department::all(),
267 4
                'language_requirments' => LanguageRequirement::all(),
268 4
                'security_clearances' => SecurityClearance::all(),
269 4
                'job' => $job,
270 4
                'form_action_url' => route(/** @scrutinizer ignore-type */ ...$route), // phpcs:ignore
0 ignored issues
show
Bug introduced by
$route is expanded, but the parameter $name of route() does not expect variable arguments. ( Ignorable by Annotation )

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

270
                'form_action_url' => route(/** @scrutinizer ignore-type */ /** @scrutinizer ignore-type */ ...$route), // phpcs:ignore
Loading history...
271 4
                'skills' => $skills,
272 4
                'skill_levels' => $skillLevels,
273 4
                'skill_template' => $skillLangs,
274
            ]
275
        );
276
    }
277
278
    /**
279
     * Create a new resource in storage
280
     *
281
     * @param \Illuminate\Http\Request $request   Incoming request object.
282
     * @param \App\Models\JobPoster    $jobPoster Optional Job Poster object.
283
     *
284
     * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse A redirect to the Job Index
285
     */
286 2
    public function store(Request $request, JobPoster $jobPoster = null)
287
    {
288
        // Don't allow edits for published Job Posters
289
        // Also check auth while we're at it
290 2
        if (isset($jobPoster)) {
291
            $this->authorize('update', $jobPoster);
292
            JobPosterValidator::validateUnpublished($jobPoster);
293
        } else {
294 2
            $this->authorize('create', JobPoster::class);
295
        }
296
297 2
        $input = $request->input();
298
299 2
        $job = (isset($jobPoster) ? $jobPoster : new JobPoster());
300
301 2
        $job->manager_id = $request->user()->manager->id;
302 2
        $job->published = ($input['submit'] == 'publish');
303
304 2
        $this->fillAndSaveJobPoster($input, $job);
305
306 2
        $this->fillAndSaveJobPosterTasks($input, $job, isset($jobPoster));
307
308 2
        $this->fillAndSaveJobPosterQuestions($input, $job, isset($jobPoster));
309
310 2
        $this->fillAndSaveJobPosterCriteria($input, $job, isset($jobPoster));
311
312 2
        return redirect(route('manager.jobs.index'));
313
    }
314
315
    /**
316
     * Fill Job Poster model's properties and save
317
     *
318
     * @param mixed[]               $input     Field values.
319
     * @param \App\Models\JobPoster $jobPoster Job Poster object.
320
     *
321
     * @return void
322
     */
323 2
    protected function fillAndSaveJobPoster(array $input, JobPoster $jobPoster) : void
324
    {
325 2
        $jobPoster->fill(
326
            [
327 2
                'job_term_id' => JobTerm::where('name', 'month')->firstOrFail()->id,
328 2
                'term_qty' => $input['term_qty'],
329 2
                'open_date_time' => new Date($input['open_date'] . $input['open_time']),
330 2
                'close_date_time' => new Date($input['close_date'] . $input['close_time']),
331 2
                'start_date_time' => new Date($input['start_date_time']),
332 2
                'department_id' => $input['department'],
333 2
                'province_id' => $input['province'],
334 2
                'salary_min' => $input['salary_min'],
335 2
                'salary_max' => $input['salary_max'],
336 2
                'noc' => $input['noc'],
337 2
                'classification' => $input['classification'],
338 2
                'security_clearance_id' => $input['security_clearance'],
339 2
                'language_requirement_id' => $input['language_requirement'],
340 2
                'remote_work_allowed' => (isset($input['remote_work_allowed']) ? $input['remote_work_allowed'] : false),
341
                'en' => [
342 2
                    'city' => $input['city'],
343 2
                    'title' => $input['title']['en'],
344 2
                    'impact' => $input['impact']['en'],
345 2
                    'branch' => $input['branch']['en'],
346 2
                    'division' => $input['division']['en'],
347 2
                    'education' => $input['education']['en'],
348
                ],
349
                'fr' => [
350 2
                    'city' => $input['city'],
351 2
                    'title' => $input['title']['fr'],
352 2
                    'impact' => $input['impact']['fr'],
353 2
                    'branch' => $input['branch']['fr'],
354 2
                    'division' => $input['division']['fr'],
355 2
                    'education' => $input['education']['fr'],
356
                ],
357
            ]
358
        );
359 2
        $jobPoster->save();
360 2
    }
361
362
    /**
363
     * Fill Job Poster's tasks and save
364
     *
365
     * @param mixed[]               $input     Field values.
366
     * @param \App\Models\JobPoster $jobPoster Job Poster object.
367
     * @param boolean               $replace   Remove existing relationships.
368
     *
369
     * @return void
370
     */
371 2
    protected function fillAndSaveJobPosterTasks(array $input, JobPoster $jobPoster, bool $replace) : void
372
    {
373 2
        if ($replace) {
374
            $jobPoster->job_poster_key_tasks()->delete();
375
        }
376
377 2
        if (!array_key_exists('task', $input) || !is_array($input['task'])) {
378 2
            return;
379
        }
380
381
        foreach ($input['task'] as $task) {
382
            $jobPosterTask = new JobPosterKeyTask();
383
            $jobPosterTask->job_poster_id = $jobPoster->id;
384
            $jobPosterTask->fill(
385
                [
386
                    'en' => [
387
                        'description' => $task['en']
388
                    ],
389
                    'fr' => [
390
                        'description' => $task['fr']
391
                    ]
392
                ]
393
            );
394
            $jobPosterTask->save();
395
        }
396
    }
397
398
    /**
399
     * Fill Job Poster's questions and save
400
     *
401
     * @param mixed[]               $input     Field values.
402
     * @param \App\Models\JobPoster $jobPoster Job Poster object.
403
     * @param boolean               $replace   Remove existing relationships.
404
     *
405
     * @return void
406
     */
407 2
    protected function fillAndSaveJobPosterQuestions(array $input, JobPoster $jobPoster, bool $replace) : void
408
    {
409 2
        if ($replace) {
410
            $jobPoster->job_poster_questions()->delete();
411
        }
412
413 2
        if (!array_key_exists('question', $input) || !is_array($input['question'])) {
414 2
            return;
415
        }
416
417
        foreach ($input['question'] as $question) {
418
            $jobQuestion = new JobPosterQuestion();
419
            $jobQuestion->job_poster_id = $jobPoster->id;
420
            $jobQuestion->fill(
421
                [
422
                    'en' => [
423
                        'question' => $question['question']['en'],
424
                        'description' => $question['description']['en']
425
                    ],
426
                    'fr' => [
427
                        'question' => $question['question']['fr'],
428
                        'description' => $question['description']['fr']
429
                    ]
430
                ]
431
            );
432
            $jobQuestion->save();
433
        }
434
    }
435
436
    /**
437
     * Fill Job Poster's criteria and save
438
     *
439
     * @param mixed[]               $input     Field values.
440
     * @param \App\Models\JobPoster $jobPoster Job Poster object.
441
     * @param boolean               $replace   Remove existing relationships.
442
     *
443
     * @return void
444
     */
445 2
    protected function fillAndSaveJobPosterCriteria(array $input, JobPoster $jobPoster, bool $replace) : void
446
    {
447 2
        if ($replace) {
448
            $jobPoster->criteria()->delete();
449
        }
450
451 2
        if (!array_key_exists('criteria', $input) || !is_array($input['criteria'])) {
452 2
            return;
453
        }
454
455
        $criteria = $input['criteria'];
456
457
        $combinedCriteria = [];
458
        if (isset($criteria['old'])) {
459
            $combinedCriteria = array_replace_recursive($combinedCriteria, $criteria['old']);
460
        }
461
        if (isset($criteria['new'])) {
462
            $combinedCriteria = array_replace_recursive($combinedCriteria, $criteria['new']);
463
        }
464
465
        if (! empty($combinedCriteria)) {
466
            foreach ($combinedCriteria as $criteriaType => $criteriaTypeInput) {
467
                foreach ($criteriaTypeInput as $skillType => $skillTypeInput) {
468
                    foreach ($skillTypeInput as $criteriaInput) {
469
                        $criteria = new Criteria();
470
                        $criteria->job_poster_id = $jobPoster->id;
471
                        $criteria->fill(
472
                            [
473
                                'criteria_type_id' => CriteriaType::where('name', $criteriaType)->firstOrFail()->id,
474
                                'skill_id' => $criteriaInput['skill_id'],
475
                                'skill_level_id' => $criteriaInput['skill_level_id'],
476
                                'en' => [
477
                                    'description' => $criteriaInput['description']['en'],
478
                                ],
479
                                'fr' => [
480
                                    'description' => $criteriaInput['description']['fr'],
481
                                ],
482
                            ]
483
                        );
484
                        $criteria->save();
485
                    }
486
                }
487
            }
488
        }
489
    }
490
491
    /**
492
     * Get the localized default questions and add them to an array.
493
     *
494
     * @return mixed[]|void
495
     */
496 2
    protected function populateDefaultQuestions()
497
    {
498
        $defaultQuestions = [
499 2
            'en' => array_values(Lang::get('manager/job_create', [], 'en')['questions']),
500 2
            'fr' => array_values(Lang::get('manager/job_create', [], 'fr')['questions']),
501
        ];
502
503 2
        if (count($defaultQuestions['en']) !== count($defaultQuestions['fr'])) {
504
            Log::warning('There must be the same number of French and English default questions for a Job Poster.');
505
            return;
506
        }
507
508 2
        $jobQuestions = [];
509
510 2
        for ($i = 0; $i < count($defaultQuestions['en']); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
511 2
            $jobQuestion = new JobPosterQuestion();
512 2
            $jobQuestion->fill(
513
                [
514
                    'en' => [
515 2
                        'question' => $defaultQuestions['en'][$i],
516
                    ],
517
                    'fr' => [
518 2
                        'question' => $defaultQuestions['fr'][$i],
519
                    ]
520
                ]
521
            );
522
            // Workaround for Default Questions with empty descriptions
523
            // throwing an error during save.
524
            // The id isn't actually used during the fillAndSaveJobPosterQuestions
525
            // method call.
526 2
            $jobQuestion->id = $i + 1;
527 2
            $jobQuestions[] = $jobQuestion;
528
        }
529
530 2
        return $jobQuestions;
531
    }
532
}
533