• 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

57.34
/modules/core/functions.php
1
<?php
2

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

9
/**
10
 * Format a value for display
11
 * @subpackage core/functions
12
 * @param string $name value name to find/format
13
 * @param array $haystack details to search for the value name
14
 * @param bool $type optional format type
15
 * @param mixed $default value to return if the name is not found
16
 * @return string
17
 */
18
if (!hm_exists('display_value')) {
19
function display_value($name, $haystack, $type=false, $default='') {
20
    if (!array_key_exists($name, $haystack)) {
1✔
21
        return $default;
1✔
22
    }
23
    $value = $haystack[$name];
1✔
24
    $res = false;
1✔
25
    if ($type) {
1✔
26
        $name = $type;
1✔
27
    }
28
    switch($name) {
29
        case 'from':
1✔
30
            $value = preg_replace("/(\<.+\>)/U", '', $value);
1✔
31
            $res = str_replace('"', '', $value);
1✔
32
            break;
1✔
33
        case 'date':
1✔
34
            $res = human_readable_interval($value);
1✔
35
            break;
1✔
36
        case 'time':
1✔
37
            $res = strtotime($value);
1✔
38
            break;
1✔
39
        default:
40
            $res = $value;
1✔
41
            break;
1✔
42
    }
43
    return $res;
1✔
44
}}
45

46
/**
47
 * Valid interface langs
48
 * @subpackage core/functions
49
 * @return array
50
 */
51
if (!hm_exists('interface_langs')) {
52
function interface_langs() {
53
    return array(
4✔
54
        'en' => 'English',
4✔
55
        'de' => 'German',
4✔
56
        'es' => 'Spanish',
4✔
57
        'fa' => 'Farsi',
4✔
58
        'fr' => 'French',
4✔
59
        'et' => 'Estonian',
4✔
60
        'id' => 'Indonesian',
4✔
61
        'it' => 'Italian',
4✔
62
        'ru' => 'Russian',
4✔
63
        'ro' => 'Romanian',
4✔
64
        'nl' => 'Dutch',
4✔
65
        'ja' => 'Japanese',
4✔
66
        'hu' => 'Hungarian',
4✔
67
        'pl' => 'Polish',
4✔
68
        'pt-BR' => 'Brazilian Portuguese',
4✔
69
        'az' => 'Azerbaijani',
4✔
70
        'zh-Hans' => 'Chinese Simplified',
4✔
71
        'zh-TW' => 'Traditional Chinese',
4✔
72
    );
4✔
73
}}
74

75
/**
76
 * Tranlate a human readable time string
77
 * @subpackage core/functions
78
 * @param string $str string to translate
79
 * @param object $output_mod Hm_Output_Module
80
 * @return string
81
 */
82
if (!hm_exists('translate_time_str')) {
83
function translate_time_str($str, $output_mod) {
84
    $parts = explode(',', $str);
1✔
85
    $res = array();
1✔
86
    foreach ($parts as $part) {
1✔
87
        $part = trim($part);
1✔
88
        if (preg_match("/(\d+)/", $part, $matches)) {
1✔
89
            $res[] = sprintf($output_mod->trans(preg_replace("/(\d+)/", '%d', $part)), $matches[1]);
1✔
90
        }
91
    }
92
    if (!empty($res)) {
1✔
93
        return implode(', ', $res);
1✔
94
    }
95
    return $str;
1✔
96
}}
97

98
/**
99
 * Format a data source to be a valid JS object
100
 * @subpackage core/functions
101
 * @param array $array values to format
102
 * @param object $output_mod Hm_Output_Module
103
 * @return string
104
 */
105
if (!hm_exists('format_data_sources')) {
106
function format_data_sources($array, $output_mod) {
107
    $result = '';
2✔
108
    $default = false;
2✔
109
    $groups = group_data_sources($array);
2✔
110
    foreach ($groups as $group_name => $sources) {
2✔
111
        $objects = array();
1✔
112
        foreach ($sources as $values) {
1✔
113
            $items = array();
1✔
114
            $custom = array_key_exists('type', $values) && $values['type'] == 'custom';
1✔
115
            foreach ($values as $name => $value) {
1✔
116
                if ($custom && $name == 'params') {
1✔
117
                    $value_output = json_encode($value);
×
118
                } else {
119
                    $value_output = '"'.$output_mod->html_safe($value).'"';
1✔
120
                }
121

122
                $items[] = $output_mod->html_safe($name).':'.$value_output;
1✔
123
            }
124
            $objects[] = '{'.implode(',', $items).'}';
1✔
125
        }
126
        $function = 'hm_data_sources';
1✔
127
        if ($group_name != 'default') {
1✔
128
            $function .= '_'.$group_name;
1✔
129
        }
130
        else {
131
            $default = true;
1✔
132
        }
133
        $result .= 'var '.$function.' = function() { return ['.implode(',', $objects).']; };';
1✔
134
    }
135
    if (!$default) {
2✔
136
        $result .= 'var hm_data_sources = function() { return []; };';
2✔
137
    }
138
    return $result;
2✔
139
}}
140

