• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cypht-org / cypht / 26230827274

21 May 2026 02:01PM UTC coverage: 72.803% (-5.4%) from 78.201%
26230827274

push

travis-ci

web-flow
Merge pull request #1972 from IrAlfred/remove-duplicate-testdox-flag-in-coverage-report

fix(workflow): remove duplicate --testdox flag causing coverage job exit code 1

4797 of 6589 relevant lines covered (72.8%)

7.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.6
/modules/core/message_functions.php
1
<?php
2

3
/**
4
 * Core modules
5
 * @package modules
6
 * @subpackage core
7
 */
8

9
/**
10
 * Format a message body that has HMTL markup
11
 * @subpackage core/functions
12
 * @param string $str message HTML
13
 * @param bool $images allow external images
14
 * @return string
15
 */
16
if (!hm_exists('format_msg_html')) {
17
function format_msg_html($str, $images=false) {
18
    $str = mb_eregi_replace('</body>', '', $str);
2✔
19

20
    $config = HTMLPurifier_Config::createDefault();
2✔
21
    $config->set('HTML.DefinitionID', 'hm-message');
2✔
22
    $config->set('HTML.DefinitionRev', 1);
2✔
23
    $config->set('Cache.DefinitionImpl', null);
2✔
24
    $config->set('HTML.TargetBlank', true);
2✔
25
    $config->set('HTML.TargetNoopener', true);
2✔
26
    $config->set('HTML.ForbiddenElements', ['html', 'head']);
2✔
27
    $config->set('CSS.AllowTricky', true);
2✔
28

29
    if (!$images) {
2✔
30
        $config->set('URI.DisableExternalResources', true);
2✔
31
    }
32
    $config->set('URI.AllowedSchemes', array('mailto' => true, 'data' => true, 'http' => true, 'https' => true));
2✔
33
    $config->set('Filter.ExtractStyleBlocks.TidyImpl', true);
2✔
34

35
    if ($def = $config->maybeGetRawHTMLDefinition()) {
2✔
36
        $html_tags = ['img', 'script', 'iframe', 'audio', 'embed', 'source', 'track', 'video', 'a'];
2✔
37
        foreach ($html_tags as $tag) {
2✔
38
            $def->addAttribute($tag, 'data-src', 'Text');
2✔
39
        }
40
        $def->addAttribute('div', 'data-external-resources-blocked', 'Text');
2✔
41
    }
42

43
    try {
44
        $purifier = new HTMLPurifier($config);
2✔
45
        return $purifier->purify($str);
2✔
46
    } catch (Exception $e) {
×
47
        return '';
×
48
    }
49
}}
50

51
/**
52
 * Sanitize HTML for email
53
 * @subpackage core/functions
54
 * @param string $html content to sanitize
55
 * @return string
56
 */
57
if (!hm_exists('sanitize_email_html')) {
58
function sanitize_email_html($html) {
59
    $html = preg_replace_callback(
×
60
        '/<([^>]+)\s*style\s*=\s*(["\'])(.*?)\2/i',
×
61
        function($matches) {
×
62
            $content = preg_replace('/background-image\s*:\s*url\([^)]*\)\s*;?\s*/i', '', $matches[3]);
×
63
            return '<' . $matches[1] . ' style=' . $matches[2] . $content . $matches[2];
×
64
        },
×
65
        $html
×
66
    );
×
67

68
    return $html;
×
69
}}
70

71
/**
72
 * Convert HTML to plain text
73
 * @param string $html content to convert
74
 * @return string
75
 */
76
if (!hm_exists('convert_html_to_text')) {
77
function convert_html_to_text($html) {
78
    $html = new HTMLToText($html);
2✔
79
    return $html->text;
2✔
80
}}
81

82
/**
83
 * Format image data
84
 * @subpackage core/functions
85
 * @param string $str binary image data
86
 * @param string $mime_type type of image
87
 * return string
88
 */
89
if (!hm_exists('format_msg_image')) {
90
function format_msg_image($str, $mime_type) {
91
    return '<img class="msg_img" alt="" src="data:image/'.$mime_type.';base64,'.chunk_split(base64_encode($str)).'" />';
1✔
92
}}
93

94
/**
95
 * Format a plain text message
96
 * @subpackage core/functions
97
 * @param string $str message text
98
 * @param object $output_mod Hm_Output_Module
99
 */
