2
0
Эх сурвалжийг харах

Merge pull request #5195 from mailcow/staging

2023-04b
Patrick Schult 2 жил өмнө
parent
commit
8f28666916

+ 7 - 4
data/Dockerfiles/dockerapi/dockerapi.py

@@ -381,11 +381,14 @@ class DockerUtils:
       for container in self.docker_client.containers.list(filters={"id": container_id}):
         sane_name = re.sub(r'\W+', '', request_json['maildir'])
         vmail_name = request_json['maildir'].replace("'", "'\\''")
-        index_name = request_json['maildir'].split("/")
-        index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''")
         cmd_vmail = "if [[ -d '/var/vmail/" + vmail_name + "' ]]; then /bin/mv '/var/vmail/" + vmail_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"
-        cmd_vmail_index = "if [[ -d '/var/vmail_index/" + index_name + "' ]]; then /bin/mv '/var/vmail_index/" + index_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "_index'; fi"
-        cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index]
+        index_name = request_json['maildir'].split("/")
+        if len(index_name) > 1:
+          index_name = index_name[1].replace("'", "'\\''") + "@" + index_name[0].replace("'", "'\\''")
+          cmd_vmail_index = "if [[ -d '/var/vmail_index/" + index_name + "' ]]; then /bin/mv '/var/vmail_index/" + index_name + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "_index'; fi"
+          cmd = ["/bin/bash", "-c", cmd_vmail + " && " + cmd_vmail_index]
+        else:
+          cmd = ["/bin/bash", "-c", cmd_vmail]
         maildir_cleanup = container.exec_run(cmd, user='vmail')
         return exec_run_handler('generic', maildir_cleanup)
   # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password

+ 4 - 0
data/web/css/build/011-datatables.css

@@ -342,6 +342,10 @@ div.dataTables_wrapper div.dt-row {
   position: relative;
 }
 
+div.dataTables_wrapper span.sorting-value {
+  display: none;
+}
+
 div.dataTables_scrollHead table.dataTable {
   margin-bottom: 0 !important;
 }

+ 3 - 1
data/web/css/site/mailbox.css

@@ -66,4 +66,6 @@ table tbody tr td input[type="checkbox"] {
   padding: .2em .4em .3em !important;
   background-color: #ececec!important;
 }
-
+.badge.bg-info .bi {
+  font-size: inherit;
+}

+ 11 - 6
data/web/css/themes/mailcow-darkmode.css

@@ -20,6 +20,11 @@ legend {
     background-color: #7a7a7a !important;
     border-color: #5c5c5c !important;
 }
+.btn-dark {
+  color: #000 !important;;
+  background-color: #f6f6f6 !important;;
+  border-color: #ddd !important;;
+}
 .btn-check:checked+.btn-secondary, .btn-check:active+.btn-secondary, .btn-secondary:active, .btn-secondary.active, .show>.btn-secondary.dropdown-toggle {
     border-color: #7a7a7a !important;
 }
@@ -299,22 +304,22 @@ a:hover {
 }
 
 
-table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover, 
+table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before:hover,
 table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before:hover {
   background-color: #7a7a7a !important;
 }
-table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before, 
+table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,
 table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before {
   background-color: #7a7a7a !important;
   border: 1.5px solid #5c5c5c !important;
   color: #fff !important;
 }
-table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before, 
+table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,
 table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before {
   background-color: #949494;
 }
-table.dataTable.dtr-inline.collapsed>tbody>tr>td.child, 
-table.dataTable.dtr-inline.collapsed>tbody>tr>th.child, 
+table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,
+table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,
 table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
   background-color: #444444;
 }