141
/**
142
 * Group data sources by the "group" attribute if it exists, otherwise use "default"
143
 * @subpackage core/functions
144
 * @param array $array list of data sources
145
 * @return array
146
 */
147
if (!hm_exists('group_data_sources')) {
148
function group_data_sources($array) {
149
    $groups = array();
2✔
150
    foreach($array as $vals) {
2✔
151
        $key = 'default';
1✔
152
        if (array_key_exists('group', $vals)) {
1✔
153
            $key = $vals['group'];
1✔
154
        }
155
        $groups[$key][] = $vals;
1✔
156
    }
157
    return $groups;
2✔
158
}}
159

160
/**
161
 * Determine if E-mail modules are active
162
 * @subpackage core/functions
163
 * @param array $mod_list list of active module sets
164
 * @return mixed
165
 */
166
if (!hm_exists('email_is_active')) {
167
function email_is_active($mod_list) {
168
    if (in_array('imap', $mod_list, true)) {
4✔
169
        return true;
4✔
170
    }
171
    return false;
4✔
172
}}
173

174
/**
175
 * Validate an E-mail using RFC 3696
176
 * @subpackage core/functions
177
 * @param string $val value to check
178
 * @param bool $allow_local flag to allow local addresses with no domain
179
 * @return bool
180
 */
181
if (!hm_exists('is_email_address')) {
182
function is_email_address($val, $allow_local=false) {
183
    $val = trim($val, "<>");
8✔
184
    $val = trim($val);
8✔
185

186
    if (!$val || mb_strlen($val) > 320) { // RFC 5321: max 320 chars
8✔
187
        return false;
2✔
188
    }
189

190
    $at_pos = mb_strrpos($val, '@');
8✔
191
    if ($at_pos === false) {
8✔
192
        // No @ symbol - only valid if $allow_local is true
193
        return $allow_local && validate_local_full($val);
4✔
194
    }
195

196
    if ($at_pos === 0 || $at_pos === mb_strlen($val) - 1) {
7✔
197
        return false; // @ at beginning or end
×
198
    }
199

200
    $local = mb_substr($val, 0, $at_pos);
7✔
201
    $domain = mb_substr($val, $at_pos + 1);
7✔
202

203
    if (!$local || mb_strlen($local) > 64) { // RFC 5321: max 64 chars for local part
7✔
204
        return false;
×
205
    }
206

207
    if (!$domain || mb_strlen($domain) > 255) { // RFC 5321: max 255 chars for domain
7✔
208
        return false;
×
209
    }
210

211
    // Validate local part
212
    if (!validate_local_full($local)) {
7✔
213
        return false;
1✔
214
    }
215

216
    // Validate domain (supports standard domains and domain literals)
217
    if (!validate_domain_full($domain)) {
7✔
218
        return false;
1✔
219
    }
220

221
    return true;
7✔
222
}}
223

224
/**
225
 * Do email domain part checks per RFC 3696 section 2
226
 * @subpackage core/functions
227
 * @param string $val value to check
228
 * @return bool
229
 */