100
if (!hm_exists('format_msg_text')) {
101
function format_msg_text($str, $output_mod, $links=true) {
102
    $str = str_replace("\t", '    ', $str);
1✔
103
    $str = nl2br(str_replace(' ', '<wbr>', ($output_mod->html_safe($str)))).'<br />';
1✔
104
    $str = preg_replace("/(&(?!amp)[^;]+;)/", " $1", $str);
1✔
105
    if ($links) {
1✔
106
        $link_regex = "/((http|ftp|rtsp)s?:\/\/(%[[:digit:]A-Fa-f][[:digit:]A-Fa-f]|[-_\.!~\*';\/\?#:@&=\+$,%[:alnum:]])+)/m";
1✔
107
        $str = preg_replace($link_regex, "<a href=\"$1\" target=\"_blank\" rel=\"noopener\">$1</a>", $str);
1✔
108
    }
109
    $str = preg_replace("/ (&[^;]+;)/", "$1", $str);
1✔
110
    $str = str_replace('<wbr>', '&#160;<wbr>', $str);
1✔
111
    return preg_replace("/^(&gt;.*<br \/>)/m", "<span class=\"reply_quote\">$1</span>", $str);
1✔
112
}}
113

114
/**
115
 * Format reply text
116
 * @subpackage core/functions
117
 * @param string $txt message text
118
 * @return string
119
 */
120
if (!hm_exists('format_reply_text')) {
121
function format_reply_text($txt) {
122
    $lines = explode("\n", $txt);
5✔
123
    $new_lines = array();
5✔
124
    foreach ($lines as $line) {
5✔
125
        $pre = '> ';
5✔
126
        if (preg_match("/^(>\s*)+/", $line, $matches)) {
5✔
127
            $pre .= $matches[1];
1✔
128
        }
129
        $wrap = 75 + mb_strlen($pre);
5✔
130
        $new_lines[] = preg_replace("/$pre /", "$pre", "> ".wordwrap($line, $wrap, "\n$pre"));
5✔
131
    }
132
    return implode("\n", $new_lines);
5✔
133
}}
134

135
/**
136
 * Get reply to address
137
 * @subpackage core/functions
138
 * @param array $headers message headers
139
 * @param string $type type (forward, reply, reply_all)
140
 * @return string
141
 */
142
if (!hm_exists('reply_to_address')) {
143
function reply_to_address($headers, $type) {
144
    $msg_to = '';
2✔
145
    $msg_cc = '';
2✔
146
    $headers = lc_headers($headers);
2✔
147
    $parsed = array();
2✔
148

149
    if ($type == 'forward') {
2✔
150
        return $msg_to;
1✔
151
    }
152
    foreach (array('reply-to', 'from', 'sender', 'return-path') as $fld) {
2✔
153
        if (array_key_exists($fld, $headers)) {
2✔
154
            list($parsed, $msg_to) = format_reply_address($headers[$fld], $parsed);
2✔
155
            if ($msg_to) {
2✔
156
                break;
2✔
157
            }
158
        }
159
    }
160
    if ($type == 'reply_all') {
2✔
161
        if (array_key_exists('cc', $headers)) {
1✔
162
            list($cc_parsed, $msg_cc) = format_reply_address($headers['cc'], $parsed);
1✔
163
            $parsed += $cc_parsed;
1✔
164
        }
165
        if (array_key_exists('to', $headers)) {
1✔
166
            list($parsed, $recips) = format_reply_address($headers['to'], $parsed);
1✔
167
            if ($recips) {
1✔
168
                if ($msg_cc) {
1✔
169
                    $msg_cc .= ', '.$recips;
1✔
170
                }
171
                else {
172
                    $msg_cc = $recips;
1✔
173
                }
174
            }
175
        }
176
    }
177
    return array($msg_to, $msg_cc);
2✔
178
}}
179

180
/*
181
 * Format a reply address line
182
 * @param string $fld the field values from the E-mail being replied to
183
 * @param array $excluded list of parsed addresses to exclude
184
 * @return string
185
 */
