Passed
Push — master ( 7f2ac9...01b5e4 )
by Sebastian
04:22
created

RequestHelper::configureLogging()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
c 1
b 0
f 0
dl 0
loc 24
rs 9.8666
cc 3
nc 3
nop 1
1
<?php
2
/**
3
 * File containing the {@link RequestHelper} class.
4
 * @package Application Utils
5
 * @subpackage RequestHelper
6
 * @see RequestHelper
7
 */
8
9
namespace AppUtils;
10
11
/**
12
 * Handles sending POST requests with file attachments and regular variables.
13
 * Creates the raw request headers required for the request and sends them
14
 * using file_get_contents with the according context parameters.
15
 *
16
 * @package Application Utils
17
 * @subpackage RequestHelper
18
 * @author Sebastian Mordziol <[email protected]>
19
 */
20
class RequestHelper
21
{
22
    const FILETYPE_TEXT = 'text/plain';
23
24
    const FILETYPE_XML = 'text/xml';
25
    
26
    const FILETYPE_HTML = 'text/html';
27
    
28
    const ENCODING_UTF8 = 'UTF-8';
29
30
    const TRANSFER_ENCODING_BASE64 = 'BASE64';
31
32
    const TRANSFER_ENCODING_8BIT = '8BIT';
33
    
34
    const TRANSFER_ENCODING_BINARY = 'BINARY';
35
    
36
    const ERROR_REQUEST_FAILED = 17902;
37
    
38
    const ERROR_CURL_INIT_FAILED = 17903;
39
    
40
    const ERROR_CANNOT_OPEN_LOGFILE = 17904;
41
42
   /**
43
    * @var string
44
    */
45
    protected $eol = "\r\n";
46
47
   /**
48
    * @var string
49
    */
50
    protected $mimeBoundary;
51
52
   /**
53
    * @var string
54
    */
55
    protected $data = '';
56
57
   /**
58
    * @var string
59
    */
60
    protected $destination;
61
62
   /**
63
    * @var array
64
    */
65
    protected $headers = array();
66
    
67
   /**
68
    * Whether to verify SSL certificates.
69
    * @var bool
70
    */
71
    protected $verifySSL = true;
72
    
73
   /**
74
    * @var RequestHelper_Boundaries
75
    */
76
    protected $boundaries;
77
    
78
   /**
79
    * @var RequestHelper_Response|NULL
80
    */
81
    protected $response;
82
83
   /**
84
    * @var integer
85
    */
86
    protected $timeout = 30;
87
    
88
   /**
89
    * @var string
90
    */
91
    protected $logfile = '';
92
93
   /**
94
    * @var resource|NULL
95
    */
96
    protected $logfilePointer = null;
97
    
98
   /**
99
    * Creates a new request helper to send POST data to the specified destination URL.
100
    * @param string $destinationURL
101
    */
102
    public function __construct(string $destinationURL)
103
    {
104
        $this->destination = $destinationURL;
105
        $this->mimeBoundary = str_repeat('-', 20).md5('request-helper-boundary');
106
        $this->boundaries = new RequestHelper_Boundaries($this);
107
        
108
        requireCURL();
109
    }
110
    
111
    public function getMimeBoundary() : string
112
    {
113
        return $this->mimeBoundary;
114
    }
115
    
116
    public function getMimeBody() : string
117
    {
118
        return $this->boundaries->render();
119
    }
120
    
121
    public function getEOL() : string
122
    {
123
        return $this->eol;
124
    }
125
    
126
    public function setTimeout(int $seconds) : RequestHelper
127
    {
128
        $this->timeout = $seconds;
129
        
130
        return $this;
131
    }
132
    
133
   /**
134
    * Enables verbose logging of the CURL request, which
135
    * is then redirected to the target file.
136
    * 
137
    * @param string $targetFile
138
    * @return RequestHelper
139
    */
140
    public function enableLogging(string $targetFile) : RequestHelper
141
    {
142
        $this->logfile = $targetFile;
143
        
144
        return $this;
145
    }
146
147
   /**
148
    * Adds a file to be sent with the request.
149
    *
150
    * @param string $varName The variable name to send the file in
151
    * @param string $fileName The name of the file as it should be received at the destination
152
    * @param string $content The raw content of the file
153
    * @param string $contentType The content type, use the constants to specify this
154
    * @param string $encoding The encoding of the file, use the constants to specify this
155
    */
156
    public function addFile(string $varName, string $fileName, string $content, string $contentType = '', string $encoding = '') : RequestHelper
157
    {
158
        $this->boundaries->addFile($varName, $fileName, $content, $contentType, $encoding);
159
        
160
        return $this;
161
    }
162
    
163
   /**
164
    * Adds arbitrary content.
165
    * 
166
    * @param string $varName The variable name to send the content in.
167
    * @param string $content
168
    * @param string $contentType
169
    */
170
    public function addContent(string $varName, string $content, string $contentType) : RequestHelper
171
    {
172
        $this->boundaries->addContent($varName, $content, $contentType);
173
        
174
        return $this;
175
    }
176
177
    /**
178
     * Adds a variable to be sent with the request. If it
179
     * already exists, its value is overwritten.
180
     *
181
     * @param string $name
182
     * @param string $value
183
     */
184
    public function addVariable(string $name, string $value) : RequestHelper
185
    {
186
        $this->boundaries->addVariable($name, $value);
187
        
188
        return $this;
189
    }
190
    
191
   /**
192
    * Sets an HTTP header to include in the request.
193
    * 
194
    * @param string $name
195
    * @param string $value
196
    * @return RequestHelper
197
    */
198
    public function setHeader(string $name, string $value) : RequestHelper
199
    {
200
        $this->headers[$name] = $value;
201
        
202
        return $this;
203
    }
204
    
205
   /**
206
    * Disables SSL certificate checking.
207
    * 
208
    * @return RequestHelper
209
    */
210
    public function disableSSLChecks() : RequestHelper
211
    {
212
        $this->verifySSL = false;
213
        return $this;
214
    }
215
   
216
   /**
217
    * @var integer
218
    */
219
    protected $contentLength = 0;
220
221
   /**
222
    * Sends the POST request to the destination, and returns
223
    * the response text.
224
    *
225
    * The response object is stored internally, so after calling
226
    * this method it may be retrieved at any moment using the
227
    * {@link getResponse()} method.
228
    *
229
    * @return string
230
    * @see RequestHelper::getResponse()
231
    * @throws RequestHelper_Exception
232
    * 
233
    * @see RequestHelper::ERROR_REQUEST_FAILED
234
    */
235
    public function send() : string
236
    {
237
        $info = parseURL($this->destination);
238
        
239
        $ch = $this->createCURL($info);
240
241
        $output = curl_exec($ch);
242
243
        if(isset($this->logfilePointer))
244
        {
245
            fclose($this->logfilePointer);
246
        }
247
        
248
        $info = curl_getinfo($ch);
249
        
250
        $this->response = new RequestHelper_Response($this, $info);
251
        
252
        // CURL will complain about an empty response when the 
253
        // server sends a 100-continue code. That should not be
254
        // regarded as an error.
255
        if($output === false && $this->response->getCode() !== 100)
256
        {
257
            $this->response->setError(
258
                curl_errno($ch),
259
                curl_error($ch)
260
            );
261
        }
262
        else
263
        {
264
            $this->response->setBody((string)$output);
265
        }
266
        
267
        curl_close($ch);
268
        
269
        return $this->response->getResponseBody();
270
    }
271
    
272
    public function getBody() : string
273
    {
274
        return $this->data;
275
    }
276
    
277
   /**
278
    * Creates a new CURL resource configured according to the
279
    * request's settings.
280
    * 
281
    * @param URLInfo $url
282
    * @throws RequestHelper_Exception
283
    * @return resource
284
    */
285
    protected function createCURL(URLInfo $url)
286
    {
287
        $ch = curl_init();
288
        
289
        if(!is_resource($ch))
290
        {
291
            throw new RequestHelper_Exception(
292
                'Could not initialize a new cURL instance.',
293
                'Calling curl_init returned false. Additional information is not available.',
294
                self::ERROR_CURL_INIT_FAILED
295
            );
296
        }
297
298
        $this->setHeader('Content-Length', (string)$this->boundaries->getContentLength());
299
        $this->setHeader('Content-Type', 'multipart/form-data; boundary=' . $this->mimeBoundary);
300
301
        curl_setopt($ch, CURLOPT_POST, true);
302
        curl_setopt($ch, CURLOPT_URL, $url->getNormalizedWithoutAuth());
303
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
304
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
305
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
306
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->renderHeaders());
307
        curl_setopt($ch, CURLOPT_POSTFIELDS, $this->boundaries->render());
308
        curl_setopt($ch, CURLOPT_FAILONERROR, true);
309
        
310
        $loggingEnabled = $this->configureLogging($ch);
311
        
312
        if(!$loggingEnabled) 
313
        {
314
            curl_setopt($ch, CURLINFO_HEADER_OUT, true);
315
        }
316
        
317
        if($this->verifySSL)
318
        {
319
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
320
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
321
        }
322
        
323
        if($url->hasUsername())
324
        {
325
            curl_setopt($ch, CURLOPT_USERNAME, $url->getUsername());
326
            curl_setopt($ch, CURLOPT_PASSWORD, $url->getPassword());
327
        }
328
        
329
        return $ch;
330
    }