230
if (!hm_exists('validate_domain_full')) {
231
function validate_domain_full($val) {
232
    if (!$val || mb_strlen($val) > 255) { // RFC 5321: max 255 chars
7✔
233
        return false;
×
234
    }
235

236
    // Check for domain literal (IP address in brackets)
237
    if (substr($val, 0, 1) === '[' && substr($val, -1) === ']') {
7✔
238
        $literal = substr($val, 1, -1);
×
239

240
        // IPv4: [192.168.1.1]
241
        if (preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $literal)) {
×
242
            // Validate IPv4 ranges (0-255)
243
            $parts = explode('.', $literal);
×
244
            foreach ($parts as $part) {
×
245
                if ((int)$part > 255) {
×
246
                    return false;
×
247
                }
248
            }
249
            return true;
×
250
        }
251

252
        // IPv6: [IPv6:2001:db8::1]
253
        if (preg_match('/^IPv6:/i', $literal)) {
×
254
            $ipv6 = substr($literal, 5);
×
255

256
            // Basic IPv6 validation
257
            if (!$ipv6 || !preg_match('/^[a-fA-F0-9:]+$/', $ipv6)) {
×
258
                return false;
×
259
            }
260

261
            // Check for valid :: usage (at most one occurrence)
262
            $double_colon_count = substr_count($ipv6, '::');
×
263
            if ($double_colon_count > 1) {
×
264
                return false;
×
265
            }
266

267
            // Validate groups
268
            if ($double_colon_count === 1) {
×
269
                // With :: compression
270
                $segments = explode('::', $ipv6);
×
271
                $left_parts = $segments[0] ? explode(':', $segments[0]) : [];
×
272
                $right_parts = isset($segments[1]) && $segments[1] ? explode(':', $segments[1]) : [];
×
273
                $total_parts = count($left_parts) + count($right_parts);
×
274

275
                if ($total_parts >= 8) {
×
276
                    return false;
×
277
                }
278

279
                $parts = array_merge($left_parts, $right_parts);
×
280
            } else {
281
                // No :: compression, must have exactly 8 groups
282
                $parts = explode(':', $ipv6);
×
283
                if (count($parts) !== 8) {
×
284
                    return false;
×
285
                }
286
            }
287

288
            // Validate each part (1-4 hex digits)
289
            foreach ($parts as $part) {
×
290
                if ($part === '') continue;
×
291
                if (strlen($part) > 4 || !preg_match('/^[a-fA-F0-9]+$/', $part)) {
×
292
                    return false;
×
293
                }
294
            }
295

296
            return true;
×
297
        }
298

299
        return false; // Unknown domain literal format
×
300
    }
301

302
    // Standard domain validation
303
    // Split by dots to get labels
304
    $labels = explode('.', $val);
7✔
305
    if (count($labels) < 2) { // At least domain.tld
7✔
306
        return false;
1✔
307
    }
308

309
    foreach ($labels as $label) {
7✔
310
        if (!$label || mb_strlen($label) > 63) { // RFC 1035: max 63 chars per label
7✔
311
            return false;
×
312
        }
313

314
        // Labels cannot start or end with hyphen
315
        if (substr($label, 0, 1) === '-' || substr($label, -1) === '-') {
7✔
316
            return false;
×
317
        }
318

319
        // Allow alphanumeric, hyphen, and Unicode for IDN
320
        if (!preg_match('/^[a-zA-Z0-9\-\x{0080}-\x{FFFF}]+$/u', $label)) {
7✔
321
            return false;
×
322
        }
323
    }
324

325
    return true;
7✔
326
}}
327

328
/**
329
 * Do email local part checks per RFC 3696 section 3
330
 * @subpackage core/functions
331
 * @param string $val value to check
332
 * @return bool
333
 */
334
if (!hm_exists('validate_local_full')) {
335
function validate_local_full($val) {
336
    if (!$val || mb_strlen($val) > 64) { // RFC 5321: max 64 chars
7✔
337
        return false;
×
338
    }
339

340
    // Check if local part is quoted
341
    if (substr($val, 0, 1) === '"' && substr($val, -1) === '"') {
7✔
342
        // Quoted string - almost anything goes inside quotes
343
        $quoted = substr($val, 1, -1);
×
344

345
        // Check for unescaped quotes (quotes must be escaped with backslash)
346
        // This is a simplified check - proper implementation would need to handle escaped backslashes
347
        $temp = str_replace('\\\\', '', $quoted); // Remove escaped backslashes
×
348
        $temp = str_replace('\\"', '', $temp);     // Remove escaped quotes
×
349
        if (strpos($temp, '"') !== false) {
×
350
            return false; // Found unescaped quote
×
351
        }
352

353
        return true;
×
354
    }
355

356
    // Unquoted local part - standard rules
357
    // Cannot start or end with dot, no consecutive dots
358
    if (substr($val, 0, 1) === '.' || substr($val, -1) === '.' || strpos($val, '..') !== false) {
7✔
359
        return false;
×
360
    }
361

362
    // Allow only permitted characters for unquoted local part
363
    // A-Z a-z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~
364
    if (!preg_match('/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+$/', $val)) {
7✔
365
        return false;
1✔
366
    }
367

368
    return true;
7✔
369
}}
370

