Issues (12)

src/Slack.php (7 issues)

1
<?php 
2
namespace Sreedev\Slack;
3
4
use Exception;
5
6
class Slack
7
{
8
    private $api_endpoint = "https://slack.com/api";
9
    private $request_successful = false;
10
    private $last_error         = '';
11
    private $api_token = '';
12
    private $last_response      = array();
13
    private $last_request       = array();
14
    private $verify_ssl = false;
15
16
    const TIMEOUT = 10;
17
    
18
    /**
19
     * __construct
20
     *
21
     * @param  mixed $api_token
22
     * @return void
23
     */
24
    public function __construct($api_token)
25
    {
26
        $this->api_token = $api_token;
27
    }
28
29
    
30
    /**
31
     * Chat
32
     *
33
     * @return Chat class instance
0 ignored issues
show
The type Sreedev\Slack\Chat was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
34
     */
35
    public function Chat($method, $data)
36
    {
37
        return new \Sreedev\Slack\Api\Chat($this, $method, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Sreedev\Slack...($this, $method, $data) returns the type Sreedev\Slack\Api\Chat which is incompatible with the documented return type Sreedev\Slack\Chat.
Loading history...
38
    }
39
    
40
    /**
41
     * Calls
42
     *
43
     * @param  mixed $method
44
     * @param  mixed $data
45
     * @return void
46
     */
47
    public function Calls($method, $data)
48
    {
49
        return new \Sreedev\Slack\Api\Calls($this, $method, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Sreedev\Slack...($this, $method, $data) returns the type Sreedev\Slack\Api\Calls which is incompatible with the documented return type void.
Loading history...
50
    }
51
    
52
    /**
53
     * Conversations
54
     *
55
     * @param  mixed $method
56
     * @param  mixed $data
57
     * @return void
58
     */
59
    public function Conversations($method, $data)
60
    {
61
        return new \Sreedev\Slack\Api\Conversations($this, $method, $data);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Sreedev\Slack...($this, $method, $data) returns the type Sreedev\Slack\Api\Conversations which is incompatible with the documented return type void.
Loading history...
62
    }
63
   
64
65
     /**
66
     * Performs the underlying HTTP request. Not very exciting.
67
     *
68
     * @param  string $http_verb The HTTP verb to use: get, post, put, patch, delete
69
     * @param  string $method    The API method to be called
70
     * @param  array  $args      Assoc array of parameters to be passed
71
     * @param int     $timeout
72
     *
73
     * @return array|false Assoc array of decoded result
74
     */
75
    public function makeRequest($http_verb, $method, $args = array(), $timeout = self::TIMEOUT)
76
    {
77
        if( $this->api_token == '')
78
        {
79
            throw new Exception('\Exception');
80
        }
81
        $url = $this->api_endpoint . '/' . $method;
82
83
        $response = $this->prepareStateForRequest($http_verb, $method, $url, $timeout);
84
85
        $httpHeader = array(
86
            'Content-type: application/json',
87
            'Authorization: Bearer '. $this->api_token
88
        );
89
90
91
        if (isset($args["language"])) {
92
            $httpHeader[] = "Accept-Language: " . $args["language"];
93
        }
94
95
        if ($http_verb === 'put') {
96
            $httpHeader[] = 'Allow: PUT, PATCH, POST';
97
        }
98
99
        $ch = curl_init();
100
        curl_setopt($ch, CURLOPT_URL, $url);
0 ignored issues
show
It seems like $ch can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

100
        curl_setopt(/** @scrutinizer ignore-type */ $ch, CURLOPT_URL, $url);
Loading history...
101
        curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeader);
102
        curl_setopt($ch, CURLOPT_USERAGENT, 'Sreedev/laravel-slack (github.com/rsreedevan/laravel-slack)');
103
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
104
        curl_setopt($ch, CURLOPT_VERBOSE, true);
105
        curl_setopt($ch, CURLOPT_HEADER, true);
106
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
107
        //curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->verify_ssl);
108
        curl_setopt($ch, CURLOPT_ENCODING, '');
109
        curl_setopt($ch, CURLINFO_HEADER_OUT, true);
110
111
        switch ($http_verb) {
112
            case 'POST':
113
                curl_setopt($ch, CURLOPT_POST, true);
114
                $this->attachRequestPayload($ch, $args);
0 ignored issues
show
It seems like $ch can also be of type false; however, parameter $ch of Sreedev\Slack\Slack::attachRequestPayload() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

114
                $this->attachRequestPayload(/** @scrutinizer ignore-type */ $ch, $args);
Loading history...
115
                break;
116
117
            case 'GET':
118
                $query = http_build_query($args, '', '&');
119
                curl_setopt($ch, CURLOPT_URL, $url . '?' . $query);
120
                break;
121
122
            case 'DELETE':
123
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
124
                break;
125
126
            case 'PATCH':
127
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
128
                $this->attachRequestPayload($ch, $args);
129
                break;
130
131
            case 'PUT':
132
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
133
                $this->attachRequestPayload($ch, $args);
134
                break;
135
        }
136
137
        $responseContent     = curl_exec($ch);
138
        $response['headers'] = curl_getinfo($ch);
139
        $response            = $this->setResponseState($response, $responseContent, $ch);
140
        $formattedResponse   = $this->formatResponse($response);
141
142
        curl_close($ch);
143
144
        $isSuccess = $this->determineSuccess($response, $formattedResponse, $timeout);
145
146
        return is_array($formattedResponse) ? $formattedResponse : $isSuccess;
0 ignored issues
show
Bug Best Practice introduced by
The expression return is_array($formatt...edResponse : $isSuccess also could return the type boolean which is incompatible with the documented return type array|false.
Loading history...
147
    }
148
149
    /**
150
     * @param string  $http_verb
151
     * @param string  $method
152
     * @param string  $url
153
     * @param integer $timeout
154
     *
155
     * @return array
156
     */
157
    private function prepareStateForRequest($http_verb, $method, $url, $timeout)
158
    {
159
        $this->last_error = '';
160
161
        $this->request_successful = false;
162
163
        $this->last_response = array(
164
            'headers'     => null, // array of details from curl_getinfo()
165
            'httpHeaders' => null, // array of HTTP headers
166
            'body'        => null // content of the response
167
        );
168
169
        $this->last_request = array(
170
            'method'  => $http_verb,
171
            'path'    => $method,
172
            'url'     => $url,
173
            'body'    => '',
174
            'timeout' => $timeout,
175
        );
176
177
        return $this->last_response;
178
    }
179
180
    /**
181
     * Get the HTTP headers as an array of header-name => header-value pairs.
182
     *
183
     * The "Link" header is parsed into an associative array based on the
184
     * rel names it contains. The original value is available under
185
     * the "_raw" key.
186
     *
187
     * @param string $headersAsString
188
     *
189
     * @return array
190
     */
191
    private function getHeadersAsArray($headersAsString)
192
    {
193
        $headers = array();
194
195
        foreach (explode("\r\n", $headersAsString) as $i => $line) {
196
            if (preg_match('/HTTP\/[1-2]/', substr($line, 0, 7)) === 1) { // http code
197
                continue;
198
            }
199
200
            $line = trim($line);
201
            if (empty($line)) {
202
                continue;
203
            }
204
205
            list($key, $value) = explode(': ', $line);
206
207
            if ($key == 'Link') {
208
                $value = array_merge(
209
                    array('_raw' => $value),
210
                    $this->getLinkHeaderAsArray($value)
211
                );
212
            }
213
214
            $headers[$key] = $value;
215
        }
216
217
        return $headers;
218
    }
219
220
    /**
221
     * Extract all rel => URL pairs from the provided Link header value
222
     *
223
     * Mailchimp only implements the URI reference and relation type from
224
     * RFC 5988, so the value of the header is something like this:
225
     *
226
     * 'https://us13.api.mailchimp.com/schema/3.0/Lists/Instance.json; rel="describedBy",
227
     * <https://us13.admin.mailchimp.com/lists/members/?id=XXXX>; rel="dashboard"'
228
     *
229
     * @param string $linkHeaderAsString
230
     *
231
     * @return array
232
     */
233
    private function getLinkHeaderAsArray($linkHeaderAsString)
234
    {
235
        $urls = array();
236
237
        if (preg_match_all('/<(.*?)>\s*;\s*rel="(.*?)"\s*/', $linkHeaderAsString, $matches)) {
238
            foreach ($matches[2] as $i => $relName) {
239
                $urls[$relName] = $matches[1][$i];
240
            }
241
        }
242
243
        return $urls;
244
    }
245
246
    /**
247
     * Encode the data and attach it to the request
248
     *
249
     * @param   resource $ch   cURL session handle, used by reference
250
     * @param   array    $data Assoc array of data to attach
251
     */
252
    private function attachRequestPayload(&$ch, $data)
253
    {
254
        $encoded                    = json_encode($data);
255
        $this->last_request['body'] = $encoded;
256
        curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded);
257
    }
258
259
    /**
260
     * Decode the response and format any error messages for debugging
261
     *
262
     * @param array $response The response from the curl request
263
     *
264
     * @return array|false    The JSON decoded into an array
265
     */
266
    private function formatResponse($response)
267
    {
268
        $this->last_response = $response;
269
270
        if (!empty($response['body'])) {
271
            return json_decode($response['body'], true);
272
        }
273
274
        return false;
275
    }
276
277
    /**
278
     * Do post-request formatting and setting state from the response
279
     *
280
     * @param array    $response        The response from the curl request
281
     * @param string   $responseContent The body of the response from the curl request
282
     * @param resource $ch              The curl resource
283
     *
284
     * @return array    The modified response
285
     */
286
    private function setResponseState($response, $responseContent, $ch)
287
    {
288
        if ($responseContent === false) {
289
            $this->last_error = curl_error($ch);
290
        } else {
291
292
            $headerSize = $response['headers']['header_size'];
293
294
            $response['httpHeaders'] = $this->getHeadersAsArray(substr($responseContent, 0, $headerSize));
295
            $response['body']        = substr($responseContent, $headerSize);
296
297
            if (isset($response['headers']['request_header'])) {
298
                $this->last_request['headers'] = $response['headers']['request_header'];
299
            }
300
        }
301
302
        return $response;
303
    }
304
305
    /**
306
     * Check if the response was successful or a failure. If it failed, store the error.
307
     *
308
     * @param array       $response          The response from the curl request
309
     * @param array|false $formattedResponse The response body payload from the curl request
310
     * @param int         $timeout           The timeout supplied to the curl request.
311
     *
312
     * @return bool     If the request was successful
313
     */
314
    private function determineSuccess($response, $formattedResponse, $timeout)
315
    {
316
        $status = $this->findHTTPStatus($response, $formattedResponse);
317
318
        if ($status >= 200 && $status <= 299) {
319
            $this->request_successful = true;
320
            return true;
321
        }
322
323
        if (isset($formattedResponse['detail'])) {
324
            $this->last_error = sprintf('%d: %s', $formattedResponse['status'], $formattedResponse['detail']);
325
            return false;
326
        }
327
328
        if ($timeout > 0 && $response['headers'] && $response['headers']['total_time'] >= $timeout) {
329
            $this->last_error = sprintf('Request timed out after %f seconds.', $response['headers']['total_time']);
330
            return false;
331
        }
332
333
        $this->last_error = 'Unknown error, call getLastResponse() to find out what happened.';
334
        return false;
335
    }
336
337
    /**
338
     * Find the HTTP status code from the headers or API response body
339
     *
340
     * @param array       $response          The response from the curl request
341
     * @param array|false $formattedResponse The response body payload from the curl request
342
     *
343
     * @return int  HTTP status code
344
     */
345
    private function findHTTPStatus($response, $formattedResponse)
346
    {
347
        if (!empty($response['headers']) && isset($response['headers']['http_code'])) {
348
            return (int)$response['headers']['http_code'];
349
        }
350
351
        if (!empty($response['body']) && isset($formattedResponse['status'])) {
352
            return (int)$formattedResponse['status'];
353
        }
354
355
        return 418;
356
    }
357
}