Completed
Branch master (8e0976)
by Adam
04:13
created

SubmitController::getPreview()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 12
nop 1
dl 0
loc 35
rs 8.7377
c 0
b 0
f 0
1
<?php
2
3
namespace Coyote\Http\Controllers\Job;
4
5
use Coyote\Events\JobWasSaved;
6
use Coyote\Firm;
7
use Coyote\Firm\Benefit;
8
use Coyote\Http\Forms\Job\FirmForm;
9
use Coyote\Http\Forms\Job\JobForm;
10
use Coyote\Http\Resources\Firm as FirmResource;
11
use Coyote\Job;
12
use Coyote\Http\Controllers\Controller;
13
use Coyote\Notifications\JobCreatedNotification;
14
use Coyote\Repositories\Contracts\FirmRepositoryInterface as FirmRepository;
15
use Coyote\Repositories\Contracts\JobRepositoryInterface as JobRepository;
16
use Coyote\Repositories\Contracts\PlanRepositoryInterface as PlanRepository;
17
use Coyote\Repositories\Criteria\EagerLoading;
18
use Coyote\Services\Job\Draft;
19
use Coyote\Services\Job\Loader;
20
use Coyote\Services\UrlBuilder\UrlBuilder;
21
use Illuminate\Http\Request;
22
use Coyote\Services\Stream\Objects\Job as Stream_Job;
23
use Coyote\Services\Stream\Activities\Create as Stream_Create;
24
use Coyote\Services\Stream\Activities\Update as Stream_Update;
25
26
class SubmitController extends Controller
27
{
28
    /**
29
     * @var JobRepository
30
     */
31
    private $job;
32
33
    /**
34
     * @var FirmRepository
35
     */
36
    private $firm;
37
38
    /**
39
     * @var PlanRepository
40
     */
41
    private $plan;
42
43
    /**
44
     * @param JobRepository $job
45
     * @param FirmRepository $firm
46
     * @param PlanRepository $plan
47
     */
48
    public function __construct(JobRepository $job, FirmRepository $firm, PlanRepository $plan)
49
    {
50
        parent::__construct();
51
52
        $this->middleware('job.forget');
53
        $this->middleware('job.session', ['except' => ['getIndex']]);
54
55
        $this->breadcrumb->push('Praca', route('job.home'));
56
57
        $this->job = $job;
58
        $this->firm = $firm;
59
        $this->plan = $plan;
60
    }
61
62
    /**
63
     * @param Draft $draft
64
     * @param Loader $loader
65
     * @param int $id
66
     * @return \Illuminate\View\View
67
     */
68
    public function getIndex(Draft $draft, Loader $loader, $id = null)
69
    {
70
        /** @var \Coyote\Job $job */
71
        if ($id === null && $draft->has(Job::class)) {
72
            // get form content from session
73
            $job = $draft->get(Job::class);
74
        } else {
75
            $job = $this->job->findOrNew($id);
76
            abort_if($job->exists && $job->is_expired, 404);
77
78
            $job = $loader->init($job);
79
        }
80
81
        $this->authorize('update', $job);
82
        $this->authorize('update', $job->firm);
83
84
        $form = $this->createForm(JobForm::class, $job);
85
        $draft->put(Job::class, $job);
86
87
        $this->breadcrumb($job);
88
89
        return $this->view('job.submit.home', [
90
            'popular_tags'      => $this->job->getPopularTags(),
91
            'form'              => $form,
92
            'form_errors'       => $form->errors() ? $form->errors()->toJson() : '[]',
93
            'job'               => $form->toJson(),
94
            // firm information (in order to show firm nam on the button)
95
            'firm'              => $job->firm,
96
            // is plan is still going on?
97
            'is_plan_ongoing'   => $job->is_publish,
98
            'plans'             => $this->plan->active()->toJson()
0 ignored issues
show
Bug introduced by
The method toJson cannot be called on $this->plan->active() (of type array<integer,object<Coyote\Plan>>).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
99
        ]);
100
    }
101
102
    /**
103
     * @param Request $request
104
     * @param Draft $draft
105
     * @return \Illuminate\Http\RedirectResponse
106
     */
107
    public function postIndex(Request $request, Draft $draft)
108
    {
109
        /** @var \Coyote\Job $job */
110
        $job = clone $draft->get(Job::class);
111
112
        $form = $this->createForm(JobForm::class, $job);
113
        $form->validate();
114
115
        // only fillable columns! we don't want to set fields like "city" or "tags" because they don't really exists in db.
116
        $job->fill($form->all());
117
118
        $draft->put(Job::class, $job);
119
120
        return $this->next($request, $draft, redirect()->route('job.submit.firm'));
121
    }
122
123
    /**
124
     * @param Draft $draft
125
     * @return \Illuminate\View\View
126
     */
127
    public function getFirm(Draft $draft)
128
    {
129
        /** @var \Coyote\Job $job */
130
        $job = clone $draft->get(Job::class);
131
132
        // get all firms assigned to user...
133
        $this->firm->pushCriteria(new EagerLoading(['benefits', 'industries', 'gallery']));
134
135
        $firms = json_encode(FirmResource::collection($this->firm->findAllBy('user_id', $job->user_id))->toArray($this->request));
136
137
        $this->breadcrumb($job);
138
139
        $form = $this->createForm(FirmForm::class, $job->firm);
140
141
        return $this->view('job.submit.firm')->with([
142
            'job'               => $job,
143
            'firm'              => $form->toJson(),
144
            'firms'             => $firms,
145
            'form'              => $form,
146
            'form_errors'       => $form->errors() ? $form->errors()->toJson() : '[]',
147
            'benefits'          => $form->get('benefits')->getChildrenValues(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Coyote\Services\FormBuilder\Fields\Field as the method getChildrenValues() does only exist in the following sub-classes of Coyote\Services\FormBuilder\Fields\Field: Coyote\Services\FormBuilder\Fields\Choice, Coyote\Services\FormBuilder\Fields\Collection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
148
            'default_benefits'  => Benefit::getBenefitsList(), // default benefits,
149
        ]);
150
    }
151
152
    /**
153
     * @param Request $request
154
     * @param Draft $draft
155
     * @return \Illuminate\Http\RedirectResponse
156
     */
157
    public function postFirm(Request $request, Draft $draft)
158
    {
159
        /** @var \Coyote\Job $job */
160
        $job = $draft->get(Job::class);
161
162
        $form = $this->createForm(FirmForm::class, $job->firm);
163
        $form->validate();
164
165
        if ($job->firm->exists) {
166
            // syncOriginalAttribute() is important if user changes firm
167
            $job->firm->syncOriginalAttribute('id');
168
        }
169
170
        $draft->put(Job::class, $job);
171
172
        return $this->next($request, $draft, redirect()->route('job.submit.preview'));
173
    }
174
175
    /**
176
     * @param Draft $draft
177
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
178
     */
179
    public function getPreview(Draft $draft)
180
    {
181
        /** @var \Coyote\Job $job */
182
        $job = clone $draft->get(Job::class);
183
184
        $this->breadcrumb($job);
185
186
        $tags = $job->tags()->orderBy('priority', 'DESC')->with('category')->get()->groupCategory();
187
188
        $parser = app('parser.job');
189
190
        foreach (['description', 'requirements', 'recruitment'] as $name) {
191
            if (!empty($job[$name])) {
192
                $job[$name] = $parser->parse($job[$name]);
193
            }
194
        }
195
196
        if ($job->firm->is_private) {
197
            $job->firm()->dissociate();
198
        }
199
200
        if (!empty($job->firm->description)) {
201
            $job->firm->description = $parser->parse($job->firm->description);
202
        }
203
204
        return $this->view('job.submit.preview', [
205
            'job'               => $job,
206
            'firm'              => $job->firm ? $job->firm->toJson() : '{}',
207
            'tags'              => $tags,
208
            'rates_list'        => Job::getRatesList(),
209
            'seniority_list'    => Job::getSeniorityList(),
210
            'employment_list'   => Job::getEmploymentList(),
211
            'employees_list'    => Firm::getEmployeesList(),
212
        ]);
213
    }
214
215
    /**
216
     * @param Draft $draft
217
     * @return \Illuminate\Http\RedirectResponse
218
     * @throws \Illuminate\Auth\Access\AuthorizationException
219
     */
220
    public function save(Draft $draft)
221
    {
222
        /** @var \Coyote\Job $job */
223
        $job = clone $draft->get(Job::class);
224
225
        $this->authorize('update', $job);
226
227
        $tags = [];
228
        if (count($job->tags)) {
229
            $order = 0;
230
231
            foreach ($job->tags as $tag) {
232
                $model = $tag->firstOrCreate(['name' => $tag->name]);
0 ignored issues
show
Documentation Bug introduced by
The method firstOrCreate does not exist on object<Coyote\Tag>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
233
234
                $tags[$model->id] = [
235
                    'priority'  => $tag->pivot->priority ?? 0,
0 ignored issues
show
Documentation introduced by
The property pivot does not exist on object<Coyote\Tag>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
236
                    'order'     => ++$order
237
                ];
238
            }
239
        }
240
241
        $features = [];
242
        foreach ($job->features as $feature) {
243
            $features[$feature->id] = $feature->pivot->toArray();
244
        }
245
246
        $this->transaction(function () use (&$job, $draft, $tags, $features) {
247
            $activity = $job->id ? Stream_Update::class : Stream_Create::class;
248
249
            if ($job->firm->is_private) {
250
                $job->firm()->dissociate();
251
            // firm name is required to save firm
252
            } elseif ($job->firm->name) {
253
                // user might click on "add new firm" button in form. make sure user_id is set up.
254
                $job->firm->setDefaultUserId($this->userId);
255
256
                $this->authorize('update', $job->firm);
257
258
                // fist, we need to save firm because firm might not exist.
259
                $job->firm->save();
260
261
                // reassociate job with firm. user could change firm, that's why we have to do it again.
262
                $job->firm()->associate($job->firm);
263
                // remove old benefits and save new ones.
264
                $job->firm->benefits()->push($job->firm->benefits);
265
                // sync industries
266
                $job->firm->industries()->sync($job->firm->industries);
267
                $job->firm->gallery()->push($job->firm->gallery);
268
            }
269
270
            $job->save();
271
            $job->locations()->push($job->locations);
272
273
            $job->tags()->sync($tags);
274
            $job->features()->sync($features);
275
276
            if ($job->wasRecentlyCreated || !$job->is_publish) {
277
                $job->payments()->create(['plan_id' => $job->plan_id, 'days' => $job->plan->length]);
278
            }
279
280
            stream($activity, (new Stream_Job)->map($job));
281
            $draft->forget();
282
283
            event(new JobWasSaved($job)); // we don't queue listeners for this event
284
285
            return $job;
286
        });
287
288
        if ($job->wasRecentlyCreated) {
289
            $job->user->notify(new JobCreatedNotification($job));
290
        }
291
292
        $paymentUuid = $job->getPaymentUuid();
293
        if ($paymentUuid !== null) {
294
            return redirect()
295
                ->route('job.payment', [$paymentUuid])
296
                ->with('success', 'Oferta została dodana, lecz nie jest jeszcze promowana. Uzupełnij poniższy formularz, aby zakończyć.');
297
        }
298
299
        return redirect()->to(UrlBuilder::job($job))->with('success', 'Oferta została prawidłowo dodana.');
300
    }
301
302
    /**
303
     * @param $job
304
     */
305
    private function breadcrumb($job)
306
    {
307
        if (empty($job['id'])) {
308
            $this->breadcrumb->push('Wystaw ofertę pracy', route('job.submit'));
309
        } else {
310
            $this->breadcrumb->push($job['title'], route('job.offer', [$job['id'], $job['slug']]));
311
            $this->breadcrumb->push('Edycja oferty', route('job.submit'));
312
        }
313
    }
314
315
    /**
316
     * @param Request $request
317
     * @param Draft $draft
318
     * @param \Illuminate\Http\RedirectResponse $next
319
     * @return \Illuminate\Http\RedirectResponse
320
     */
321
    private function next(Request $request, Draft $draft, $next)
322
    {
323
        if ($request->get('done')) {
324
            return $this->save($draft);
325
        }
326
327
        return $next;
328
    }
329
}
330