371
/**
372
 * Get Oauth2 server info
373
 * @subpackage core/functions
374
 * @param object $config site config object
375
 * @return array
376
 */
377
if (!hm_exists('get_oauth2_data')) {
378
function get_oauth2_data($config) {
379
    return [
1✔
380
        'gmail' => $config->get('gmail',[]),
1✔
381
        'outlook' => $config->get('outlook',[]),
1✔
382
        'office365' => $config->get('office365',[]),
1✔
383
    ];
1✔
384
}}
385

386
/**
387
 * Process user input for a site setting and prep it to be saved
388
 * @subpackage core/functions
389
 * @param string $type the name of the setting
390
 * @param object $handler hm hanndler module object
391
 * @param function $callback a function to sanitize the submitted value
392
 * @param mixed $default a default to use if callback is not submitted
393
 * @param bool $checkbox true if this is a checkbox setting
394
 * @return void
395
 */
396
if (!hm_exists('process_site_setting')) {
397
function process_site_setting($type, $handler, $callback=false, $default=false, $checkbox=false) {
398
    if ($checkbox) {
22✔
399
        list($success, $form) = $handler->process_form(array('save_settings'));
6✔
400
        if (array_key_exists($type, $handler->request->post)) {
6✔
401
            $form = array($type => $handler->request->post[$type]);
6✔
402
        }
403
        else {
404
            $form = array($type => false);
6✔
405
        }
406
    }
407
    else {
408
        list($success, $form) = $handler->process_form(array('save_settings', $type));
17✔
409
    }
410
    $new_settings = $handler->get('new_user_settings', array());
22✔
411
    $settings = $handler->get('user_settings', array());
22✔
412

413
    if ($success) {
22✔
414
        if (function_exists($callback)) {
22✔
415
            $result = $callback($form[$type], $type, $handler);
22✔
416
        }
417
        else {
418
            $result = $default;
1✔
419
        }
420
        $new_settings[$type.'_setting'] = $result;
22✔
421
    }
422
    else {
423
        $settings[$type] = $handler->user_config->get($type.'_setting', $default);
1✔
424
    }
425
    $handler->out('new_user_settings', $new_settings, false);
22✔
426
    $handler->out('user_settings', $settings, false);
22✔
427
}}
428

429
/**
430
 * Return a date for a "received since" value, or just sanitize it
431
 * @subpackage core/functions
432
 * @param string $val "received since" value to process
433
 * @param bool $validate flag to limit to validation only
434
 */
435
if (!hm_exists('process_since_argument')) {
436
function process_since_argument($val, $validate=false) {
437
    $date = false;
7✔
438
    $valid = false;
7✔
439
    if (in_array($val, array('-1 week', '-2 weeks', '-4 weeks', '-6 weeks', '-6 months', '-1 year', '-5 years'), true)) {
7✔
440
        $valid = $val;
3✔
441
        $date = date('j-M-Y', strtotime($val));
3✔
442
    }
443
    else {
444
        $val = 'today';
5✔
445
        $valid = $val;
5✔
446
        $date = date('j-M-Y');
5✔
447
    }
448
    if ($validate) {
7✔
449
        return $valid;
7✔
450
    }
451
    return $date;
1✔
452
}}
453

454
/**
455
 * Sanitize a "since" setting value for combined pages
456
 * @subpackage core/functions
457
 * @param string $val value to check
458
 * @return sanitized value
459
 */
460
if (!hm_exists('since_setting_callback')) {
461
function since_setting_callback($val) {
462
    return process_since_argument($val, true);
5✔
463
}}
464

465
/**
466
 * Sanitize a max per source value
467
 * @subpackage core/functions
468
 * @param int $val request max
469
 * @return sanitized max
470
 */
471
if (!hm_exists('max_source_setting_callback')) {
472
function max_source_setting_callback($val) {
473
    if ($val > MAX_PER_SOURCE || $val < 0) {
5✔
474
        return DEFAULT_PER_SOURCE;
1✔
475
    }
476
    return $val;
5✔
477
}}
478

479
/**
480
 * Save user settings from the session to permanent storage
481
 * @subpackage core/functions
482
 * @param object $handler hm handler module object
483
 * @param array $form sanitized user input
484
 * @param bool $logout true if this is a save + logout request
485
 * @return void
486
 */