186
if (!hm_exists('format_reply_address')) {
187
function format_reply_address($fld, $excluded) {
188
    $addr = process_address_fld(trim($fld));
2✔
189
    $res = array();
2✔
190
    foreach ($addr as $v) {
2✔
191
        $skip = false;
2✔
192
        foreach ($excluded as $ex) {
2✔
193
            if (mb_strtolower($v['email']) == mb_strtolower($ex['email'])) {
1✔
194
                $skip = true;
1✔
195
                break;
1✔
196
            }
197
        }
198
        if (!$skip) {
2✔
199
            $res[] = $v;
2✔
200
        }
201
    }
202
    if ($res) {
2✔
203
        return array($addr, implode(', ', array_map(function($v) {
2✔
204
            if (trim($v['label'])) {
2✔
205
                return str_replace([',', ';'], '', $v['label']).' '.$v['email'];
1✔
206
            }
207
            else {
208
                return $v['email'];
2✔
209
            }
210
        }, $res)));
2✔
211
    }
212
    return array($addr, '');
1✔
213
}}
214

215
/**
216
 * Get reply to subject
217
 * @subpackage core/functions
218
 * @param array $headers message headers
219
 * @param string $type type (forward, reply, reply_all)
220
 * @return string
221
 */
222
if (!hm_exists('reply_to_subject')) {
223
function reply_to_subject($headers, $type) {
224
    $subject = '';
2✔
225
    if (array_key_exists('Subject', $headers)) {
2✔
226
        if ($type == 'reply' || $type == 'reply_all') {
1✔
227
            if (!preg_match("/^re:/i", trim($headers['Subject']))) {
1✔
228
                $subject = sprintf("Re: %s", $headers['Subject']);
1✔
229
            }
230
        }
231
        elseif ($type == 'forward') {
1✔
232
            if (!preg_match("/^fwd:/i", trim($headers['Subject']))) {
1✔
233
                $subject = sprintf("Fwd: %s", $headers['Subject']);
1✔
234
            }
235
        }
236
        if (!$subject) {
1✔
237
            $subject = $headers['Subject'];
1✔
238
        }
239
    }
240
    return $subject;
2✔
241
}}
242

243
/**
244
 * Get reply message lead in
245
 * @subpackage core/functions
246
 * @param array $headers message headers
247
 * @param string $type type (forward, reply, reply_all)
248
 * @param string $to reply to value
249
 * @param object $output_mod output module object
250
 * @return string
251
 */
252
if (!hm_exists('reply_lead_in')) {
253
function reply_lead_in($headers, $type, $to, $output_mod) {
254
    $lead_in = '';
2✔
255
    if ($type == 'reply' || $type == 'reply_all') {
2✔
256
        if (array_key_exists('Date', $headers)) {
2✔
257
            if ($to) {
1✔
258
                $lead_in = sprintf($output_mod->trans('On %s %s said')."\n\n", $headers['Date'], $to);
1✔
259
            }
260
            else {
261
                $lead_in = sprintf($output_mod->trans('On %s, somebody said')."\n\n", $headers['Date']);
2✔
262
            }
263
        }
264
    }
265
    elseif ($type == 'forward') {
1✔
266
        $flds = array();
1✔
267
        foreach( array('From', 'Date', 'Subject', 'To', 'Cc') as $fld) {
1✔
268
            if (array_key_exists($fld, $headers)) {
1✔
269
                $flds[$fld] = $headers[$fld];
1✔
270
            }
271
        }
272
        $lead_in = "\n\n----- ".$output_mod->trans('begin forwarded message')." -----\n\n";
1✔
273
        foreach ($flds as $fld => $val) {
1✔
274
            $lead_in .= $fld.': '.$val."\n";
1✔
275
        }
276
        $lead_in .= "\n";
1✔
277
    }
278
    return $lead_in;
2✔
279
}}
280

281
/**
282
 * Format reply field details
283
 * @subpackage core/functions
284
 * @param array $headers message headers
285
 * @param string $body message body
286
 * @param string $lead_in body lead in text
287
 * @param string $reply_type type (forward, reply, reply_all)
288
 * @param array $struct message structure details
289
 * @param int $html set to 1 if the output should be HTML
290
 * @return array
291
 */
