Passed
Push — feature/job-status-transitions ( 688dc7...931d64 )
by Tristan
11:00 queued 06:03
created

JobController   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 458
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 38
eloc 217
c 7
b 0
f 0
dl 0
loc 458
rs 9.36

10 Methods

Rating   Name   Duplication   Size   Complexity  
A populateDefaultQuestions() 0 28 3
A hrIndex() 0 6 1
A index() 0 24 1
A createAsManager() 0 17 1
A fillAndSaveJobPosterQuestions() 0 28 5
A managerIndex() 0 29 3
C show() 0 124 12
A store() 0 26 3
A destroy() 0 3 1
A downloadApplicants() 0 51 5
1
<?php
2
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\Request;
9
use App\Http\Controllers\Controller;
10
use App\Models\JobApplication;
11
use Carbon\Carbon;
12
use App\Models\JobPoster;
13
use App\Models\JobPosterQuestion;
14
use App\Models\Lookup\ApplicationStatus;
15
use App\Models\Lookup\CitizenshipDeclaration;
16
use App\Models\Lookup\VeteranStatus;
17
use App\Models\Manager;
18
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
19
use App\Services\Validation\JobPosterValidator;
20
use Facades\App\Services\WhichPortal;
21
use Illuminate\Support\Facades\Response;
22
use Illuminate\Support\Facades\Validator;
23
24
class JobController extends Controller
25
{
26
    /**
27
     * Display a listing of JobPosters.
28
     *
29
     * @return \Illuminate\Http\Response
30
     */
31
    public function index()
32
    {
33
        $now = Carbon::now();
34
35
        // Find published jobs that are currently open for applications.
36
        // Eager load required relationships: Department, Province, JobTerm.
37
        // Eager load the count of submitted applications, to prevent the relationship
38
        // from being actually loaded and firing off events.
39
        $jobs = JobPoster::where('open_date_time', '<=', $now)
40
            ->where('close_date_time', '>=', $now)
41
            ->where('internal_only', false)
42
            ->where('published', true)
43
            ->with([
44
                'department',
45
                'province',
46
                'job_term',
47
            ])
48
            ->withCount([
49
                'submitted_applications',
50
            ])
51
            ->get();
52
        return view('applicant/job_index', [
0 ignored issues
show
Bug introduced by
The function view was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

52
        return /** @scrutinizer ignore-call */ view('applicant/job_index', [
Loading history...
53
            'job_index' => Lang::get('applicant/job_index'),
54
            'jobs' => $jobs
55
        ]);
56
    }
57
58
    /**
59
     * Display a listing of a manager's JobPosters.
60
     *
61
     * @return \Illuminate\Http\Response
62
     */
63
    public function managerIndex()
64
    {
65
        $manager = Auth::user()->manager;
66
67
        $jobs = JobPoster::where('manager_id', $manager->id)
68
            ->with('classification')
69
            ->withCount('submitted_applications')
70
            ->get();
71
72
        foreach ($jobs as &$job) {
73
            $chosen_lang = $job->chosen_lang;
74
75
            // Show chosen lang title if current title is empty.
76
            if (empty($job->title)) {
77
                $job->title = $job->getTranslation('title', $chosen_lang);
78
                $job->trans_required = true;
79
            }
80
81
            // Always preview and edit in the chosen language.
82
            $job->preview_link = LaravelLocalization::getLocalizedURL($chosen_lang, route('manager.jobs.show', $job));
0 ignored issues
show
Bug introduced by
The function route was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

82
            $job->preview_link = LaravelLocalization::getLocalizedURL($chosen_lang, /** @scrutinizer ignore-call */ route('manager.jobs.show', $job));
Loading history...
83
            $job->edit_link = LaravelLocalization::getLocalizedURL($chosen_lang, route('manager.jobs.edit', $job));
84
        }
85
86
87
        return view('manager/job_index', [
0 ignored issues
show
Bug introduced by
The function view was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

87
        return /** @scrutinizer ignore-call */ view('manager/job_index', [
Loading history...
88
            // Localization Strings.
89
            'jobs_l10n' => Lang::get('manager/job_index'),
90
            // Data.
91
            'jobs' => $jobs,
92
        ]);
93
    }
94
95
    /**
96
     * Display a listing of a hr advisor's JobPosters.
97
     *
98
     * @return \Illuminate\Http\Response
99
     */
100
    public function hrIndex(Request $request)
101
    {
102
        $hrAdvisor = $request->user()->hr_advisor;
103
        return view('hr_advisor/job_index', [
0 ignored issues
show
Bug introduced by
The function view was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

103
        return /** @scrutinizer ignore-call */ view('hr_advisor/job_index', [
Loading history...
104
            'title' => Lang::get('hr_advisor/job_index.title'),
105
            'hr_advisor_id' => $hrAdvisor->id
106
        ]);
107
    }
108
109
110
111
112
    /**
113
     * Delete a draft Job Poster.
114
     *
115
     * @param  \Illuminate\Http\Request $request   Incoming request object.
116
     * @param  \App\Models\JobPoster    $jobPoster Job Poster object.
117
     * @return \Illuminate\Http\Response
118
     */
119
    public function destroy(Request $request, JobPoster $jobPoster)
120
    {
121
        $jobPoster->delete();
122
    }
123
124
    /**
125
     * Display the specified job poster.
126
     *
127
     * @param  \Illuminate\Http\Request $request   Incoming request object.
128
     * @param  \App\Models\JobPoster    $jobPoster Job Poster object.
129
     * @return \Illuminate\Http\Response
130
     */
131
    public function show(Request $request, JobPoster $jobPoster)
132
    {
133
        $jobPoster->load([
134
            'department',
135
            'criteria.skill.skill_type',
136
            'manager.team_culture',
137
            'manager.work_environment'
138
        ]);
139
140
        $user = Auth::user();
141
142
        // TODO: Improve workplace photos, and reference them in template direction from WorkEnvironment model.
143
        $workplacePhotos = [];
144
        foreach ($jobPoster->manager->work_environment->workplace_photo_captions as $photoCaption) {
145
            $workplacePhotos[] = [
146
                'description' => $photoCaption->description,
147
                'url' => '/images/user.png'
148
            ];
149
        }
150
151
        // TODO: replace route('manager.show',manager.id) in templates with link using slug.
152
        $criteria = [
153
            'essential' => $jobPoster->criteria->filter(
154
                function ($value, $key) {
155
                    return $value->criteria_type->name == 'essential';
156
                }
157
            ),
158
            'asset' => $jobPoster->criteria->filter(
159
                function ($value, $key) {
160
                    return $value->criteria_type->name == 'asset';
161
                }
162
            ),
163
        ];
164
165
        $jobLang = Lang::get('applicant/job_post');
166
167
        $applyButton = [];
168
        if (WhichPortal::isManagerPortal()) {
169
            $applyButton = [
170
                'href' => route('manager.jobs.edit', $jobPoster->id),
0 ignored issues
show
Bug introduced by
The function route was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

170
                'href' => /** @scrutinizer ignore-call */ route('manager.jobs.edit', $jobPoster->id),
Loading history...
171
                'title' => $jobLang['apply']['edit_link_title'],
172
                'text' => $jobLang['apply']['edit_link_label'],
173
            ];
174
        } elseif (WhichPortal::isHrPortal()) {
175
            if ($jobPoster->hr_advisors->contains('user_id', $user->id)) {
176
                $applyButton = [
177
                    'href' => route('hr_advisor.jobs.summary', $jobPoster->id),
178
                    'title' => null,
179
                    'text' => Lang::get('hr_advisor/job_summary.summary_title'),
180
                ];
181
            } else {
182
                $applyButton = [
183
                    'href' => route('hr_advisor.jobs.index'),
184
                    'title' => null,
185
                    'text' => Lang::get('hr_advisor/job_index.title'),
186
                ];
187
            }
188
        } elseif (Auth::check() && $jobPoster->isOpen()) {
189
            $application = JobApplication::where('applicant_id', Auth::user()->applicant->id)
190
            ->where('job_poster_id', $jobPoster->id)->first();
191
            // If applicants job application is not draft anymore then link to application preview page.
192
            if ($application != null && $application->application_status->name != 'draft') {
193
                $applyButton = [
194
                    'href' => route('applications.show', $application->id),
195
                    'title' => $jobLang['apply']['view_link_title'],
196
                    'text' => $jobLang['apply']['view_link_label'],
197
                ];
198
            } else {
199
                $applyButton = [
200
                    'href' => route('job.application.edit.1', $jobPoster->id),
201
                    'title' => $jobLang['apply']['apply_link_title'],
202
                    'text' => $jobLang['apply']['apply_link_label'],
203
                ];
204
            }
205
        } elseif (Auth::guest() && $jobPoster->isOpen()) {
206
            $applyButton = [
207
                'href' => route('job.application.edit.1', $jobPoster->id),
208
                'title' => $jobLang['apply']['login_link_title'],
209
                'text' => $jobLang['apply']['login_link_label'],
210
            ];
211
        } else {
212
            $applyButton = [
213
                'href' => null,
214
                'title' => null,
215
                'text' => $jobLang['apply']['job_closed_label'],
216
            ];
217
        }
218
219
        $jpb_release_date = strtotime('2019-08-21 16:18:17');
220
        $job_created_at = strtotime($jobPoster->created_at);
221
222
        // If the job poster is created after the release of the JPB.
223
        // Then, render with updated poster template.
224
        // Else, render with old poster template.
225
        if ($job_created_at > $jpb_release_date) {
226
            // Updated job poster (JPB).
227
            return view(
0 ignored issues
show
Bug introduced by
The function view was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

227
            return /** @scrutinizer ignore-call */ view(
Loading history...
228
                'applicant/jpb_job_post',
229
                [
230
                    'job_post' => $jobLang,
231
                    'frequencies' => Lang::get('common/lookup/frequency'),
232
                    'skill_template' => Lang::get('common/skills'),
233
                    'job' => $jobPoster,
234
                    'manager' => $jobPoster->manager,
235
                    'criteria' => $criteria,
236
                    'apply_button' => $applyButton,
237
                ]
238
            );
239
        } else {
240
            // Old job poster.
241
            return view(
242
                'applicant/job_post',
243
                [
244
                    'job_post' => $jobLang,
245
                    'frequencies' => Lang::get('common/lookup/frequency'),
246
                    'manager' => $jobPoster->manager,
247
                    'manager_profile_photo_url' => '/images/user.png', // TODO get real photo.
248
                    'team_culture' => $jobPoster->manager->team_culture,
249
                    'work_environment' => $jobPoster->manager->work_environment,
250
                    'workplace_photos' => $workplacePhotos,
251
                    'job' => $jobPoster,
252
                    'criteria' => $criteria,
253
                    'apply_button' => $applyButton,
254
                    'skill_template' => Lang::get('common/skills'),
255
                ]
256
            );
257
        }
258
    }
259
260
    /**
261
     * Display the form for editing an existing Job Poster
262
     * Only allows editing fields that don't appear on the react-built Job Poster Builder.
263
     *
264
     * @param  \Illuminate\Http\Request $request   Incoming request object.
265
     * @param  \App\Models\JobPoster    $jobPoster Job Poster object.
266
     * @return \Illuminate\Http\Response
267
     */
268
    public function edit(Request $request, JobPoster $jobPoster)
269
    {
270
        $manager = $jobPoster->manager;
271
272
        if ($jobPoster->job_poster_questions === null || $jobPoster->job_poster_questions->count() === 0) {
273
            $jobPoster->job_poster_questions()->saveMany($this->populateDefaultQuestions());
274
            $jobPoster->refresh();
275
        }
276
277
        return view(
0 ignored issues
show
Bug introduced by
The function view was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

277
        return /** @scrutinizer ignore-call */ view(
Loading history...
278
            'manager/job_create',
279
            [
280
                // Localization Strings.
281
                'job_l10n' => Lang::get('manager/job_edit'),
282
                // Data.
283
                'manager' => $manager,
284
                'job' => $jobPoster,
285
                'form_action_url' => route('admin.jobs.update', $jobPoster),
0 ignored issues
show
Bug introduced by
The function route was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

285
                'form_action_url' => /** @scrutinizer ignore-call */ route('admin.jobs.update', $jobPoster),
Loading history...
286
            ]
287
        );
288
    }
289
290
    /**
291
     * Create a blank job poster for the specified manager
292
     *
293
     * @param  \App\Models\Manager $manager Incoming Manager object.
294
     * @return \Illuminate\Http\Response Job Create view
295
     */
296
    public function createAsManager(Manager $manager)
297
    {
298
        $jobPoster = new JobPoster();
299
        $jobPoster->manager_id = $manager->id;
300
301
        // Save manager-specific info to the job poster - equivalent to the intro step of the JPB
302
        $divisionEn = $manager->getTranslation('division', 'en');
303
        $divisionFr = $manager->getTranslation('division', 'fr');
304
        $jobPoster->fill([
305
            'department_id' => $manager->department_id,
306
            'division' => ['en' => $divisionEn],
307
            'division' => ['fr' => $divisionFr],
308
        ]);
309
310
        $jobPoster->save();
311
312
        return redirect()->route('manager.jobs.edit', $jobPoster->id);
0 ignored issues
show
Bug introduced by
The function redirect was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

312
        return /** @scrutinizer ignore-call */ redirect()->route('manager.jobs.edit', $jobPoster->id);
Loading history...
313
    }
314
315
    /**
316
     * Update a resource in storage
317
     * NOTE: Only saves fields that are not on the react-built Job Poster Builder
318
     *
319
     * @param  \Illuminate\Http\Request $request   Incoming request object.
320
     * @param  \App\Models\JobPoster    $jobPoster Optional Job Poster object.
321
     * @return \Illuminate\Http\Response
322
     */
323
    public function store(Request $request, JobPoster $jobPoster)
324
    {
325
        // Don't allow edits for published Job Posters
326
        // Also check auth while we're at it.
327
        $this->authorize('update', $jobPoster);
328
        JobPosterValidator::validateUnpublished($jobPoster);
329
330
        $input = $request->input();
331
332
        if ($jobPoster->manager_id == null) {
333
            $jobPoster->manager_id = $request->user()->manager->id;
334
            $jobPoster->save();
335
        }
336
337
        $validator = Validator::make($request->input('question'), [
338
            '*.question.*' => 'required|string',
339
            '*.description.*' => 'required|string'
340
        ]);
341
342
        if ($validator->fails()) {
343
            return redirect(route('admin.jobs.edit', $jobPoster->id));
0 ignored issues
show
Bug introduced by
The function redirect was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

343
            return /** @scrutinizer ignore-call */ redirect(route('admin.jobs.edit', $jobPoster->id));
Loading history...
Bug introduced by
The function route was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

343
            return redirect(/** @scrutinizer ignore-call */ route('admin.jobs.edit', $jobPoster->id));
Loading history...
344
        }
345
346
        $this->fillAndSaveJobPosterQuestions($input, $jobPoster, true);
347
348
        return redirect(route('manager.jobs.show', $jobPoster->id));
349
    }
350
351
    /**
352
     * Fill Job Poster's questions and save
353
     *
354
     * @param  mixed[]               $input     Field values.
355
     * @param  \App\Models\JobPoster $jobPoster Job Poster object.
356
     * @param  boolean               $replace   Remove existing relationships.
357
     * @return void
358
     */
359
    protected function fillAndSaveJobPosterQuestions(array $input, JobPoster $jobPoster, bool $replace) : void
360
    {
361
        if ($replace) {
362
            $jobPoster->job_poster_questions()->delete();
363
        }
364
365
        if (!array_key_exists('question', $input) || !is_array($input['question'])) {
366
            return;
367
        }
368
369
        foreach ($input['question'] as $question) {
370
            $jobQuestion = new JobPosterQuestion();
371
            $jobQuestion->job_poster_id = $jobPoster->id;
372
            $jobQuestion->fill(
373
                [
374
                    'question' => [
375
                        'en' => $question['question']['en'],
376
                        'fr' => $question['question']['fr']
377
378
                    ],
379
                    'description' => [
380
                        'en' => $question['description']['en'],
381
                        'fr' => $question['description']['fr']
382
                    ]
383
                ]
384
            );
385
            $jobPoster->save();
386
            $jobQuestion->save();
387
        }
388
    }
389
390
    /**
391
     * Get the localized default questions and add them to an array.
392
     *
393
     * @return mixed[]|void
394
     */
395
    protected function populateDefaultQuestions()
396
    {
397
        $defaultQuestions = [
398
            'en' => array_values(Lang::get('manager/job_create', [], 'en')['questions']),
399
            'fr' => array_values(Lang::get('manager/job_create', [], 'fr')['questions']),
400
        ];
401
402
        if (count($defaultQuestions['en']) !== count($defaultQuestions['fr'])) {
403
            Log::warning('There must be the same number of French and English default questions for a Job Poster.');
404
            return;
405
        }
406
407
        $jobQuestions = [];
408
409
        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...
410
            $jobQuestion = new JobPosterQuestion();
411
            $jobQuestion->fill(
412
                [
413
                    'question' => [
414
                        'en' => $defaultQuestions['en'][$i],
415
                        'fr' => $defaultQuestions['fr'][$i],
416
                    ]
417
                ]
418
            );
419
            $jobQuestions[] = $jobQuestion;
420
        }
421
422
        return $jobQuestions;
423
    }
424
425
    /**
426
     * Downloads a CSV file with the applicants who have applied to the job poster.
427
     *
428
     * @param  \App\Models\JobPoster $jobPoster Job Poster object.
429
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
430
     */
431
    protected function downloadApplicants(JobPoster $jobPoster)
432
    {
433
        $tables = [];
434
        // The first row in the array represents the names of the columns in the spreadsheet.
435
        $tables[0] = ['Status', 'Applicant Name', 'Email', 'Language'];
436
437
        $application_status_id = ApplicationStatus::where('name', 'submitted')->first()->id;
438
        $applications = JobApplication::where('job_poster_id', $jobPoster->id)
439
            ->where('application_status_id', $application_status_id)
440
            ->get();
441
442
        $index = 1;
443
        foreach ($applications as $application) {
444
            $status = '';
445
            $username = $application->user_name;
446
            $user_email = $application->user_email;
447
            $language = strtoupper($application->preferred_language->name);
448
            // If the applicants veteran status name is NOT 'none' then set status to veteran.
449
            $non_veteran = VeteranStatus::where('name', 'none')->first()->id;
450
            if ($application->veteran_status_id != $non_veteran) {
451
                $status = 'Veteran';
452
            } else {
453
                // Check if the applicant is a canadian citizen.
454
                $canadian_citizen = CitizenshipDeclaration::where('name', 'citizen')->first()->id;
455
                if ($application->citizenship_declaration->id == $canadian_citizen) {
456
                    $status = 'Citizen';
457
                } else {
458
                    $status = 'Non-citizen';
459
                }
460
            }
461
            $tables[$index] = [$status, $username, $user_email, $language];
462
            $index++;
463
        }
464
465
        $filename = $jobPoster->id . '-' . 'applicants-data.csv';
466
467
        // Open file.
468
        $file = fopen($filename, 'w');
469
        // Iterate through tables and add each line to csv file.
470
        foreach ($tables as $line) {
471
            fputcsv($file, $line);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fputcsv() does only seem to accept resource, 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

471
            fputcsv(/** @scrutinizer ignore-type */ $file, $line);
Loading history...
472
        }
473
        // Close open file.
474
        fclose($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

474
        fclose(/** @scrutinizer ignore-type */ $file);
Loading history...
475
476
        $headers = [
477
            'Content-Type' => 'text/csv',
478
            'Content-Disposition' => 'attachment; filename=' . $filename,
479
        ];
480
481
        return Response::download($filename, $filename, $headers);
482
    }
483
}
484