1: <?php
2:
3: /**
4: * Core modules
5: * @package modules
6: * @subpackage core
7: */
8:
9: /**
10: * Simple search form for the folder list
11: * @subpackage core/output
12: */
13: class Hm_Output_search_from_folder_list extends Hm_Output_Module {
14: /**
15: * Add a search form to the top of the folder list
16: */
17: protected function output() {
18: $res = '<li class="menu_search mb-2"><form method="get">';
19: $res .= '<div class="input-group">';
20: if (!$this->get('hide_folder_icons')) {
21: $res .= '<a href="?page=search" class="input-group-text" id="basic-addon1">' .
22: '<i class="bi bi-search"></i>' .
23: '</a>';
24: }
25: $res .= '<input type="hidden" name="page" value="search" />'.
26: '<input type="search" class="search_terms form-control form-control-sm" aria-describedby="basic-addon1" '.
27: 'name="search_terms" placeholder="'.$this->trans('Search').'" /></div></form></li>';
28: if ($this->format == 'HTML5') {
29: return $res;
30: }
31: $this->concat('formatted_folder_list', $res);
32: }
33: }
34:
35: /**
36: * Start the search results content section
37: * @subpackage core/output
38: */
39: class Hm_Output_search_content_start extends Hm_Output_Module {
40: /**
41: * Leaves two open div tags that are closed in Hm_Output_search_content_end and Hm_Output_search_form
42: */
43: protected function output() {
44: return '<div class="search_content px-0"><div class="content_title px-3 d-flex align-items-center">'.
45: message_controls($this).$this->trans('Search');
46: }
47: }
48:
49: /**
50: * End of the search results content section
51: * @subpackage core/output
52: */
53: class Hm_Output_search_content_end extends Hm_Output_Module {
54: /**
55: * Closes a div opened in Hm_Output_search_content_start
56: */
57: protected function output() {
58: return '</div>';
59: }
60: }
61:
62: /**
63: * Unsaved data reminder
64: * @subpackage core/output
65: */
66: class Hm_Output_save_reminder extends Hm_Output_Module {
67: protected function output() {
68: if ($this->get('single_server_mode')) {
69: return '';
70: }
71: $changed = $this->get('changed_settings', array());
72: if (!empty($changed)) {
73: return '<div class="save_reminder"><a title="'.$this->trans('You have unsaved changes').
74: '" href="?page=save"><i class="bi bi-save2-fill fs-4"></i></a></div>';
75: }
76: return '';
77: }
78: }
79:
80: /**
81: * Start the search form
82: * @subpackage core/output
83: */
84: class Hm_Output_search_form_start extends Hm_Output_Module {
85: protected function output() {
86: return '<div class="search_form"><form class="d-flex align-items-center" method="get">';
87: }
88: }
89:
90: /**
91: * Search form content
92: * @subpackage core/output
93: */
94: class Hm_Output_search_form_content extends Hm_Output_Module {
95: protected function output() {
96: $terms = $this->get('search_terms', '');
97:
98: return '<input type="hidden" name="page" value="search" />'.
99: ' <label class="screen_reader" for="search_terms">'.$this->trans('Search Terms').'</label>'.
100: '<input required placeholder="'.$this->trans('Search Terms').'" id="search_terms" type="search" class="search_terms form-control form-control-sm" name="search_terms" value="'.$this->html_safe($terms).'" />'.
101: ' <label class="screen_reader" for="search_fld">'.$this->trans('Search Field').'</label>'.
102: search_field_selection($this->get('search_fld', DEFAULT_SEARCH_FLD), $this).
103: ' <label class="screen_reader" for="search_since">'.$this->trans('Search Since').'</label>'.
104: message_since_dropdown($this->get('search_since', DEFAULT_SEARCH_SINCE), 'search_since', $this).
105: combined_sort_dialog($this).
106: ' | <input type="submit" class="search_update btn btn-primary btn-sm" value="'.$this->trans('Update').'" />'.
107: ' <input type="button" class="search_reset btn btn-light border btn-sm" value="'.$this->trans('Reset').'" />';
108: }
109: }
110:
111: /**
112: * Finish the search form
113: * @subpackage core/output
114: */
115: class Hm_Output_search_form_end extends Hm_Output_Module {
116: protected function output() {
117: $source_link = '<a href="#" title="'.$this->trans('Sources').'" class="source_link"><i class="bi bi-folder-fill refresh_list"></i></a>';
118: $refresh_link = '<a class="refresh_link ms-3" title="'.$this->trans('Refresh').'" href="#"><i class="bi bi-arrow-clockwise refresh_list"></i></a>';
119: return '</form></div>'.
120: list_controls($refresh_link, false, $source_link).list_sources($this->get('data_sources', array()), $this).'</div>';
121: }
122: }
123:
124: /**
125: * Closes the search results table
126: * @subpackage core/output
127: */
128: class Hm_Output_search_results_table_end extends Hm_Output_Module {
129: /**
130: */
131: protected function output() {
132: return '</tbody></table>';
133: }
134: }
135:
136: /**
137: * Some inline JS used by the search page
138: * @subpackage core/output
139: */
140: class Hm_Output_js_search_data extends Hm_Output_Module {
141: /**
142: * adds two JS functions used on the search page
143: */
144: protected function output() {
145: return '<script type="text/javascript" id="search-data">'.
146: 'var hm_search_terms = function() { return "'.$this->html_safe($this->get('search_terms', ''), true).'"; };'.
147: 'var hm_run_search = function() { return "'.$this->html_safe($this->get('run_search', 0)).'"; };'.
148: '</script>';
149: }
150: }
151:
152: /**
153: * Outputs the end of the login or logout form
154: * @subpackage core/output
155: */
156: class Hm_Output_login_end extends Hm_Output_Module {
157: /**
158: * Closes the login form
159: */
160: protected function output() {
161: $fancy_login= $this->get('fancy_login_allowed');
162: if(!$fancy_login){
163: return '</form>';
164: }
165: return '</form></div>';
166: }
167: }
168:
169: /**
170: * Outputs the start of the login or logout form
171: * @subpackage core/output
172: */
173: class Hm_Output_login_start extends Hm_Output_Module {
174: /**
175: * Looks at the current login state and outputs the correct form
176: */
177: protected function output() {
178: $fancy_login = $this->get('fancy_login_allowed');
179: if (!$fancy_login) {
180: if (!$this->get('router_login_state')) {
181: return '<form class="login_form" method="POST">';
182: }
183: else {
184: return '<form class="logout_form" method="POST">';
185: }
186: } else {
187: if (!$this->get('router_login_state')) {
188: $css = '<style type="text/css">body,html{max-width:100vw !important; max-height:100vh !important; overflow:hidden !important;}.form-container{background-color:#f1f1f1;'.
189: 'background: linear-gradient( rgba(4, 26, 0, 0.85), rgba(4, 26, 0, 0.85)), url('.WEB_ROOT.'modules/core/assets/images/cloud.jpg);'.
190: 'background-attachment: fixed;background-position: center;background-repeat: no-repeat;background-size: cover;'.
191: 'display:grid; place-items:center; height:100vh; width:100vw;} .logged_out{display:block !important;}.sys_messages'.
192: '{position:fixed;right:20px;top:15px;min-height:30px;display:none;background-color:#fff;color:teal;'.
193: 'margin-top:0px;padding:15px;padding-bottom:5px;white-space:nowrap;border:solid 1px #999;border-radius:'.
194: '5px;filter:drop-shadow(4px 4px 4px #ccc);z-index:101;}.g-recaptcha{margin:0px 10px 10px 10px;}.mobile .g-recaptcha{'.
195: 'margin:0px 10px 5px 10px;}.title{font-weight:normal;padding:0px;margin:0px;margin-left:20px;'.
196: 'margin-bottom:20px;letter-spacing:-1px;color:#999;}html,body{min-width:100px !important;'.
197: 'background-color:#fff;}body{background:linear-gradient(180deg,#faf6f5,#faf6f5,#faf6f5,#faf6f5,'.
198: '#fff);font-size:1em;color:#333;font-family:Arial;padding:0px;margin:0px;min-width:700px;'.
199: 'font-size:100%;}input,option,select{font-size:100%;padding:3px;}textarea,select,input{border:solid '.
200: '1px #ddd;background-color:#fff;color:#333;border-radius:3px;}.screen_reader{position:absolute'.
201: ';top:auto;width:1px;height:1px;overflow:hidden;}.login_form{display:flex; justify-content:space-evenly; align-items:center; flex-direction:column;font-size:90%;'.
202: 'padding-top:60px;height:360px;border-radius:20px 20px 20px 20px;margin:0px;background-color:rgba(0,0,0,.6);'.
203: 'min-width:300px;}.login_form input{clear:both;float:left;padding:4px;'.
204: 'margin-top:10px;margin-bottom:10px;}#username,#password{width:200px; height:25px;} .err{color:red !important;}.long_session'.
205: '{float:left;}.long_session input{padding:0px;float:none;font-size:18px;}.mobile .long_session{float:left;clear:both;} @media screen and (min-width:400px){.login_form{min-width:400px;}}'.
206: '.user-icon_signin{display:block; background-color:white; border-radius:100%; padding:10px; height:40px; margin-top:-120px; box-shadow: #6eb549 .4px 2.4px 6.2px; }'.
207: '.label_signin{width:210px; margin:0px 0px -18px 0px;color:#fff;opacity:0.7;} @media (max-height : 500px){ .user-icon_signin{display:none;}}
208: </style>';
209:
210: return $css.'<div class="form-container"><form class="login_form" method="POST">';
211: }
212: else {
213: return '<div class="form-container"><form class="logout_form" method="POST">';
214: }
215: }
216: }
217: }
218:
219: /**
220: * Outputs the login or logout form
221: * @subpackage core/output
222: */
223: class Hm_Output_login extends Hm_Output_Module {
224: /**
225: * Looks at the current login state and outputs the correct form
226: */
227: protected function output() {
228: $stay_logged_in = '';
229: if ($this->get('allow_long_session')) {
230: $stay_logged_in = '<div class="form-check form-switch long-session">
231: <input type="checkbox" id="stay_logged_in" value="1" name="stay_logged_in" class="form-check-input">
232: <label class="form-check-label" for="stay_logged_in">'.$this->trans('Stay logged in').'</label>
233: </div>';
234: }
235: if (!$this->get('router_login_state')) {
236: $fancy_login = $this->get('fancy_login_allowed');
237: if(!$fancy_login){
238: return '<div class="bg-light"><div class="d-flex align-items-center justify-content-center vh-100 p-3">
239: <div class="card col-12 col-md-6 col-lg-4 p-3">
240: <div class="card-body">
241: <p class="text-center"><img class="w-50" src="'.WEB_ROOT. 'modules/core/assets/images/logo_dark.svg"></p>
242: <div class="mt-5">
243: <div class="mb-3 form-floating">
244: <input autofocus required type="text" placeholder="'.$this->trans('Username').'" id="username" name="username" class="form-control">
245: <label for="username" class="form-label screen-reader">'.$this->trans('Username').'</label>
246: </div>
247: <div class="mb-3 form-floating">
248: <input required type="password" id="password" placeholder="'.$this->trans('Password').'" name="password" class="form-control">
249: <label for="password" class="form-label screen-reader">'.$this->trans('Password').'</label>
250: </div>'.
251: '<div class="d-grid">'.$stay_logged_in.
252: '<input type="hidden" name="hm_page_key" value="'.Hm_Request_Key::generate().'" />
253: <input type="submit" id="login" class="btn btn-primary btn-lg" value="'.$this->trans('Login').'">
254: </div>
255: </div>
256: </div>
257: </div>
258: </div></div>';
259: } else {
260: return '<svg class="user-icon_signin" viewBox="0 0 20 20"><path d="M12.075,10.812c1.358-0.853,2.242-2.507,2.242-4.037c0-2.181-1.795-4.618-4.198-4.618S5.921,4.594,5.921,6.775c0,1.53,0.884,3.185,2.242,4.037c-3.222,0.865-5.6,3.807-5.6,7.298c0,0.23,0.189,0.42,0.42,0.42h14.273c0.23,0,0.42-0.189,0.42-0.42C17.676,14.619,15.297,11.677,12.075,10.812 M6.761,6.775c0-2.162,1.773-3.778,3.358-3.778s3.359,1.616,3.359,3.778c0,2.162-1.774,3.778-3.359,3.778S6.761,8.937,6.761,6.775 M3.415,17.69c0.218-3.51,3.142-6.297,6.704-6.297c3.562,0,6.486,2.787,6.705,6.297H3.415z"></path></svg>'.
261: '<img src="'.WEB_ROOT. 'modules/core/assets/images/logo.svg" style="height:90px;">'.
262: '<!--h1 class="title">'.$this->html_safe($this->get('router_app_name', '')).'</h1-->'.
263: ' <input type="hidden" name="hm_page_key" value="'.Hm_Request_Key::generate().'" />'.
264: ' <label class="label_signin" for="username">'.$this->trans('Username').'</label>'.
265: '<input autofocus required type="text" placeholder="'.$this->trans('Username').'" id="username" name="username" value="">'.
266: ' <label class="label_signin" for="password">'.$this->trans('Password').'</label>'.
267: '<input required type="password" id="password" placeholder="'.$this->trans('Password').'" name="password">'.
268: $stay_logged_in.' <input style="cursor:pointer; display:block; width:210px; background-color:#6eb549; color:white; height:40px;" type="submit" id="login" value="'.$this->trans('Login').'" />';
269: }
270:
271: }
272: else {
273: $settings = $this->get('changed_settings', array());
274: $single = $this->get('single_server_mode');
275: $changed = 0;
276: if (!$single && count($settings) > 0) {
277: $changed = 1;
278: }
279:
280: return '<div class="modal fade" id="confirmLogoutModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="confirmLogoutModalLabel" aria-hidden="true">
281: <div class="modal-dialog">
282: <div class="modal-content">
283: <div class="modal-header">
284: <h5 class="modal-title" id="confirmLogoutModalLabel">'.$this->trans('Do you want to log out?').'</h5>
285: <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
286: </div>
287: <div class="modal-body">
288: <input type="hidden" name="hm_page_key" value="'.Hm_Request_Key::generate().'" />
289: <p class="text-wrap">'.$this->trans('Unsaved changes will be lost! Re-enter your password to save and exit.').' <a href="?page=save">'.$this->trans('More info').'</a></p>
290: <input type="text" value="'.$this->html_safe($this->get('username', 'cypht_user')).'" autocomplete="username" style="display: none;"/>
291: <div class="my-3 form-floating">
292: <input id="logout_password" autocomplete="current-password" name="password" class="form-control warn_on_paste" type="password" placeholder="'.$this->trans('Password').'">
293: <label for="logout_password" class="form-label screen-reader">'.$this->trans('Password').'</label>
294: </div>
295: </div>
296: <div class="modal-footer">
297: <input class="cancel_logout save_settings btn btn-secondary" data-bs-dismiss="modal" type="button" value="'.$this->trans('Cancel').'" />
298: <input class="save_settings btn btn-primary" id="logout_without_saving" type="submit" name="logout" value="'.$this->trans('Just Logout').'" />
299: <input class="save_settings btn btn-primary" type="submit" name="save_and_logout" value="'.$this->trans('Save and Logout').'" />
300: </div>
301: </div>
302: </div>
303: </div>';
304: }
305: }
306: }
307:
308: /**
309: * Start the content section for the servers page
310: * @subpackage core/output
311: */
312: class Hm_Output_server_content_start extends Hm_Output_Module {
313: /**
314: * The server_content div is closed in Hm_Output_server_content_end
315: */
316: protected function output() {
317: return '<div class="content_title">'.$this->trans('Servers').
318: '</div><div class="server_content">';
319: }
320: }
321:
322: /**
323: * Close the server content section
324: * @subpackage core/output
325: */
326: class Hm_Output_server_content_end extends Hm_Output_Module {
327: /**
328: * Closes the div tag opened in Hm_Output_server_content_start
329: */
330: protected function output() {
331: return '</div>';
332: }
333: }
334:
335: /**
336: * Output a date
337: * @subpackage core/output
338: */
339: class Hm_Output_date extends Hm_Output_Module {
340: /**
341: * Currently not show in the display (hidden with CSS)
342: */
343: protected function output() {
344: return '<div class="date">'.$this->html_safe($this->get('date')).'</div>';
345: }
346: }
347:
348: /**
349: * Display system messages
350: * @subpackage core/output
351: */
352: class Hm_Output_msgs extends Hm_Output_Module {
353: /**
354: * Error level messages start with ERR and will be shown in red
355: */
356: protected function output() {
357: $res = '';
358: $msgs = Hm_Msgs::getRaw();
359: $logged_out_class = '';
360: if (!$this->get('router_login_state') && !empty($msgs)) {
361: $logged_out_class = ' logged_out';
362: }
363: $res .= '<div class="position-fixed top-0 col-sm-4 col-md-3 end-0 mt-3 me-3 sys_messages'.$logged_out_class.'"></div>';
364: $res .= '<script type="text/javascript">var hm_msgs = '.json_encode($msgs).'</script>';
365: return $res;
366: }
367: }
368:
369: /**
370: * Start the HTML5 document
371: * @subpackage core/output
372: */
373: class Hm_Output_header_start extends Hm_Output_Module {
374: /**
375: * Doctype, html, head, and a meta tag for charset. The head section is not closed yet
376: */
377: protected function output() {
378: $lang = 'en';
379: $dir = 'ltr';
380: if ($this->lang) {
381: $lang = mb_strtolower(str_replace('_', '-', $this->lang));
382: }
383: if ($this->dir) {
384: $dir = $this->dir;
385: }
386: $class = $dir."_page";
387: $res = '<!DOCTYPE html><html dir="'.$this->html_safe($dir).'" class="'.
388: $this->html_safe($class).'" lang='.$this->html_safe($lang).'><head>'.
389: '<meta name="apple-mobile-web-app-capable" content="yes" />'.
390: '<meta name="mobile-web-app-capable" content="yes" />'.
391: '<meta name="apple-mobile-web-app-status-bar-style" content="black" />'.
392: '<meta name="theme-color" content="#888888" /><meta charset="utf-8" />';
393:
394: if ($this->get('router_login_state')) {
395: $res .= '<meta name="referrer" content="no-referrer" />';
396: }
397: return $res;
398: }
399: }
400:
401: /**
402: * Close the HTML5 head tag
403: * @subpackage core/output
404: */
405: class Hm_Output_header_end extends Hm_Output_Module {
406: /**
407: * Close the head tag opened in Hm_Output_header_start
408: */
409: protected function output() {
410: return '</head>';
411: }
412: }
413:
414: /**
415: * Start the content section
416: * @subpackage core/output
417: */
418: class Hm_Output_content_start extends Hm_Output_Module {
419: /**
420: * Outputs the starting body tag and a noscript warning. Clears the local session cache
421: * if not logged in, or adds a page wide key used by ajax requests
422: */
423: protected function output() {
424: $res = '<body class="'.($this->get('is_mobile', false) ? 'mobile' : '').'"><noscript class="noscript">'.
425: sprintf($this->trans('You need to have Javascript enabled to use %s, sorry about that!'),
426: $this->html_safe($this->get('router_app_name'))).'</noscript>';
427: $res .= '<script type="text/javascript">window.inAppContext = true;</script>';
428: if (!$this->get('router_login_state')) {
429: $res .= '<script type="text/javascript">sessionStorage.clear();</script>';
430: }
431: else {
432: $res .= '<input type="hidden" id="hm_page_key" value="'.$this->html_safe(Hm_Request_Key::generate()).'" />';
433: }
434: if (!$this->get('single_server_mode') && count($this->get('changed_settings', array())) > 0) {
435: $res .= '<a class="unsaved_icon" href="?page=save" title="'.$this->trans('Unsaved Changes').
436: '"><i class="bi bi-save2-fill fs-5 unsaved_reminder"></i></a>';
437: }
438: $res .= '<div class="cypht-layout">';
439: return $res;
440: }
441: }
442:
443: /**
444: * Outputs HTML5 head tag content
445: * @subpackage core/output
446: */
447: class Hm_Output_header_content extends Hm_Output_Module {
448: /**
449: * Setup a title, base URL, a tab icon, and viewport settings
450: */
451: protected function output() {
452: $title = '';
453: if (!$this->get('router_login_state')) {
454: $title = $this->get('router_app_name');
455: }
456: elseif ($this->exists('page_title')) {
457: $title .= $this->get('page_title');
458: }
459: elseif ($this->exists('mailbox_list_title')) {
460: $title .= ' '.implode('-', $this->get('mailbox_list_title', array()));
461: }
462: if (!trim((string) $title) && $this->exists('router_page_name')) {
463: $title = '';
464: if ($this->get('list_path') == 'message_list') {
465: $title .= ' '.ucwords(str_replace('_', ' ', $this->get('list_path')));
466: }
467: elseif ($this->get('router_page_name') == 'notfound') {
468: $title .= ' Nope';
469: }
470: else {
471: $title .= ' '.ucwords(str_replace('_', ' ', $this->get('router_page_name')));
472: }
473: }
474: return '<title>'.$this->trans(trim((string) $title)).'</title>'.
475: '<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">'.
476: '<link rel="icon" class="tab_icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo'.
477: 'AAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAFVJR'.
478: 'EFUOI3NkkEKACEMA2d92fpzfVn3oHhYqAF7qIFeSpImUMjGA1jEoEQTFKAC/UDbp3bhBRqj0m7a5C78F56Rx5MEdUB'.
479: 'HFMlkV09ogN3xB7kG+fgA0tc160Jy09wAAAAASUVORK5CYII=" >'.
480: '<base href="'.$this->html_safe($this->get('router_url_path')).'" />';
481: }
482: }
483:
484: /**
485: * Output CSS
486: * @subpackage core/output
487: */
488: class Hm_Output_header_css extends Hm_Output_Module {
489: /**
490: * In debug mode adds each module css file to the page, otherwise uses the combined version
491: */
492: protected function output() {
493: $res = '';
494: $mods = $this->get('router_module_list');
495: if (! $this->get('theme')) {
496: $res .= '<link href="' . WEB_ROOT . 'modules/themes/assets/default/css/default.css?v=' . CACHE_ID . '" media="all" rel="stylesheet" type="text/css" />';
497: }
498: if (DEBUG_MODE) {
499: $res .= '<link href="'.WEB_ROOT.'vendor/twbs/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet" type="text/css" />';
500: foreach (glob(APP_PATH.'modules'.DIRECTORY_SEPARATOR.'**', GLOB_ONLYDIR | GLOB_MARK) as $name) {
501: $rel_name = str_replace(APP_PATH, '', $name);
502: $mod = str_replace(array('modules', DIRECTORY_SEPARATOR), '', $rel_name);
503: if (in_array($mod, $mods, true) && is_readable(sprintf("%ssite.css", $name))) {
504: $res .= '<link href="'.WEB_ROOT.sprintf("%ssite.css", $rel_name).'" media="all" rel="stylesheet" type="text/css" />';
505: }
506: }
507: // load pcss3t.cs only if one of: ['contacts','local_contacts','ldap_contacts','gmail_contacts'] is enabled
508: if(count(array_intersect(['contacts','local_contacts','ldap_contacts','gmail_contacts'], $mods)) > 0){
509: $res .= '<link href="'.WEB_ROOT.'third_party/contact-group.css" media="all" rel="stylesheet" type="text/css" />';
510: }
511: $res .= '<link href="'.WEB_ROOT.'third_party/nprogress.css" media="all" rel="stylesheet" type="text/css" />';
512: }
513: else {
514: $res .= '<link href="'.WEB_ROOT.'site.css?v='.CACHE_ID.'" ';
515: if (defined('CSS_HASH') && CSS_HASH) {
516: $res .= 'integrity="'.CSS_HASH.'" ';
517: }
518: $res .= 'media="all" rel="stylesheet" type="text/css" />';
519: }
520: $res .= '<style type="text/css">@font-face {font-family:"Behdad";'.
521: 'src:url("'.WEB_ROOT.'modules/core/assets/fonts/Behdad/Behdad-Regular.woff2") format("woff2"),'.
522: 'url("'.WEB_ROOT.'modules/core/assets/fonts/Behdad/Behdad-Regular.woff") format("woff");</style>';
523: return $res;
524: }
525: }
526:
527: /**
528: * Output JS
529: * @subpackage core/output
530: */
531: class Hm_Output_page_js extends Hm_Output_Module {
532: /**
533: * In debug mode adds each module js file to the page, otherwise uses the combined version.
534: * Includes the cash.js library, and the forge lib if it's needed.
535: */
536: protected function output() {
537: if (DEBUG_MODE) {
538: $res = '';
539: $js_exclude_dependencies = explode(',', $this->get('router_js_exclude_deps', ''));
540: $js_lib = get_js_libs($js_exclude_dependencies);
541: if ($this->get('encrypt_ajax_requests', '') || $this->get('encrypt_local_storage', '')) {
542: $js_lib .= '<script type="text/javascript" src="'.WEB_ROOT.'third_party/forge.min.js"></script>';
543: }
544: $core = false;
545: $mods = $this->get('router_module_list');
546: foreach (glob(APP_PATH.'modules'.DIRECTORY_SEPARATOR.'**', GLOB_ONLYDIR | GLOB_MARK) as $name) {
547: $rel_name = str_replace(APP_PATH, '', $name);
548: $mod = str_replace(array('modules', DIRECTORY_SEPARATOR), '', $rel_name);
549: if (in_array($mod, $mods, true)) {
550: $directoriesPattern = str_replace('/', DIRECTORY_SEPARATOR, "{*,*/*}");
551: foreach (glob($name.'js_modules' . DIRECTORY_SEPARATOR . $directoriesPattern . "*.js", GLOB_BRACE) as $js) {
552: if (preg_match('/\[(.+)\]/', $js, $matches)) {
553: $dep = $matches[1];
554: if (in_array($dep, $js_exclude_dependencies)) {
555: continue;
556: }
557: }
558: $res .= '<script type="text/javascript" src="'.WEB_ROOT.str_replace(APP_PATH, '', $js).'"></script>';
559: }
560:
561: if ($rel_name == 'modules'.DIRECTORY_SEPARATOR.'core'.DIRECTORY_SEPARATOR) {
562: $core = $rel_name;
563: continue;
564: }
565:
566: if (is_readable(sprintf("%ssite.js", $name))) {
567: $res .= '<script type="text/javascript" src="'.WEB_ROOT.sprintf("%ssite.js", $rel_name).'"></script>';
568: }
569: }
570: }
571: if ($core) {
572: $res = // Load navigation utilities used by subsequent modules' handlers
573: '<script type="text/javascript" src="' . WEB_ROOT . 'modules/core/navigation/utils.js"></script>'.
574: '<script type="text/javascript" src="'.WEB_ROOT.sprintf("%ssite.js", $core).'"></script>'.
575: $res;
576: /* Load navigation js modules
577: * routes.js, navigation.js
578: * They have to be loaded after each module's js files, because routes.js depend on the handlers defined in the modules.
579: * Therefore, navigation.js is also loaded after routes.js, because the routes should be loaded beforehand to be able to navigate.
580: */
581: foreach (['routes', 'navigation', 'navbar'] as $js) {
582: $res .= '<script type="text/javascript" src="'.WEB_ROOT.sprintf("%snavigation/%s.js", $core, $js).'"></script>';
583: }
584: }
585: return $js_lib.$res;
586: }
587: else {
588: $res = '<script type="text/javascript" ';
589: if (defined('JS_HASH') && JS_HASH) {
590: $res .= 'integrity="'.JS_HASH.'" ';
591: }
592: $res .= 'src="'.WEB_ROOT.'site.js?v='.CACHE_ID.'" async></script>';
593: return $res;
594: }
595: }
596: }
597:
598: /**
599: * End the page content
600: * @subpackage core/output
601: */
602: class Hm_Output_content_end extends Hm_Output_Module {
603: /**
604: * Closes the layout wrapper, body, and html tags
605: */
606: protected function output() {
607: return '</div></body></html>';
608: }
609: }
610:
611: /**
612: * Outputs page details for the JS
613: * @subpackage core/output
614: */
615: class Hm_Output_js_data extends Hm_Output_Module {
616: /**
617: * Uses function wrappers to make the data immutable from JS
618: */
619: protected function output() {
620: $settings = $this->get('user_settings', array());
621: $enable_snooze = $settings['enable_snooze'] ?? DEFAULT_ENABLE_SNOOZE;
622: $enable_collect_address_on_send = $settings['enable_collect_address_on_send_setting'] ?? DEFAULT_ENABLE_COLLECT_ADDRESS_ON_SEND;
623: $res = '<script type="text/javascript" id="data-store">'.
624: 'var globals = {};'.
625: 'var hm_is_logged = function () { return '.($this->get('is_logged') ? '1' : '0').'; };'.
626: 'var hm_empty_folder = function() { return "'.$this->trans('So alone').'"; };'.
627: 'var hm_mobile = function() { return '.($this->get('is_mobile') ? '1' : '0').'; };'.
628: 'var hm_debug = function() { return "' . ((DEBUG_MODE || DEFAULT_DEBUG_LOG) ? '1' : '0') . '"; };'.
629: 'var hm_mailto = function() { return '.($this->get('mailto_handler') ? '1' : '0').'; };'.
630: 'var hm_page_name = function() { return "'.$this->html_safe($this->get('router_page_name')).'"; };'.
631: 'var hm_language_direction = function() { return "'.$this->html_safe($this->dir).'"; };'.
632: 'var hm_list_path = function() { return "'.$this->html_safe($this->get('list_path', '')).'"; };'.
633: 'var hm_list_parent = function() { return "'.$this->html_safe($this->get('list_parent', '')).'"; };'.
634: 'var hm_msg_uid = function() { return Hm_Utils.get_from_global("msg_uid", "'.$this->html_safe($this->get('uid', '')).'"); };'.
635: 'var hm_encrypt_ajax_requests = function() { return "'.$this->html_safe($this->get('encrypt_ajax_requests', '')).'"; };'.
636: 'var hm_encrypt_local_storage = function() { return "'.$this->html_safe($this->get('encrypt_local_storage', '')).'"; };'.
637: 'var hm_web_root_path = function() { return "'.WEB_ROOT.'"; };'.
638: 'var hm_flag_image_src = function() { return "<i class=\"bi bi-star-half\"></i>"; };'.
639: 'var hm_check_dirty_flag = function() { return '.($this->get('warn_for_unsaved_changes', '') ? '1' : '0').'; };'.
640: format_data_sources($this->get('data_sources', array()), $this);
641:
642: if (!$this->get('disable_delete_prompt', DEFAULT_DISABLE_DELETE_PROMPT)) {
643: $res .= 'var hm_delete_prompt = function() { return confirm("'.$this->trans('This action cannot be undone. Are you sure you want to delete this?').'"); };';
644: }
645: else {
646: $res .= 'var hm_delete_prompt = function() { return true; };';
647: }
648: $res .= 'window.hm_current_lang = "'.$this->lang.'";'.
649: 'window.hm_translations = '.json_encode($this->all_trans()).';'.
650: 'var hm_trans = function(key, lang = window.hm_current_lang) {'.
651: ' const langTranslations = window.hm_translations && window.hm_translations[lang];'.
652: ' if (langTranslations && langTranslations[key] !== undefined && langTranslations[key] !== false) {'.
653: ' return langTranslations[key];'.
654: ' }'.
655: ' return key;'.
656: '};';
657: $res .= 'window.hm_default_timezone = "'.$this->get('default_timezone','UTC').'";';
658: $res .= 'window.hm_default_setting_enable_snooze = ' . ($enable_snooze ? 'true' : 'false') . ';' . PHP_EOL;
659: $res .= 'window.hm_enable_collect_address_on_send = ' . ($enable_collect_address_on_send ? 'true' : 'false') . ';' . PHP_EOL;
660: $res .= 'var hm_module_is_supported = function(module) {'.
661: ' return '.json_encode($this->get('enabled_modules', array())).'.indexOf(module) !== -1;'.
662: '};';
663: $res .= '</script>';
664: return $res;
665: }
666: }
667:
668:
669: /**
670: * Start the main form on the settings page
671: * @subpackage core/output
672: */
673: class Hm_Output_start_settings_form extends Hm_Output_Module {
674: /**
675: * Opens a div, form and table
676: */
677: protected function output() {
678: return '<div class="user_settings px-0"><div class="content_title px-3">'.$this->trans('Site Settings').'</div>'.
679: '<form method="POST"><input type="hidden" name="hm_page_key" value="'.$this->html_safe(Hm_Request_Key::generate()).'" />'.
680: '<div class="px-3"><table class="settings_table table table-borderless"><colgroup>'.
681: '<col class="label_col"><col class="setting_col"></colgroup>';
682: }
683: }
684:
685: /**
686: * Outputs the start page option on the settings page
687: * @subpackage core/output
688: */
689: class Hm_Output_start_page_setting extends Hm_Output_Module {
690: /**
691: * Can be any of the main combined pages
692: */
693: protected function output() {
694: $options = start_page_opts();
695: $settings = $this->get('user_settings', array());
696: $res = '';
697: $reset = '';
698:
699: if (array_key_exists('start_page', $settings)) {
700: $start_page = $settings['start_page'];
701: }
702: else {
703: $start_page = DEFAULT_START_PAGE;
704: }
705: $res = '<tr class="general_setting"><td><label for="start_page">'.
706: $this->trans('First page after login').'</label></td>'.
707: '<td><select class="form-select form-select-sm w-auto" id="start_page" name="start_page">';
708: foreach ($options as $label => $val) {
709: $res .= '<option ';
710: if ($start_page == $val) {
711: $res .= 'selected="selected" ';
712: if ($start_page != DEFAULT_START_PAGE) {
713: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_select"></i></span>';
714: }
715: }
716: $res .= 'value="'.$val.'">'.$this->trans($label).'</option>';
717: }
718: $res .= '</select>'.$reset.'</td></tr>';
719: return $res;
720: }
721: }
722:
723: /**
724: * Outputs the default sort order option on the settings page
725: * @subpackage core/output
726: */
727: class Hm_Output_default_sort_order_setting extends Hm_Output_Module {
728: /**
729: * Can be any of the main combined pages
730: */
731: protected function output() {
732: $options = default_sort_order_opts();
733: $settings = $this->get('user_settings', array());
734: $reset = '';
735:
736: if (array_key_exists('default_sort_order', $settings)) {
737: $default_sort_order = $settings['default_sort_order'];
738: }
739: else {
740: $default_sort_order = null;
741: }
742: $res = '<tr class="general_setting"><td><label for="default_sort_order">'.
743: $this->trans('Default message sort order').'</label></td>'.
744: '<td><select class="form-select form-select-sm w-auto" id="default_sort_order" name="default_sort_order">';
745: foreach ($options as $val => $label) {
746: $res .= '<option ';
747: if ($default_sort_order == $val) {
748: $res .= 'selected="selected" ';
749: if ($default_sort_order != 'arrival') {
750: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_select"></i></span>';
751: }
752: }
753: $res .= 'value="'.$val.'">'.$this->trans($label).'</option>';
754: }
755: $res .= '</select>'.$reset.'</td></tr>';
756: return $res;
757: }
758: }
759:
760: /**
761: * Outputs the list style option on the settings page
762: * @subpackage core/output
763: */
764: class Hm_Output_list_style_setting extends Hm_Output_Module {
765: /**
766: * Can be either "news style" or the default "email style"
767: */
768: protected function output() {
769: $options = array('email_style' => 'Email', 'news_style' => 'News');
770: $settings = $this->get('user_settings', array());
771: $reset = '';
772:
773: if (array_key_exists('list_style', $settings)) {
774: $list_style = $settings['list_style'];
775: }
776: else {
777: $list_style = DEFAULT_LIST_STYLE;
778: }
779: $res = '<tr class="general_setting"><td><label for="list_style">'.
780: $this->trans('Message list style').'</label></td>'.
781: '<td><select class="form-select form-select-sm w-auto" id="list_style" name="list_style" data-default-value="'.DEFAULT_LIST_STYLE.'">';
782: foreach ($options as $val => $label) {
783: $res .= '<option ';
784: if ($list_style == $val) {
785: $res .= 'selected="selected" ';
786: if ($list_style != DEFAULT_LIST_STYLE) {
787: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_select"></i></span>';
788: }
789: }
790: $res .= 'value="'.$val.'">'.$this->trans($label).'</option>';
791: }
792: $res .= '</select>'.$reset.'</td></tr>';
793: return $res;
794: }
795: }
796:
797: /**
798: * @subpackage core/output
799: */
800: class Hm_Output_mailto_handler_setting extends Hm_Output_Module {
801: protected function output() {
802: $settings = $this->get('user_settings');
803: if (array_key_exists('mailto_handler', $settings) && $settings['mailto_handler']) {
804: $checked = ' checked="checked"';
805: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>';
806: }
807: else {
808: $checked = '';
809: $reset = '';
810: }
811: return '<tr class="general_setting"><td><label class="form-check-label" for="mailto_handler">'.$this->trans('Allow handling of mailto links').'</label></td>'.
812: '<td><input class="form-check-input" type="checkbox" '.$checked.' value="1" id="mailto_handler" name="mailto_handler" data-default-value="false" />'.$reset.'</td></tr>';
813: }
814: }
815:
816: /**
817: * @subpackage core/output
818: */
819: class Hm_Output_no_folder_icon_setting extends Hm_Output_Module {
820: protected function output() {
821: $settings = $this->get('user_settings');
822: if (array_key_exists('no_folder_icons', $settings) && $settings['no_folder_icons']) {
823: $checked = ' checked="checked"';
824: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>';
825: }
826: else {
827: $checked = '';
828: $reset = '';
829: }
830: return '<tr class="general_setting"><td><label class="form-check-label" for="no_folder_icons">'.$this->trans('Hide folder list icons').'</label></td>'.
831: '<td><input class="form-check-input" type="checkbox" '.$checked.' value="1" id="no_folder_icons" name="no_folder_icons" data-default-value="false" />'.$reset.'</td></tr>';
832: }
833: }
834:
835: /**
836: * @subpackage core/output
837: */
838: class Hm_Output_no_password_setting extends Hm_Output_Module {
839: protected function output() {
840: $settings = $this->get('user_settings');
841: $reset = '';
842: if (array_key_exists('no_password_save', $settings) && $settings['no_password_save']) {
843: $checked = ' checked="checked"';
844: }
845: else {
846: $checked = '';
847: }
848: if(isset($settings['no_password_save']) && $settings['no_password_save'] !== DEFAULT_NO_PASSWORD_SAVE) {
849: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>';
850: }
851: return '<tr class="general_setting"><td><label class="form-check-label" for="no_password_save">'.$this->trans('Don\'t save account passwords between logins').'</label></td>'.
852: '<td><input class="form-check-input" type="checkbox" '.$checked.' value="1" id="no_password_save" name="no_password_save" data-default-value="'.(DEFAULT_NO_PASSWORD_SAVE ? 'true' : 'false') . '" />'.$reset.'</td></tr>';
853: }
854: }
855:
856: /**
857: * @subpackage core/output
858: */
859: class Hm_Output_delete_prompt_setting extends Hm_Output_Module {
860: protected function output() {
861: $settings = $this->get('user_settings');
862: $reset = '';
863: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>';
864: if (array_key_exists('disable_delete_prompt', $settings) && $settings['disable_delete_prompt']) {
865: $checked = ' checked="checked"';
866: }
867: else {
868: $checked = '';
869: }
870: if(isset($settings['disable_delete_prompt']) && $settings['disable_delete_prompt'] !== DEFAULT_DISABLE_DELETE_PROMPT) {
871: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>';
872: }
873: return '<tr class="general_setting"><td><label class="form-check-label" for="disable_delete_prompt">'.$this->trans('Disable prompts when deleting').'</label></td>'.
874: '<td><input class="form-check-input" type="checkbox" '.$checked.' value="1" id="disable_delete_prompt" name="disable_delete_prompt" data-default-value="'.(DEFAULT_DISABLE_DELETE_PROMPT ? 'true' : 'false') . '" />'.$reset.'</td></tr>';
875: }
876: }
877:
878: /**
879: * @subpackage core/output
880: */
881: class Hm_Output_delete_attachment_setting extends Hm_Output_Module {
882: protected function output() {
883: $checked = '';
884: $reset = '';
885: $settings = $this->get('user_settings');
886: if (array_key_exists('allow_delete_attachment', $settings) && $settings['allow_delete_attachment']) {
887: $checked = ' checked="checked"';
888: }
889: else {
890: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>';
891: }
892: return '<tr class="general_setting"><td><label class="form-check-label" for="allow_delete_attachment">'.$this->trans('Allow delete attachment').'</label></td>'.
893: '<td><input class="form-check-input" type="checkbox" '.$checked.' value="1" id="allow_delete_attachment" name="allow_delete_attachment" data-default-value="false" />'.$reset.'</td></tr>';
894: }
895: }
896:
897: /**
898: * Starts the Flagged section on the settings page
899: * @subpackage core/output
900: */
901: class Hm_Output_start_flagged_settings extends Hm_Output_Module {
902: /**
903: * Settings in this section control the flagged messages view
904: */
905: protected function output() {
906: return '<tr><td data-target=".flagged_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
907: '<i class="bi bi-flag-fill fs-5 me-2"></i>'.
908: $this->trans('Flagged').'</td></tr>';
909: }
910: }
911:
912: /**
913: * Start the Everything section on the settings page
914: * @subpackage core/output
915: */
916: class Hm_Output_start_everything_settings extends Hm_Output_Module {
917: /**
918: * Setttings in this section control the Everything view
919: */
920: protected function output() {
921: if ($this->get('single_server_mode')) {
922: return '';
923: }
924: return '<tr><td data-target=".all_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
925: '<i class="bi bi-box2-fill fs-5 me-2"></i>'.
926: $this->trans('Everything').'</td></tr>';
927: }
928: }
929:
930: /**
931: * Start the Unread section on the settings page
932: * @subpackage core/output
933: */
934: class Hm_Output_start_unread_settings extends Hm_Output_Module {
935: /**
936: * Settings in this section control the Unread view
937: */
938: protected function output() {
939: return '<tr><td data-target=".unread_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
940: '<i class="bi bi-envelope-fill fs-5 me-2"></i>'.
941: $this->trans('Unread').'</td></tr>';
942: }
943: }
944:
945: /**
946: * Start the E-mail section on the settings page.
947: * @subpackage core/output
948: */
949: class Hm_Output_start_all_email_settings extends Hm_Output_Module {
950: /**
951: * Settings in this section control the All E-mail view. Skipped if there are no
952: * E-mail enabled module sets.
953: */
954: protected function output() {
955: if (!email_is_active($this->get('router_module_list'))) {
956: return '';
957: }
958: if ($this->get('single_server_mode')) {
959: return '';
960: }
961: return '<tr><td data-target=".email_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
962: '<i class="bi bi-envelope-fill fs-5 me-2"></i>'.
963: $this->trans('All Email').'</td></tr>';
964: }
965: }
966:
967: /**
968: * Start the search box for settings
969: * @subpackage core/output
970: */
971: class Hm_Output_start_search_settings extends Hm_Output_Module {
972: /**
973: * General settings like langauge and timezone will go here
974: */
975: protected function output() {
976: return '<tr><td colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2 py-4">'.
977: '<form><input type="search" id="settingsSearch" class="form-control" placeholder="'.$this->trans('Type a setting').'">'.
978: '<div id="noSettingsFound" class="d-none mt-3">'.$this->trans('No settings found!').'</div>'.
979: '</td></tr>';
980: }
981: }
982:
983: /**
984: * Start the general settings section
985: * @subpackage core/output
986: */
987: class Hm_Output_start_general_settings extends Hm_Output_Module {
988: /**
989: * General settings like langauge and timezone will go here
990: */
991: protected function output() {
992: return '<tr><td data-target=".general_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
993: '<i class="bi bi-gear-wide-connected fs-5 me-2"></i>'.
994: $this->trans('General').'</td></tr>';
995: }
996: }
997:
998: /**
999: * Option for the maximum number of messages per source for the Unread page
1000: * @subpackage core/output
1001: */
1002: class Hm_Output_unread_source_max_setting extends Hm_Output_Module {
1003: /**
1004: * Processed by Hm_Handler_process_unread_source_max_setting
1005: */
1006: protected function output() {
1007: $sources = DEFAULT_UNREAD_PER_SOURCE;
1008: $settings = $this->get('user_settings', array());
1009: $reset = '';
1010: if (array_key_exists('unread_per_source', $settings)) {
1011: $sources = $settings['unread_per_source'];
1012: }
1013: if ($sources != DEFAULT_UNREAD_PER_SOURCE) {
1014: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>';
1015: }
1016: return '<tr class="unread_setting"><td><label for="unread_per_source">'.
1017: $this->trans('Max messages per source').'</label></td>'.
1018: '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" size="2" id="unread_per_source" name="unread_per_source" value="'.$this->html_safe($sources).'" data-default-value="'.DEFAULT_UNREAD_PER_SOURCE.'" />'.$reset.'</td></tr>';
1019: }
1020: }
1021:
1022: /**
1023: * Option for the "received since" date range for the Unread page
1024: * @subpackage core/output
1025: */
1026: class Hm_Output_unread_since_setting extends Hm_Output_Module {
1027: /**
1028: * Processed by Hm_Handler_process_unread_since_setting
1029: */
1030: protected function output() {
1031: $settings = $this->get('user_settings', array());
1032: $since = DEFAULT_UNREAD_SINCE;
1033: if (array_key_exists('unread_since', $settings) && $settings['unread_since']) {
1034: $since = $settings['unread_since'];
1035: }
1036: return '<tr class="unread_setting"><td><label for="unread_since">'.
1037: $this->trans('Show messages received since').'</label></td>'.
1038: '<td>'.message_since_dropdown($since, 'unread_since', $this,DEFAULT_UNREAD_SINCE).'</td></tr>';
1039: }
1040: }
1041:
1042: /**
1043: * Option for the maximum number of messages per source for the Flagged page
1044: * @subpackage core/output
1045: */
1046: class Hm_Output_flagged_source_max_setting extends Hm_Output_Module {
1047: /**
1048: * Processed by Hm_Handler_process_flagged_source_max_setting
1049: */
1050: protected function output() {
1051: $sources = DEFAULT_FLAGGED_PER_SOURCE;
1052: $settings = $this->get('user_settings', array());
1053: $reset = '';
1054: if (array_key_exists('flagged_per_source', $settings)) {
1055: $sources = $settings['flagged_per_source'];
1056: }
1057: if ($sources != DEFAULT_FLAGGED_PER_SOURCE) {
1058: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>';
1059: }
1060: return '<tr class="flagged_setting"><td><label for="flagged_per_source">'.
1061: $this->trans('Max messages per source').'</label></td>'.
1062: '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" size="2" id="flagged_per_source" name="flagged_per_source" value="'.$this->html_safe($sources).'" data-default-value="'.DEFAULT_FLAGGED_PER_SOURCE.'" />'.$reset.'</td></tr>';
1063: }
1064: }
1065:
1066: /**
1067: * Option for the "received since" date range for the Flagged page
1068: * @subpackage core/output
1069: */
1070: class Hm_Output_flagged_since_setting extends Hm_Output_Module {
1071: protected function output() {
1072: /**
1073: * Processed by Hm_Handler_process_flagged_since_setting
1074: */
1075: $since = DEFAULT_FLAGGED_SINCE;
1076: $settings = $this->get('user_settings', array());
1077: if (array_key_exists('flagged_since', $settings) && $settings['flagged_since']) {
1078: $since = $settings['flagged_since'];
1079: }
1080: return '<tr class="flagged_setting"><td><label for="flagged_since">'.
1081: $this->trans('Show messages received since').'</label></td>'.
1082: '<td>'.message_since_dropdown($since, 'flagged_since', $this, DEFAULT_FLAGGED_SINCE).'</td></tr>';
1083: }
1084: }
1085:
1086: /**
1087: * Option for the maximum number of messages per source for the All E-mail page
1088: * @subpackage core/output
1089: */
1090: class Hm_Output_all_email_source_max_setting extends Hm_Output_Module {
1091: /**
1092: * Processed by Hm_Handler_process_all_email_source_max_setting
1093: */
1094: protected function output() {
1095: if (!email_is_active($this->get('router_module_list'))) {
1096: return '';
1097: }
1098: $sources = DEFAULT_ALL_EMAIL_PER_SOURCE;
1099: $settings = $this->get('user_settings', array());
1100: $reset = '';
1101: if (array_key_exists('all_email_per_source', $settings)) {
1102: $sources = $settings['all_email_per_source'];
1103: }
1104: if ($sources != DEFAULT_ALL_EMAIL_PER_SOURCE) {
1105: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>';
1106: }
1107: return '<tr class="email_setting"><td><label for="all_email_per_source">'.
1108: $this->trans('Max messages per source').'</label></td>'.
1109: '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" size="2" id="all_email_per_source" name="all_email_per_source" value="'.$this->html_safe($sources).'" data-default-value="'.DEFAULT_ALL_EMAIL_PER_SOURCE.'" />'.$reset.'</td></tr>';
1110: }
1111: }
1112:
1113: /**
1114: * Option for the maximum number of messages per source for the Everything page
1115: * @subpackage core/output
1116: */
1117: class Hm_Output_all_source_max_setting extends Hm_Output_Module {
1118: /**
1119: * Processed by Hm_Handler_process_all_source_max_setting
1120: */
1121: protected function output() {
1122: $sources = DEFAULT_ALL_PER_SOURCE;
1123: $settings = $this->get('user_settings', array());
1124: $reset = '';
1125: if (array_key_exists('all_per_source', $settings)) {
1126: $sources = $settings['all_per_source'];
1127: }
1128: if ($sources != DEFAULT_ALL_PER_SOURCE) {
1129: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>';
1130: }
1131: return '<tr class="all_setting"><td><label for="all_per_source">'.
1132: $this->trans('Max messages per source').'</label></td>'.
1133: '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" size="2" id="all_per_source" name="all_per_source" value="'.$this->html_safe($sources).'" data-default-value="'.DEFAULT_ALL_PER_SOURCE.'" />'.$reset.'</td></tr>';
1134: }
1135: }
1136:
1137: /**
1138: * Option for the "received since" date range for the All E-mail page
1139: * @subpackage core/output
1140: */
1141: class Hm_Output_all_email_since_setting extends Hm_Output_Module {
1142: /**
1143: * Processed by Hm_Handler_process_all_email_since_setting
1144: */
1145: protected function output() {
1146: if (!email_is_active($this->get('router_module_list'))) {
1147: return '';
1148: }
1149: $since = DEFAULT_ALL_EMAIL_SINCE;
1150: $settings = $this->get('user_settings', array());
1151: if (array_key_exists('all_email_since', $settings) && $settings['all_email_since']) {
1152: $since = $settings['all_email_since'];
1153: }
1154: return '<tr class="email_setting"><td><label for="all_email_since">'.
1155: $this->trans('Show messages received since').'</label></td>'.
1156: '<td>'.message_since_dropdown($since, 'all_email_since', $this, DEFAULT_ALL_EMAIL_SINCE).'</td></tr>';
1157: }
1158: }
1159:
1160: /**
1161: * Option for the "received since" date range for the Everything page
1162: * @subpackage core/output
1163: */
1164: class Hm_Output_all_since_setting extends Hm_Output_Module {
1165: /**
1166: * Processed by Hm_Handler_process_all_since_setting
1167: */
1168: protected function output() {
1169: $since = DEFAULT_ALL_SINCE;
1170: $settings = $this->get('user_settings', array());
1171: if (array_key_exists('all_since', $settings) && $settings['all_since']) {
1172: $since = $settings['all_since'];
1173: }
1174: return '<tr class="all_setting"><td><label for="all_since">'.
1175: $this->trans('Show messages received since').'</label></td>'.
1176: '<td>'.message_since_dropdown($since, 'all_since', $this, DEFAULT_ALL_SINCE).'</td></tr>';
1177: }
1178: }
1179:
1180: /**
1181: * Option for the language setting
1182: * @subpackage core/output
1183: */
1184: class Hm_Output_language_setting extends Hm_Output_Module {
1185: /**
1186: * Processed by Hm_Handler_process_language_setting
1187: */
1188: protected function output() {
1189: $langs = interface_langs();
1190: $translated = array();
1191: $reset = '';
1192: foreach ($langs as $code => $name) {
1193: $translated[$code] = $this->trans($name);
1194: }
1195: asort($translated);
1196: $mylang = $this->get('language', '');
1197: $res = '<tr class="general_setting"><td><label for="language">'.
1198: $this->trans('Language').'</label></td>'.
1199: '<td><select id="language" class="form-select form-select-sm w-auto" name="language">';
1200: foreach ($translated as $id => $lang) {
1201: $res .= '<option ';
1202: if ($id == $mylang) {
1203: $res .= 'selected="selected" ';
1204: if ($id != 'en') {
1205: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_select"></i></span>';
1206: }
1207: }
1208: $res .= 'value="'.$id.'">'.$lang.'</option>';
1209: }
1210: $res .= '</select>'.$reset.'</td></tr>';
1211: return $res;
1212: }
1213: }
1214:
1215: /**
1216: * Option for the timezone setting
1217: * @subpackage core/output
1218: */
1219: class Hm_Output_timezone_setting extends Hm_Output_Module {
1220: /**
1221: * Processed by Hm_Handler_process_timezone_setting
1222: */
1223: protected function output() {
1224: $zones = timezone_identifiers_list();
1225: $settings = $this->get('user_settings', array());
1226: $reset = '';
1227: if (array_key_exists('timezone', $settings)) {
1228: $myzone = $settings['timezone'];
1229: }
1230: else {
1231: $myzone = $this->get('default_timezone','UTC');
1232: }
1233: $res = '<tr class="general_setting"><td><label for="timezone">'.
1234: $this->trans('Timezone').'</label></td><td><select class="w-auto form-select form-select-sm" id="timezone" name="timezone">';
1235: foreach ($zones as $zone) {
1236: $res .= '<option ';
1237: if ($zone == $myzone) {
1238: $res .= 'selected="selected" ';
1239: if ($zone != $this->get('default_timezone','UTC')) {
1240: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_timezone"></i></span>';
1241: }
1242: }
1243: $res .= 'value="'.$zone.'">'.$zone.'</option>';
1244: }
1245: $res .= '</select>'.$reset.'</td></tr>';
1246: return $res;
1247: }
1248: }
1249:
1250: /**
1251: * Option to enable/disable message list icons
1252: * @subpackage core/output
1253: */
1254: class Hm_Output_msg_list_icons_setting extends Hm_Output_Module {
1255: protected function output() {
1256: $checked = '';
1257: $settings = $this->get('user_settings', array());
1258: $reset = '';
1259: if (array_key_exists('show_list_icons', $settings) && $settings['show_list_icons']) {
1260: $checked = ' checked="checked"';
1261: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>';
1262: }
1263: return '<tr class="general_setting"><td><label class="form-check-label" for="show_list_icons">'.
1264: $this->trans('Show icons in message lists').'</label></td>'.
1265: '<td><input class="form-check-input" type="checkbox" '.$checked.' id="show_list_icons" name="show_list_icons" data-default-value="false" value="1" />'.$reset.'</td></tr>';
1266: }
1267: }
1268:
1269: /**
1270: * Ends the settings table
1271: * @subpackage core/output
1272: */
1273: class Hm_Output_end_settings_form extends Hm_Output_Module {
1274: /**
1275: * Closes the table, form and div opened in Hm_Output_start_settings_form
1276: */
1277: protected function output() {
1278: return '<tr><td class="submit_cell" colspan="2">'.
1279: '<input class="save_settings btn btn-primary" type="submit" name="save_settings" value="'.$this->trans('Save').'" />'.
1280: '</td></tr></table></div></form>'.
1281: '<div class="px-3 d-flex justify-content-end"><form method="POST"><input type="hidden" name="hm_page_key" value="'.$this->html_safe(Hm_Request_Key::generate()).'" />'.
1282: '<input class="reset_factory_button btn btn-light border" type="submit" name="reset_factory" value="'.$this->trans('Restore Defaults').'" /></form></div>'.
1283: '</div>';
1284: }
1285: }
1286:
1287: /**
1288: * Start the folder list
1289: * @subpackage core/output
1290: */
1291: class Hm_Output_folder_list_start extends Hm_Output_Module {
1292: /**
1293: * Opens the folder list nav tag
1294: */
1295: protected function output() {
1296: $res = '<nav class="folder_cell"><div class="folder_list">';
1297: return $res;
1298: }
1299: }
1300:
1301: /**
1302: * Start the folder list content
1303: * @subpackage core/output
1304: */
1305: class Hm_Output_folder_list_content_start extends Hm_Output_Module {
1306: /**
1307: * Creates a modfiable string called formatted_folder_list other modules append to
1308: */
1309: protected function output() {
1310: if ($this->format == 'HTML5') {
1311: return '';
1312: }
1313: $this->out('formatted_folder_list', '', false);
1314: }
1315: }
1316:
1317: /**
1318: * Start the Main menu section of the folder list
1319: * @subpackage core/output
1320: */
1321: class Hm_Output_main_menu_start extends Hm_Output_Module {
1322: /**
1323: * Opens a div and unordered list tag
1324: */
1325: protected function output() {
1326: $res = '';
1327: if (DEBUG_MODE or DEFAULT_DEBUG_LOG) {
1328: $res .= '<span title="'.
1329: $this->trans('Running in debug mode. See https://cypht.org/install.html Section 6 for more detail.').
1330: '" class="debug_title">'.$this->trans('Debug').'</span>';
1331: }
1332: $theme = $this->get('theme');
1333: $logo = $theme === 'darkly' ? 'modules/core/assets/images/logo.svg' : 'modules/core/assets/images/logo_dark.svg' ;
1334: $res .= '<a href="?page=home" class="menu_home"><img class="app-logo" src="'.WEB_ROOT.$logo.'"></a>';
1335: $res .= '<div class="main"><ul class="folders">';
1336: if ($this->format == 'HTML5') {
1337: return $res;
1338: }
1339: $this->concat('formatted_folder_list', $res);
1340: }
1341: }
1342:
1343: /**
1344: * Content for the Main menu section of the folder list
1345: * @subpackage core/output
1346: */
1347: class Hm_Output_main_menu_content extends Hm_Output_Module {
1348: /**
1349: * Links to default pages: Home, Everything, Unread and Flagged
1350: * @todo break this up into smaller modules
1351: */
1352: protected function output() {
1353: $res = '';
1354: $email = false;
1355: if (array_key_exists('email_folders', merge_folder_list_details($this->get('folder_sources', array())))) {
1356: $email = true;
1357: }
1358: $total_accounts = count($this->get('imap_servers', array())) + count($this->get('feeds', array()));
1359: if ($total_accounts > 1) {
1360: $res .= '<li class="menu_combined_inbox"><a class="unread_link" href="?page=message_list&amp;list_path=combined_inbox">';
1361: if (!$this->get('hide_folder_icons')) {
1362: $res .= '<i class="bi bi-box2-fill menu-icon"></i>';
1363: }
1364: $res .= '<span class="nav-label">'.$this->trans('Everything').'</span</a><span class="combined_inbox_count"></span></li>';
1365: }
1366: $res .= '<li class="menu_unread d-flex align-items-center"><a class="unread_link d-flex align-items-center" href="?page=message_list&amp;list_path=unread">';
1367: if (!$this->get('hide_folder_icons')) {
1368: $res .= '<i class="bi bi-envelope-fill menu-icon"></i>';
1369: }
1370: $res .= '<span class="nav-label">'.$this->trans('Unread').'</span></a><span class="total_unread_count badge rounded-pill text-bg-info ms-2 px-1"></span></li>';
1371: $res .= '<li class="menu_flagged"><a class="unread_link" href="?page=message_list&amp;list_path=flagged">';
1372: if (!$this->get('hide_folder_icons')) {
1373: $res .= '<i class="bi bi-flag-fill menu-icon"></i>';
1374: }
1375: $res .= '<span class="nav-label">'.$this->trans('Flagged').'</span></a> <span class="flagged_count"></span></li>';
1376: $res .= '<li class="menu_junk"><a class="unread_link" href="?page=message_list&amp;list_path=junk">';
1377: if (!$this->get('hide_folder_icons')) {
1378: $res .= '<i class="bi bi-envelope-x-fill menu-icon"></i>';
1379: }
1380: $res .= '<span class="nav-label">'.$this->trans('Junk').'</span></a></li>';
1381: $res .= '<li class="menu_trash"><a class="unread_link" href="?page=message_list&amp;list_path=trash">';
1382: if (!$this->get('hide_folder_icons')) {
1383: $res .= '<i class="bi bi-trash3-fill menu-icon"></i>';
1384: }
1385: $res .= '<span class="nav-label">'.$this->trans('Trash').'</span></a></li>';
1386: $res .= '<li class="menu_drafts"><a class="unread_link" href="?page=message_list&amp;list_path=drafts">';
1387: if (!$this->get('hide_folder_icons')) {
1388: $res .= '<i class="bi bi-pencil-square menu-icon"></i>';
1389: }
1390: $res .= '<span class="nav-label">'.$this->trans('Drafts').'</span></a></li>';
1391: $settings = $this->get('user_settings', array());
1392: if (array_key_exists('enable_snooze_setting', $settings) && $settings['enable_snooze_setting']) {
1393: $res .= '<li class="menu_snoozed"><a class="unread_link" href="?page=message_list&amp;list_path=snoozed">';
1394: if (!$this->get('hide_folder_icons')) {
1395: $res .= '<i class="bi bi-clock-fill menu-icon"></i>';
1396: }
1397: $res .= '<span class="nav-label">'.$this->trans('Snoozed').'</span></a></li>';
1398: }
1399:
1400: if ($this->format == 'HTML5') {
1401: return $res;
1402: }
1403: $this->concat('formatted_folder_list', $res);
1404: }
1405: }
1406:
1407: /**
1408: * Close the Main menu section of the folder list
1409: * @subpackage core/output
1410: */
1411: class Hm_Output_main_menu_end extends Hm_Output_Module {
1412: protected function output() {
1413: $res = '</ul></div>';
1414: if ($this->format == 'HTML5') {
1415: return $res;
1416: }
1417: $this->concat('formatted_folder_list', $res);
1418: }
1419: }
1420:
1421: /**
1422: * Output the E-mail section of the folder list
1423: * @subpackage core/output
1424: */
1425: class Hm_Output_email_menu_content extends Hm_Output_Module {
1426: /**
1427: * Displays a list of all configured E-mail accounts
1428: */
1429: protected function output() {
1430: $res = '';
1431: $folder_sources = merge_folder_list_details($this->get('folder_sources'));
1432: $single = $this->get('single_server_mode');
1433: foreach ($folder_sources as $src => $content) {
1434: $parts = explode('_', $src);
1435: array_pop($parts);
1436: $name = ucwords(implode(' ', $parts));
1437: $class = $this->html_safe($src);
1438: if (!$single) {
1439: $res .= '<div class="src_name d-flex justify-content-between pe-2" data-bs-toggle="collapse" role="button" data-bs-target=".'.$this->html_safe($src).'">'.$this->trans($name).
1440: '<i class="bi bi-chevron-down"></i></div>';
1441: $class .= ' collapse';
1442: }
1443:
1444: $res .= '<div class="'.$class.'"><ul class="folders">';
1445: if ($name == 'Email' && count($this->get('imap_servers', array())) > 1) {
1446: $res .= '<li class="menu_email"><a class="unread_link" href="?page=message_list&amp;list_path=email">';
1447: if (!$this->get('hide_folder_icons')) {
1448: $res .= '<i class="bi bi-globe-americas menu-icon"></i>';
1449: }
1450: $res .= $this->trans('All').'</a> <span class="unread_mail_count"></span></li>';
1451: }
1452: $res .= $content.'</ul></div>';
1453: }
1454: if ($this->format == 'HTML5') {
1455: return $res;
1456: }
1457: $this->concat('formatted_folder_list', $res);
1458: }
1459: }
1460:
1461: /**
1462: * Start the Settings section of the folder list
1463: * @subpackage core/output
1464: */
1465: class Hm_Output_settings_menu_start extends Hm_Output_Module {
1466: /**
1467: * Opens an unordered list
1468: */
1469: protected function output() {
1470: $res = '<div class="src_name d-flex justify-content-between pe-2" data-bs-toggle="collapse" role="button" data-bs-target=".settings">'.$this->trans('Settings').
1471: '<i class="bi bi-chevron-down"></i></div>'.
1472: '<ul id="settings-menu-start" class="collapse settings folders">';
1473: if ($this->format == 'HTML5') {
1474: return $res;
1475: }
1476: $this->concat('formatted_folder_list', $res);
1477: }
1478: }
1479:
1480: /**
1481: * Save settings page content
1482: * @subpackage core/output
1483: */
1484: class Hm_Output_save_form extends Hm_Output_Module {
1485: /**
1486: * Outputs save form
1487: */
1488: protected function output() {
1489: $changed = $this->get('changed_settings', array());
1490: $res = '<div class="save_settings_page p-0"><div class="content_title px-3">'.$this->trans('Save Settings').'</div>';
1491: $res .= '<div class="save_details p-3">'.$this->trans('Settings are not saved permanently on the server unless you explicitly allow it. '.
1492: 'If you don\'t save your settings, any changes made since you last logged in will be deleted when your '.
1493: 'session expires or you logout. You must re-enter your password for security purposes to save your settings '.
1494: 'permanently.');
1495: $res .= '<div class="save_subtitle mt-3"><b>'.$this->trans('Unsaved Changes').'</b></div>';
1496: $res .= '<ul class="unsaved_settings">';
1497: if (!empty($changed)) {
1498: $changed = array_count_values($changed);
1499: foreach ($changed as $change => $num) {
1500: $res .= '<li>'.$this->trans($change).' ('.$this->html_safe($num).'X)</li>';
1501: }
1502: }
1503: else {
1504: $res .= '<li>'.$this->trans('No changes need to be saved').'</li>';
1505: }
1506: $res .= '</ul></div><div class="save_perm_form px-3"><form method="post">'.
1507: '<input type="hidden" name="hm_page_key" value="'.$this->html_safe(Hm_Request_Key::generate()).'" />'.
1508: '<input type="text" value="'.$this->html_safe($this->get('username', 'cypht_user')).'" autocomplete="username" style="display: none;"/>'.
1509: '<label class="screen_reader" for="password">Password</label><input required id="password" '.
1510: 'name="password" autocomplete="current-password" class="save_settings_password form-control mb-2 warn_on_paste" type="password" placeholder="'.$this->trans('Password').'" />'.
1511: '<input class="save_settings btn btn-primary me-2" type="submit" name="save_settings_permanently" value="'.$this->trans('Save').'" />'.
1512: '<input class="save_settings btn btn-outline-secondary me-2" type="submit" name="save_settings_permanently_then_logout" value="'.$this->trans('Save and Logout').'" />'.
1513: '</form><form method="post"><input type="hidden" name="hm_page_key" value="'.$this->html_safe(Hm_Request_Key::generate()).'" />'.
1514: '<input class="save_settings btn btn-outline-secondary" type="submit" name="logout" value="'.$this->trans('Just Logout').'" />'.
1515: '</form></div>';
1516:
1517: $res .= '</div>';
1518: return $res;
1519: }
1520: }
1521:
1522: /**
1523: * Servers link for the Settings menu section of the folder list
1524: * @subpackage core/output
1525: */
1526: class Hm_Output_settings_servers_link extends Hm_Output_Module {
1527: /**
1528: * Outputs links to the Servers settings pages
1529: */
1530: protected function output() {
1531: $res = '<li class="menu_servers"><a class="unread_link" href="?page=servers">';
1532: if (!$this->get('hide_folder_icons')) {
1533: $res .= '<i class="bi bi-pc-display-horizontal menu-icon"></i>';
1534: }
1535: $res .= $this->trans('Servers').'</a></li>';
1536: $this->concat('formatted_folder_list', $res);
1537: }
1538: }
1539:
1540: /**
1541: * Site link for the Settings menu section of the folder list
1542: * @subpackage core/output
1543: */
1544: class Hm_Output_settings_site_link extends Hm_Output_Module {
1545: /**
1546: * Outputs links to the Site Settings pages
1547: */
1548: protected function output() {
1549: $res = '<li class="menu_settings"><a class="unread_link" href="?page=settings">';
1550: if (!$this->get('hide_folder_icons')) {
1551: $res .= '<i class="bi bi-gear-wide-connected menu-icon"></i>';
1552: }
1553: $res .= $this->trans('Site').'</a></li>';
1554: $this->concat('formatted_folder_list', $res);
1555: }
1556: }
1557:
1558: /**
1559: * Save link for the Settings menu section of the folder list
1560: * @subpackage core/output
1561: */
1562: class Hm_Output_settings_save_link extends Hm_Output_Module {
1563: /**
1564: * Outputs links to the Servers and Site Settings pages
1565: */
1566: protected function output() {
1567: if ($this->get('single_server_mode')) {
1568: return;
1569: }
1570: $res = '<li class="menu_save"><a class="unread_link" href="?page=save">';
1571: if (!$this->get('hide_folder_icons')) {
1572: $res .= '<i class="bi bi-download menu-icon"></i>';
1573: }
1574: $res .= $this->trans('Save').'</a></li>';
1575: $this->concat('formatted_folder_list', $res);
1576: }
1577: }
1578:
1579: /**
1580: * Closes the Settings menu in the folder list
1581: * @subpackage core/output
1582: */
1583: class Hm_Output_settings_menu_end extends Hm_Output_Module {
1584: /**
1585: * Closes the ul tag opened in Hm_Output_settings_menu_start
1586: */
1587: protected function output() {
1588: $res = '</ul>';
1589: if ($this->format == 'HTML5') {
1590: return $res;
1591: }
1592: $this->concat('formatted_folder_list', $res);
1593: }
1594: }
1595:
1596: /**
1597: * End of the content section of the folder list
1598: * @subpackage core/output
1599: */
1600: class Hm_Output_folder_list_content_end extends Hm_Output_Module {
1601: /**
1602: * Adds collapse and reload links
1603: */
1604: protected function output() {
1605: $res = '<div class="sidebar-footer">';
1606: $res .= '<a class="logout_link" href="#" title="'. $this->trans('Logout') .'">';
1607: if (!$this->get('hide_folder_icons')) {
1608: $res .= '<i class="bi bi-power menu-icon"></i>';
1609: }
1610: $res .= '<span class="nav-label">' . $this->trans('Logout') .'</span>';
1611: $res .= '</a>';
1612: $res .= '<a href="#" class="update_message_list" title="'. $this->trans('Reload') .'">';
1613: if (!$this->get('hide_folder_icons')) {
1614: $res .= '<i class="bi bi-arrow-clockwise menu-icon"></i>';
1615: }
1616: $res .= '<span class="nav-label">' . $this->trans('Reload') . '</span>';
1617: $res .= '</a>';
1618:
1619: // Add menu-toggle for desktop only (not mobile)
1620: $res .= '<div class="menu-toggle fw-bold cursor-pointer no_mobile"><i class="bi bi-list fs-5 fw-bold"></i></div>';
1621:
1622: /** Sidebar footer end */
1623:
1624:
1625: if ($this->format == 'HTML5') {
1626: return $res;
1627: }
1628: $this->concat('formatted_folder_list', $res);
1629: }
1630: }
1631:
1632: /**
1633: * Closes the folder list
1634: * @subpackage core/output
1635: */
1636: class Hm_Output_folder_list_end extends Hm_Output_Module {
1637: /**
1638: * Closes the div and nav tags opened in Hm_Output_folder_list_start
1639: */
1640: protected function output() {
1641: return '</div></nav>';
1642: }
1643: }
1644:
1645: /**
1646: * Starts the main content section
1647: * @subpackage core/output
1648: */
1649: class Hm_Output_content_section_start extends Hm_Output_Module {
1650: /**
1651: * Opens a main tag for the primary content section
1652: */
1653: protected function output() {
1654: return '<main class="container-fluid content_cell" id="cypht-main"><div class="offline">'.$this->trans('Offline').'</div><div class="row m-0">';
1655: }
1656: }
1657:
1658: /**
1659: * Closes the main content section
1660: * @subpackage core/output
1661: */
1662: class Hm_Output_content_section_end extends Hm_Output_Module {
1663: /**
1664: * Closes the main tag opened in Hm_Output_content_section_start
1665: */
1666: protected function output() {
1667: return '</div></main>';
1668: }
1669: }
1670:
1671: /**
1672: * modals
1673: * @subpackage core/output
1674: */
1675: class Hm_Output_modals extends Hm_Output_Module {
1676: /**
1677: * Outputs modals
1678: */
1679: protected function output() {
1680: $share_folder_modal = '<div class="modal fade" id="shareFolderModal" tabindex="-1" aria-labelledby="shareFolderModalLabel" aria-hidden="true">';
1681: $share_folder_modal .= '<div class="modal-dialog modal-lg">';
1682: $share_folder_modal .= '<div class="modal-content">';
1683: $share_folder_modal .= '<div class="modal-header">';
1684: $share_folder_modal .= '<h5 class="modal-title" id="shareFolderModalLabel">'.$this->trans('Edit Permissions').'</h5>';
1685: $share_folder_modal .= '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>';
1686: $share_folder_modal .= '</div>';
1687:
1688: $share_folder_modal .= '<div class="modal-body">';
1689: $share_folder_modal .= '<div class="row">';
1690:
1691: $share_folder_modal .= '<div class="col-lg-8 col-md-12">';
1692:
1693: $share_folder_modal .= '<div id="loadingSpinner" class="text-center">';
1694: $share_folder_modal .= '<div class="spinner-border text-primary" role="status">';
1695: $share_folder_modal .= '<span class="visually-hidden">Loading...</span>';
1696: $share_folder_modal .= '</div>';
1697: $share_folder_modal .= '</div>';
1698:
1699: $share_folder_modal .= '<table class="table table-striped" id="permissionTable" style="display:none;">';
1700: $share_folder_modal .= '<thead>';
1701: $share_folder_modal .= '<tr>';
1702: $share_folder_modal .= '<th>'.$this->trans('User').'</th>';
1703: $share_folder_modal .= '<th>'.$this->trans('Permissions').'</th>';
1704: $share_folder_modal .= '<th>'.$this->trans('Actions').'</th>';
1705: $share_folder_modal .= '</tr>';
1706: $share_folder_modal .= '</thead>';
1707: $share_folder_modal .= '<tbody></tbody>';
1708: $share_folder_modal .= '</table>';
1709:
1710: $share_folder_modal .= '</div>';
1711:
1712: $share_folder_modal .= '<div class="col-lg-4 col-md-12">';
1713: $share_folder_modal .= '<form id="shareForm" action="" method="POST">';
1714: $share_folder_modal .= '<input type="hidden" name="server_id" id="server_id" value="">';
1715: $share_folder_modal .= '<input type="hidden" name="folder_uid" id="folder_uid" value="">';
1716: $share_folder_modal .= '<input type="hidden" name="folder" id="folder" value="">';
1717:
1718: $share_folder_modal .= '<div class="mb-3 row">';
1719: $share_folder_modal .= '<div class="col-12">';
1720: $share_folder_modal .= '<label class="form-label">'.$this->trans('Identifier').'</label>';
1721: $share_folder_modal .= '<div>';
1722: $share_folder_modal .= '<input type="radio" name="identifier" value="user" id="identifierUser" checked>';
1723: $share_folder_modal .= '<label for="identifierUser">'.$this->trans('User').':</label>';
1724: $share_folder_modal .= '<input type="text" class="form-control d-inline-block" id="email" name="email" required placeholder="Enter email">';
1725: $share_folder_modal .= '</div>';
1726: $share_folder_modal .= '<div>';
1727: $share_folder_modal .= '<input type="radio" name="identifier" value="all" id="identifierAll">';
1728: $share_folder_modal .= '<label for="identifierAll">'.$this->trans('All users (anyone)').'</label>';
1729: $share_folder_modal .= '</div>';
1730: $share_folder_modal .= '<div>';
1731: $share_folder_modal .= '<input type="radio" name="identifier" value="guests" id="identifierGuests">';
1732: $share_folder_modal .= '<label for="identifierGuests">'.$this->trans('Guests (anonymous)').'</label>';
1733: $share_folder_modal .= '</div>';
1734: $share_folder_modal .= '</div>';
1735: $share_folder_modal .= '</div>';
1736:
1737: $share_folder_modal .= '<div class="mb-3 row">';
1738: $share_folder_modal .= '<div class="col-12">';
1739: $share_folder_modal .= '<label class="form-label">'.$this->trans('Access Rights').'</label>';
1740: $share_folder_modal .= '<div>';
1741: $share_folder_modal .= '<input type="checkbox" name="access_read" id="accessRead" checked>';
1742: $share_folder_modal .= '<label for="accessRead">'.$this->trans('Read').'</label>';
1743: $share_folder_modal .= '</div>';
1744: $share_folder_modal .= '<div>';
1745: $share_folder_modal .= '<input type="checkbox" name="access_write" id="accessWrite">';
1746: $share_folder_modal .= '<label for="accessWrite">'.$this->trans('Write').'</label>';
1747: $share_folder_modal .= '</div>';
1748: $share_folder_modal .= '<div>';
1749: $share_folder_modal .= '<input type="checkbox" name="access_delete" id="accessDelete">';
1750: $share_folder_modal .= '<label for="accessDelete">'.$this->trans('Delete').'</label>';
1751: $share_folder_modal .= '</div>';
1752: $share_folder_modal .= '<div>';
1753: $share_folder_modal .= '<input type="checkbox" name="access_other" id="accessOther">';
1754: $share_folder_modal .= '<label for="accessOther">'.$this->trans('Other').'</label>';
1755: $share_folder_modal .= '</div>';
1756: $share_folder_modal .= '</div>';
1757: $share_folder_modal .= '</div>';
1758:
1759: $share_folder_modal .= '<div class="modal-footer">';
1760: $share_folder_modal .= '<button type="submit" class="btn btn-primary">'.$this->trans('Save').'</button>';
1761: $share_folder_modal .= '<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">'.$this->trans('Cancel').'</button>';
1762: $share_folder_modal .= '</div>';
1763: $share_folder_modal .= '</form>';
1764: $share_folder_modal .= '</div>';
1765:
1766: $share_folder_modal .= '</div>';
1767: $share_folder_modal .= '</div>';
1768:
1769: $share_folder_modal .= '</div>';
1770: $share_folder_modal .= '</div>';
1771: $share_folder_modal .= '</div>';
1772:
1773: return $share_folder_modal;
1774: }
1775: }
1776:
1777: /**
1778: * Starts the message view page
1779: * @subpackage core/output
1780: */
1781: class Hm_Output_message_start extends Hm_Output_Module {
1782: /**
1783: * @todo this is pretty ugly, clean it up and remove module specific stuff
1784: */
1785: protected function output() {
1786: if ($this->in('list_parent', array('advanced_search', 'github_all', 'sent', 'search', 'flagged', 'combined_inbox', 'unread', 'feeds', 'email'))) {
1787: if ($this->get('list_parent') == 'combined_inbox') {
1788: $list_name = $this->trans('Everything');
1789: }
1790: elseif ($this->get('list_parent') == 'email') {
1791: $list_name = $this->trans('All Email');
1792: }
1793: else {
1794: $list_name = $this->trans(ucwords(str_replace('_', ' ', $this->get('list_parent', ''))));
1795: }
1796: $additional = '';
1797: if ($this->get('list_parent') == 'advanced_search') {
1798: $page = 'advanced_search';
1799: }
1800: elseif ($this->get('list_parent') == 'search') {
1801: $page = 'search';
1802: foreach (['list_page', 'list_sort', 'search_terms', 'search_fld', 'search_since'] as $field) {
1803: if ($value = $this->get($field)) {
1804: $field = $field == 'list_sort' ? 'sort' : $field;
1805: $additional .= "&amp;$field=" . $this->html_safe($value);
1806: }
1807: }
1808: }
1809: else {
1810: $page = 'message_list';
1811: }
1812: $title = '<a href="?page='.$page.'&amp;list_path='.$this->html_safe($this->get('list_parent')).$additional.'">'.$list_name.'</a>';
1813: if (count($this->get('mailbox_list_title', array())) > 0) {
1814: $mb_title = array_map( function($v) { return $this->trans($v); }, $this->get('mailbox_list_title', array()));
1815: if (($key = array_search($list_name, $mb_title)) !== false) {
1816: unset($mb_title[$key]);
1817: }
1818: $title .= '<i class="bi bi-caret-right-fill path_delim"></i>'.
1819: '<a href="?page=message_list&amp;list_path='.$this->html_safe($this->get('list_path')).'">'.
1820: implode('<i class="bi bi-caret-right-fill path_delim"></i>',
1821: array_map( function($v) { return $this->trans($v); }, $mb_title)).'</a>';
1822: }
1823: }
1824: elseif ($this->get('mailbox_list_title')) {
1825: $url = '?page=message_list&amp;list_path='.$this->html_safe($this->get('list_path'));
1826: if ($this->get('list_page', 0)) {
1827: $url .= '&list_page='.$this->html_safe($this->get('list_page'));
1828: }
1829: if ($this->get('list_filter', '')) {
1830: $url .= '&filter='.$this->html_safe($this->get('list_filter'));
1831: }
1832: if ($this->get('list_sort', '')) {
1833: $url .= '&sort='.$this->html_safe($this->get('list_sort'));
1834: }
1835: $title = '<a href="'.$url.'">'.
1836: implode('<i class="bi bi-caret-right-fill path_delim"></i>',
1837: array_map( function($v) { return $this->trans($v); }, $this->get('mailbox_list_title', array()))).'</a>';
1838: }
1839: else {
1840: $title = '';
1841: }
1842: $res = '';
1843: if ($this->get('uid')) {
1844: $res .= '<input type="hidden" class="msg_uid" value="'.$this->html_safe($this->get('uid')).'" />';
1845: }
1846: $res .= '<div class="content_title">'.$title.'</div>';
1847: $res .= '<div class="msg_text">';
1848: return $res;
1849: }
1850: }
1851:
1852: /**
1853: * Close the message view content section
1854: * @subpackage core/output
1855: */
1856: class Hm_Output_message_end extends Hm_Output_Module {
1857: /**
1858: * Closes the div opened in Hm_Output_message_start
1859: */
1860: protected function output() {
1861: return '</div>';
1862: }
1863: }
1864:
1865: /**
1866: * Not found content for a bad page request
1867: * @subpackage core/output
1868: */
1869: class Hm_Output_notfound_content extends Hm_Output_Module {
1870: /**
1871: * Simple "page not found" page content
1872: */
1873: protected function output() {
1874: $res = '<div class="content_title">'.$this->trans('Page Not Found!').'</div>';
1875: $res .= '<div class="empty_list"><br />'.$this->trans('Nothingness').'</div>';
1876: return $res;
1877: }
1878: }
1879:
1880: /**
1881: * Start the table for a message list
1882: * @subpackage core/output
1883: */
1884: class Hm_Output_message_list_start extends Hm_Output_Module {
1885: /**
1886: * Uses the message_list_fields input to determine the format.
1887: */
1888: protected function output() {
1889:
1890: $col_flds = array();
1891: $header_flds = array();
1892: foreach ($this->get('message_list_fields', array()) as $vals) {
1893: if ($vals[0]) {
1894: $col_flds[] = sprintf('<col class="%s">', $vals[0]);
1895: }
1896: if ($vals[1] && $vals[2]) {
1897: $header_flds[] = sprintf('<th class="%s">%s</th>', $vals[1], $this->trans($vals[2]));
1898: }
1899: else {
1900: $header_flds[] = '<th></th>';
1901: }
1902: }
1903: $res = '<div class="p-3">';
1904: $res .= '<table class="message_table table">';
1905: if (!$this->get('no_message_list_headers')) {
1906: if (!empty($col_flds)) {
1907: $res .= '<colgroup>'.implode('', $col_flds).'</colgroup>';
1908: }
1909: if (!empty($header_flds)) {
1910: $res .= '<thead><tr>'.implode('', $header_flds).'</tr></thead>';
1911: }
1912: }
1913: $res .= '<tbody class="message_table_body">';
1914: return $res;
1915: }
1916: }
1917:
1918: /**
1919: * Output the heading for the home page
1920: * @subpackage core/output
1921: */
1922: class Hm_Output_home_heading extends Hm_Output_Module {
1923: /**
1924: */
1925: protected function output() {
1926: return '<div class="content_title">'.$this->trans('Home').'</div>';
1927: }
1928: }
1929:
1930: /**
1931: * Output password dialogs if no_password_save is active
1932: * @subpackage core/output
1933: */
1934: class Hm_Output_home_password_dialogs extends Hm_Output_Module {
1935: /**
1936: * Allow the user to input passwords for this session
1937: */
1938: protected function output() {
1939: $missing = $this->get('missing_pw_servers', array());
1940: if (count($missing) > 0) {
1941: $res = '<div class="home_password_dialogs mt-3 col-lg-6 col-md-5 col-sm-12">';
1942: $res .= '<div class="card"><div class="card-body">';
1943: $res .= '<div class="card_title"><h4>Passwords</h4></div><p>'.$this->trans('You have elected to not store passwords between logins.').
1944: ' '.$this->trans('Enter your passwords below to gain access to these services during this session.').'</p>';
1945:
1946: foreach ($missing as $vals) {
1947: $id = $this->html_safe(sprintf('%s_%s', mb_strtolower($vals['type']), $vals['id']));
1948: $res .= '<div class="div_'.$id.' mt-3">'.$this->html_safe($vals['type']).' '.$this->html_safe($vals['name']).
1949: ' '.$this->html_safe($vals['user']).' '.$this->html_safe($vals['server']).' <div class="input-group mt-2"><input placeholder="'.$this->trans('Password').
1950: '" type="password" class="form-control pw_input" id="update_pw_'.$id.'" /> <input type="button" class="pw_update btn btn-primary" data-id="'.$id.
1951: '" value="'.$this->trans('Update').'" /></div></div>';
1952: }
1953: $res .= '</div></div></div>';
1954: return $res;
1955: }
1956: }
1957: }
1958:
1959: /**
1960: * Output the heading for a message list
1961: * @subpackage core/output
1962: */
1963: class Hm_Output_message_list_heading extends Hm_Output_Module {
1964: /**
1965: * Title, list controls, and message controls
1966: */
1967: protected function output() {
1968: $search_field = '';
1969: $terms = $this->get('search_terms', '');
1970: if ($this->get('custom_list_controls', '')) {
1971: $config_link = $this->get('custom_list_controls');
1972: $source_link = '';
1973: $refresh_link = '<a class="refresh_link" title="'.$this->trans('Refresh').'" href="#"><i class="bi bi-arrow-clockwise refresh_list"></i></a>';
1974: }
1975: elseif (!$this->get('no_list_controls', false)) {
1976: $source_link = '<a href="#" title="'.$this->trans('Sources').'" class="source_link"><i class="bi bi-folder-fill refresh_list"></i></a>';
1977: if ($this->get('list_path') == 'combined_inbox') {
1978: $path = 'all';
1979: }
1980: else {
1981: $path = $this->get('list_path');
1982: }
1983: $config_link = '<a title="'.$this->trans('Configure').'" href="?page=settings#'.$path.'_setting"><i class="bi bi-gear-wide refresh_list"></i></a>';
1984: $refresh_link = '<a class="refresh_link" title="'.$this->trans('Refresh').'" href="#"><i class="bi bi-arrow-clockwise refresh_list"></i></a>';
1985: //$search_field = '<form method="GET">
1986: //<input type="hidden" name="page" value="message_list" />
1987: //<input type="hidden" name="list_path" value="'.$this->html_safe($this->get('list_path')).'"/>
1988: //<input required type="search" placeholder="'.$this->trans('Search').'" id="search_terms" class="imap_keyword" name="search_terms" value="'.$this->html_safe($terms).'"/></form>';
1989:
1990: }
1991: else {
1992: $config_link = '';
1993: $source_link = '';
1994: $refresh_link = '';
1995: $search_field = '';
1996: }
1997: $res = '';
1998: $res .= '<div class="message_list p-0 '.$this->html_safe($this->get('list_path')).'_list"><div class="content_title d-flex flex-wrap justify-content-between px-3 align-items-center">';
1999: $res .= '<div class="d-flex align-items-center gap-1 flex-wrap mb-2 mb-md-0">' . message_controls($this).'<div class="mailbox_list_title ms-2">'.
2000: implode('<i class="bi bi-caret-right-fill path_delim"></i>', array_map( function($v) { return $this->trans($v); },
2001: $this->get('mailbox_list_title', array()))).'</div>';
2002: if (!$this->get('is_mobile') && mb_substr((string) $this->get('list_path'), 0, 5) != 'imap_') {
2003: $res .= combined_sort_dialog($this);
2004: }
2005: $res .= '</div>';
2006: $res .= message_list_meta($this->module_output(), $this);
2007: $res .= list_controls($refresh_link, $config_link, $source_link, $search_field);
2008: $res .= list_sources($this->get('data_sources', array()), $this);
2009: $res .= '</div>';
2010: return $res;
2011: }
2012: }
2013:
2014: /**
2015: * End a message list table
2016: * @subpackage core/output
2017: */
2018: class Hm_Output_message_list_end extends Hm_Output_Module {
2019: /**
2020: * Close the table opened in Hm_Output_message_list_start
2021: */
2022: protected function output() {
2023: $res = '</tbody></table></div></div>';
2024: return $res;
2025: }
2026: }
2027:
2028: /**
2029: * Add move/copy dialog to the search page
2030: * @subpackage imap/output
2031: */
2032: class Hm_Output_search_move_copy_controls extends Hm_Output_Module {
2033: protected function output() {
2034: $res = '<span class="ctr_divider"></span> <div class="d-flex align-items-start gap-1 dropdown"><a class="imap_move disabled_input btn btn-sm btn-secondary" href="#" data-action="copy" data-bs-toggle="dropdown">'.$this->trans('Copy').'</a>';
2035: $res .= '<a class="imap_move disabled_input btn btn-sm btn-secondary" href="#" data-action="move" data-bs-toggle="dropdown">'.$this->trans('Move').'</a>';
2036: $res .= '<div class="move_to_location dropdown-menu" data-bs-auto-close="outside"></div>';
2037: $res .= '<input type="hidden" class="move_to_type" value="" />';
2038: $res .= '<input type="hidden" class="move_to_string1" value="'.$this->trans('Move to ...').'" />';
2039: $res .= '<input type="hidden" class="move_to_string2" value="'.$this->trans('Copy to ...').'" />';
2040: $res .= '<input type="hidden" class="move_to_string3" value="'.$this->trans('Removed non-IMAP messages from selection. They cannot be moved or copied').'" /></div>';
2041: // $res = "<strong>COPY/MOVE</strong>";
2042: $this->concat('msg_controls_extra', $res);
2043: }
2044: }
2045:
2046: /**
2047: * Starts the Junk section on the settings page
2048: * @subpackage core/output
2049: */
2050: class Hm_Output_start_junk_settings extends Hm_Output_Module {
2051: /**
2052: * Settings in this section control the flagged messages view
2053: */
2054: protected function output() {
2055: return '<tr><td data-target=".junk_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
2056: '<i class="bi bi-envelope-x-fill fs-5 me-2"></i>'.
2057: $this->trans('Junk').'</td></tr>';
2058: }
2059: }
2060:
2061: /**
2062: * Option for the maximum number of messages per source for the Junk page
2063: * @subpackage core/output
2064: */
2065: class Hm_Output_junk_source_max_setting extends Hm_Output_Module {
2066: /**
2067: * Processed by Hm_Handler_process_all_source_max_setting
2068: */
2069: protected function output() {
2070: $sources = DEFAULT_JUNK_PER_SOURCE;
2071: $settings = $this->get('user_settings', array());
2072: $reset = '';
2073: if (array_key_exists('junk_per_source', $settings)) {
2074: $sources = $settings['junk_per_source'];
2075: }
2076: if ($sources != DEFAULT_JUNK_PER_SOURCE) {
2077: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>';
2078: }
2079: return '<tr class="junk_setting"><td><label for="junk_per_source">'.
2080: $this->trans('Max messages per source').'</label></td>'.
2081: '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" size="2" id="junk_per_source" name="junk_per_source" value="'.$this->html_safe($sources).'" data-default-value="'.DEFAULT_JUNK_PER_SOURCE.'" />'.$reset.'</td></tr>';
2082: }
2083: }
2084:
2085: /**
2086: * Option for the "junk since" date range for the Junk page
2087: * @subpackage core/output
2088: */
2089: class Hm_Output_junk_since_setting extends Hm_Output_Module {
2090: /**
2091: * Processed by Hm_Handler_process_junk_since_setting
2092: */
2093: protected function output() {
2094: $since = DEFAULT_JUNK_SINCE;
2095: $settings = $this->get('user_settings', array());
2096: if (array_key_exists('junk_since', $settings) && $settings['junk_since']) {
2097: $since = $settings['junk_since'];
2098: }
2099: return '<tr class="junk_setting"><td><label for="junk_since">'.
2100: $this->trans('Show junk messages since').'</label></td>'.
2101: '<td>'.message_since_dropdown($since, 'junk_since', $this, DEFAULT_JUNK_SINCE).'</td></tr>';
2102: }
2103: }
2104:
2105: /**
2106: * Starts the Trash section on the settings page
2107: * @subpackage core/output
2108: */
2109: class Hm_Output_start_trash_settings extends Hm_Output_Module {
2110: /**
2111: * Settings in this section control the flagged messages view
2112: */
2113: protected function output() {
2114: return '<tr><td data-target=".trash_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
2115: '<i class="bi bi-trash3-fill fs-5 me-2"></i>'.
2116: $this->trans('Trash').'</td></tr>';
2117: }
2118: }
2119:
2120: /**
2121: * Option for the maximum number of messages per source for the Trash page
2122: * @subpackage core/output
2123: */
2124: class Hm_Output_trash_source_max_setting extends Hm_Output_Module {
2125: /**
2126: * Processed by Hm_Handler_process_all_source_max_setting
2127: */
2128: protected function output() {
2129: $sources = DEFAULT_TRASH_PER_SOURCE;
2130: $settings = $this->get('user_settings', array());
2131: $reset = '';
2132: if (array_key_exists('trash_per_source', $settings)) {
2133: $sources = $settings['trash_per_source'];
2134: }
2135: if ($sources != DEFAULT_TRASH_PER_SOURCE) {
2136: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>';
2137: }
2138: return '<tr class="trash_setting"><td><label for="trash_per_source">'.
2139: $this->trans('Max messages per source').'</label></td>'.
2140: '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" size="2" id="trash_per_source" name="trash_per_source" value="'.$this->html_safe($sources).'" data-default-value="'.DEFAULT_TRASH_PER_SOURCE.'" />'.$reset.'</td></tr>';
2141: }
2142: }
2143:
2144: /**
2145: * Option for the "trash since" date range for the Trash page
2146: * @subpackage core/output
2147: */
2148: class Hm_Output_trash_since_setting extends Hm_Output_Module {
2149: /**
2150: * Processed by Hm_Handler_process_trash_since_setting
2151: */
2152: protected function output() {
2153: $since = DEFAULT_TRASH_SINCE;
2154: $settings = $this->get('user_settings', array());
2155: if (array_key_exists('trash_since', $settings) && $settings['trash_since']) {
2156: $since = $settings['trash_since'];
2157: }
2158: return '<tr class="trash_setting"><td><label for="trash_since">'.
2159: $this->trans('Show trash messages since').'</label></td>'.
2160: '<td>'.message_since_dropdown($since, 'trash_since', $this, DEFAULT_TRASH_SINCE).'</td></tr>';
2161: }
2162: }
2163: /**
2164: * Starts the Snoozed section on the settings page
2165: * @subpackage core/output
2166: */
2167: class Hm_Output_start_snoozed_settings extends Hm_Output_Module {
2168: /**
2169: * Settings in this section control the snoozed messages view
2170: */
2171: protected function output() {
2172: return '<tr><td data-target=".snoozed_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
2173: '<i class="bi bi-clock-fill fs-5 me-2"></i>'.
2174: $this->trans('Snoozed').'</td></tr>';
2175: }
2176: }
2177: /**
2178: * Option for the maximum number of messages per source for the Snoozed page
2179: * @subpackage core/output
2180: */
2181: class Hm_Output_snoozed_source_max_setting extends Hm_Output_Module {
2182: /**
2183: * Processed by Hm_Handler_process_snoozed_source_max_setting
2184: */
2185: protected function output() {
2186: $sources = DEFAULT_SNOOZED_PER_SOURCE;
2187: $settings = $this->get('user_settings', array());
2188: $reset = '';
2189: if (array_key_exists('snoozed_per_source', $settings)) {
2190: $sources = $settings['snoozed_per_source'];
2191: }
2192: if ($sources != DEFAULT_SNOOZED_PER_SOURCE) {
2193: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>';
2194: }
2195: return '<tr class="snoozed_setting"><td><label for="snoozed_per_source">'.
2196: $this->trans('Max messages per source for Snoozed').'</label></td>' .
2197: '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" size="2" id="snoozed_per_source" name="snoozed_per_source" value="'.$this->html_safe($sources).'" data-default-value="'.DEFAULT_SNOOZED_PER_SOURCE.'" />'.$reset.'</td></tr>';
2198: }
2199: }
2200: /**
2201: * Option for the snoozed messages date range
2202: * @subpackage core/output
2203: */
2204: class Hm_Output_snoozed_since_setting extends Hm_Output_Module {
2205: /**
2206: * Processed by Hm_Handler_process_snoozed_since_setting
2207: */
2208: protected function output() {
2209: $since = DEFAULT_SNOOZED_SINCE;
2210: $settings = $this->get('user_settings', array());
2211: if (array_key_exists('snoozed_since', $settings) && $settings['snoozed_since']) {
2212: $since = $settings['snoozed_since'];
2213: }
2214: return '<tr class="snoozed_setting"><td><label for="snoozed_since">'.
2215: $this->trans('Show snoozed messages since').'</label></td>'.
2216: '<td>'.message_since_dropdown($since, 'snoozed_since', $this, DEFAULT_SNOOZED_SINCE).'</td></tr>';
2217: }
2218: }
2219: /**
2220: * Starts the Draft section on the settings page
2221: * @subpackage core/output
2222: */
2223: class Hm_Output_start_drafts_settings extends Hm_Output_Module {
2224: /**
2225: * Settings in this section control the flagged messages view
2226: */
2227: protected function output() {
2228: return '<tr><td data-target=".drafts_setting" colspan="2" class="settings_subtitle cursor-pointer border-bottom p-2">'.
2229: '<i class="bi bi-pencil-square fs-5 me-2"></i>'.
2230: $this->trans('Drafts').'</td></tr>';
2231: }
2232: }
2233:
2234: /**
2235: * Option for the maximum number of messages per source for the Draft page
2236: * @subpackage core/output
2237: */
2238: class Hm_Output_drafts_source_max_setting extends Hm_Output_Module {
2239: /**
2240: * Processed by Hm_Handler_process_all_source_max_setting
2241: */
2242: protected function output() {
2243: $sources = DEFAULT_DRAFT_PER_SOURCE;
2244: $settings = $this->get('user_settings', array());
2245: $reset = '';
2246: if (array_key_exists('drafts_per_source', $settings)) {
2247: $sources = $settings['drafts_per_source'];
2248: }
2249: if ($sources != DEFAULT_DRAFT_PER_SOURCE) {
2250: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_input"></i></span>';
2251: }
2252: return '<tr class="drafts_setting"><td><label for="drafts_per_source">'.
2253: $this->trans('Max messages per source').'</label></td>'.
2254: '<td class="d-flex"><input class="form-control form-control-sm w-auto" type="text" size="2" id="drafts_per_source" name="drafts_per_source" value="'.$this->html_safe($sources).'" data-default-value="'.DEFAULT_DRAFT_PER_SOURCE.'" />'.$reset.'</td></tr>';
2255: }
2256: }
2257:
2258: /**
2259: * Option for the "draft since" date range for the Draft page
2260: * @subpackage core/output
2261: */
2262: class Hm_Output_drafts_since_setting extends Hm_Output_Module {
2263: /**
2264: * Processed by Hm_Handler_process_draft_since_setting
2265: */
2266: protected function output() {
2267: $since = DEFAULT_DRAFT_SINCE;
2268: $settings = $this->get('user_settings', array());
2269: if (array_key_exists('drafts_since', $settings) && $settings['drafts_since']) {
2270: $since = $settings['drafts_since'];
2271: }
2272: return '<tr class="drafts_setting"><td><label for="drafts_since">'.
2273: $this->trans('Show draft messages since').'</label></td>'.
2274: '<td>'.message_since_dropdown($since, 'drafts_since', $this, DEFAULT_DRAFT_SINCE).'</td></tr>';
2275: }
2276: }
2277:
2278: /**
2279: * Option to warn user when he has unsaved changes.
2280: * @subpackage imap/output
2281: */
2282: class Hm_Output_warn_for_unsaved_changes_setting extends Hm_Output_Module {
2283: protected function output() {
2284: $checked = '';
2285: $settings = $this->get('user_settings', array());
2286: $reset = '';
2287: if (array_key_exists('warn_for_unsaved_changes', $settings) && $settings['warn_for_unsaved_changes']) {
2288: $checked = ' checked="checked"';
2289: $reset = '<span class="tooltip_restore" restore_aria_label="Restore default value"><i class="bi bi-arrow-counterclockwise refresh_list reset_default_value_checkbox"></i></span>';
2290: }
2291: return '<tr class="general_setting"><td><label class="form-check-label" for="warn_for_unsaved_changes">'.
2292: $this->trans('Warn for unsaved changes').'</label></td>'.
2293: '<td><input type="checkbox" '.$checked.' id="warn_for_unsaved_changes" name="warn_for_unsaved_changes" class="form-check-input" value="1" data-default-value="false" />'.$reset.'</td></tr>';
2294: }
2295: }
2296:
2297: class Hm_Output_server_config_stepper extends Hm_Output_Module {
2298: protected function output() {
2299: $accordionTitle = '';
2300: $configuredText = $this->trans('Configured') .' ';
2301: $hasEssentialModuleActivated = false;
2302:
2303: $hasImapActivated = in_array('imap', $this->get('router_module_list'), true);
2304: $hasSmtpActivated = in_array('smtp', $this->get('router_module_list'), true);
2305: $hasJmapActivated = in_array('jmap', $this->get('router_module_list'), true);
2306:
2307: if($hasImapActivated){
2308: $servers = array_filter($this->get('imap_servers', array()), fn($s) => ($s['type'] ?? null) !== 'ews');
2309: $imap_servers_count = count($servers);
2310: $accordionTitle .= 'IMAP';
2311: $configuredText .= '<span class="imap_server_count"> ' . $imap_servers_count .'</span> IMAP';
2312: $hasEssentialModuleActivated = true;
2313: }
2314:
2315: if($hasJmapActivated){
2316:
2317: $jmap_servers_count = count(array_filter($this->get('imap_servers', array()), function($v) { return array_key_exists('type', $v) && $v['type'] == 'jmap'; }));
2318: if($accordionTitle != ''){
2319: $accordionTitle .= ' - ';
2320: $configuredText .= ' / ';
2321: }
2322: $accordionTitle .= 'JMAP';
2323: $configuredText .= '<span class="jmap_server_count">' . $jmap_servers_count .'</span> JMAP';
2324: $hasEssentialModuleActivated = true;
2325: }
2326:
2327: if($hasSmtpActivated){
2328: $servers = array_filter($this->get('smtp_servers', array()), fn($s) => ($s['type'] ?? null) !== 'ews');
2329: $smtp_servers_count = count($servers);
2330: if($accordionTitle != ''){
2331: $accordionTitle .= ' - ';
2332: $configuredText .= ' / ';
2333: }
2334: $accordionTitle .= 'SMTP';
2335: $configuredText .= '<span class="smtp_server_count">' . $smtp_servers_count .'</span> SMTP';
2336: $hasEssentialModuleActivated = true;
2337: }
2338:
2339: $accordionTitle .= ' Servers';
2340:
2341: // When essential module is not activated, we don't display the accordion
2342: if(!$hasEssentialModuleActivated) return '';
2343:
2344: $serverList = null;
2345:
2346: if(class_exists('Nux_Quick_Services')){
2347: $serverList = Nux_Quick_Services::option_list(false, $this);
2348: }
2349:
2350: $hideClass = 'd-none';
2351:
2352: // Don't hide this section if at least one of the essential module is activated
2353: if($hasImapActivated || $hasSmtpActivated || $hasJmapActivated){
2354: $hideClass = '';
2355: }
2356:
2357: $res = '<div class="smtp_imap_server_setup '. $hideClass .'">
2358: <div data-target=".server_config_section" class="server_section border-bottom cursor-pointer px-1 py-3 pe-auto">
2359: <a href="#" class="pe-auto">
2360: <i class="bi bi-envelope-fill me-3"></i>
2361: <b> '.$accordionTitle.'</b>
2362: </a>
2363: <div class="server_count">'.$configuredText.'</div>
2364: </div>
2365: <div class="server_config_section px-4 pt-3 me-0">
2366: <div class="stepper col-12 col-xl-7 mb-4" id="srv_setup_stepper_stepper">
2367: <div class="step-container">
2368: <div id="step_config_1" class="step step_config">
2369: <div class="step_config-title">
2370: <h2>'.$this->trans('Step 1').'</h2>
2371: <span>('.$this->trans('Authentication').')</span>
2372: </div>
2373: <div>
2374: <form class=" me-0" method="POST">
2375: <input type="hidden" name="hm_page_key" value="'.$this->html_safe(Hm_Request_Key::generate()).'" />
2376: <input type="hidden" name="srv_setup_stepper_imap_server_id" id="srv_setup_stepper_imap_server_id" />
2377: <input type="hidden" name="srv_setup_stepper_smtp_server_id" id="srv_setup_stepper_smtp_server_id" />
2378: <div class="form-floating mb-3">
2379: <input required type="text" id="srv_setup_stepper_profile_name" name="srv_setup_stepper_profile_name" class="txt_fld form-control" value="" placeholder="'.$this->trans('Name').'">
2380: <label class="" for="srv_setup_stepper_profile_name">'.$this->trans('Name').'</label>
2381: <span id="srv_setup_stepper_profile_name-error" class="invalid-feedback"></span>
2382: </div>
2383: <div class="form-floating mb-3">
2384: <input required type="text" id="srv_setup_stepper_email" name="srv_setup_stepper_email" class="txt_fld form-control warn_on_paste" value="" placeholder="'.$this->trans('Email or Username').'" autocomplete="username">
2385: <label class="" for="srv_setup_stepper_email">'.$this->trans('Email or Username').'</label>
2386: <span id="srv_setup_stepper_email-error" class="invalid-feedback"></span>
2387: </div>
2388: <div class="form-floating mb-3">
2389: <input required type="password" id="srv_setup_stepper_password" name="srv_setup_stepper_password" class="txt_fld form-control warn_on_paste" value="" placeholder="'.$this->trans('Password').'" autocomplete="new-password">
2390: <label class="" for="srv_setup_stepper_password">'.$this->trans('Password').'</label>
2391: <span id="srv_setup_stepper_password-error" class="invalid-feedback"></span>
2392: </div>
2393: </form>
2394: </div>
2395: <div class="step_config-actions mt-4 d-flex justify-content-between">
2396: <button class="btn btn-primary px-5" onclick="display_config_step(0);resetQuickSetupForm();">'.$this->trans('Cancel').'</button>
2397: <button class="btn btn-primary px-5" id="step_config_action_next" onclick="display_config_step(2)">'.$this->trans('Next').'</button>
2398: </div>
2399: </div>
2400: <div id="step_config_2" class="step step_config">
2401: <div class="step_config-title">
2402: <h2>'.$this->trans('Step 2').'</h2>
2403: <span>('.$this->trans('Mail server configuration').')</span>
2404: </div>
2405: <div>
2406: <form>
2407: <div class="form-floating mb-3">
2408: <select class="form-select" id="srv_setup_stepper_provider" onchange="handleProviderChange(this)" label="'.$this->trans('Provider').'">
2409: <option value="">'.$this->trans('Other').'</option>'.$serverList.'
2410: </select>
2411: <label for="srv_setup_stepper_provider">'.$this->trans('Provider').'</label>
2412: </div>';
2413:
2414: if($hasSmtpActivated && $hasImapActivated) {
2415: $res .= '
2416: <div class="form-check form-switch">
2417: <input class="form-check-input" type="checkbox" role="switch" onchange="handleSmtpImapCheckboxChange(this)" id="srv_setup_stepper_is_sender" checked>
2418: <label class="form-check-label" for="srv_setup_stepper_is_sender">'.$this->trans('Sender account').'</label>
2419: </div>
2420: <div class="form-check form-switch">
2421: <input class="form-check-input" type="checkbox" role="switch" onchange="handleSmtpImapCheckboxChange(this)" id="srv_setup_stepper_is_receiver" checked>
2422: <label class="form-check-label" for="srv_setup_stepper_is_receiver">'.$this->trans('Receiver account').'</label>
2423: </div>
2424: <span id="srv_setup_stepper_serve_type-error" class="invalid-feedback"></span>
2425: ';
2426: }
2427:
2428: $res .= '<div class="step_config-smtp_imap_bloc">';
2429:
2430: return $res;
2431: }
2432: }
2433:
2434: class Hm_Output_server_config_stepper_end_part extends Hm_Output_Module {
2435: protected function output() {
2436: $res = '</div>';
2437:
2438: if(in_array('profiles', $this->get('router_module_list'), true)) {
2439: $res .= '
2440: <div class="form-check form-switch mt-3" id="srv_setup_stepper_profile_checkbox_bloc">
2441: <input class="form-check-input" type="checkbox" role="switch" onchange="handleCreateProfileCheckboxChange(this)" id="srv_setup_stepper_create_profile" checked>
2442: <label class="form-check-label" for="srv_setup_stepper_create_profile">'.$this->trans('Create Profile').'</label>
2443: </div>
2444: <div class="ms-3" id="srv_setup_stepper_profile_bloc">
2445: <div class="form-floating mb-2">
2446: <input required type="text" id="srv_setup_stepper_profile_reply_to" name="srv_setup_stepper_profile_reply_to" class="txt_fld form-control" value="" placeholder="'.$this->trans('Reply to').'">
2447: <label class="" for="srv_setup_stepper_profile_reply_to">'.$this->trans('Reply to').'</label>
2448: </div>
2449: <div class="form-floating mb-2">
2450: <input required type="text" id="srv_setup_stepper_profile_signature" name="srv_setup_stepper_profile_signature" class="txt_fld form-control" value="" placeholder="'.$this->trans('Signature').'">
2451: <label class="" for="srv_setup_stepper_profile_signature">'.$this->trans('Signature').'</label>
2452: </div>
2453: <div class="form-check" id="srv_setup_stepper_profile_checkbox_bloc">
2454: <input class="form-check-input" type="checkbox" role="switch" id="srv_setup_stepper_profile_is_default" checked>
2455: <label class="form-check-label" for="srv_setup_stepper_profile_is_default">'.$this->trans('Set this profile default').'</label>
2456: </div>
2457: </div>
2458: ';
2459: }
2460:
2461: $res .= '</form>
2462: </div>
2463: <div class="step_config-actions mt-4 d-flex justify-content-between">
2464: <button class="btn btn-danger px-3" onclick="display_config_step(0);resetQuickSetupForm();">'.$this->trans('Cancel').'</button>
2465: <button class="btn btn-primary px-4" onclick="display_config_step(1)">'.$this->trans('Previous').'</button>
2466: <button class="btn btn-primary px-3" id="step_config_action_finish" onclick="display_config_step(3)" id="stepper-action-finish">'.$this->trans('Finish').'</button>
2467: </div>
2468: </div>
2469: <div id="step_config_0" class="step_config current_config_step">
2470: <button class="imap-jmap-smtp-btn btn btn-primary px-4" id="add_new_server_button" onclick="display_config_step(1)"><i class="bi bi-plus-square-fill me-2"></i> '.$this->trans('Add a new server').'</button>
2471: </div>
2472: </div>
2473: </div>
2474: <div class="d-block">';
2475:
2476: return $res;
2477: }
2478: }
2479:
2480: class Hm_Output_server_config_stepper_accordion_end_part extends Hm_Output_Module {
2481: protected function output() {
2482: return '</div></div></div>';
2483: }
2484: }
2485:
2486: class Hm_Output_privacy_settings extends Hm_Output_Module {
2487: static $settings = [
2488: 'images_whitelist' => [
2489: 'type' => 'text',
2490: 'label' => 'External images whitelist',
2491: 'description' => 'Cypht automatically prevents untrusted external images from loading in messages. Add senders from whom you want to allow images to load.',
2492: 'separator' => ','
2493: ]
2494: ];
2495:
2496: protected function output()
2497: {
2498: return getSettingsSectionOutput('privacy', $this->trans('Privacy'), 'shield', self::$settings, $this->get('user_settings', array()));
2499: }
2500: }
2501:
2502: class Hm_output_combined_message_list extends Hm_Output_Module {
2503: protected function output() {
2504: $messageList = [];
2505: $style = $this->get('news_list_style') || $this->get('is_mobile') ? 'news' : 'email';
2506: if ($this->get('imap_combined_inbox_data')) {
2507: $messageList = array_merge($messageList, format_imap_message_list($this->get('imap_combined_inbox_data'), $this, 'combined_inbox', $style));
2508: }
2509: if ($this->get('feed_list_data')) {
2510: $messageList = array_merge($messageList, $this->get('feed_list_data'), Hm_Output_filter_feed_list_data::formatMessageList($this));
2511: }
2512: $this->out('formatted_message_list', $messageList);
2513: }
2514: }
2515: