Completed
Push — develop ( b559a4...dfd31d )
by
unknown
10:04
created

ApplyController::indexAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
dl 0
loc 18
rs 9.4285
c 2
b 1
f 0
cc 1
eloc 11
nc 1
nop 0
1
<?php
2
/**
3
 * YAWIK
4
 *
5
 * @filesource
6
 * @copyright (c) 2013 - 2016 Cross Solution (http://cross-solution.de)
7
 * @license   MIT
8
 */
9
10
/** Applications controllers */
11
namespace Applications\Controller;
12
13
use Applications\Entity\Contact;
14
use Applications\Listener\Events\ApplicationEvent;
15
use Zend\Mvc\Controller\AbstractActionController;
16
use Zend\Mvc\MvcEvent;
17
use Applications\Entity\Application;
18
use Zend\View\Model\ViewModel;
19
use Zend\View\Model\JsonModel;
20
use Core\Form\Container;
21
use Core\Form\SummaryForm;
22
use Core\Entity\PermissionsInterface;
23
use Applications\Entity\Status;
24
25
/**
26
 * there are basically two ways to use this controller,
27
 * (1) either you have a form, and want to accumulate inputs, or you want to create a form on an existing or new application
28
 * (2) or want to do some action on a concrete form.
29
 *
30
 * for both you need the applyId, which is NOT the application-id, the applyId is an alias for the Job, it can be some human-readable text or an external ID.
31
 * the application to an applyId is found by the combination of the user and the job, that is represented by the applyId.
32
 * this approach ensures, that applications stick to the related job.
33
 *
34
 * nonetheless, there is an exception, for the posts for updating the application, the application-id is needed.
35
 *
36
 * if you use the do as query-parameter, you have to customize the do-Action for the special purpose that is assigned to the do parameter in the query
37
 *
38
 * @method \Acl\Controller\Plugin\Acl acl()
39
 * @method \Core\Controller\Plugin\Notification notification()
40
 * @method \Core\Controller\Plugin\Mailer mailer()
41
 * @method \Auth\Controller\Plugin\Auth auth()
42
 * @author Mathias Gelhausen <[email protected]>
43
 */
44
class ApplyController extends AbstractActionController
45
{
46
    
47
    protected $container;
48
    
49
    public function attachDefaultListeners()
50
    {
51
        parent::attachDefaultListeners();
52
        $events = $this->getEventManager();
53
        $events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'preDispatch'), 10);
54
        $serviceLocator  = $this->getServiceLocator();
55
        $defaultServices = $serviceLocator->get('DefaultListeners');
56
        $events->attach($defaultServices);
0 ignored issues
show
Documentation introduced by
$defaultServices is of type object|array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
57
        return $this;
58
    }
59
    
60
    public function preDispatch(MvcEvent $e)
