Passed
Push — master ( 31cd8d...f5f2bf )
by Matt
05:47
created

LeverService   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 290
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 97
dl 0
loc 290
rs 10
c 0
b 0
f 0
wmc 24

5 Methods

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