@@ -327,7 +332,7 @@ table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty {
 }
 .btn.btn-outline-secondary {
   color: #fff !important;
-  border-color: #7a7a7a !important;  
+  border-color: #7a7a7a !important;
 }
 .btn-check:checked+.btn-outline-secondary, .btn-check:active+.btn-outline-secondary, .btn-outline-secondary:active, .btn-outline-secondary.active, .btn-outline-secondary.dropdown-toggle.show {
     background-color: #9b9b9b !important;

+ 46 - 8
data/web/inc/functions.inc.php

@@ -1015,20 +1015,58 @@ function formatBytes($size, $precision = 2) {
   }
   return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
 }
-function update_sogo_static_view() {
+function update_sogo_static_view($mailbox = null) {
   if (getenv('SKIP_SOGO') == "y") {
     return true;
   }
   global $pdo;
   global $lang;
-  $stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES
-    WHERE TABLE_NAME = 'sogo_view'");
-  $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
-  if ($num_results != 0) {
-    $stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
-      SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view");
-    $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
+
+  $mailbox_exists = false;
+  if ($mailbox !== null) {
+    // Check if the mailbox exists
+    $stmt = $pdo->prepare("SELECT username FROM mailbox WHERE username = :mailbox AND active = '1'");
+    $stmt->execute(array(':mailbox' => $mailbox));
+    $row = $stmt->fetch(PDO::FETCH_ASSOC);  
+    if ($row){
+      $mailbox_exists = true;
+    }
+  }
+
+  $query = "REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
+            SELECT
+              mailbox.username,
+              mailbox.domain,
+              mailbox.username,
+              IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.force_pw_update')) = '0',
+                 IF(JSON_UNQUOTE(JSON_VALUE(attributes, '$.sogo_access')) = 1, password, '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
+                 '{SSHA256}A123A123A321A321A321B321B321B123B123B321B432F123E321123123321321'),
+              mailbox.name,
+              mailbox.username,
+              IFNULL(GROUP_CONCAT(ga.aliases ORDER BY ga.aliases SEPARATOR ' '), ''),
+              IFNULL(gda.ad_alias, ''),
+              IFNULL(external_acl.send_as_acl, ''),
+              mailbox.kind,
+              mailbox.multiple_bookings
+            FROM
+              mailbox
+              LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
+              LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username
+              LEFT OUTER JOIN grouped_sender_acl_external external_acl ON external_acl.username = mailbox.username
+            WHERE
+              mailbox.active = '1'";
+  
+  if ($mailbox_exists) {
+    $query .= " AND mailbox.username = :mailbox";
+    $stmt = $pdo->prepare($query);
+    $stmt->execute(array(':mailbox' => $mailbox));
+  } else {
+    $query .= " GROUP BY mailbox.username";
+    $stmt = $pdo->query($query);
   }
+  
+  $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
+  
   flush_memcached();
 }
 function edit_user_account($_data) {

+ 9 - 1
data/web/inc/functions.mailbox.inc.php

@@ -1264,11 +1264,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             ));
           }
 
+          update_sogo_static_view($username);
           $_SESSION['return'][] = array(
             'type' => 'success',
             'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
             'msg' => array('mailbox_added', htmlspecialchars($username))
           );
+          return true;
         break;
         case 'resource':
           $domain             = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
@@ -3130,7 +3132,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
               'msg' => array('mailbox_modified', $username)
             );
+
+            update_sogo_static_view($username);
           }
+          return true;
         break;
         case 'mailbox_templates':
           if ($_SESSION['mailcow_cc_role'] != "admin") {
@@ -5053,12 +5058,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               );
               continue;
             }
+            
+            update_sogo_static_view($username);
             $_SESSION['return'][] = array(
               'type' => 'success',
               'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
               'msg' => array('mailbox_removed', htmlspecialchars($username))
             );
           }
+          return true;
         break;
         case 'mailbox_templates':
           if ($_SESSION['mailcow_cc_role'] != "admin") {
@@ -5264,7 +5272,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
       }
     break;
   }
-  if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource')) && getenv('SKIP_SOGO') != "y") {
+  if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'resource')) && getenv('SKIP_SOGO') != "y") {
     update_sogo_static_view();
   }
 }