61
    {
62
        /* @var $application \Applications\Entity\Application */
63
        if ($this->params()->fromQuery('do')) {
64
            $e->getRouteMatch()->setParam('action', 'do');
65
            return;
66
        }
67
68
        /* @var $request    \Zend\Http\Request */
69
        /* @var $repository \Applications\Repository\Application */
70
        /* @var $container  \Applications\Form\Apply */
71
        $request      = $this->getRequest();
72
        $services     = $this->getServiceLocator();
73
        $repositories = $services->get('repositories');
74
        $repository   = $repositories->get('Applications/Application');
75
        $container    = $services->get('forms')->get('Applications/Apply');
76
        
77
        if ($request->isPost()) {
78
            $appId = $this->params()->fromPost('applicationId');
79
            if (!$appId) {
80
                throw new \RuntimeException('Missing application id.');
81
            }
82
            $routeMatch = $e->getRouteMatch();
83
84
            if ('recruiter-preview' == $appId) {
85
                $routeMatch->setParam('action', 'process-preview');
86
                return;
87
            }
88
89
            $application = $repository->find($appId);
90
            if (!$application) {
91
                throw new \RuntimeException('Invalid application id.');
92
            }
93
94
            $action     = 'process';
95
96
            $routeMatch->setParam('action', $action);
97
        } else {
98
            $user  = $this->auth()->getUser();
99
            $appId = $this->params('applyId');
100
            if (!$appId) {
101
                throw new \RuntimeException('Missing apply id');
102
            }
103
104
            /* @var \Jobs\Entity\Job $job */
105
            $job = $repositories->get('Jobs/Job')->findOneByApplyId($appId);
106
107
            if (!$job) {
108
                $e->getRouteMatch()->setParam('action', 'job-not-found');
109
                return;
110
            }
111
112
            switch ($job->getStatus()) {
113
                case \Jobs\Entity\Status::ACTIVE:
114
                    break;
115
                default:
116
                    $e->getRouteMatch()->setParam('action', 'job-not-found');
117
                    return;
118
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
119
            }
120
121
            if ($user === $job->getUser()) {
122
                $application = new \Applications\Entity\Application();
123
                $application->setContact(new Contact());
124
                $application->setJob($job);
125
                $application->setId('recruiter-preview');
126
            } else {
127
                $subscriberUri = $this->params()->fromQuery('subscriber');
128
                $application   = $repository->findDraft($user, $appId);
129
130
                if ($application) {
131
                    /* @var $form \Auth\Form\UserInfo */
132
                    $form = $container->getForm('contact.contact');
133
                    $form->setDisplayMode('summary');
134
135
                    if ($subscriberUri) {
136
                        $subscriber = $application->getSubscriber();
137
                        if (!$subscriber || $subscriber->uri != $subscriberUri) {
138
                            $subscriber = $repositories->get('Applications/Subscriber')->findbyUri($subscriberUri, /*create*/ true);
139
                            $application->setSubscriber($subscriber);
140
                            $subscriber->getname();
141
                        }
142
                    }
143
                } else {
144
                    if (!$job) {
145
                        $e->getRouteMatch()->setParam('action', 'job-not-found');
146
                        return;
147
                    }
148
                    if ($job->getUriApply()) {
149
                        return $this->redirect($job->getUriApply());
0 ignored issues
show
Unused Code introduced by
The call to ApplyController::redirect() has too many arguments starting with $job->getUriApply().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
150
                    }
151
152
                    /* @var $application \Applications\Entity\Application */
153
                    $application = $repository->create();
154
                    $application->setIsDraft(true)
155
                                ->setContact($user->getInfo())
156
                                ->setUser($user)
157
                                ->setJob($job);
158
159
                    if ($subscriberUri) {
160
                        $subscriber = $repositories->get('Applications/Subscriber')->findbyUri($subscriberUri, /*create*/ true);
161
                        $application->setSubscriber($subscriber);
162
                    }
163
164
                    $repositories->store($application);
165
                    /*
166
                     * If we had copy an user image, we need to refresh its data
167
                     * to populate the length property.
168
                     */
169
                    if ($image = $application->getContact()->getImage()) {
170
                        $repositories->refresh($image);
171
                    }
172
                }
173
            }
174
        }
175
        
176
        $container->setEntity($application);
177
        $this->configureContainer($container);
178
        $this->container = $container;
179
    }
180
    
181
    public function jobNotFoundAction()
182
    {
183
        $this->response->setStatusCode(410);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\Stdlib\ResponseInterface as the method setStatusCode() does only exist in the following implementations of said interface: Zend\Http\PhpEnvironment\Response, Zend\Http\Response, Zend\Http\Response\Stream.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
184
        $model = new ViewModel(
185
            [ 'content' => /*@translate*/ 'Invalid apply id']
186
        );
187
        $model->setTemplate('applications/error/not-found');
188
        return $model;
189
    }
190
    
191
    public function indexAction()
192
    {
193
        /* @var \Applications\Form\Apply $form */
194
        $form        = $this->container;
195
        $application = $form->getEntity();
196
        
197
        $form->setParam('applicationId', $application->id);
0 ignored issues
show
Bug introduced by
Accessing id on the interface Core\Entity\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
198
199
        $model = new ViewModel(
200
            array(
201
            'form' => $form,
202
            'isApplicationValid' => $this->checkApplication($application),
203
            'application' => $application,
204
            )
205
        );
206
        $model->setTemplate('applications/apply/index');
207
        return $model;
208
    }
209
    
210
    public function oneClickApplyAction()
