Passed
Push — master ( 44072b...31cd8d )
by Matt
06:19
created

LeverService::getClient()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 6
nop 0
dl 0
loc 25
rs 9.8333
c 0
b 0
f 0
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 yii\web\UploadedFile;
21
use GuzzleHttp\Client;
22
use yii\base\Exception;
23
24
/**
25
 * @author    Working Concept
26
 * @package   Lever
27
 * @since     1.0.0
28
 */
29
class LeverService extends Component
30
{
31
    // Constants
32
    // =========================================================================
33
34
    /**
35
     * @event ApplyEvent Triggered before an application is validated.
36
     */
37
    const EVENT_BEFORE_VALIDATE_APPLICATION = 'beforeValidateApplication';
38
39
    /**
40
     * @event ApplyEvent Triggered before an application is sent to Lever.
41
     */
42
    const EVENT_BEFORE_SEND_APPLICATION = 'beforeSendApplication';
43
44
    /**
45
     * @event ApplyEvent Triggered after an application is sent to Lever.
46
     */
47
    const EVENT_AFTER_SEND_APPLICATION = 'afterSendApplication';
48
49
50
    // Public Properties
51
    // =========================================================================
52
53
    /**
54
     * @var \workingconcept\lever\models\Settings
55
     */
56
    public $settings;
57
58
    /**
59
     * @var array Populated with error message string(s) if submission fails.
60
     */
61
    public $errors = [];
62
63
    /**
64
     * @var string
65
     */
66
    protected static $apiBaseUrl = 'https://api.lever.co/v0/';
67
68
    /**
69
     * @var boolean
70
     */
71
    protected $isConfigured;
72
73
74
    // Private Properties
75
    // =========================================================================
76
77
    /**
78
     * @var \GuzzleHttp\Client
79
     */
80
    private $_client;
81
82
83
    // Public Methods
84
    // =========================================================================
85
86
    /**
87
     * Initializes the service.
88
     *
89
     * @return void
90
     */
91
    public function init()
92
    {
93
        parent::init();
94
95
        // populate the settings
96
        $this->settings = Lever::$plugin->getSettings();
97
    }
98
99
    /**
100
     * Returns a configured Guzzle client.
101
     *
102
     * @return Client
103
     * @throws \Exception if our API key is missing.
104
     */
105
    public function getClient(): Client
106
    {
107
        // see if we've got the stuff to do the things
108
        $this->isConfigured = ! empty($this->settings->apiKey) &&
109
            ! empty($this->settings->site);
110
111
        if ( ! $this->isConfigured)
112
        {
113
            throw new Exception('Lever plugin not configured.');
114
        }
115
116
        if ($this->_client === null)
117
        {
118
            $this->_client = new Client([
119
                'base_uri' => self::$apiBaseUrl,
120
                'headers' => [
121
                    'Content-Type' => 'application/json; charset=utf-8',
122
                    'Accept'       => 'application/json'
123
                ],
124
                'verify' => false,
125
                'debug' => false
126
            ]);
127
        }
128
129
        return $this->_client;
130
    }
131
132
    /**
133
     * Gets a list of job postings.
134
     * https://github.com/lever/postings-api/blob/master/README.md#get-a-list-of-job-postings
135
     *
136
     * @param array $params Key/value array of valid API query parameters.
137
     *                      [
138
     *                          'mode' => '',
139
     *                          'skip' => '',
140
     *                          'limit' => '',
141
     *                          'location' => '',
142
     *                          'commitment' => '',
143
     *                          'team' => '',
144
     *                          'department' => '',
145
     *                          'level' => '',
146
     *                          'group' => ''
147
     *                      ]
148
     *
149
     * @return array
150
     * @throws \Exception if our API key is missing.
151
     */
152
    public function getJobs($params = []): array
153
    {
154
        // TODO: collect paginated results
155
156
        $requestUrl = sprintf('postings/%s', $this->settings->site);
157
158
        $supportedParams = [
159
            'mode',
160
            'skip',
161
            'limit',
162
            'location',
163
            'commitment',
164
            'team',
165
            'department',
166
            'level',
167
            'group'
168
        ];
169
170
        if ( ! empty($params))
171
        {
172
            $includedParams = [];
173
174
            foreach ($params as $key => $value)
175
            {
176
                if (in_array($key, $supportedParams))
177
                {
178
                    $includedParams[$key] = $value;
179
                }
180
            }
181
182
            if (count($includedParams))
183
            {
184
                $queryString = http_build_query($includedParams);
185
                $requestUrl .= '?' . $queryString;
186
            }
187
        }
188
189
        $response = $this->getClient()->get($requestUrl);
190
        $responseData = json_decode($response->getBody());
191
192
        $jobs = [];
193
194
        foreach ($responseData as $jobData)
195
        {
196
            $jobs[] = new LeverJob($jobData);
197
        }
198
199
        return $jobs;
200
    }
201
202
    /**
203
     * Gets a specific job posting.
204
     * https://github.com/lever/postings-api/blob/master/README.md#get-a-specific-job-posting
205
     *
206
     * @param  string  $jobId  Lever job identifier
207
     * 
208
     * @return mixed
209
     * @throws \Exception if our API key is missing.
210
     */
211
    public function getJobById($jobId)
212
    {
213
        try 
214
        {
215
            $response = $this->getClient()->get(sprintf(
216
                'postings/%s/%s',
217
                $this->settings->site,
218
                $jobId
219
            ));
220
221
            $responseData = json_decode($response->getBody());
222
223
            return new LeverJob($responseData);
224
        }
225
        catch(\GuzzleHttp\Exception\RequestException $e) 
226
        {
227
            if ($e->getCode() === 404)
228
            {
229
                // retired and invalid job postings should 404 peacefully
230
                return false;
231
            }
232
233
            throw $e;
234
        }
235
    }
236
237
    /**
238
     * Sends job posting to Lever.
239
     * https://github.com/lever/postings-api/blob/master/README.md#apply-to-a-job-posting
240
     *
241
     * @param int  $jobPostId  Lever job identifier
242
     * @param bool $test       Whether or not we want to post to our own controller here for testing
243
     * 
244
     * @return boolean
245
     * @throws
246
     */
247
    public function applyForJob($jobPostId, $test = false)
248
    {
249
        $request = Craft::$app->getRequest();
250
        $postUrl = sprintf('postings/%s/%s?key=%s',
251
            $this->settings->site,
252
            $jobPostId,
253
            $this->settings->apiKey
254
        );
255
256
        if ($test)
257
        {
258
            // reconfigure client for testing
259
            $this->_client = new Client([
260
                'base_uri' => UrlHelper::baseUrl(),
261
                'headers' => [
262
                    'Content-Type' => 'application/json; charset=utf-8',
263
                    'Accept'       => 'application/json'
264
                ],
265
                'verify' => false,
266
                'debug' => false
267
            ]);
268
269
            // https://site.local/actions/lever/apply/test
270
            $postUrl = UrlHelper::actionUrl('lever/apply/test');
271
        }
272
273
        $resumeIncluded = ! empty($_FILES['resume']['tmp_name'])
274
            && ! empty($_FILES['resume']['name']);
275
276
        if ( ! $application = new LeverJobApplication([
277
            'name'     => $request->getParam('name'),
278
            'email'    => $request->getParam('email'),
279
            'phone'    => $request->getParam('phone'),
280
            'org'      => $request->getParam('org'),
281
            'urls'     => $request->getParam('urls'),
282
            'comments' => $request->getParam('comments'),
283
            'ip'       => $request->getUserIP(),
284
            'silent'   => $this->settings->applySilently,
285
            'source'   => $this->settings->applicationSource,
286
        ]))
287
        {
288
            array_merge($this->errors, $application->getErrors());
289
            return false;
290
        }
291
292
        if ($resumeIncluded)
293
        {
294
            $application->resume = UploadedFile::getInstanceByName('resume');
295
        }
296
297
        $event = new ApplyEvent([ 'application' => $application ]);
298
299
        if ($this->hasEventHandlers(self::EVENT_BEFORE_VALIDATE_APPLICATION))
300
        {
301
            $this->trigger(self::EVENT_BEFORE_VALIDATE_APPLICATION, $event);
302
            $application = $event->application;
303
        }
304
305
        if ( ! $application->validate())
306
        {
307
            $this->errors = array_merge($this->errors, $application->getErrors());
308
            return false;
309
        }
310
311
        if ($this->hasEventHandlers(self::EVENT_BEFORE_SEND_APPLICATION))
312
        {
313
            $this->trigger(self::EVENT_BEFORE_SEND_APPLICATION, $event);
314
        }
315
316
        if ($event->isSpam)
317
        {
318
            Craft::info('Spammy job application ignored.', 'lever');
319
320
            // pretend it's fine so they feel good about themselves
321
            return true;
322
        }
323
324
        if ($response = $this->getClient()->post(
325
            $postUrl,
326
            [ 'multipart' => $application->toMultiPartPostData() ]
327
        ))
328
        {
329
            $responseIsHealthy = $response->getStatusCode() === 200 &&
330
                isset($response->getBody()->applicationId);
0 ignored issues
show
Bug introduced by
Accessing applicationId on the interface Psr\Http\Message\StreamInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
331
332
            if ($responseIsHealthy)
333
            {
334
                if ($this->hasEventHandlers(self::EVENT_AFTER_SEND_APPLICATION))
335
                {
336
                    $this->trigger(self::EVENT_AFTER_SEND_APPLICATION, $event);
337
                }
338
339
                return true;
340
            }
341
342
            $this->errors[] = Craft::t('lever', 'Your application could not be submitted.');
343
            return false;
344
        }
345
346
        $this->errors[] = Craft::t('lever', 'There was a problem submitting your application.');
347
        return false;
348
    }
349
}