292
if (!hm_exists('reply_format_body')) {
293
function reply_format_body($headers, $body, $lead_in, $reply_type, $struct, $html) {
294
    $msg = '';
2✔
295
    $type = 'textplain';
2✔
296
    if (array_key_exists('type', $struct) && array_key_exists('subtype', $struct)) {
2✔
297
        $type = mb_strtolower($struct['type']).mb_strtolower($struct['subtype']);
2✔
298
    }
299
    if ($html == 1) {
2✔
300
        $msg = format_reply_as_html($body, $type, $reply_type, $lead_in);
1✔
301
    }
302
    else {
303
        $msg = format_reply_as_text($body, $type, $reply_type, $lead_in);
2✔
304
    }
305
    return $msg;
2✔
306
}}
307

308
/**
309
 * Format reply text as HTML
310
 * @subpackage core/functions
311
 * @param string $body message body
312
 * @param string $type MIME type
313
 * @param string $reply_type type (forward, reply, reply_all)
314
 * @param string $lead_in body lead in text
315
 * @return string
316
 */
317
if (!hm_exists('format_reply_as_html')) {
318
function format_reply_as_html($body, $type, $reply_type, $lead_in) {
319
    if ($type == 'textplain') {
2✔
320
        if ($reply_type == 'reply' || $reply_type == 'reply_all') {
2✔
321
            $msg = nl2br($lead_in.format_reply_text($body));
2✔
322
        }
323
        elseif ($reply_type == 'forward') {
1✔
324
            $msg = nl2br($lead_in.$body);
2✔
325
        }
326
    }
327
    elseif ($type == 'texthtml') {
1✔
328
        $msg = nl2br($lead_in).'<hr /><blockquote>'.format_msg_html($body).'</blockquote>';
1✔
329
    }
330
    return $msg;
2✔
331
}}
332

333
/**
334
 * Format reply text as text
335
 * @subpackage core/functions
336
 * @param string $body message body
337
 * @param string $type MIME type
338
 * @param string $reply_type type (forward, reply, reply_all)
339
 * @param string $lead_in body lead in text
340
 * @return string
341
 */
342
if (!hm_exists('format_reply_as_text')) {
343
function format_reply_as_text($body, $type, $reply_type, $lead_in) {
344
    $msg = '';
3✔
345
    if ($type == 'texthtml') {
3✔
346
        if ($reply_type == 'reply' || $reply_type == 'reply_all') {
1✔
347
            $msg = $lead_in.format_reply_text(convert_html_to_text($body));
1✔
348
        }
349
        elseif ($reply_type == 'forward') {
1✔
350
            $msg = $lead_in.convert_html_to_text($body);
1✔
351
        }
352
    }
353
    elseif ($type == 'textplain') {
3✔
354
        if ($reply_type == 'reply' || $reply_type == 'reply_all') {
3✔
355
            $msg = $lead_in.format_reply_text($body);
3✔
356
        }
357
        else {
358
            $msg = $lead_in.$body;
1✔
359
        }
360
    }
361
    return $msg;
3✔
362
}}
363

364
/**
365
 * Convert header keys to lowercase versions
366
 * @param array $headers message headers
367
 * @return array
368
 */
369
if (!hm_exists('lc_headers')) {
370
function lc_headers($headers) {
371
    return array_change_key_case($headers, CASE_LOWER);
3✔
372
}}
373

374
/**
375
 * Get the in-reply-to message id for replied
376
 * @subpackage core/functions
377
 * @param array $headers message headers
378
 * @param string $type reply type
379
 * @return string
380
 */
381
if (!hm_exists('reply_to_id')) {
382
function reply_to_id($headers, $type) {
383
    $id = '';
2✔
384
    $headers = lc_headers($headers);
2✔
385
    if ($type != 'forward' && array_key_exists('message-id', $headers)) {
2✔
386
        $id = $headers['message-id'];
1✔
387
    }
388
    return $id;
2✔
389
}}
390

391
/**
392
 * Get reply field details
393
 * @subpackage core/functions
394
 * @param string $body message body
395
 * @param array $headers message headers
396
 * @param array $struct message structure details
397
 * @param int $html set to 1 if the output should be HTML
398
 * @param string $type optional type (forward, reply, reply_all)
399
 * @param object $output_mod output module object
400
 * @param string $type the reply type
401
 * @return array
402
 */