487
if (!hm_exists('save_user_settings')) {
488
function save_user_settings($handler, $form, $logout) {
489
    $user = $handler->session->get('username', false);
2✔
490
    $path = $handler->config->get('user_settings_dir', false);
2✔
491

492
    if ($handler->session->auth($user, $form['password'])) {
2✔
493
        $pass = $form['password'];
2✔
494
    }
495
    else {
496
        Hm_Msgs::add('Incorrect password, could not save settings to the server', 'warning');
1✔
497
        $pass = false;
1✔
498
    }
499
    if ($user && $path && $pass) {
2✔
500
        try {
501
            $handler->user_config->save($user, $pass);
2✔
502
            $handler->session->set('changed_settings', array());
2✔
503
            if ($logout) {
2✔
504
                $handler->session->destroy($handler->request);
2✔
505
                Hm_Msgs::add('Saved user data on logout', 'info');
2✔
506
                Hm_Msgs::add('Session destroyed on logout', 'info');
2✔
507
            }
508
            else {
509
                Hm_Msgs::add('Settings saved', 'info');
2✔
510
            }
511
        } catch (Exception $e) {
×
512
            Hm_Msgs::add('Could not save settings: ' . $e->getMessage(), 'warning');
×
513
        }
514
    }
515

516
}}
517

518
/**
519
 * Setup commonly used modules for an ajax request
520
 * @subpackage core/functions
521
 * @param string $name the page id
522
 * @param string $source the module set name
523
 * @return void
524
 */
525
if (!hm_exists('setup_base_ajax_page')) {
526
function setup_base_ajax_page($name, $source=false) {
527
    add_handler($name, 'login', false, $source);
4✔
528
    add_handler($name, 'default_page_data', true, $source);
4✔
529
    add_handler($name, 'load_user_data', true, $source);
4✔
530
    add_handler($name, 'language',  true, $source);
4✔
531
    add_handler($name, 'date', true, $source);
4✔
532
    add_handler($name, 'http_headers', true, $source);
4✔
533
}}
534

535
/**
536
 * Setup commonly used modules for a page
537
 * @subpackage core/functions
538
 * @param string $name the page id
539
 * @param string $source the module set name
540
 * @param bool $use_layout true if this page uses the application layout
541
 * @return void
542
 */
543
if (!hm_exists('setup_base_page')) {
544
function setup_base_page($name, $source=false, $use_layout=true) {
545
    add_handler($name, 'stay_logged_in', false, $source);
4✔
546
    add_handler($name, 'login', false, $source);
4✔
547
    add_handler($name, 'default_page_data', true, $source);
4✔
548
    add_handler($name, 'load_user_data', true, $source);
4✔
549
    add_handler($name, 'message_list_type', true);
4✔
550
    add_handler($name, 'language',  true, $source);
4✔
551
    add_handler($name, 'process_search_terms', true, $source);
4✔
552
    add_handler($name, 'title', true, $source);
4✔
553
    add_handler($name, 'date', true, $source);
4✔
554
    add_handler($name, 'save_user_data', true, $source);
4✔
555
    add_handler($name, 'logout', true, $source);
4✔
556
    add_handler($name, 'http_headers', true, $source);
4✔
557
    add_handler($name, 'version_upgrade_checker', true, $source);
4✔
558

559
    add_output($name, 'header_start', false, $source);
4✔
560
    add_output($name, 'header_css', false, $source);
4✔
561
    add_output($name, 'header_content', false, $source);
4✔
562
    add_output($name, 'js_data', false, $source);
4✔
563
    add_output($name, 'js_search_data', true, $source);
4✔
564
    add_output($name, 'header_end', false, $source);
4✔
565
    add_output($name, 'msgs', false, $source);
4✔
566
    add_output($name, 'content_start', false, $source);
4✔
567
    if($use_layout) {
4✔
568
        add_output($name, 'login_start', false, $source);
4✔
569
        add_output($name, 'login', false, $source);
4✔
570
        add_output($name, 'login_end', false, $source);
4✔
571
        add_output($name, 'date', true, $source);
4✔
572
        add_output($name, 'folder_list_start', true, $source);
4✔
573
        add_output($name, 'folder_list_end', true, $source);
4✔
574
        add_output($name, 'content_section_start', true, $source);
4✔
575
        add_output($name, 'version_upgrade_checker', true, $source, 'content_section_start', 'after');
4✔
576
        add_output($name, 'content_section_end', true, $source);
4✔
577
        add_output($name, 'modals', true, $source);
4✔
578
        add_output($name, 'save_reminder', true, $source);
4✔
579
    }
580
    add_output($name, 'content_end', false, $source, 'page_js', 'after');
4✔
581
    add_output($name, 'page_js', false, $source);
4✔
582
}}
583

