Completed
Push — master ( 8d4f58...cc7481 )
by Dmytro
02:29
created

Http::forceHttpStatus()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 2
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Asymptix\web;
4
5
/**
6
 * Http protocol functionality and other connected tools.
7
 *
8
 * @category Asymptix PHP Framework
9
 * @author Dmytro Zarezenko <[email protected]>
10
 * @copyright (c) 2009 - 2017, Dmytro Zarezenko
11
 *
12
 * @git https://github.com/Asymptix/Framework
13
 * @license http://opensource.org/licenses/MIT
14
 */
15
class Http {
16
17
    const POST = "POST";
18
    const GET  = "GET";
19
20
    /**
21
     * Redirect to the given url.
22
     *
23
     * @param string $url The URL to redirect to.
24
     * @param array<mixed> $params Associative array of query parameters.
25
     * @param bool $session Whether to append session information.
26
     */
27 View Code Duplication
    public static function http_redirect($url, $params = [], $session = false) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
28
        $paramsString = "";
29
        foreach ($params as $key => $value) {
30
            $paramsString.= "&" . $key . "=" . $value;
31
        }
32
        if ($session) {
33
            $paramsString.= "&" . session_name() . "=" . session_id();
34
        }
35
        $paramsString = substr($paramsString, 1);
36
        if ($paramsString) {
37
            $paramsString = "?" . $paramsString;
38
        }
39
        header("Location: " . $url . $paramsString);
40
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method http_redirect() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
41
    }
42
43
    /**
44
     * Perform HTTP redirect with saving POST params in session.
45
     *
46
     * @param string $url URL redirect to.
47
     * @param array<mixed> $postData List of post params to save.
48
     * @param bool $serialize Serialize transmitted POST data values or not.
49
     */
50
    public static function httpRedirect($url = "", $postData = [], $serialize = true) {
0 ignored issues
show
Coding Style introduced by
httpRedirect uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
httpRedirect uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
51
        if (preg_match("#^http[s]?://.+#", $url)) { // absolute url
52
            if (function_exists("http_redirect")) {
53
                http_redirect($url);
54
            } else {
55
                self::http_redirect($url);
56
            }
57
        } else { // same domain (relative url)
58
            if (!empty($postData)) {
59
                if (is_array($postData)) {
60
                    if (!Session::exists('_post') || !is_array($_SESSION['_post'])) {
61
                        Session::set('_post', []);
62
                    }
63
64
                    foreach ($postData as $fieldName => $fieldValue) {
65
                        Session::set("_post[{$fieldName}]", $serialize ? serialize($fieldValue) : $fieldValue);
66
                    }
67
                } else {
68
                    throw new HttpException("Wrong POST data.");
69
                }
70
            }
71
            if (function_exists("http_redirect")) {
72
                http_redirect("http://" . $_SERVER['SERVER_NAME'] . "/" . $url);
73
            } else {
74
                self::http_redirect("http://" . $_SERVER['SERVER_NAME'] . "/" . $url);
75
            }
76
        }
77
    }
78
79
    /**
80
     * Perform HTTP redirect with saving POST params in session.
81
     *
82
     * @param string $url URL redirect to.
83
     * @param array<mixed> $postData List of post params to save.
84
     * @param bool $serialize Serialize transmitted POST data values or not.
85
     */
86
    public static function redirect($url = "", $postData = [], $serialize = true) {
87
        self::httpRedirect($url, $postData, $serialize);
88
    }
89
90
    /**
91
     * Returns clients IP-address.
92
     *
93
     * @return string
94
     */
95
    public static function getIP() {
0 ignored issues
show
Coding Style introduced by
getIP uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
96
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) { //check ip from share internet
97
            return $_SERVER['HTTP_CLIENT_IP'];
98
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { //to check ip is pass from proxy
99
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
100
        }
101
102
        return $_SERVER['REMOTE_ADDR'];
103
    }
104
105
    /**
106
     * Returns HTTP referrer.
107
     *
108
     * @return string
109
     */
110
    public static function getReferrer() {
0 ignored issues
show
Coding Style introduced by
getReferrer uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
111
        return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : "";
112
    }
113
114
    /**
115
     * Gets the address that the provided URL redirects to,
116
     * or FALSE if there's no redirect.
117
     *
118
     * @param string $url URL.
119
     *
120
     * @return mixed String with redirect URL or FALSE if no redirect.
121
     * @throws HttpException
122
     */
123
    public static function getRedirectUrl($url) {
124
        $urlParts = @parse_url($url);
125
        if (!$urlParts) {
126
            return false;
127
        }
128
        if (!isset($urlParts['host'])) { //can't process relative URLs
129
            return false;
130
        }
131
132
        if (!isset($urlParts['path'])) {
133
            $urlParts['path'] = '/';
134
        }
135
136
        $sock = fsockopen($urlParts['host'], (isset($urlParts['port']) ? (int) $urlParts['port'] : 80), $errno, $errstr, 30);
137
        if (!$sock) {
138
            throw new HttpException("$errstr ($errno)");
139
        }
140
141
        $request = "HEAD " . $urlParts['path'] . (isset($urlParts['query']) ? '?' . $urlParts['query'] : '') . " HTTP/1.1\r\n";
142
        $request.= 'Host: ' . $urlParts['host'] . "\r\n";
143
        $request.= "Connection: Close\r\n\r\n";
144
        fwrite($sock, $request);
145
        $response = '';
146
        while (!feof($sock)) {
147
            $response.= fread($sock, 8192);
148
        }
149
        fclose($sock);
150
151
        if (preg_match('/^Location: (.+?)$/m', $response, $matches)) {
152
            if (substr($matches[1], 0, 1) == "/") {
153
                return $urlParts['scheme'] . "://" . $urlParts['host'] . trim($matches[1]);
154
            }
155
156
            return trim($matches[1]);
157
        }
158
159
        return false;
160
    }