403
if (!hm_exists('format_reply_fields')) {
404
function format_reply_fields($body, $headers, $struct, $html, $output_mod, $type='reply') {
405
    $msg_to = '';
1✔
406
    $msg = '';
1✔
407
    $subject = reply_to_subject($headers, $type);
1✔
408
    $msg_id = reply_to_id($headers, $type);
1✔
409
    list($msg_to, $msg_cc) = reply_to_address($headers, $type);
1✔
410
    $lead_in = reply_lead_in($headers, $type, $msg_to, $output_mod);
1✔
411
    $msg = reply_format_body($headers, $body, $lead_in, $type, $struct, $html);
1✔
412
    return array($msg_to, $msg_cc, $subject, $msg, $msg_id);
1✔
413
}}
414

415
/**
416
 * decode mail fields to human readable text
417
 * @param string $string field to decode
418
 * @return string decoded field
419
 */
420
if (!hm_exists('decode_fld')) {
421
function decode_fld($string) {
422
    if (mb_strpos($string, '=?') === false) {
1✔
423
        return $string;
1✔
424
    }
425
    $string = preg_replace("/\?=\s+=\?/", '?==?', $string);
1✔
426
    if (preg_match_all("/(=\?[^\?]+\?(q|b)\?[^\?]+\?=)/i", $string, $matches)) {
1✔
427
        foreach ($matches[1] as $v) {
1✔
428
            $fld = mb_substr($v, 2, -2);
1✔
429
            $charset = mb_strtolower(mb_substr($fld, 0, mb_strpos($fld, '?')));
1✔
430
            $fld = mb_substr($fld, (mb_strlen($charset) + 1));
1✔
431
            $encoding = $fld[0];
1✔
432
            $fld = mb_substr($fld, (mb_strpos($fld, '?') + 1));
1✔
433
            if (mb_strtoupper($encoding) == 'B') {
1✔
434
                $fld = convert_to_utf8(base64_decode($fld), $charset);
1✔
435
            }
436
            elseif (mb_strtoupper($encoding) == 'Q') {
1✔
437
                $fld = convert_to_utf8(quoted_printable_decode(str_replace('_', ' ', $fld)), $charset);
1✔
438
            }
439
            $string = str_replace($v, $fld, $string);
1✔
440
        }
441
    }
442
    return trim($string);
1✔
443
}}
444

445
if (!hm_exists('convert_to_utf8')) {
446
function convert_to_utf8($data, $from_encoding) {
447
    try {
448
        $data = mb_convert_encoding($data, 'UTF-8', $from_encoding);
1✔
449
    } catch (ValueError $e) {
×
450
        $data = iconv($from_encoding, 'UTF-8', $data);
×
451
    }
452
    return $data;
1✔
453
}}
454

455
/**
456
 * @subpackage core/class
457
 */
458
class HTMLToText {
459

460
    public $text = '';
461
    private $current = false;
462
    private $blocks = array('table', 'li', 'div', 'h1', 'h2', 'br', 'h3', 'h4', 'h5', 'p', 'tr');
463
    private $skips = array('head', 'script', 'style');
464

465
    function __construct($html) {
2✔
466
        $doc = new DOMDocument();
2✔
467
        libxml_use_internal_errors(true);
2✔
468

469
        // Check if already valid UTF-8, if not convert it
470
        if (!mb_check_encoding($html, 'UTF-8')) {
2✔
471
            // Try to detect and convert the encoding
472
            $html = mb_convert_encoding($html, 'UTF-8', mb_detect_encoding($html, mb_list_encodings(), true));
×
473
        }
474
        $doc->loadHTML('<?xml encoding="UTF-8">' . $html);
2✔
475
        libxml_clear_errors();
2✔
476

477
        if (trim($html) && $doc->hasChildNodes()) {
2✔
478
            $this->parse_nodes($doc->childNodes);
2✔
479
        }
480
        $this->text = trim(strip_tags(html_entity_decode(preg_replace("/\n{2,}/m", "\n\n", $this->text), ENT_QUOTES, "UTF-8")));
2✔
481
    }
482

483
    function block($tag) {
2✔
484
        in_array($tag, $this->blocks) && $this->current != $tag ? $this->text .= "\n" : false;
2✔
485
        $this->current = $tag;
2✔
486
    }
487

488
    function parse_nodes($nodes) {
2✔
489
        $trims = " \t\n\r\0\x0B";
2✔
490
        foreach ($nodes as $node) {
2✔
491
            if (!in_array($node->nodeName, $this->skips)) {
2✔
492
                $this->block($node->nodeName);
2✔
493
                if ($node->nodeName == '#text' && trim($node->textContent, $trims)) {
2✔
494
                    $this->text .= trim($node->textContent, $trims)." ";
2✔
495
                }
496
                $node->hasChildNodes() ? $this->parse_nodes($node->childNodes) : false;
2✔
497
            }
498
        }
499
    }
500
}
501