331
    
332
   /**
333
    * @param resource $ch
334
    * @return bool Whether logging is enabled.
335
    */
336
    protected function configureLogging($ch) : bool
337
    {
338
        if(empty($this->logfile))
339
        {
340
            return false;
341
        }
342
        
343
        $res = fopen($this->logfile, 'w+');
344
        
345
        if($res === false)
346
        {
347
            throw new RequestHelper_Exception(
348
                'Cannot open logfile for writing.',
349
                sprintf('Tried accessing the file at [%s].', $this->logfile),
350
                self::ERROR_CANNOT_OPEN_LOGFILE
351
            );
352
        }
353
        
354
        $this->logfilePointer = $res;
355
        
356
        curl_setopt($ch, CURLOPT_VERBOSE, true);
357
        curl_setopt($ch, CURLOPT_STDERR, $res);
358
        
359
        return true;
360
    }
361
362
   /**
363
    * Compiles the associative headers array into
364
    * the format understood by CURL, namely an indexed
365
    * array with one header string per entry.
366
    * 
367
    * @return array
368
    */
369
    protected function renderHeaders() : array
370
    {
371
        $result = array();
372
        
373
        $this->setHeader('Expect', '');
374
        
375
        foreach($this->headers as $name => $value) {
376
            $result[] = $name.': '.$value;
377
        }
378
        
379
        return $result;
380
    }
381
    
382
   /**
383
    * Retrieves the raw response header, in the form of an indexed
384
    * array containing all response header lines.
385
    * 
386
    * @return array
387
    */
388
    public function getResponseHeader() : array
389
    {
390
        $response = $this->getResponse();
391
        
392
        if($response !== null) {
393
            return $response->getHeaders();
394
        }
395
396
        return array();
397
    }
398
399
    /**
400
     * After calling the {@link send()} method, this may be used to
401
     * retrieve the response text from the POST request.
402
     *
403
     * @return RequestHelper_Response|NULL
404
     */
405
    public function getResponse() : ?RequestHelper_Response
406
    {
407
        return $this->response;
408
    }
409
    
410
    public function getHeaders()
411
    {
412
        return $this->headers;
413
    }
414
}
415