584
/**
585
 * Merge array details for folder sources
586
 * @subpackage core/functions
587
 * @param array $folder_sources list of folder list entries
588
 * @return array
589
 */
590
if (!hm_exists('merge_folder_list_details')) {
591
function merge_folder_list_details($folder_sources) {
592
    $res = array();
3✔
593
    if (!is_array($folder_sources)) {
3✔
594
        return $res;
1✔
595
    }
596
    foreach ($folder_sources as $vals) {
3✔
597
        if (array_key_exists($vals[0], $res)) {
3✔
598
            $res[$vals[0]] .= $vals[1];
1✔
599
        }
600
        else {
601
            $res[$vals[0]] = $vals[1];
3✔
602
        }
603
    }
604
    ksort($res);
3✔
605
    return $res;
3✔
606
}}
607

608
/**
609
 * Determine the correct TLS connection type to use based
610
 * on what this version of PHP supports
611
 * @return const
612
 */
613
if (!hm_exists('get_tls_stream_type')) {
614
function get_tls_stream_type() {
615
    $method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
1✔
616
    if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
1✔
617
        $method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
1✔
618
        $method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
1✔
619
    }
620
    if (defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT')) {
1✔
621
        $method |= STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
1✔
622
    }
623
    return $method;
1✔
624
}}
625

626
/**
627
 * List of valid start page options
628
 * @return array
629
 */
630
if (!hm_exists('start_page_opts')) {
631
function start_page_opts() {
632
    return array(
5✔
633
        'None' => 'none',
5✔
634
        'Home' => 'page=home',
5✔
635
        'Everything' => 'page=message_list&list_path=combined_inbox',
5✔
636
        'Unread' => 'page=message_list&list_path=unread',
5✔
637
        'Flagged' => 'page=message_list&list_path=flagged',
5✔
638
        'Compose' => 'page=compose'
5✔
639
    );
5✔
640
}}
641

642
/**
643
 * List of valid default sort order options
644
 * @return array
645
 */
646
if (!hm_exists('default_sort_order_opts')) {
647
function default_sort_order_opts() {
648
    return array(
×
649
        'arrival' => 'Arrival Date',
×
650
        'date' => 'Sent Date',
×
651
    );
×
652
}}
653

654
/**
655
 * See if a host + username is already in a server list
656
 * @param class $list class to check
657
 * @param int $id server id to get hostname from
658
 * @param string $user username to check for
659
 * @return bool
660
 */
661
if (!hm_exists('in_server_list')) {
662
function in_server_list($list, $id, $user) {
663
    $exists = false;
1✔
664
    $server = $list::dump($id);
1✔
665
    $name = false;
1✔
666
    if (is_array($server) && array_key_exists('server', $server)) {
1✔
667
        $name = $server['server'];
1✔
668
    }
669
    if (!$name) {
1✔
670
        return false;
1✔
671
    }
672
    foreach ($list::dump() as $server_id => $vals) {
1✔
673
        if ($id == $server_id) {
1✔
674
            continue;
1✔
675
        }
676
        if (array_key_exists('user', $vals) && $vals['user'] == $user && $vals['server'] == $name) {
1✔
677
            $exists = true;
1✔
678
            break;
1✔
679
        }
680
    }
681
    return $exists;
1✔
682
}}
683

684
/**
685
 * Perform a check on last added server
686
 * It gets deleted if already configured
687
 *
688
 * @param string $list class to process on check
689
 * @param string $user username to check for
690
 * @return bool
691
 */
692
if (!hm_exists('can_save_last_added_server')) {
693
function can_save_last_added_server($list, $user) {
694
    $servers = $list::dump(false, true);
×
695
    $ids = array_keys($servers);
×
696
    $new_id = array_pop($ids);
×
697
    if (in_server_list($list, $new_id, $user)) {
×
698
        $list::del($new_id);
×
699
        $type = explode('_', $list)[1];
×
700
        Hm_Msgs::add('This ' . $type . ' server and username are already configured', 'warning');
×
701
        return false;
×
702
    }
703
    return true;
×
704
}}
705

706
/**
707
 * @subpackage core/functions
708
 */