502
/**
503
 * trim a potential E-mail value
504
 * @param $val string E-mail value
505
 * @return string trimmed value
506
 */
507
if (!hm_exists('addr_split')) {
508
function trim_email($val) {
509
    $seps = array(',', ';');
3✔
510
    $misc = array('"', "'", '>', '<');
3✔
511
    return trim($val, implode(array_merge($misc, $seps)));
3✔
512
}}
513

514
/**
515
 * Split an address field
516
 * @param $str string field value
517
 * @param $seps array break chars
518
 * @return array results
519
 */
520
if (!hm_exists('addr_split')) {
521
function addr_split($str, $seps = array(',', ';')) {
522
    $str = preg_replace('/(\s){2,}/', ' ', $str);
4✔
523
    $max = mb_strlen($str);
4✔
524
    $word = '';
4✔
525
    $words = array();
4✔
526
    $capture = false;
4✔
527
    $capture_chars = array('"' => '"', '(' => ')', '<' => '>');
4✔
528
    for ($i=0;$i<$max;$i++) {
4✔
529
        $char = mb_substr($str, $i, 1);
4✔
530
        if ($capture && $capture_chars[$capture] == $char) {
4✔
531
            $capture = false;
2✔
532
        }
533
        elseif (!$capture && in_array($char, array_keys($capture_chars))) {
4✔
534
            $capture = $char;
2✔
535
        }
536

537
        if (!$capture && in_array($char, $seps)) {
4✔
538
            $words[] = trim($word);
2✔
539
            $word = '';
2✔
540
        }
541
        else {
542
            $word .= $char;
4✔
543
        }
544
    }
545
    $words[] = trim($word);
4✔
546
    return $words;
4✔
547
}}
548

549
/**
550
 * Parse an address field
551
 * @param $str string field value
552
 * @return array results
553
 */
554
if (!hm_exists('addr_parse')) {
555
function addr_parse($str) {
556
    $label = array();
3✔
557
    $email = '';
3✔
558
    $comment = array();
3✔
559
    foreach (addr_split($str, array(' ')) as $token) {
3✔
560
        if (is_email_address(trim_email($token))) {
3✔
561
            $email = trim_email($token);
3✔
562
        }
563
        else {
564
            $label[] = $token;
2✔
565
        }
566
    }
567
    $label = implode(' ', $label);
3✔
568
    if (preg_match('/\([^)]+\)/', $label, $matches)) {
3✔
569
        foreach ($matches as $match) {
1✔
570
            $comment[] = $match;
1✔
571
            $label = str_replace($match, '', $label);
1✔
572
        }
573
        $comment = implode(',', $comment);
1✔
574
    }
575
    else {
576
        $comment = '';
3✔
577
    }
578
    return array('email' => $email, 'label' => preg_replace('/[\pZ\pC]+/u', ' ', trim($label, ' \'"')), 'comment' => $comment);
3✔
579
}}
580

581
/**
582
 * Parse an address field
583
 * @param $fld string field value
584
 * @return array results
585
 */
586
if (!hm_exists('process_address_fld')) {
587
function process_address_fld($fld) {
588
    $res = array();
3✔
589
    $count = 0;
3✔
590
    $pre = false;
3✔
591
    foreach (addr_split($fld) as $str) {
3✔
592
        $addr = addr_parse($str);
3✔
593
        if ($addr['email']) {
3✔
594
            if ($pre) {
3✔
595
                $addr['label'] = $pre.' '.$addr['label'];
×
596
                $pre = false;
×
597
            }
598
            $res[$count] = $addr;
3✔
599
        }
600
        elseif ($addr['label']) {
1✔
601
            $pre = $addr['label'];
1✔
602
        }
603
        $count++;
3✔
604
    }
605
    return $res;
3✔
606
}}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc