Completed
Push — master ( 6706d8...93737b )
by
unknown
07:43
created

Helpers::mb_ucwords()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 15
nc 6
nop 1
1
<?php
2
namespace Dompdf;
3
4
class Helpers
5
{
6
    /**
7
     * print_r wrapper for html/cli output
8
     *
9
     * Wraps print_r() output in < pre > tags if the current sapi is not 'cli'.
10
     * Returns the output string instead of displaying it if $return is true.
11
     *
12
     * @param mixed $mixed variable or expression to display
13
     * @param bool $return
14
     *
15
     * @return string|null
16
     */
17
    public static function pre_r($mixed, $return = false)
18
    {
19
        if ($return) {
20
            return "<pre>" . print_r($mixed, true) . "</pre>";
21
        }
22
23
        if (php_sapi_name() !== "cli") {
24
            echo "<pre>";
25
        }
26
27
        print_r($mixed);
28
29
        if (php_sapi_name() !== "cli") {
30
            echo "</pre>";
31
        } else {
32
            echo "\n";
33
        }
34
35
        flush();
36
37
        return null;
38
    }
39
40
      /**
41
     * builds a full url given a protocol, hostname, base path and url
42
     *
43
     * @param string $protocol
44
     * @param string $host
45
     * @param string $base_path
46
     * @param string $url
47
     * @return string
48
     *
49
     * Initially the trailing slash of $base_path was optional, and conditionally appended.
50
     * However on dynamically created sites, where the page is given as url parameter,
51
     * the base path might not end with an url.
52
     * Therefore do not append a slash, and **require** the $base_url to ending in a slash
53
     * when needed.
54
     * Vice versa, on using the local file system path of a file, make sure that the slash
55
     * is appended (o.k. also for Windows)
56
     */
57
    public static function build_url($protocol, $host, $base_path, $url)
58
    {
59
        $protocol = mb_strtolower($protocol);
60
        if (strlen($url) == 0) {
61
            //return $protocol . $host . rtrim($base_path, "/\\") . "/";
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
62
            return $protocol . $host . $base_path;
63
        }
64
65
        // Is the url already fully qualified, a Data URI, or a reference to a named anchor?
66
        if (mb_strpos($url, "://") !== false || mb_substr($url, 0, 1) === "#" || mb_strpos($url, "data:") === 0 || mb_strpos($url, "mailto:") === 0) {
67
            return $url;
68
        }
69
70
        $ret = $protocol;
71
72
        if (!in_array(mb_strtolower($protocol), array("http://", "https://", "ftp://", "ftps://"))) {
73
            //On Windows local file, an abs path can begin also with a '\' or a drive letter and colon
74
            //drive: followed by a relative path would be a drive specific default folder.
75
            //not known in php app code, treat as abs path
76
            //($url[1] !== ':' || ($url[2]!=='\\' && $url[2]!=='/'))
0 ignored issues
show
Unused Code Comprehensibility introduced by
73% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
77
            if ($url[0] !== '/' && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' || (mb_strlen($url) > 1 && $url[0] !== '\\' && $url[1] !== ':'))) {
78
                // For rel path and local acess we ignore the host, and run the path through realpath()
79
                $ret .= realpath($base_path) . '/';
80
            }
81
            $ret .= $url;
82
            $ret = preg_replace('/\?(.*)$/', "", $ret);
83
            return $ret;
84
        }
85
86
        // Protocol relative urls (e.g. "//example.org/style.css")
87
        if (strpos($url, '//') === 0) {
88
            $ret .= substr($url, 2);
89
            //remote urls with backslash in html/css are not really correct, but lets be genereous
90
        } elseif ($url[0] === '/' || $url[0] === '\\') {
91
            // Absolute path
92
            $ret .= $host . $url;
93
        } else {
94
            // Relative path
95
            //$base_path = $base_path !== "" ? rtrim($base_path, "/\\") . "/" : "";
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
96
            $ret .= $host . $base_path . $url;
97
        }
98
99
        return $ret;
100
    }
101
102
    /**
103
     * Builds a HTTP Content-Disposition header string using `$dispositionType`
104
     * and `$filename`.
105
     *
106
     * If the filename contains any characters not in the ISO-8859-1 character
107
     * set, a fallback filename will be included for clients not supporting the
108
     * `filename*` parameter.
109
     *
110
     * @param string $dispositionType
111
     * @param string $filename
112
     * @return string
113
     */
114
    public static function buildContentDispositionHeader($dispositionType, $filename)
115
    {
116
        $encoding = mb_detect_encoding($filename);
117
        $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
118
        $fallbackfilename = str_replace("\"", "", $fallbackfilename);
119
        $encodedfilename = rawurlencode($filename);
120
121
        $contentDisposition = "Content-Disposition: $dispositionType; filename=\"$fallbackfilename\"";
122
        if ($fallbackfilename !== $filename) {
123
            $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
124
        }
125
126
        return $contentDisposition;
127
    }
128
129
    /**
130
     * Converts decimal numbers to roman numerals
131
     *
132
     * @param int $num
133
     *
134
     * @throws Exception
135
     * @return string
136
     */
137
    public static function dec2roman($num)
138
    {
139
140
        static $ones = array("", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix");
141
        static $tens = array("", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc");
142
        static $hund = array("", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm");
143
        static $thou = array("", "m", "mm", "mmm");
144
145
        if (!is_numeric($num)) {
146
            throw new Exception("dec2roman() requires a numeric argument.");
147
        }
148
149
        if ($num > 4000 || $num < 0) {
150
            return "(out of range)";
151
        }
152
153
        $num = strrev((string)$num);
154
155
        $ret = "";
156
        switch (mb_strlen($num)) {
157
            /** @noinspection PhpMissingBreakStatementInspection */
158
            case 4:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
159
                $ret .= $thou[$num[3]];
160
            /** @noinspection PhpMissingBreakStatementInspection */
161
            case 3:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
162
                $ret .= $hund[$num[2]];
163
            /** @noinspection PhpMissingBreakStatementInspection */
164
            case 2:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
165
                $ret .= $tens[$num[1]];
166
            /** @noinspection PhpMissingBreakStatementInspection */
167
            case 1:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
168
                $ret .= $ones[$num[0]];
169
            default:
170
                break;
171
        }
172
173
        return $ret;
174
    }
175
176
    /**
177
     * Determines whether $value is a percentage or not
178
     *
179
     * @param float $value
180
     *
181
     * @return bool
182
     */
183
    public static function is_percent($value)
184
    {
185
        return false !== mb_strpos($value, "%");
186
    }
187
188
    /**
189
     * Parses a data URI scheme
190
     * http://en.wikipedia.org/wiki/Data_URI_scheme
191
     *
192
     * @param string $data_uri The data URI to parse
193
     *
194
     * @return array|bool The result with charset, mime type and decoded data
195
     */
196
    public static function parse_data_uri($data_uri)
197
    {
198
        if (!preg_match('/^data:(?P<mime>[a-z0-9\/+-.]+)(;charset=(?P<charset>[a-z0-9-])+)?(?P<base64>;base64)?\,(?P<data>.*)?/is', $data_uri, $match)) {
199
            return false;
200
        }
201
202
        $match['data'] = rawurldecode($match['data']);
203
        $result = array(
204
            'charset' => $match['charset'] ? $match['charset'] : 'US-ASCII',
205
            'mime' => $match['mime'] ? $match['mime'] : 'text/plain',
206
            'data' => $match['base64'] ? base64_decode($match['data']) : $match['data'],
207
        );
208
209
        return $result;
210
    }
211
212
    /**
213
     * Encodes a Uniform Resource Identifier (URI) by replacing non-alphanumeric
214
     * characters with a percent (%) sign followed by two hex digits, excepting
215
     * characters in the URI reserved character set.
216
     *
217
     * Assumes that the URI is a complete URI, so does not encode reserved
218
     * characters that have special meaning in the URI.
219
     *
220
     * Simulates the encodeURI function available in JavaScript
221
     * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI
222
     *
223
     * Source: http://stackoverflow.com/q/4929584/264628
224
     *
225
     * @param string $uri The URI to encode
226
     * @return string The original URL with special characters encoded
227
     */
228
    public static function encodeURI($uri) {
229
        $unescaped = array(
230
            '%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%7E'=>'~',
231
            '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')'
232
        );
233
        $reserved = array(
234
            '%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':',
235
            '%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$'
236
        );
237
        $score = array(
238
            '%23'=>'#'
239
        );
240
        return strtr(rawurlencode(rawurldecode($uri)), array_merge($reserved,$unescaped,$score));
241
    }
242
243
    /**
244
     * Decoder for RLE8 compression in windows bitmaps
245
     * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
246
     *
247
     * @param string $str Data to decode
248
     * @param integer $width Image width
249
     *
250
     * @return string
251
     */
252
    public static function rle8_decode($str, $width)
253
    {
254
        $lineWidth = $width + (3 - ($width - 1) % 4);
255
        $out = '';
256
        $cnt = strlen($str);
257
258
        for ($i = 0; $i < $cnt; $i++) {
259
            $o = ord($str[$i]);
260
            switch ($o) {
261
                case 0: # ESCAPE
262
                    $i++;
263
                    switch (ord($str[$i])) {
264 View Code Duplication
                        case 0: # NEW LINE
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
265
                            $padCnt = $lineWidth - strlen($out) % $lineWidth;
266
                            if ($padCnt < $lineWidth) {
267
                                $out .= str_repeat(chr(0), $padCnt); # pad line
268
                            }
269
                            break;
270 View Code Duplication
                        case 1: # END OF FILE
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
271
                            $padCnt = $lineWidth - strlen($out) % $lineWidth;
272
                            if ($padCnt < $lineWidth) {
273
                                $out .= str_repeat(chr(0), $padCnt); # pad line
274
                            }
275
                            break 3;
276
                        case 2: # DELTA
277
                            $i += 2;
278
                            break;
279
                        default: # ABSOLUTE MODE
280
                            $num = ord($str[$i]);
281
                            for ($j = 0; $j < $num; $j++) {
282
                                $out .= $str[++$i];
283
                            }
284
                            if ($num % 2) {
285
                                $i++;
286
                            }
287
                    }
288
                    break;
289
                default:
290
                    $out .= str_repeat($str[++$i], $o);
291
            }
292
        }
293
        return $out;
294
    }
295
296
    /**
297
     * Decoder for RLE4 compression in windows bitmaps
298
     * see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
299
     *
300
     * @param string $str Data to decode
301
     * @param integer $width Image width
302
     *
303
     * @return string
304
     */
305
    public static function rle4_decode($str, $width)
306
    {
307
        $w = floor($width / 2) + ($width % 2);
308
        $lineWidth = $w + (3 - (($width - 1) / 2) % 4);
309
        $pixels = array();
310
        $cnt = strlen($str);
311
        $c = 0;
312
313
        for ($i = 0; $i < $cnt; $i++) {
314
            $o = ord($str[$i]);
315
            switch ($o) {
316
                case 0: # ESCAPE
317
                    $i++;
318
                    switch (ord($str[$i])) {
319
                        case 0: # NEW LINE
320
                            while (count($pixels) % $lineWidth != 0) {
321
                                $pixels[] = 0;
322
                            }
323
                            break;
324
                        case 1: # END OF FILE
325
                            while (count($pixels) % $lineWidth != 0) {
326
                                $pixels[] = 0;
327
                            }
328
                            break 3;
329
                        case 2: # DELTA
330
                            $i += 2;
331
                            break;
332
                        default: # ABSOLUTE MODE
333
                            $num = ord($str[$i]);
334
                            for ($j = 0; $j < $num; $j++) {
335
                                if ($j % 2 == 0) {
336
                                    $c = ord($str[++$i]);
337
                                    $pixels[] = ($c & 240) >> 4;
338
                                } else {
339
                                    $pixels[] = $c & 15;
340
                                }
341
                            }
342
343
                            if ($num % 2 == 0) {
344
                                $i++;
345
                            }
346
                    }
347
                    break;
348
                default:
349
                    $c = ord($str[++$i]);
350
                    for ($j = 0; $j < $o; $j++) {
351
                        $pixels[] = ($j % 2 == 0 ? ($c & 240) >> 4 : $c & 15);
352
                    }
353
            }
354
        }
355
356
        $out = '';
357
        if (count($pixels) % 2) {
358
            $pixels[] = 0;
359
        }
360
361
        $cnt = count($pixels) / 2;
362
363
        for ($i = 0; $i < $cnt; $i++) {
364
            $out .= chr(16 * $pixels[2 * $i] + $pixels[2 * $i + 1]);
365
        }
366
367
        return $out;
368
    }
369
370
    /**
371
     * parse a full url or pathname and return an array(protocol, host, path,
372
     * file + query + fragment)
373
     *
374
     * @param string $url
375
     * @return array
376
     */
377
    public static function explode_url($url)
0 ignored issues
show
Coding Style introduced by
explode_url 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...
378
    {
379
        $protocol = "";
0 ignored issues
show
Unused Code introduced by
$protocol is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
380
        $host = "";
381
        $path = "";
382
        $file = "";
383
384
        $arr = parse_url($url);
385
        if ( isset($arr["scheme"]) ) {
386
            $arr["scheme"] = mb_strtolower($arr["scheme"]);
387
        }
388
389
        // Exclude windows drive letters...
390
        if (isset($arr["scheme"]) && $arr["scheme"] !== "file" && strlen($arr["scheme"]) > 1) {
391
            $protocol = $arr["scheme"] . "://";
392
393
            if (isset($arr["user"])) {
394
                $host .= $arr["user"];
395
396
                if (isset($arr["pass"])) {
397
                    $host .= ":" . $arr["pass"];
398
                }
399
400
                $host .= "@";
401
            }
402
403
            if (isset($arr["host"])) {
404
                $host .= $arr["host"];
405
            }
406
407
            if (isset($arr["port"])) {
408
                $host .= ":" . $arr["port"];
409
            }
410
411
            if (isset($arr["path"]) && $arr["path"] !== "") {
412
                // Do we have a trailing slash?
413
                if ($arr["path"][mb_strlen($arr["path"]) - 1] === "/") {
414
                    $path = $arr["path"];
415
                    $file = "";
416
                } else {
417
                    $path = rtrim(dirname($arr["path"]), '/\\') . "/";
418
                    $file = basename($arr["path"]);
419
                }
420
            }
421
422
            if (isset($arr["query"])) {
423
                $file .= "?" . $arr["query"];
424
            }
425
426
            if (isset($arr["fragment"])) {
427
                $file .= "#" . $arr["fragment"];
428
            }
429
430
        } else {
431
432
            $i = mb_stripos($url, "file://");
433
            if ($i !== false) {
434
                $url = mb_substr($url, $i + 7);
435
            }
436
437
            $protocol = ""; // "file://"; ? why doesn't this work... It's because of
438
            // network filenames like //COMPU/SHARENAME
439
440
            $host = ""; // localhost, really
441
            $file = basename($url);
442
443
            $path = dirname($url);
444
445
            // Check that the path exists
446
            if ($path !== false) {
447
                $path .= '/';
448
449
            } else {
450
                // generate a url to access the file if no real path found.
451
                $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
452
453
                $host = isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : php_uname("n");
454
455
                if (substr($arr["path"], 0, 1) === '/') {
456
                    $path = dirname($arr["path"]);
457
                } else {
458
                    $path = '/' . rtrim(dirname($_SERVER["SCRIPT_NAME"]), '/') . '/' . $arr["path"];
459
                }
460
            }
461
        }
462
463
        $ret = array($protocol, $host, $path, $file,
464
            "protocol" => $protocol,
465
            "host" => $host,
466
            "path" => $path,
467
            "file" => $file);
468
        return $ret;
469
    }
470
471
    /**
472
     * Print debug messages
473
     *
474
     * @param string $type The type of debug messages to print
475
     * @param string $msg The message to show
476
     */
477
    public static function dompdf_debug($type, $msg)
478
    {
479
        global $_DOMPDF_DEBUG_TYPES, $_dompdf_show_warnings, $_dompdf_debug;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
480
        if (isset($_DOMPDF_DEBUG_TYPES[$type]) && ($_dompdf_show_warnings || $_dompdf_debug)) {
481
            $arr = debug_backtrace();
482
483
            echo basename($arr[0]["file"]) . " (" . $arr[0]["line"] . "): " . $arr[1]["function"] . ": ";
484
            Helpers::pre_r($msg);
485
        }
486
    }
487
488
    /**
489
     * Stores warnings in an array for display later
490
     * This function allows warnings generated by the DomDocument parser
491
     * and CSS loader ({@link Stylesheet}) to be captured and displayed
492
     * later.  Without this function, errors are displayed immediately and
493
     * PDF streaming is impossible.
494
     * @see http://www.php.net/manual/en/function.set-error_handler.php
495
     *
496
     * @param int $errno
497
     * @param string $errstr
498
     * @param string $errfile
499
     * @param string $errline
500
     *
501
     * @throws Exception
502
     */
503
    public static function record_warnings($errno, $errstr, $errfile, $errline)
0 ignored issues
show
Unused Code introduced by
The parameter $errfile is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $errline is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
504
    {
505
        // Not a warning or notice
506
        if (!($errno & (E_WARNING | E_NOTICE | E_USER_NOTICE | E_USER_WARNING))) {
507
            throw new Exception($errstr . " $errno");
508
        }
509
510
        global $_dompdf_warnings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
511
        global $_dompdf_show_warnings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
512
513
        if ($_dompdf_show_warnings) {
514
            echo $errstr . "\n";
515
        }
516
517
        $_dompdf_warnings[] = $errstr;
518
    }
519
520
    /**
521
     * @param $c
522
     * @return bool|string
523
     */
524
    public static function unichr($c)
525
    {
526
        if ($c <= 0x7F) {
527
            return chr($c);
528
        } else if ($c <= 0x7FF) {
529
            return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
530
        } else if ($c <= 0xFFFF) {
531
            return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
532
            . chr(0x80 | $c & 0x3F);
533
        } else if ($c <= 0x10FFFF) {
534
            return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
535
            . chr(0x80 | $c >> 6 & 0x3F)
536
            . chr(0x80 | $c & 0x3F);
537
        }
538
        return false;
539
    }
540
541
    /**
542
     * Converts a CMYK color to RGB
543
     *
544
     * @param float|float[] $c
545
     * @param float $m
546
     * @param float $y
547
     * @param float $k
548
     *
549
     * @return float[]
550
     */
551
    public static function cmyk_to_rgb($c, $m = null, $y = null, $k = null)
552
    {
553
        if (is_array($c)) {
554
            list($c, $m, $y, $k) = $c;
555
        }
556
557
        $c *= 255;
558
        $m *= 255;
559
        $y *= 255;
560
        $k *= 255;
561
562
        $r = (1 - round(2.55 * ($c + $k)));
563
        $g = (1 - round(2.55 * ($m + $k)));
564
        $b = (1 - round(2.55 * ($y + $k)));
565
566
        if ($r < 0) {
567
            $r = 0;
568
        }
569
        if ($g < 0) {
570
            $g = 0;
571
        }
572
        if ($b < 0) {
573
            $b = 0;
574
        }
575
576
        return array(
577
            $r, $g, $b,
578
            "r" => $r, "g" => $g, "b" => $b
579
        );
580
    }
581
582
    /**
583
     * getimagesize doesn't give a good size for 32bit BMP image v5
584
     *
585
     * @param string $filename
586
     * @return array The same format as getimagesize($filename)
587
     */
588
    public static function dompdf_getimagesize($filename, $context = null)
589
    {
590
        static $cache = array();
591
592
        if (isset($cache[$filename])) {
593
            return $cache[$filename];
594
        }
595
596
        list($width, $height, $type) = getimagesize($filename);
597
598
        // Custom types
599
        $types = array(
600
            IMAGETYPE_JPEG => "jpeg",
601
            IMAGETYPE_GIF  => "gif",
602
            IMAGETYPE_BMP  => "bmp",
603
            IMAGETYPE_PNG  => "png",
604
        );
605
606
        $type = isset($types[$type]) ? $types[$type] : null;
607
608
        if ($width == null || $height == null) {
609
            list($data, $headers) = Helpers::getFileContent($filename, $context);
0 ignored issues
show
Unused Code introduced by
The assignment to $headers is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
610
611
            if (substr($data, 0, 2) === "BM") {
612
                $meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data);
613
                $width = (int)$meta['width'];
614
                $height = (int)$meta['height'];
615
                $type = "bmp";
616
            }
617
            else {
618
                if (strpos($data, "<svg") !== false) {
619
                    $doc = new \Svg\Document();
620
                    $doc->loadFile($filename);
621
622
                    list($width, $height) = $doc->getDimensions();
623
                    $type = "svg";
624
                }
625
            }
626
627
        }
628
629
        return $cache[$filename] = array($width, $height, $type);
630
    }
631
632
    /**
633
     * Credit goes to mgutt
634
     * http://www.programmierer-forum.de/function-imagecreatefrombmp-welche-variante-laeuft-t143137.htm
635
     * Modified by Fabien Menager to support RGB555 BMP format
636
     */
637
    public static function imagecreatefrombmp($filename, $context = null)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
638
    {
639
        if (!function_exists("imagecreatetruecolor")) {
640
            trigger_error("The PHP GD extension is required, but is not installed.", E_ERROR);
641
            return false;
642
        }
643
644
        // version 1.00
645
        if (!($fh = fopen($filename, 'rb'))) {
646
            trigger_error('imagecreatefrombmp: Can not open ' . $filename, E_USER_WARNING);
647
            return false;
648
        }
649
650
        $bytes_read = 0;
651
652
        // read file header
653
        $meta = unpack('vtype/Vfilesize/Vreserved/Voffset', fread($fh, 14));
654
655
        // check for bitmap
656 View Code Duplication
        if ($meta['type'] != 19778) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
657
            trigger_error('imagecreatefrombmp: ' . $filename . ' is not a bitmap!', E_USER_WARNING);
658
            return false;
659
        }
660
661
        // read image header
662
        $meta += unpack('Vheadersize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vcolors/Vimportant', fread($fh, 40));
663
        $bytes_read += 40;
664
665
        // read additional bitfield header
666
        if ($meta['compression'] == 3) {
667
            $meta += unpack('VrMask/VgMask/VbMask', fread($fh, 12));
668
            $bytes_read += 12;
669
        }
670
671
        // set bytes and padding
672
        $meta['bytes'] = $meta['bits'] / 8;
673
        $meta['decal'] = 4 - (4 * (($meta['width'] * $meta['bytes'] / 4) - floor($meta['width'] * $meta['bytes'] / 4)));
674
        if ($meta['decal'] == 4) {
675
            $meta['decal'] = 0;
676
        }
677
678
        // obtain imagesize
679
        if ($meta['imagesize'] < 1) {
680
            $meta['imagesize'] = $meta['filesize'] - $meta['offset'];
681
            // in rare cases filesize is equal to offset so we need to read physical size
682
            if ($meta['imagesize'] < 1) {
683
                $meta['imagesize'] = @filesize($filename) - $meta['offset'];
684 View Code Duplication
                if ($meta['imagesize'] < 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
685
                    trigger_error('imagecreatefrombmp: Can not obtain filesize of ' . $filename . '!', E_USER_WARNING);
686
                    return false;
687
                }
688
            }
689
        }
690
691
        // calculate colors
692
        $meta['colors'] = !$meta['colors'] ? pow(2, $meta['bits']) : $meta['colors'];
693
694
        // read color palette
695
        $palette = array();
696
        if ($meta['bits'] < 16) {
697
            $palette = unpack('l' . $meta['colors'], fread($fh, $meta['colors'] * 4));
698
            // in rare cases the color value is signed
699
            if ($palette[1] < 0) {
700
                foreach ($palette as $i => $color) {
701
                    $palette[$i] = $color + 16777216;
702
                }
703
            }
704
        }
705
706
        // ignore extra bitmap headers
707
        if ($meta['headersize'] > $bytes_read) {
708
            fread($fh, $meta['headersize'] - $bytes_read);
709
        }
710
711
        // create gd image
712
        $im = imagecreatetruecolor($meta['width'], $meta['height']);
713
        $data = fread($fh, $meta['imagesize']);
714
715
        // uncompress data
716
        switch ($meta['compression']) {
717
            case 1:
718
                $data = Helpers::rle8_decode($data, $meta['width']);
719
                break;
720
            case 2:
721
                $data = Helpers::rle4_decode($data, $meta['width']);
722
                break;
723
        }
724
725
        $p = 0;
726
        $vide = chr(0);
727
        $y = $meta['height'] - 1;
728
        $error = 'imagecreatefrombmp: ' . $filename . ' has not enough data!';
729
730
        // loop through the image data beginning with the lower left corner
731
        while ($y >= 0) {
732
            $x = 0;
733
            while ($x < $meta['width']) {
734
                switch ($meta['bits']) {
735
                    case 32:
736
                    case 24:
737 View Code Duplication
                        if (!($part = substr($data, $p, 3 /*$meta['bytes']*/))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
738
                            trigger_error($error, E_USER_WARNING);
739
                            return $im;
740
                        }
741
                        $color = unpack('V', $part . $vide);
742
                        break;
743
                    case 16:
744 View Code Duplication
                        if (!($part = substr($data, $p, 2 /*$meta['bytes']*/))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
745
                            trigger_error($error, E_USER_WARNING);
746
                            return $im;
747
                        }
748
                        $color = unpack('v', $part);
749
750
                        if (empty($meta['rMask']) || $meta['rMask'] != 0xf800) {
751
                            $color[1] = (($color[1] & 0x7c00) >> 7) * 65536 + (($color[1] & 0x03e0) >> 2) * 256 + (($color[1] & 0x001f) << 3); // 555
752
                        } else {
753
                            $color[1] = (($color[1] & 0xf800) >> 8) * 65536 + (($color[1] & 0x07e0) >> 3) * 256 + (($color[1] & 0x001f) << 3); // 565
754
                        }
755
                        break;
756
                    case 8:
757
                        $color = unpack('n', $vide . substr($data, $p, 1));
758
                        $color[1] = $palette[$color[1] + 1];
759
                        break;
760
                    case 4:
761
                        $color = unpack('n', $vide . substr($data, floor($p), 1));
762
                        $color[1] = ($p * 2) % 2 == 0 ? $color[1] >> 4 : $color[1] & 0x0F;
763
                        $color[1] = $palette[$color[1] + 1];
764
                        break;
765
                    case 1:
766
                        $color = unpack('n', $vide . substr($data, floor($p), 1));
767
                        switch (($p * 8) % 8) {
768
                            case 0:
769
                                $color[1] = $color[1] >> 7;
770
                                break;
771
                            case 1:
772
                                $color[1] = ($color[1] & 0x40) >> 6;
773
                                break;
774
                            case 2:
775
                                $color[1] = ($color[1] & 0x20) >> 5;
776
                                break;
777
                            case 3:
778
                                $color[1] = ($color[1] & 0x10) >> 4;
779
                                break;
780
                            case 4:
781
                                $color[1] = ($color[1] & 0x8) >> 3;
782
                                break;
783
                            case 5:
784
                                $color[1] = ($color[1] & 0x4) >> 2;
785
                                break;
786
                            case 6:
787
                                $color[1] = ($color[1] & 0x2) >> 1;
788
                                break;
789
                            case 7:
790
                                $color[1] = ($color[1] & 0x1);
791
                                break;
792
                        }
793
                        $color[1] = $palette[$color[1] + 1];
794
                        break;
795
                    default:
796
                        trigger_error('imagecreatefrombmp: ' . $filename . ' has ' . $meta['bits'] . ' bits and this is not supported!', E_USER_WARNING);
797
                        return false;
798
                }
799
                imagesetpixel($im, $x, $y, $color[1]);
800
                $x++;
801
                $p += $meta['bytes'];
802
            }
803
            $y--;
804
            $p += $meta['decal'];
805
        }
806
        fclose($fh);
807
        return $im;
808
    }
809
810
    /**
811
     * Gets the content of the file at the specified path using one of
812
     * the following methods, in preferential order:
813
     *  - file_get_contents: if allow_url_fopen is true or the file is local
814
     *  - curl: if allow_url_fopen is false and curl is available
815
     *
816
     * @param string $uri
817
     * @param resource $context (ignored if curl is used)
818
     * @param int $offset
819
     * @param int $maxlen (ignored if curl is used)
820
     * @return bool|array
821
     */
822
    public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null)