211
    {
212
        /* @var \Applications\Entity\Application $application */
213
        $application = $this->container->getEntity();
214
        $job = $application->getJob();
215
        $atsMode = $job->getAtsMode();
216
        
217
        // check for one click apply
218
        if (!($atsMode->isIntern() && $atsMode->getOneClickApply()))
219
        {
220
            // redirect to regular application
221
            return $this->redirect()
222
                ->toRoute('lang/apply', ['applyId' => $job->getApplyId()]);
223
        }
224
        
225
        $network = $this->params('network');
226
227
        $hybridAuth = $this->getServiceLocator()
228
            ->get('HybridAuthAdapter')
229
            ->getHybridAuth();
230
        /* @var $authProfile \Hybrid_User_Profile */
231
        $authProfile = $hybridAuth->authenticate($network)
232
           ->getUserProfile();
233
234
        /* @var \Auth\Entity\SocialProfiles\AbstractProfile $profile */
235
        $profile = $this->plugin('Auth/SocialProfiles')->fetch($network);
236
237
        $contact = $application->getContact();
238
        $contact->setEmail($authProfile->emailVerified ?: $authProfile->email);
239
        $contact->setFirstName($authProfile->firstName);
240
        $contact->setLastName($authProfile->lastName);
241
        $contact->setBirthDay($authProfile->birthDay);
242
        $contact->setBirthMonth($authProfile->birthMonth);
243
        $contact->setBirthYear($authProfile->birthYear);
244
        $contact->setPostalCode($authProfile->zip);
245
        $contact->setCity($authProfile->city);
246
        $contact->setStreet($authProfile->address);
247
        $contact->setPhone($authProfile->phone);
248
        $contact->setGender($authProfile->gender);
249
250
        $profiles = $application->getProfiles();
251
        $profiles->add($profile);
252
253
        $cv = $application->getCv();
254
        $cv->setEmployments($profile->getEmployments());
255
        $cv->setEducations($profile->getEducations());
256
257
        if ($authProfile->photoURL)
258
        {
259
            $response = (new \Zend\Http\Client($authProfile->photoURL, ['sslverifypeer' => false]))->send();
260
            $file = new \Doctrine\MongoDB\GridFSFile();
261
            $file->setBytes($response->getBody());
262
            
263
            $image = new \Applications\Entity\Attachment();
264
            $image->setName($contact->getLastName().$contact->getFirstName());
265
            $image->setType($response->getHeaders()->get('Content-Type')->getFieldValue());
0 ignored issues
show
Bug introduced by
The method getFieldValue does only exist in Zend\Http\Header\HeaderInterface, but not in ArrayIterator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
266
            $image->setFile($file);
267
            $image->setPermissions($application->getPermissions());
268
            
269
            $contact->setImage($image);
270
        }
271
        
272
        $urlOptions = [];
273
        
274
        if ($this->params('immediately'))
275
        {
276
            $application->getAttributes()->setAcceptedPrivacyPolicy(true);
277
            $urlOptions = [
278
                'query' => [
279
                    'do' => 'send'
280
                ]
281
            ];
282
        }
283
        
284
        return $this->redirect()
285
           ->toRoute('lang/apply', ['applyId' => $job->getApplyId()], $urlOptions);
286
    }
287
288
    public function processPreviewAction()
289
    {
290
        return new JsonModel(array('valid' => false, 'errors' => array()));
291
    }
292
    
293
    public function processAction()
0 ignored issues
show
Coding Style introduced by
processAction uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
processAction uses the super-global variable $_FILES which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
294
    {
295
        $formName  = $this->params()->fromQuery('form');
296
        $form      = $this->container->getForm($formName);
297
        $postData  = $form->getOption('use_post_array') ? $_POST : array();
298
        $filesData = $form->getOption('use_files_array') ? $_FILES : array();
299
        $data      = array_merge($postData, $filesData);
300
301
        $form->setData($data);
302
        
303 View Code Duplication
        if (!$form->isValid()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
304
            return new JsonModel(
305
                array(
306
                'valid' => false,
307
                'errors' => $form->getMessages(),
308
                )
309
            );
310
        }
311
        $application = $this->container->getEntity();
312
        $this->getServiceLocator()->get('repositories')->store($application);
313
        
314
        if ('file-uri' === $this->params()->fromPost('return')) {
315
            $basepath = $this->getServiceLocator()->get('ViewHelperManager')->get('basepath');
316
            $content = $basepath($form->getHydrator()->getLastUploadedFile()->getUri());
317
        } else {
318
            if ($form instanceof SummaryForm) {
319
                $form->setRenderMode(SummaryForm::RENDER_SUMMARY);
320
                $viewHelper = 'summaryform';
321
            } else {
322
                $viewHelper = 'form';
323
            }
324
            $content = $this->getServiceLocator()->get('ViewHelperManager')->get($viewHelper)->__invoke($form);
325
        }
326
        
327
        return new JsonModel(
328
            array(
329
            'valid' => $form->isValid(),
330
            'content' => $content,
331
            'isApplicationValid' => $this->checkApplication($application)
332
            )
333
        );
334
    }