709
if (!hm_exists('profiles_by_smtp_id')) {
710
function profiles_by_smtp_id($profiles, $id) {
711
    $res = array();
1✔
712
    foreach ($profiles as $vals) {
1✔
713
        if (!is_array($vals)) {
1✔
714
            continue;
1✔
715
        }
716
        if ($vals['smtp_id'] == $id) {
×
717
            $res[] = $vals;
×
718
        }
719
    }
720
    return $res;
1✔
721
}}
722

723
/**
724
 * @subpackage cores/functions
725
 */
726
function get_special_folders($mod, $id) {
727
    $server = Hm_IMAP_List::dump($id);
×
728
    if (!$server) {
×
729
        return array();
×
730
    }
731
    $specials = $mod->user_config->get('special_imap_folders', array());
×
732
    foreach ($specials as $vals) {
×
733
        if (array_key_exists('imap_user', $vals) &&
×
734
            array_key_exists('imap_server', $vals) &&
×
735
            $server['server'] == $vals['imap_server'] &&
×
736
            $server['user'] == $vals['imap_user']) {
×
737

738
            return $vals;
×
739
        }
740
    }
741
    if (array_key_exists($id, $specials)) {
×
742
        return $specials[$id];
×
743
    }
744
    return array();
×
745
}
746

747
/**
748
 * @subpackage core/functions
749
 */
750
if (!hm_exists('check_file_upload')) {
751
function check_file_upload($request, $key) {
752
    if (!is_array($request->files) || !array_key_exists($key, $request->files)) {
×
753
        return false;
×
754
    }
755
    if (!is_array($request->files[$key]) || !array_key_exists('tmp_name', $request->files[$key])) {
×
756
        return false;
×
757
    }
758
    return true;
×
759
}}
760

761
function privacy_setting_callback($val, $key, $mod) {
762
    $setting = Hm_Output_privacy_settings::$settings[$key];
×
763
    $key .= '_setting';
×
764
    $user_setting = $mod->user_config->get($key);
×
765
    $update = $mod->request->post['update'];
×
766
    $pop = $mod->request->post['pop'];
×
767

768
    if ($update) {
×
769
        if ($pop) {
×
770
            $new_value = implode($setting['separator'], array_filter(explode($setting['separator'], $user_setting), function($item) use ($val) {
×
771
                return $item != $val;
×
772
            }));
×
773
        } else {
774
            $new_value = implode($setting['separator'], array_filter(array_merge(explode($setting['separator'], $user_setting), [$val])));
×
775
        }
776
        
777
        $mod->user_config->set($key, $new_value);
×
778

779
        $user_data = $mod->session->get('user_data', array());
×
780
        $user_data[$key] = $new_value;
×
781
        $mod->session->set('user_data', $user_data);
×
782
        $mod->session->record_unsaved('Privacy settings updated');
×
783

784
        return $new_value;
×
785
    }
786
    return $val;
×
787
}
788

789
if (!hm_exists('get_scheduled_date')) {
790
    function get_scheduled_date($format, $only_label = false) {
791
        switch ($format) {
792
            case 'later_in_day':
×
793
                $date_string = 'today 18:00';
×
794
                $label = 'Later in the day';
×
795
                break;
×
796
            case 'tomorrow':
×
797
                $date_string = '+1 day 08:00';
×
798
                $label = 'Tomorrow';
×
799
                break;
×
800
            case 'next_weekend':
×
801
                $date_string = 'next Saturday 08:00';
×
802
                $label = 'Next weekend';
×
803
                break;
×
804
            case 'next_week':
×
805
                $date_string = 'next week 08:00';
×
806
                $label = 'Next week';
×
807
                break;
×
808
            case 'next_month':
×
809
                $date_string = 'next month 08:00';
×
810
                $label = 'Next month';
×
811
                break;
×
812
            default:
813
                $date_string = $format;
×
814
                $label = 'Certain date';
×
815
                break;
×
816
        }
817

818
        $time = strtotime($date_string);
×
819

820
        if ($only_label) {
×
821
            return [$label, date('D, H:i', $time)];
×
822
        }
823

824
        return date('D, d M Y H:i T', $time);
×
825
    }
826
}
827

828

829
/**
830
 * @subpackage imap/functions
831
 */
832
if (!hm_exists('nexter_formats')) {
833
function nexter_formats() {
834
    $values = array(
×
835
        'tomorrow',
×
836
        'next_weekend',
×
837
        'next_week',
×
838
        'next_month'
×
839
    );
×
840
    if (date('H') <= 16) {
×
841
        array_push($values, 'later_in_day');
×
842
    }
843
    return $values;
×
844
}}
845