+ 13 - 13
data/web/js/site/admin.js

@@ -117,8 +117,8 @@ jQuery(function($){
           data: 'tfa_active',
           defaultContent: '',
             render: function (data, type) {
-            if(data == 1) return '<i class="bi bi-check-lg"></i>';
-            else return '<i class="bi bi-x-lg"></i>';
+            if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
+            else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -126,8 +126,8 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            if(data == 1) return '<i class="bi bi-check-lg"></i>';
-            else return '<i class="bi bi-x-lg"></i>';
+            if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
+            else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -260,8 +260,8 @@ jQuery(function($){
           data: 'tfa_active',
           defaultContent: '',
           render: function (data, type) {
-            if(data == 1) return '<i class="bi bi-check-lg"></i>';
-            else return '<i class="bi bi-x-lg"></i>';
+            if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
+            else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -269,8 +269,8 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            if(data == 1) return '<i class="bi bi-check-lg"></i>';
-            else return '<i class="bi bi-x-lg"></i>';
+            if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
+            else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -337,7 +337,7 @@ jQuery(function($){
           data: 'keep_spam',
           defaultContent: '',
           render: function(data, type){
-            return 'yes'==data?'<i class="bi bi-x-lg"></i>':'no'==data&&'<i class="bi bi-check-lg"></i>';
+            return 'yes'==data?'<i class="bi bi-x-lg"><span class="sorting-value">yes</span></i>':'no'==data&&'<i class="bi bi-check-lg"><span class="sorting-value">no</span></i>';
           }
         },
         {
@@ -414,8 +414,8 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            if(data == 1) return '<i class="bi bi-check-lg"></i>';
-            else return '<i class="bi bi-x-lg"></i>';
+            if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
+            else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -492,8 +492,8 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            if(data == 1) return '<i class="bi bi-check-lg"></i>';
-            else return '<i class="bi bi-x-lg"></i>';
+            if(data == 1) return '<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>';
+            else return '<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {

+ 27 - 24
data/web/js/site/mailbox.js

@@ -607,7 +607,7 @@ jQuery(function($){
           defaultContent: '',
           responsivePriority: 6,
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'&#8212;');
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
           }
         },
         {
@@ -754,7 +754,7 @@ jQuery(function($){
           data: 'attributes.gal',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -762,7 +762,7 @@ jQuery(function($){
           data: 'attributes.backupmx',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -770,7 +770,7 @@ jQuery(function($){
           data: 'attributes.relay_all_recipients',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -778,7 +778,7 @@ jQuery(function($){
           data: 'attributes.relay_unknown_only',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -787,7 +787,7 @@ jQuery(function($){
           defaultContent: '',
           responsivePriority: 4,
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -1093,7 +1093,7 @@ jQuery(function($){
           defaultContent: '',
           responsivePriority: 4,
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'&#8212;');
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
           }
         },
         {
@@ -1164,13 +1164,13 @@ jQuery(function($){
 
             item.attributes.quota = humanFileSize(item.attributes.quota);
 
-            item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>';
-            item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"></i>';
-            item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
-            item.attributes.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
-            item.attributes.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
-            item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
-            item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"></i>';
+            item.attributes.tls_enforce_in = '<i class="text-' + (item.attributes.tls_enforce_in == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_in == 1 ? '1' : '0') + '</span></i>';
+            item.attributes.tls_enforce_out = '<i class="text-' + (item.attributes.tls_enforce_out == 1 ? 'success bi bi-lock-fill' : 'danger bi bi-unlock-fill') + '"><span class="sorting-value">' + (item.attributes.tls_enforce_out == 1 ? '1' : '0') + '</span></i>';
+            item.attributes.pop3_access = '<i class="text-' + (item.attributes.pop3_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.pop3_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.pop3_access == 1 ? '1' : '0') + '</span></i>';
+            item.attributes.imap_access = '<i class="text-' + (item.attributes.imap_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.imap_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.imap_access == 1 ? '1' : '0') + '</span></i>';
+            item.attributes.smtp_access = '<i class="text-' + (item.attributes.smtp_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.smtp_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.smtp_access == 1 ? '1' : '0') + '</span></i>';
+            item.attributes.sieve_access = '<i class="text-' + (item.attributes.sieve_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sieve_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sieve_access == 1 ? '1' : '0') + '</span></i>';
+            item.attributes.sogo_access = '<i class="text-' + (item.attributes.sogo_access == 1 ? 'success' : 'danger') + ' bi bi-' + (item.attributes.sogo_access == 1 ? 'check-lg' : 'x-lg') + '"><span class="sorting-value">' + (item.attributes.sogo_access == 1 ? '1' : '0') + '</span></i>';
             if (item.attributes.quarantine_notification === 'never') {
               item.attributes.quarantine_notification = lang.never;
             } else if (item.attributes.quarantine_notification === 'hourly') {
@@ -1188,7 +1188,6 @@ jQuery(function($){
               item.attributes.quarantine_category = lang.q_all;
             }
 
-
             if (item.template.toLowerCase() == "default"){
               item.action = '<div class="btn-group">' +
                 '<a href="/edit/template/' + encodeURIComponent(item.id) + '" class="btn btn-xs btn-xs-half btn-secondary"><i class="bi bi-pencil-fill"></i> ' + lang.edit + '</a>' +
@@ -1329,7 +1328,7 @@ jQuery(function($){
           defaultContent: '',
           responsivePriority: 4,
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'&#8212;');
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
           }
         },
         {
@@ -1440,7 +1439,7 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'&#8212;');
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
           }
         },
         {
@@ -1578,7 +1577,7 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':(0==data?'<i class="bi bi-x-lg"></i>':2==data&&'&#8212;');
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':(0==data?'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>':2==data&&'&#8212;');
           }
         },
         {
@@ -1675,7 +1674,7 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -1782,7 +1781,7 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -1917,7 +1916,7 @@ jQuery(function($){
           data: 'sogo_visible',
           defaultContent: '',
           render: function(data, type){
-            return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -1936,7 +1935,7 @@ jQuery(function($){
           defaultContent: '',
           responsivePriority: 6,
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -1952,6 +1951,10 @@ jQuery(function($){
     table.on('responsive-resize', function (e, datatable, columns){
       hideTableExpandCollapseBtn('#tab-mbox-aliases', '#alias_table');
     });
+    
+    table.on( 'draw', function (){
+        $('#alias_table [data-bs-toggle="tooltip"]').tooltip();
+    });
   }
   function draw_aliasdomain_table() {
     // just recalc width if instance already exists
@@ -2031,7 +2034,7 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {
@@ -2167,7 +2170,7 @@ jQuery(function($){
           data: 'active',
           defaultContent: '',
           render: function (data, type) {
-            return 1==data?'<i class="bi bi-check-lg"></i>':0==data&&'<i class="bi bi-x-lg"></i>';
+            return 1==data?'<i class="bi bi-check-lg"><span class="sorting-value">1</span></i>':0==data&&'<i class="bi bi-x-lg"><span class="sorting-value">0</span></i>';
           }
         },
         {

+ 7 - 2
data/web/lang/lang.cs-cz.json

@@ -105,7 +105,8 @@
         "timeout2": "Časový limit pro připojení k lokálnímu serveru",
         "username": "Uživatelské jméno",
         "validate": "Ověřit",
-        "validation_success": "Úspěšně ověřeno"
+        "validation_success": "Úspěšně ověřeno",
+        "tags": "Štítky"
     },
     "admin": {
         "access": "Přístupy",
@@ -333,7 +334,11 @@
         "username": "Uživatelské jméno",
         "validate_license_now": "Ověřit GUID na licenčním serveru",
         "verify": "Ověřit",
-        "yes": "&#10003;"
+        "yes": "&#10003;",
+        "f2b_ban_time_increment": "Délka banu je prodlužována s každým dalším banem",
+        "f2b_max_ban_time": "Maximální délka banu (s)",
+        "ip_check": "Kontrola IP",
+        "ip_check_disabled": "Kontrola IP je vypnuta. Můžete ji zapnout v <br> <strong>System > Nastavení > Options > Přizpůsobení</strong>"
     },
     "danger": {
         "access_denied": "Přístup odepřen nebo jsou neplatná data ve formuláři",

+ 1 - 1
data/web/sogo-auth.php

@@ -60,7 +60,7 @@ elseif (isset($_GET['login'])) {
           ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
         ));
         // redirect to sogo (sogo will get the correct credentials via nginx auth_request
-        header("Location: /SOGo/so/${login}");
+        header("Location: /SOGo/so/{$login}");
         exit;
       }
     }

+ 9 - 9
data/web/templates/edit/mailbox.twig

@@ -109,25 +109,25 @@
         <label class="control-label col-sm-2">{{ lang.user.quarantine_notification }}</label>
         <div class="col-sm-10">
           <div class="btn-group" data-acl="{{ acl.quarantine_notification }}">
-            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'never' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'never' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailbox }}"
             data-id="quarantine_notification"
             data-api-url='edit/quarantine_notification'
             data-api-attr='{"quarantine_notification":"never"}'>{{ lang.user.never }}</button>
-            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'hourly' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'hourly' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailbox }}"
             data-id="quarantine_notification"
             data-api-url='edit/quarantine_notification'
             data-api-attr='{"quarantine_notification":"hourly"}'>{{ lang.user.hourly }}</button>
-            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'daily' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'daily' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailbox }}"
             data-id="quarantine_notification"
             data-api-url='edit/quarantine_notification'
             data-api-attr='{"quarantine_notification":"daily"}'>{{ lang.user.daily }}</button>
-            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'weekly' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'weekly' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailbox }}"
             data-id="quarantine_notification"
@@ -141,19 +141,19 @@
         <label class="control-label col-sm-2">{{ lang.user.quarantine_category }}</label>
         <div class="col-sm-10">
           <div class="btn-group" data-acl="{{ acl.quarantine_category }}">
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'reject' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'reject' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailbox }}"
             data-id="quarantine_category"
             data-api-url='edit/quarantine_category'
             data-api-attr='{"quarantine_category":"reject"}'>{{ lang.user.q_reject }}</button>
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'add_header' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'add_header' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailbox }}"
             data-id="quarantine_category"
             data-api-url='edit/quarantine_category'
             data-api-attr='{"quarantine_category":"add_header"}'>{{ lang.user.q_add_header }}</button>
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'all' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'all' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailbox }}"
             data-id="quarantine_category"
@@ -167,13 +167,13 @@
         <label class="control-label col-sm-2" for="sender_acl">{{ lang.user.tls_policy }}</label>
         <div class="col-sm-10">
           <div class="btn-group" data-acl="{{ acl.tls_policy }}">
-            <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_in == '1' %} active"{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_in == '1' %} btn-dark"{% endif %}"
               data-action="edit_selected"
               data-item="{{ mailbox }}"
               data-id="tls_policy"
               data-api-url='edit/tls_policy'
               data-api-attr='{"tls_enforce_in": {% if get_tls_policy.tls_enforce_in == '1' %}0{% else %}1{% endif %} }'>{{ lang.user.tls_enforce_in }}</button>
-            <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_out == '1' %} active"{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_out == '1' %} btn-dark"{% endif %}"
               data-action="edit_selected"
               data-item="{{ mailbox }}"
               data-id="tls_policy"

+ 2 - 1
data/web/templates/mailbox/tab-mailboxes.twig

@@ -54,6 +54,7 @@
             <li class="dropdown-header">SMTP</li>
             <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"smtp_access":1}' href="#">{{ lang.mailbox.activate }}</a></li>
             <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"smtp_access":0}' href="#">{{ lang.mailbox.deactivate }}</a></li>
+            <li><hr class="dropdown-divider"></li>
             <li class="dropdown-header">Sieve</li>
             <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"sieve_access":1}' href="#">{{ lang.mailbox.activate }}</a></li>
             <li><a class="dropdown-item" data-action="edit_selected" data-id="mailbox" data-api-url='edit/mailbox' data-api-attr='{"sieve_access":0}' href="#">{{ lang.mailbox.deactivate }}</a></li>
@@ -61,7 +62,7 @@
           <a class="btn btn-sm btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addMailboxModal"><i class="bi bi-plus-lg"></i> {{ lang.mailbox.add_mailbox }}</a>
         </div>
         <div class="btn-group d-none d-lg-flex">
-          <a class="btn btn-sm btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>          
+          <a class="btn btn-sm btn-secondary" id="toggle_multi_select_all" data-id="mailbox" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
           <a class="btn btn-sm btn-xs-half btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
           <ul class="dropdown-menu">
             <li class="table_collapse_option"><a class="dropdown-item" data-datatables-expand="mailbox_table">{{ lang.datatables.expand_all }}</a></li>

+ 12 - 12
data/web/templates/user/tab-user-settings.twig

@@ -12,19 +12,19 @@
         <div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.tag_handling }}:</div>
         <div class="col-sm-9 col-12">
           <div class="btn-group" data-acl="{{ acl.delimiter_action }}">
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'subfolder' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'subfolder' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="delimiter_action"
             data-api-url='edit/delimiter_action'
             data-api-attr='{"tagged_mail_handler":"subfolder"}'>{{ lang.user.tag_in_subfolder }}</button>
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'subject' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'subject' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="delimiter_action"
             data-api-url='edit/delimiter_action'
             data-api-attr='{"tagged_mail_handler":"subject"}'>{{ lang.user.tag_in_subject }}</button>
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if get_tagging_options == 'none' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if get_tagging_options == 'none' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="delimiter_action"
@@ -40,13 +40,13 @@
         <div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.tls_policy }}:</div>
         <div class="col-sm-9 col-12">
           <div class="btn-group" data-acl="{{ acl.tls_policy }}">
-            <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_in == '1' %} active"{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_in == '1' %} btn-dark"{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="tls_policy"
             data-api-url='edit/tls_policy'
             data-api-attr='{"tls_enforce_in": {% if get_tls_policy.tls_enforce_in == '1' %}0{% else %}1{% endif %} }'>{{ lang.user.tls_enforce_in }}</button>
-            <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary{% if get_tls_policy.tls_enforce_out == '1' %} active"{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-half d-block d-sm-inline btn-light{% if get_tls_policy.tls_enforce_out == '1' %} btn-dark"{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="tls_policy"
@@ -61,25 +61,25 @@
         <div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.quarantine_notification }}:</div>
         <div class="col-sm-9 col-12">
           <div class="btn-group" data-acl="{{ acl.quarantine_notification }}">
-            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'never' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'never' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="quarantine_notification"
             data-api-url='edit/quarantine_notification'
             data-api-attr='{"quarantine_notification":"never"}'>{{ lang.user.never }}</button>
-            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'hourly' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'hourly' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="quarantine_notification"
             data-api-url='edit/quarantine_notification'
             data-api-attr='{"quarantine_notification":"hourly"}'>{{ lang.user.hourly }}</button>
-            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'daily' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'daily' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="quarantine_notification"
             data-api-url='edit/quarantine_notification'
             data-api-attr='{"quarantine_notification":"daily"}'>{{ lang.user.daily }}</button>
-            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary{% if quarantine_notification == 'weekly' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light{% if quarantine_notification == 'weekly' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="quarantine_notification"
@@ -93,19 +93,19 @@
         <div class="col-sm-3 col-12 text-sm-end text-start text-xs-bold mb-4">{{ lang.user.quarantine_category }}:</div>
         <div class="col-sm-9 col-12">
           <div class="btn-group" data-acl="{{ acl.quarantine_category }}">
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'reject' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'reject' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="quarantine_category"
             data-api-url='edit/quarantine_category'
             data-api-attr='{"quarantine_category":"reject"}'>{{ lang.user.q_reject }}</button>
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'add_header' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'add_header' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="quarantine_category"
             data-api-url='edit/quarantine_category'
             data-api-attr='{"quarantine_category":"add_header"}'>{{ lang.user.q_add_header }}</button>
-            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-secondary{% if quarantine_category == 'all' %} active{% endif %}"
+            <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline btn-light{% if quarantine_category == 'all' %} btn-dark{% endif %}"
             data-action="edit_selected"
             data-item="{{ mailcow_cc_username }}"
             data-id="quarantine_category"

+ 3 - 3
docker-compose.yml

@@ -76,7 +76,7 @@ services:
             - clamd
 
     rspamd-mailcow:
-      image: mailcow/rspamd:1.93
+      image: mailcow/rspamd:1.92
       stop_grace_period: 30s
       depends_on:
         - dovecot-mailcow
@@ -169,7 +169,7 @@ services:
             - phpfpm
 
     sogo-mailcow:
-      image: mailcow/sogo:1.116
+      image: mailcow/sogo:1.117
       environment:
         - DBNAME=${DBNAME}
         - DBUSER=${DBUSER}
@@ -510,7 +510,7 @@ services:
             - watchdog
 
     dockerapi-mailcow:
-      image: mailcow/dockerapi:2.02
+      image: mailcow/dockerapi:2.03
       security_opt:
         - label=disable
       restart: always

+ 1 - 1
generate_config.sh

@@ -261,7 +261,7 @@ COMPOSE_PROJECT_NAME=mailcowdockerized
 # Switch here between native (compose plugin) and standalone
 # For more informations take a look at the mailcow docs regarding the configuration options.
 # Normally this should be untouched but if you decided to use either of those you can switch it manually here.
-# Please be aware that at least one of those variants should be installed on your maschine or mailcow will fail.
+# Please be aware that at least one of those variants should be installed on your machine or mailcow will fail.
 
 DOCKER_COMPOSE_VERSION=${COMPOSE_VERSION}
 

+ 3 - 3
helper-scripts/nextcloud.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?<version>.*)$
-NEXTCLOUD_VERSION=26.0.0
+NEXTCLOUD_VERSION=26.0.1
 
 echo -ne "Checking prerequisites..."
 sleep 1
@@ -97,8 +97,8 @@ elif [[ ${NC_UPDATE} == "y" ]]; then
     echo -e "\033[31mError: Nextcloud occ not found. Is Nextcloud installed?\033[0m"
     exit 1
   fi
-  if grep -q 'This version of Nextcloud is not compatible with PHP>=8.2.' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
-    echo -e "\033[31mError: This version of Nextcloud is not compatible with PHP>=8.2, we'll fix it\033[0m"
+  if grep -Pq 'This version of Nextcloud is not compatible with (?:PHP)?(?>=?)(?:PHP)?(?>.+)' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then
+    echo -e "\033[31mError: This version of Nextcloud is not compatible with the current PHP version of php-fpm-mailcow, we'll fix it\033[0m"
     wget -q https://raw.githubusercontent.com/nextcloud/server/v26.0.0/lib/versioncheck.php -O ./data/web/nextcloud/lib/versioncheck.php
 	echo -e "\e[33mPlease restart the update again.\e[0m"
   elif ! grep -q 'installed: true' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then