335
    
336
    public function doAction()
337
    {
338
        $services     = $this->getServiceLocator();
339
        $config       = $services->get('Config');
340
        $repositories = $services->get('repositories');
341
        $repository   = $repositories->get('Applications/Application');
342
        /* @var Application $application*/
343
        $application  = $repository->findDraft(
344
            $this->auth()->getUser(),
345
            $this->params('applyId')
346
        );
347
        
348
        if (!$application) {
349
            throw new \Exception('No application draft found.');
350
        }
351
        
352
        if ('abort' == $this->params()->fromQuery('do')) {
353
            $repositories->remove($application);
354
            return $this->redirect()->toRoute('lang/apply', array('applyId' => $this->params('applyId')));
355
        }
356
        
357
        if (!$this->checkApplication($application)) {
358
            $this->notification()->error(/*@translate*/ 'There are missing required informations. Your application cannot be send.');
359
            return $this->redirect()->toRoute('lang/apply', array('applyId' => $this->params('applyId')));
360
        }
361
362
        if ('sendmail' == $this->params()->fromQuery('do')) {
363
            $jobEntity         = $application->getJob();
364
365
            $mailData = array(
366
                'application' => $application,
367
                'to'          => $jobEntity->getContactEmail()
368
            );
369
            if (array_key_exists('mails', $config) && array_key_exists('from', $config['mails']) && array_key_exists('email', $config['mails']['from'])) {
370
                $mailData['from'] = $config['mails']['from']['email'];
371
            }
372
            $this->mailer('Applications/CarbonCopy', $mailData, true);
0 ignored issues
show
Unused Code introduced by
The call to ApplyController::mailer() has too many arguments starting with 'Applications/CarbonCopy'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
373
            $repositories->remove($application);
374
            //$this->notification()->success(/*@translate*/ 'Application has been send.');
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
375
            $model = new ViewModel(
376
                array(
377
                'success' => true,
378
                'job' => $jobEntity,
379
                )
380
            );
381
            $model->setTemplate('applications/apply/success');
382
            return $model;
383
        }
384
385
        $application->setIsDraft(false)
386
            ->setStatus(new Status())
387
            ->getPermissions()
388
            ->revoke($this->auth()->getUser(), PermissionsInterface::PERMISSION_CHANGE)
389
            ->inherit($application->getJob()->getPermissions());
390
391
        $events   = $services->get('Applications/Events');
392
        $events->trigger(ApplicationEvent::EVENT_APPLICATION_POST_CREATE, $this, [ 'application' => $application ]);
393
394
        $model = new ViewModel(
395
            array(
396
            'success' => true,
397
            'application' => $application,
398
            )
399
        );
400
        $model->setTemplate('applications/apply/index');
401
402
        return $model;
403
    }
404
405
    protected function checkApplication($application)
406
    {
407
        return $this->getServiceLocator()->get('validatormanager')->get('Applications/Application')
408
                    ->isValid($application);
409
    }
410
411
    /**
412
     * Configures the apply form container.
413
     *
414
     * Currently only disables elements.
415
     *
416
     * @param Container $container
417
     */
418
    protected function configureContainer(Container $container)
419
    {
420
        /* @var $application Application */
421
        $application = $container->getEntity();
422
        $job         = $application->getJob();
423
424
        /** @var $settings \Applications\Entity\Settings */
425
        $settings = $job->getUser()->getSettings('Applications');
426
        $formSettings = $settings->getApplyFormSettings();
427
428
        if ($formSettings && $formSettings->isActive()) {
429
            $container->disableElements($formSettings->getDisableElements());
430
            return;
431
        }
432
433
        $config = $this->getServiceLocator()->get('Config');
434
        $config = isset($config['form_elements_config']['Applications/Apply']['disable_elements'])
435
                ? $config['form_elements_config']['Applications/Apply']['disable_elements']
436
                : null;
437
        if ($config) {
438
            $container->disableElements($config);
439
        }
440
    }
441
}
442