846
if (!hm_exists('schedule_dropdown')) {
847
function schedule_dropdown($output, $send_now = false, $list_control = false) {
848
    $values = nexter_formats();
×
849

850
    $txt = '';
×
851
    if ($send_now) {
×
852
        $txt .= '<div class="dropdown d-inline-block">
×
853
                <a class="hlink text-decoration-none dropdown-toggle' . ($list_control ? ' border btn btn-sm btn-light': ''). '" id="dropdownMenuNexterDate" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true">'.$output->trans('Reschedule').'</a>';
×
854
    }
855
    $txt .= '<ul class="dropdown-menu nexter_dropdown schedule_dropdown" aria-labelledby="dropdownMenuNexterDate">';
×
856
    foreach ($values as $format) {
×
857
        $labels = get_scheduled_date($format, true);
×
858
        $txt .= '<li><a href="#" class="nexter_date_helper dropdown-item d-flex justify-content-between gap-5" data-value="'.$format.'"><span>'.$output->trans($labels[0]).'</span> <span class="text-end">'.$labels[1].'</span></a></li>';
×
859
    }
860
    $txt .= '<li><hr class="dropdown-divider"></li>';
×
861
    $txt .= '<li><label for="nexter_input_date" class="nexter_date_picker dropdown-item cursor-pointer">'.$output->trans('Pick a date').'</label>';
×
862
    $txt .= '<input id="nexter_input_date" type="datetime-local" min="'.date('Y-m-d\Th:m').'" class="nexter_input_date" style="visibility: hidden; position: absolute; height: 0;">';
×
863
    $txt .= '<input class="nexter_input" style="display:none;"></li>';
×
864
    if ($send_now) {
×
865
        $txt .= '<li><hr class="dropdown-divider"></li>';
×
866
        $txt .= '<li><a href="#" data-value="now" class="nexter_date_helper dropdown-item"">'.$output->trans('Send now').'</a></li>';
×
867
    }
868
    $txt .= '</ul>';
×
869
    if ($send_now) {
×
870
        $txt .= '</div>';
×
871
    }
872

873
    return $txt;
×
874
}}
875

876
/**
877
 * @subpackage imap/functions
878
 */
879
if (!hm_exists('parse_delayed_header')) {
880
    function parse_delayed_header($header, $name)
881
    {
882
        $header = str_replace("$name: ", '', $header);
×
883
        $result = [];
×
884
        foreach (explode(';', $header) as $keyValue)
×
885
        {
886
            $keyValue = trim($keyValue);
×
887
            $spacePos = strpos($keyValue, ' ');
×
888
            if ($spacePos > 0) {
×
889
                $result[rtrim(substr($keyValue, 0, $spacePos), ':')] = trim(substr($keyValue, $spacePos+1));
×
890
            } else {
891
                $result[$keyValue] = true;
×
892
            }
893
        }
894
        return $result;
×
895
    }
896
}
897

898
function getSettingsSectionOutput($section, $sectionLabel, $sectionIcon, $settingsOptions, $userSettings) {
899
    $res = '<tr><td data-target=".'. $section .'_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
×
900
        '<i class="bi bi-'. $sectionIcon . ' fs-5 me-2"></i>'. $sectionLabel .'</td></tr>';
×
901
    foreach ($settingsOptions as $key => $setting) {
×
902
        $value = $userSettings[$key] ?? '';
×
903
        ['type' => $type, 'label' => $label, 'description' => $description] = $setting;
×
904

905
        if ($type === 'checkbox') {
×
906
            $input = '<input type="checkbox" id="'.$key.'" name="'.$key.'" '.($value ? 'checked' : '').' class="form-check-input me-2">';
×
907
        } else {
908
            $input = '<input type="'.$type.'" id="'.$key.'" name="'.$key.'" value="'.$value.'" class="form-control">';
×
909
        }
910

911
        $res .= "<tr class='{$section}_setting'>" .
×
912
        "<td class='d-block d-md-table-cell'><label for='$key'>$label</label></td>" .
×
913
        "<td class='d-block d-md-table-cell'>" .
×
914
            "<div class='d-flex align-items-center'>$input</div>" .
×
915
            "<div class='setting_description'>$description</div>" .
×
916
        "</td>" .
×
917
        "</tr>";
×
918
    }
919
    return $res;
×
920
}
921

922
function isPageConfigured($page) {
923
    $pages = array_keys(Hm_Handler_Modules::dump());
×
924
    return in_array($page, $pages);
×
925
}
926

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