LeverService   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 98
c 4
b 0
f 0
dl 0
loc 282
rs 10
wmc 24

5 Methods

Rating   Name   Duplication   Size   Complexity  
A getJobById() 0 23 3
A init() 0 6 1
B applyForJob() 0 83 10
A getClient() 0 25 4
B getJobs() 0 48 6
1
<?php
2
/**
3
 * Lever plugin for Craft CMS 3.x
4
 *
5
 * Craft + Lever.
6
 *
7
 * @link      https://workingconcept.com
8
 * @copyright Copyright (c) 2018 Working Concept Inc.
9
 */
10
11
namespace workingconcept\lever\services;
12
13
use workingconcept\lever\Lever;
14
use workingconcept\lever\models\LeverJobApplication;
15
use workingconcept\lever\models\LeverJob;
16
use workingconcept\lever\events\ApplyEvent;
17
use Craft;
18
use craft\base\Component;
19
use craft\helpers\UrlHelper;
20
use GuzzleHttp\Client;
21
use yii\base\Exception;
22
23
/**
24
 * @author    Working Concept
25
 * @package   Lever
26
 * @since     1.0.0
27
 */
28
class LeverService extends Component
29
{
30
    /**
31
     * @event ApplyEvent Triggered before an application is validated.
32
     */
33
    const EVENT_BEFORE_VALIDATE_APPLICATION = 'beforeValidateApplication';
34
35
    /**
36
     * @event ApplyEvent Triggered before an application is sent to Lever.
37
     */
38
    const EVENT_BEFORE_SEND_APPLICATION = 'beforeSendApplication';
39
40
    /**
41
     * @event ApplyEvent Triggered after an application is sent to Lever.
42
     */
43
    const EVENT_AFTER_SEND_APPLICATION = 'afterSendApplication';
44
45
    /**
46
     * @var \workingconcept\lever\models\Settings
47
     */
48
    public $settings;
49
50
    /**
51
     * @var string
52
     */
53
    protected static $apiBaseUrl = 'https://api.lever.co/v0/';
54
55
    /**
56
     * @var boolean
57
     */
58
    protected $isConfigured;
59
60
    /**
61
     * @var \GuzzleHttp\Client
62
     */
63
    private $_client;
64
65
    /**
66
     * Initializes the service.
67
     *
68
     * @return void
69
     */
70
    public function init()
71
    {
72
        parent::init();
73
74
        // populate the settings
75
        $this->settings = Lever::$plugin->getSettings();
76
    }
77
78
    /**
79
     * Returns a configured Guzzle client.
80
     *
81
     * @return Client
82
     * @throws \Exception if our API key is missing.
83
     */
84
    public function getClient(): Client
85
    {
86
        // see if we've got the stuff to do the things
87
        $this->isConfigured = ! empty($this->settings->apiKey) &&
88
            ! empty($this->settings->site);
89
90
        if ( ! $this->isConfigured)
91
        {
92
            throw new Exception('Lever plugin not configured.');
93
        }
94
95
        if ($this->_client === null)
96
        {
97
            $this->_client = new Client([
98
                'base_uri' => self::$apiBaseUrl,
99
                'headers' => [
100
                    'Content-Type' => 'application/json; charset=utf-8',
101
                    'Accept'       => 'application/json'
102
                ],
103
                'verify' => false,
104
                'debug' => false
105
            ]);
106
        }
107
108
        return $this->_client;
109
    }
110
111
    /**
112
     * Gets a list of job postings.
113
     * https://github.com/lever/postings-api/blob/master/README.md#get-a-list-of-job-postings
114
     *
115
     * @param array $params Key/value array of valid API query parameters.
116
     *                      [
117
     *                          'mode' => '',
118
     *                          'skip' => '',
119
     *                          'limit' => '',
120
     *                          'location' => '',
121
     *                          'commitment' => '',
122
     *                          'team' => '',
123
     *                          'department' => '',
124
     *                          'level' => '',
125
     *                          'group' => ''
126
     *                      ]
127
     *
128
     * @return array
129
     * @throws \Exception if our API key is missing.
130
     */
131
    public function getJobs($params = []): array
132
    {
133
        // TODO: collect paginated results
134
135
        $requestUrl = sprintf('postings/%s', $this->settings->site);
136
137
        $supportedParams = [
138
            'mode',
139
            'skip',
140
            'limit',
141
            'location',
142
            'commitment',
143
            'team',
144
            'department',
145
            'level',
146
            'group'
147
        ];
148
149
        if ( ! empty($params))
150
        {
151
            $includedParams = [];
152
153
            foreach ($params as $key => $value)
154
            {
155
                if (in_array($key, $supportedParams))
156
                {
157
                    $includedParams[$key] = $value;
158
                }
159
            }
160
161
            if (count($includedParams))
162
            {
163
                $queryString = http_build_query($includedParams);
164
                $requestUrl .= '?' . $queryString;
165
            }
166
        }
167
168
        $response = $this->getClient()->get($requestUrl);
169
        $responseData = json_decode($response->getBody());
170
171
        $jobs = [];
172
173
        foreach ($responseData as $jobData)
174
        {
175
            $jobs[] = new LeverJob($jobData);
176
        }
177
178
        return $jobs;
179
    }
180
181
    /**
182
     * Gets a specific job posting.
183
     * https://github.com/lever/postings-api/blob/master/README.md#get-a-specific-job-posting
184
     *
185
     * @param  string  $jobId  Lever job identifier
186
     * 
187
     * @return mixed
188
     * @throws \Exception if our API key is missing.
189
     */
190
    public function getJobById($jobId)
191
    {
192
        try 
193
        {
194
            $response = $this->getClient()->get(sprintf(
195
                'postings/%s/%s',
196
                $this->settings->site,
197
                $jobId
198
            ));
199
200
            $responseData = json_decode($response->getBody());
201
202
            return new LeverJob($responseData);
203
        }
204
        catch(\GuzzleHttp\Exception\RequestException $e) 
205
        {
206
            if ($e->getCode() === 404)
207
            {
208
                // retired and invalid job postings should 404 peacefully
209
                return false;
210
            }
211
212
            throw $e;
213
        }
214
    }
215
216
    /**
217
     * Sends job posting to Lever.
218
     * https://github.com/lever/postings-api/blob/master/README.md#apply-to-a-job-posting
219
     *
220
     * @param int                  $jobPostId       Lever job identifier
221
     * @param LeverJobApplication  $jobApplication  Lever job identifier
222
     * @param bool                 $test            Whether or not we want to post to our own controller here for testing
223
     * 
224
     * @return boolean
225
     * @throws
226
     */
227
    public function applyForJob($jobPostId, $jobApplication, $test = false): bool
228
    {
229
        $postUrl = sprintf('postings/%s/%s?key=%s',
230
            $this->settings->site,
231
            $jobPostId,
232
            $this->settings->apiKey
233
        );
234
235
        if ($test)
236
        {
237
            // reconfigure client for testing
238
            $this->_client = new Client([
239
                'base_uri' => UrlHelper::baseUrl(),
240
                'headers' => [
241
                    'Content-Type' => 'application/json; charset=utf-8',
242
                    'Accept'       => 'application/json'
243
                ],
244
                'verify' => false,
245
                'debug' => false
246
            ]);
247
248
            // https://site.local/actions/lever/apply/test
249
            $postUrl = UrlHelper::actionUrl('lever/apply/test');
250
        }
251
252
        $event = new ApplyEvent([ 'application' => $jobApplication ]);
253
254
        if ($this->hasEventHandlers(self::EVENT_BEFORE_VALIDATE_APPLICATION))
255
        {
256
            $this->trigger(self::EVENT_BEFORE_VALIDATE_APPLICATION, $event);
257
            $jobApplication = $event->application;
258
        }
259
260
        if ( ! $jobApplication->validate())
261
        {
262
            Craft::info('Invalid job application.', 'lever');
263
            return false;
264
        }
265
266
        if ($this->hasEventHandlers(self::EVENT_BEFORE_SEND_APPLICATION))
267
        {
268
            $this->trigger(self::EVENT_BEFORE_SEND_APPLICATION, $event);
269
        }
270
271
        if ($event->isSpam)
272
        {
273
            Craft::info('Spammy job application ignored.', 'lever');
274
275
            // pretend it's fine so they feel good about themselves
276
            return true;
277
        }
278
279
        if ($response = $this->getClient()->post(
280
            $postUrl,
281
            [ 'multipart' => $jobApplication->toMultiPartPostData() ]
282
        ))
283
        {
284
            /**
285
             * Happy submission responses include an application ID.
286
             * 
287
             * Here, we make sure it's a 200 response *and* that Lever
288
             * was able to make a proper application out of it.
289
             */
290
            $responseData = json_decode($response->getBody());
291
            $responseIsHealthy = $response->getStatusCode() === 200 &&
292
                isset($responseData->applicationId);
293
294
            if ($responseIsHealthy)
295
            {
296
                if ($this->hasEventHandlers(self::EVENT_AFTER_SEND_APPLICATION))
297
                {
298
                    $this->trigger(self::EVENT_AFTER_SEND_APPLICATION, $event);
299
                }
300
301
                return true;
302
            }
303
304
            Craft::info('Application may not have been submitted.', 'lever');
305
            return false;
306
        }
307
308
        Craft::info('Application could not be sent.', 'lever');
309
        return false;
310
    }
311
}