823
    {
824
        $result = false;
825
        $headers = null;
826
        list($proto, $host, $path, $file) = Helpers::explode_url($uri);
0 ignored issues
show
Unused Code introduced by
The assignment to $host is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $path is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $file is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
827
        $is_local_path = ($proto == "" || $proto === "file://");
828
829
        set_error_handler(array("\\Dompdf\\Helpers", "record_warnings"));
830
831
        if ($is_local_path || ini_get("allow_url_fopen")) {
832
            if ($is_local_path === false) {
833
                $uri = Helpers::encodeURI($uri);
834
            }
835
            if (isset($maxlen)) {
836
                $result = file_get_contents($uri, null, $context, $offset, $maxlen);
837
            } else {
838
                $result = file_get_contents($uri, null, $context, $offset);
839
            }
840
            if (isset($http_response_header)) {
841
                $headers = $http_response_header;
842
            }
843
844
        } elseif (function_exists("curl_exec")) {
845
            $curl = curl_init($uri);
846
847
            //TODO: use $context to define additional curl options
848
            curl_setopt($curl, CURLOPT_TIMEOUT, 10);
849
            curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
850
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
851
            curl_setopt($curl, CURLOPT_HEADER, true);
852
            if ($offset > 0) {
853
                curl_setopt($curl, CURLOPT_RESUME_FROM, $offset);
854
            }
855
856
            $data = curl_exec($curl);
857
            $raw_headers = substr($data, 0, curl_getinfo($curl, CURLINFO_HEADER_SIZE));
858
            $headers = preg_split("/[\n\r]+/", trim($raw_headers));
859
            $result = substr($data, curl_getinfo($curl, CURLINFO_HEADER_SIZE));
860
            curl_close($curl);
861
        }
862
863
        restore_error_handler();
864
865
        return array($result, $headers);
866
    }
867
868
    public static function mb_ucwords($str) {
869
        $max_len = mb_strlen($str);
870
        if ($max_len === 1) {
871
            return mb_strtoupper($str);
872
        }
873
874
        $str = mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1);
875
876
        foreach (array(' ', '.', ',', '!', '?', '-', '+') as $s) {
877
            $pos = 0;
878
            while (($pos = mb_strpos($str, $s, $pos)) !== false) {
879
                $pos++;
880
                // Nothing to do if the separator is the last char of the string
881
                if ($pos !== false && $pos < $max_len) {
882
                    // If the char we want to upper is the last char there is nothing to append behind
883
                    if ($pos + 1 < $max_len) {
884
                        $str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1)) . mb_substr($str, $pos + 1);
885
                    } else {
886
                        $str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1));
887
                    }
888
                }
889
            }
890
        }
891
892
        return $str;
893
    }
894
}
895