161
162
    /**
163
     * Follows and collects all redirects, in order, for the given URL.
164
     *
165
     * @param string $url
166
     * @return array
167
     */
168
    public static function getAllRedirects($url) {
169
        $redirects = [];
170
        while ($newurl = self::getRedirectUrl($url)) {
171
            if (in_array($newurl, $redirects)) {
172
                break;
173
            }
174
            $redirects[] = $newurl;
175
            $url = $newurl;
176
        }
177
178
        return $redirects;
179
    }
180
181
    /**
182
     * Gets the address that the URL ultimately leads to.
183
     * Returns $url itself if it isn't a redirect.
184
     *
185
     * @param string $url
186
     * @return string
187
     */
188
    public static function getFinalUrl($url) {
189
        $redirects = self::getAllRedirects($url);
190
        if (count($redirects) > 0) {
191
            return array_pop($redirects);
192
        }
193
194
        return $url;
195
    }
196
197
    /**
198
     * Executes CURL async request.
199
     *
200
     * @param string $url URL.
201
     * @param array $params List of request params.
202
     * @param string $type Type of the request (GET, POST, ...).
203
     * @param int $timeout Timeout in seconds.
204
     *
205
     * @return type
206
     */
207
    public static function curlRequestAsync($url, $params, $type = self::POST, $timeout = 30) {
208
        $postParams = [];
209
        foreach ($params as $key => &$val) {
210
            if (is_array($val)) {
211
                $val = implode(',', $val);
212
            }
213
            $postParams[] = $key . '=' . urlencode($val);
214
        }
215
        $postString = implode('&', $postParams);
216
217
        $parts = parse_url($url);
218
219
        $port = isset($parts['port']) ? (int)$parts['port'] : 80;
220
221
        $sock = fsockopen($parts['host'], $port, $errno, $errstr, $timeout);
222
223
        // Data goes in the path for a GET request
224
        if ($type == self::GET) {
225
            $parts['path'].= '?' . $postString;
226
        }
227
228
        $request = "$type " . $parts['path'] . " HTTP/1.1\r\n";
229
        $request.= "Host: " . $parts['host'] . "\r\n";
230
231
        if ($type == self::POST) {
232
            $request.= "Content-Type: application/x-www-form-urlencoded\r\n";
233
            $request.= "Content-Length: " . strlen($postString) . "\r\n";
234
        }
235
        $request.= "Connection: Close\r\n";
236
        $request.= "\r\n";
237
238
        // Data goes in the request body for a POST request
239
        if ($type == self::POST && isset($postString)) {
240
            $request.= $postString;
241
        }
242
243
        fwrite($sock, $request);
244
245
        $response = "";
246
        while (!feof($sock) && $result = fgets($sock)) {
247
            $response.= $result;
248
        }
249
250
        fclose($sock);
251
252
        list($respHeader, $respBody) = preg_split("/\R\R/", $response, 2);
253
254
        $headers = array_map(['self', "pair"], explode("\r\n", $respHeader));
255
        $headerList = [];
256
        foreach ($headers as $value) {
257
            $headerList[$value['key']] = $value['value'];
258
        }
259
260
        return [
261
            'request' => $request,
262
            'response' => [
263
                'header' => $respHeader,
264
                'headerList' => $headerList,
265
                'body' => trim(http_chunked_decode($respBody))
266
            ],
267
            'errno' => $errno,
268
            'errstr' => $errstr
269
        ];
270
    }
271
272
    /**
273
     * Validates URL existence with cURL request.
274
     *
275
     * @param string $url URL.
276
     *
277
     * @return bool
278
     */
279
    public static function urlExists($url) {
280
        $ch = curl_init($url);
281
282
        curl_setopt($ch, CURLOPT_NOBODY, true);
283
        curl_setopt($ch, CURLOPT_FAILONERROR, true);
284
285
        $result = curl_exec($ch);
286
        curl_close($ch);
287
288
        return $result;
289
    }
290
291
    /**
292
     * Force HTTP status code.
293
     *
294
     * @param int $code Status code.
295
     * @param string $path Include path if needed.
296
     *
297
     * @throws HttpException If invalid HTTP status provided.
298
     */
299
    public static function forceHttpStatus($code, $path = null) {
300
        switch ($code) {
301
            //TODO: implement other statuses
302
            case (404):
303
                header('HTTP/1.0 404 Not Found', true, 404);
304
                break;
305
            default:
306
                throw new HttpException("Invalid HTTP status code '" . $code . "'");
307
        }
308
        if (!is_null($path)) {
309
            include($path);
310
        }
311
        exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method forceHttpStatus() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
312
    }
313
314
    /**
315
     * Magic methods:
316
     *    force404($path = null) - force HTTP status codes.
317
     *
318
     * @param string $name The name of the method being called.
319
     * @param type $arguments Enumerated array containing the parameters passed
320
     *           to the method.
321
     * @return mixed
322
     *
323
     * @throws HttpException If invalid method name provided.
324
     */
325
    public static function __callStatic($name, $arguments) {
326
        if (substr($name, 0, 5) === "force") {
327
            $code = (int)substr($name, 5);
328
            $path = isset($arguments[0]) ? $arguments[0] : null;
329
330
            return self::forceHttpStatus($code, $path);
331
        }
332
        throw new HttpException("Invalid HTTP class method '" . $name . "'");
333
    }
334
335
}
336
337
/**
338
 * Service exception class.
339
 */
340
class HttpException extends \Exception {}
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
341