CVE-2023-3531: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: Cross-site Scripting (XSS) - Stored in GitHub repository nilsteampassnet/teampass prior to 3.0.10.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2023-07-06T20:15Z
Repository: https://github.com/nilsteampassnet/teampass
Language: PHP
Files changed: includes/js/functions.js, install/install.queries.php, install/upgrade_operations.php, install/upgrade_run_3.0.php, install/upgrade_scripts_manager.php, pages/items.js.php, pages/items.php, sources/items.queries.php
Commit date: 2023-07-06 21:16:02 +0200
Commit message: 3.0.10

Including new time related fields
Improving XSS protection

Diff: diff --git a/includes/js/functions.js b/includes/js/functions.js
index 9a307a8993..0340b87ba8 100755
--- a/includes/js/functions.js
+++ b/includes/js/functions.js
@@ -398,13 +398,21 @@ function simplePurifier(
 )
 {
     return DOMPurify.sanitize(
         text
             .replaceAll('&lt;', '<')
+            .replaceAll('&#x3C;', '<')
+            .replaceAll('&#60;', '<')
             .replaceAll('&gt;', '>')
+            .replaceAll('&#x3E;', '>')
+            .replaceAll('&#62;', '>')
             .replaceAll('&amp;', '&')
+            .replaceAll('&#38;', '&')
+            .replaceAll('&#x26;', '&')
             .replaceAll('&quot;', '"')
+            .replaceAll('&#34;;', '"')
+            .replaceAll('&#x22;', '"')
             .replaceAll('&#39;', "'"),
         {USE_PROFILES: {html:bHtml, svg:bSvg, svgFilters: bSvgFilters}}
     );
 }
 
@@ -456,11 +464,11 @@ function fieldDomPurifierLoop(elementClass)
         purifiedField = fieldDomPurifier(
             '#' + $(element).attr('id'), 
             $(element).hasClass('purifyHtml') === true ? true : false,
             $(element).hasClass('purifySvg') === true ? true : false,
             $(element).hasClass('purifySvgFilter') === true ? true : false,
-            typeof $(element).data('purify-text') !== undefined ? $(element).data('purify-text') : ''
+            typeof $(element).data('purify-text') !== 'undefined' ? $(element).data('purify-text') : ''
         );
 
         if (purifiedField === false) {
             // Label is empty
             toastr.remove();
@@ -477,11 +485,15 @@ function fieldDomPurifierLoop(elementClass)
                 'purifyStop' : purifyStop,
                 'arrFields' : arrFields
             };
         } else {
             $(element).val(purifiedField);
-            arrFields[$(element).data('field')] = purifiedField;
+            if (typeof $(element).data('field') !== 'undefined') {
+                arrFields[$(element).data('field')] = purifiedField;
+            } else if (typeof $(element).data('field-name') !== 'undefined') {
+                arrFields[$(element).data('field-name')] = purifiedField;
+            }
         }
     });
     
     // return
     return {
diff --git a/install/install.queries.php b/install/install.queries.php
index 6e72e793e4..66d207fd8d 100755
--- a/install/install.queries.php
+++ b/install/install.queries.php
@@ -429,10 +429,13 @@ if (null !== $post_type) {
                             `auto_update_pwd_frequency` tinyint(2) NOT null DEFAULT '0',
                             `auto_update_pwd_next_date` varchar(100) NOT null DEFAULT '0',
                             `encryption_type` VARCHAR(20) NOT NULL DEFAULT 'not_set',
                             `fa_icon` varchar(100) DEFAULT NULL,
                             `item_key` varchar(500) NOT NULL DEFAULT '-1',
+                            `created_at` varchar(30) NULL,
+                            `updated_at` varchar(30) NULL,
+                            `deleted_at` varchar(30) NULL,
                             PRIMARY KEY (`id`),
                             KEY `restricted_inactif_idx` (`restricted_to`,`inactif`)
                             ) CHARSET=utf8;"
                         );
                     } elseif ($task === 'log_items') {
diff --git a/install/upgrade_operations.php b/install/upgrade_operations.php
old mode 100755
new mode 100644
index 7c750a1324..79a2f455d5
--- a/install/upgrade_operations.php
+++ b/install/upgrade_operations.php
@@ -85,57 +85,143 @@ require_once '../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
 $superGlobal = new protect\SuperGlobal\SuperGlobal();
 
 // Get POST with operation to perform
 $post_operation = filter_input(INPUT_POST, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
 if (isset($post_operation) === true && empty($post_operation) === false) {
-    // ---->
-    // OPERATION - 20230604_1 - generate key for item_key
+    if ($post_operation === '20230604_1') {
+        // ---->
+        // OPERATION - 20230604_1 - generate key for item_key
+
+        // Get items to treat
+        $rows = mysqli_query(
+            $db_link,
+            "SELECT id FROM ".$pre."items
+            WHERE item_key = '-1'
+            ORDER BY id
+            LIMIT ".$post_nb.";"
+        );
+        // Handle error on query
+        if (!$rows) {
+            echo '[{"finish":"1" , "error":"'.mysqli_error($db_link).'"}]';
+            exit();
+        }
+
+        // Get total of items to treat
+        $total = mysqli_num_rows($rows);
+
+        // Loop on items and update for requested ones
+        if ((int) $total > 0) {
+            while ($row = mysqli_fetch_array($rows, MYSQLI_ASSOC)) {
+                // Gererate a key and update
+                mysqli_query(
+                    $db_link,
+                    "UPDATE `".$pre."items`
+                    SET `item_key` = '".uniqidReal(50)."'
+                    WHERE `id` = ".$row['id'].";"
+                );
+                if (mysqli_error($db_link)) {
+                    echo '[{"finish":"1", "next":"", "error":"MySQL Error! '.addslashes(mysqli_error($db_link)).'"}]';
+                    exit();
+                }
+            }
+        }
+
+        // Manage end of operation
+        if ($total === 0) {
+            $finish = 1;
+        } else {
+            $finish = 0;
+        }
+        // ----<
+    } elseif ($post_operation === 'populateItemsTable_CreatedAt') {
+        $finish = populateItemsTable_CreatedAt($pre, $post_nb);
+    } elseif ($post_operation === 'populateItemsTable_UpdatedAt') {
+        $finish = populateItemsTable_UpdatedAt($pre);
+    } elseif ($post_operation === 'populateItemsTable_DeletedAt') {
+        $finish = populateItemsTable_DeletedAt($pre);
+    }
+}
 
-    // Get items to treat
-    $rows = mysqli_query(
+// Close connection
+mysqli_close($db_link);
+
+
+// Return back
+echo '[{"finish":"'.$finish.'" , "next":"", "error":"", "total":"'.$total.'"}]';
+
+
+
+function populateItemsTable_CreatedAt($pre, $post_nb)
+{
+    global $db_link;
+    // loop on items - created_at
+    $items = mysqli_query(
         $db_link,
-        "SELECT id FROM ".$pre."items
-        WHERE item_key = '-1'
-        ORDER BY id
-        LIMIT ".$post_nb.";"
+        "select i.id as id, ls.date as datetime
+        from `" . $pre . "items` as i
+        inner join `" . $pre . "log_items` as ls on ls.id_item = i.id
+        WHERE ls.action = 'at_creation' AND i.created_at IS NULL
+        LIMIT " . $post_nb.";"
     );
-    // Handle error on query
-    if (!$rows) {
-        echo '[{"finish":"1" , "error":"'.mysqli_error($db_link).'"}]';
-        exit();
+    while ($item = mysqli_fetch_assoc($items)) {
+        if (empty((string) $item['datetime']) === false && is_null($item['datetime']) === false) {
+            // update created_at field
+            mysqli_query(
+                $db_link,
+                "UPDATE `" . $pre . "items` SET created_at = '".$item['datetime']."' WHERE id = ".$item['id']
+            );
+        }
     }
 
-    // Get total of items to treat
-    $total = mysqli_num_rows($rows);
+    // Is it finished?
+    $remainingItems = mysqli_num_rows(
+        mysqli_query(
+            $db_link,
+            "SELECT * FROM `" . $pre . "items` WHERE created_at IS NULL"
+        )
+    );
+    return $remainingItems > 0 ? 0 : 1;
+}
 
-    // Loop on items and update for requested ones
-    if ((int) $total > 0) {
-        while ($row = mysqli_fetch_array($rows, MYSQLI_ASSOC)) {
-            // Gererate a key and update
+function populateItemsTable_UpdatedAt($pre)
+{
+    global $db_link;
+    // loop on items - updated_at
+    $items = mysqli_query(
+        $db_link,
+        "select i.id as id, (select date from " . $pre . "log_items where action = 'at_modification' and id_item=id order by date DESC limit 1) as datetime 
+        from `" . $pre . "items` as i;"
+    );
+    while ($item = mysqli_fetch_assoc($items)) {
+        if (is_null($item['datetime']) === false) {
+            // update updated_at field
             mysqli_query(
                 $db_link,
-                "UPDATE `".$pre."items`
-                SET `item_key` = '".uniqidReal(50)."'
-                WHERE `id` = ".$row['id'].";"
+                "UPDATE `" . $pre . "items` SET updated_at = '".$item['datetime']."' WHERE id = ".$item['id']
             );
-            if (mysqli_error($db_link)) {
-                echo '[{"finish":"1", "next":"", "error":"MySQL Error! '.addslashes(mysqli_error($db_link)).'"}]';
-                exit();
-            }
         }
     }
 
-    // Manage end of operation
-    if ($total === 0) {
-        $finish = 1;
-    } else {
-        $finish = 0;
-    }
-    // ----<
+    return 1;
 }
 
-// Close connection
-mysqli_close($db_link);
-
+function populateItemsTable_DeletedAt($pre)
+{
+    global $db_link;
+    // loop on items - deleted_at
+    $items = mysqli_query(
+        $db_link,
+        "select i.id as id, (select date from " . $pre . "log_items where action = 'at_deleted' and id_item=id order by date DESC limit 1) as datetime
+        from `" . $pre . "items` as i;"
+    );
+    while ($item = mysqli_fetch_assoc($items)) {
+        if (is_null($item['datetime']) === false) {
+            // update updated_at field
+            mysqli_query(
+                $db_link,
+                "UPDATE `" . $pre . "items` SET deleted_at = '".$item['datetime']."' WHERE id = ".$item['id']
+            );
+        }
+    }
 
-// Return back
-echo '[{"finish":"'.$finish.'" , "next":"", "error":"", "total":"'.$total.'"}]';
\ No newline at end of file
+    return 1;
+}
\ No newline at end of file
diff --git a/install/upgrade_run_3.0.php b/install/upgrade_run_3.0.php
index 30e7177a4f..89b47f481a 100755
--- a/install/upgrade_run_3.0.php
+++ b/install/upgrade_run_3.0.php
@@ -81,10 +81,12 @@ if (mysqli_connect(
 // Load libraries
 require_once '../includes/libraries/protect/SuperGlobal/SuperGlobal.php';
 $superGlobal = new protect\SuperGlobal\SuperGlobal();
 
 
+//---------------------------------------------------------------------
+
 //--->BEGIN 3.0.1
 
 // Ensure admin user is ready
 mysqli_query(
     $db_link,
@@ -93,10 +95,11 @@ mysqli_query(
     WHERE id = 1"
 );
 
 //---<END 3.0.1
 
+//---------------------------------------------------------------------
 
 //--->BEGIN 3.0.5
 
 // Add the INDEX process_id_idx to the processes_tasks table
 $res = checkIndexExist(
@@ -110,10 +113,11 @@ if (!$res) {
     exit();
 }
 
 //---<END 3.0.5
 
+//---------------------------------------------------------------------
 
 //--->BEGIN 3.0.6
 // Add new setting 'sending_emails_job_frequency'
 $tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'sending_emails_job_frequency'"));
 if (intval($tmp) === 0) {
@@ -150,10 +154,11 @@ if ($res === false) {
     mysqli_close($db_link);
     exit();
 }
 //---<END 3.0.6
 
+//---------------------------------------------------------------------
 
 //--->BEGIN 3.0.7
 // Alter
 try {
     mysqli_query(
@@ -170,10 +175,11 @@ mysqli_query(
     "UPDATE `" . $pre . "users` SET `treeloadstrategy` = 'full' WHERE treeloadstrategy NOT IN ('full','sequential');"
 );
 
 //---<END 3.0.7
 
+//---------------------------------------------------------------------
 
 //--->BEGIN 3.0.8
 // Add field mfa_disabled to USERS table
 $res = addColumnIfNotExist(
     $pre . 'users',
@@ -186,10 +192,11 @@ if ($res === false) {
     exit();
 }
 
 //---<END 3.0.8
 
+//---------------------------------------------------------------------
 
 //--->BEGIN 3.0.9
 // Add new setting 'reload_cache_table_task'
 $tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'reload_cache_table_task'"));
 if (intval($tmp) === 0) {
@@ -348,20 +355,65 @@ if ($res === false) {
     echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field deleted_at to table USERS! ' . mysqli_error($db_link) . '!"}]';
     mysqli_close($db_link);
     exit();
 }
 
-//
+// populate created_at, updated_at and deleted_at fields in USERS table
 $tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "users` WHERE created_at IS NOT NULL"));
 if (intval($tmp) === 0) {
     populateUsersTable($pre);
 }
+//---<END 3.0.9
 
+//---------------------------------------------------------------------
 
-// populate created_at, updated_at and deleted_at fields in USERS table
+//--->BEGIN 3.0.10
 
-//---<END 3.0.9
+// Add field created_at to ITEMS table
+$res = addColumnIfNotExist(
+    $pre . 'items',
+    'created_at',
+    "varchar(30) NULL;"
+);
+if ($res === false) {
+    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field created_at to table ITEMS! ' . mysqli_error($db_link) . '!"}]';
+    mysqli_close($db_link);
+    exit();
+}
+
+// Add field updated_at to ITEMS table
+$res = addColumnIfNotExist(
+    $pre . 'items',
+    'updated_at',
+    "varchar(30) NULL;"
+);
+if ($res === false) {
+    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field updated_at to table ITEMS! ' . mysqli_error($db_link) . '!"}]';
+    mysqli_close($db_link);
+    exit();
+}
+
+// Add field deleted_at to ITEMS table
+$res = addColumnIfNotExist(
+    $pre . 'items',
+    'deleted_at',
+    "varchar(30) NULL;"
+);
+if ($res === false) {
+    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field deleted_at to table ITEMS! ' . mysqli_error($db_link) . '!"}]';
+    mysqli_close($db_link);
+    exit();
+}
+/*
+// populate created_at, updated_at and deleted_at fields in ITEMS table
+$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "items` WHERE created_at IS NOT NULL"));
+if (intval($tmp) === 0) {
+    populateItemsTable($pre);
+}*/
+//---<END 3.0.10
+
+//---------------------------------------------------------------------
 
 // Save timestamp
 $tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "misc` WHERE type = 'admin' AND intitule = 'upgrade_timestamp'"));
 if (intval($tmp) === 0) {
     mysqli_query(
@@ -419,6 +471,6 @@ function populateUsersTable($pre)
                 $db_link,
                 "UPDATE `" . $pre . "users` SET updated_at = '".$user['datetime']."' WHERE id = ".$user['uid']
             );
         }
     }
-}
\ No newline at end of file
+}
diff --git a/install/upgrade_scripts_manager.php b/install/upgrade_scripts_manager.php
index 2f156d7f71..76296487ed 100755
--- a/install/upgrade_scripts_manager.php
+++ b/install/upgrade_scripts_manager.php
@@ -37,10 +37,13 @@ $scripts_list = array(
     array('upgrade_run_3.0.0_fields.php', 'user_id'),
     array('upgrade_run_3.0.0_suggestions.php', 'user_id'),
     array('upgrade_run_3.0.0_files.php', 'user_id'),
     array('upgrade_run_3.0.php', 'user_id'),
     array('upgrade_operations.php', '20230604_1'),
+    array('upgrade_operations.php', 'populateItemsTable_CreatedAt'),
+    array('upgrade_operations.php', 'populateItemsTable_UpdatedAt'),
+    array('upgrade_operations.php', 'populateItemsTable_DeletedAt'),
 );
 $param = '';
 
 // test if finished
 if (intval($post_file_number) >= count($scripts_list)) {
diff --git a/pages/items.js.php b/pages/items.js.php
index 7c5dfc9cb4..cb92cb50d4 100755
--- a/pages/items.js.php
+++ b/pages/items.js.php
@@ -700,10 +700,20 @@ $var['hidden_asterisk'] = '<i class="fas fa-asterisk mr-2"></i><i class="fas fa-
                         $('#form-item-category-' + category).removeClass('hidden');
                     })
                 } else {
                     $('#form-item-field, .form-item-category').addClass('hidden');
                 }
+
+
+
+                // Prepare datePicker
+                $('#form-item-deleteAfterDate, .datepicker').datepicker({
+                    format: '<?php echo str_replace(['Y', 'M'], ['yyyy', 'mm'], $SETTINGS['date_format']); ?>',
+                    todayHighlight: true,
+                    todayBtn: true,
+                    language: '<?php echo isset($_SESSION['user_language_code']) === true ? $_SESSION['user_language_code'] : 'en'; ?>'
+                });
                 
                 // Add track-change class
                 //$('#form-item-label, #form-item-description, #form-item-login, #form-item-password, #form-item-email, #form-item-url, #form-item-folder, #form-item-restrictedto, #form-item-tags, #form-item-anyoneCanModify, #form-item-deleteAfterShown, #form-item-deleteAfterDate, #form-item-anounce, .form-item-field-custom').addClass('track-change');
 
                 // Update variable
@@ -1267,14 +1277,13 @@ $var['hidden_asterisk'] = '<i class="fas fa-asterisk mr-2"></i><i class="fas fa-
         userUploadedFile = false;
 
         var data = {
             'item_id': store.get('teampassItem').id,
             'folder_id': selectedFolderId,
-            'label': DOMPurify.sanitize($('#form-item-copy-new-label').val()),
             'access_level': store.get('teampassItem').hasAccessLevel,
         }
-
+        
         // Launch action
         $.post(
             'sources/items.queries.php', {
                 type: 'delete_item',
                 data: prepareExchangedData(JSON.stringify(data), "encode", "<?php echo $_SESSION['key']; ?>"),
@@ -2676,43 +2685,29 @@ $var['hidden_asterisk'] = '<i class="fas fa-asterisk mr-2"></i><i class="fas fa-
         // Do checks
         if (arrayQuery.length > 0 || userDidAChange === true) {
             var reg = new RegExp("[.|,|;|:|!|=|+|-|*|/|#|\"|'|&]");
 
             // Sanitize text fields
-            let formLabel = fieldDomPurifier('#form-item-label', false, false, false),
-                formDescription = $('#form-item-description').summernote('code') !== "<p><br></p>" ? fieldDomPurifier('#form-item-description', true, false, false, $('#form-item-description').summernote('code')) : '',
-                formEmail = fieldDomPurifier('#form-item-email'),
-                formTags = fieldDomPurifier('#form-item-tags'),
-                formUrl = fieldDomPurifier('#form-item-url'),
-                formIcon = fieldDomPurifier('#form-item-icon');
-            if (formLabel === false || formDescription === false || formEmail === false || formTags === false || formUrl === false || formIcon === false) {
-                // Label is empty
-                toastr.remove();
-                toastr.warning(
-                    'XSS attempt detected. Field has been emptied.',
-                    'Error', {
-                        timeOut: 5000,
-                        progressBar: true
-                    }
-                );
+            purifyRes = fieldDomPurifierLoop('#form-item .purify');
+            if (purifyRes.purifyStop === true) {
+                // if purify failed, stop
                 return false;
             }
             
             // Do some easy checks
-            if (formLabel === '') {
+            if (purifyRes.arrFields['label'] === '') {
                 // Label is empty
                 toastr.remove();
                 toastr.error(
                     '<?php echo langHdl('error_label'); ?>',
                     '', {
                         timeOut: 5000,
                         progressBar: true
                     }
                 );
                 return false;
-            } else if (formTags !== '' &&
-                reg.test(formTags)
+            } else if (purifyRes.arrFields['tags'] !== '' && reg.test(purifyRes.arrFields['tags'])
             ) {
                 // Tags not wel formated
                 toastr.remove();
                 toastr.error(
                     '<?php echo langHdl('error_tags'); ?>',
@@ -2833,34 +2828,31 @@ $var['hidden_asterisk'] = '<i class="fas fa-asterisk mr-2"></i><i class="fas fa-
                     
                 //prepare data
                 var data = {
                     'anyone_can_modify': $('#form-item-anyoneCanModify').is(':checked') ? 1 : 0,
                     'complexity_level': parseInt($('#form-item-password-complex').val()),
-                    'description': formDescription,
+                    'description': $('#form-item-description').summernote('code'),
                     'diffusion_list': diffusion,
                     'diffusion_list_names': diffusionNames,
                     'folder': parseInt($('#form-item-folder').val()),
-                    'email': formEmail,
+                    'email': purifyRes.arrFields['email'],
                     'fields': fields,
                     'folder_is_personal': store.get('teampassItem').IsPersonalFolder === 1 ? 1 : 0,
                     'id': store.get('teampassItem').id,
-                    'label': formLabel,
-                    'login': DOMPurify.sanitize($('#form-item-login').val()),
+                    'label': purifyRes.arrFields['label'],
+                    'login': purifyRes.arrFields['login'],
                     'pw': $('#form-item-password').val(),
                     'restricted_to': restriction,
                     'restricted_to_roles': restrictionRole,
-                    'tags': formTags,
+                    'tags': purifyRes.arrFields['tags'],
                     'template_id': parseInt($('input.form-check-input-template:checkbox:checked').data('category-id')),
-                    'to_be_deleted_after_date': ($('#form-item-deleteAfterDate').length !== 0 &&
-                        $('#form-item-deleteAfterDate').val() !== '') ? $('#form-item-deleteAfterDate').val() : '',
-                    'to_be_deleted_after_x_views': ($('#form-item-deleteAfterShown').length !== 0 &&
-                            $('#form-item-deleteAfterShown').val() !== '' && $('#form-item-deleteAfterShown').val() >= 1) ?
-                        parseInt($('#form-item-deleteAfterShown').val()) : '',
-                    'url': formUrl,
+                    'to_be_deleted_after_date': purifyRes.arrFields['deleteAfterDate'] !== '' ? purifyRes.arrFields['deleteAfterDate'] : '',
+                    'to_be_deleted_after_x_views': parseInt(purifyRes.arrFields['deleteAfterShown']) > 0 ? parseInt(purifyRes.arrFields['deleteAfterShown']) : '',
+                    'url': purifyRes.arrFields['url'],
                     'user_id': parseInt('<?php echo $_SESSION['user_id']; ?>'),
                     'uploaded_file_id': store.get('teampassApplication').uploadedFileId === undefined ? '' : store.get('teampassApplication').uploadedFileId,
-                    'fa_icon': formIcon,
+                    'fa_icon': purifyRes.arrFields['icon'],
                 };
                 if (debugJavascript === true) {
                     console.log('SAVING DATA');
                     console.log(data);
                 }
diff --git a/pages/items.php b/pages/items.php
index 2dc1e91cad..826494eabb 100755
--- a/pages/items.php
+++ b/pages/items.php
@@ -173,22 +173,22 @@ foreach ($rows as $reccord) {
                                 <!-- LABEL -->
                                 <div class="input-group mb-3">
                                     <div class="input-group-prepend">
                                         <span class="input-group-text"><?php echo langHdl('label'); ?></span>
                                     </div>
-                                    <input id="form-item-label" type="text" class="form-control form-item-control" data-change-ongoing="" data-field-name="label">
+                                    <input id="form-item-label" type="text" class="form-control form-item-control purify" data-change-ongoing="" data-field-name="label">
                                 </div>
                                 <!-- DESCRIPTION -->
                                 <div class="mb-3">
-                                    <div id="form-item-description" class="form-item-control w-100 clear-me-html" data-field-name="description" data-change-ongoing=""></div>
+                                    <div id="form-item-description" class="form-item-control w-100 clear-me-html purify purifyHtml" data-field-name="description" data-change-ongoing=""></div>
                                 </div>
                                 <!-- LOGIN -->
                                 <div class="input-group mb-3">
                                     <div class="input-group-prepend">
                                         <span class="input-group-text"><?php echo langHdl('login'); ?></span>
                                     </div>
-                                    <input id="form-item-login" type="text" class="form-control form-item-control" data-field-name="login" data-change-ongoing="">
+                                    <input id="form-item-login" type="text" class="form-control form-item-control purify" data-field-name="login" data-change-ongoing="">
                                 </div>
                                 <!-- PASSWORD -->
                                 <div class="input-group mb-2">
                                     <div class="input-group-prepend">
                                         <span class="input-group-text"><?php echo langHdl('password'); ?></span>
@@ -241,25 +241,25 @@ foreach ($rows as $reccord) {
                                 <!-- EMAIL -->
                                 <div class="input-group mb-3 mt-3">
                                     <div class="input-group-prepend">
                                         <span class="input-group-text"><?php echo langHdl('email'); ?></span>
                                     </div>
-                                    <input id="form-item-email" type="email" class="form-control form-item-control" data-field-name="email" data-change-ongoing="">
+                                    <input id="form-item-email" type="email" class="form-control form-item-control purify" data-field-name="email" data-change-ongoing="">
                                 </div>
                                 <!-- URL -->
                                 <div class="input-group mb-3">
                                     <div class="input-group-prepend">
                                         <span class="input-group-text"><?php echo langHdl('url'); ?></span>
                                     </div>
-                                    <input id="form-item-url" type="url" class="form-control form-item-control" data-field-name="url" data-change-ongoing="">
+                                    <input id="form-item-url" type="url" class="form-control form-item-control purify" data-field-name="url" data-change-ongoing="">
                                 </div>
                                 <!-- ICON -->
                                 <div class="input-group mb-3">
                                     <div class="input-group-prepend">
                                         <span class="input-group-text"><?php echo langHdl('icon'); ?></span>
                                     </div>
-                                    <input id="form-item-icon" type="text" class="form-control form-item-control" data-field-name="icon" data-change-ongoing="">
+                                    <input id="form-item-icon" type="text" class="form-control form-item-control " data-field-name="icon" data-change-ongoing="">
                                     <span class="ml-2 col-md-1 clear-me-html" id="form-item-icon-show"></span>
                                 </div>
                                 <small class='form-text text-muted'>
                                     <?php echo langHdl('fontawesome_icon_tip'); ?><a href="<?php echo FONTAWESOME_URL;?>" target="_blank"><i class="fas fa-external-link-alt ml-1"></i></a>
                                 </small>
@@ -282,11 +282,11 @@ foreach ($rows as $reccord) {
                                 <!-- TAGS -->
                                 <div class="input-group mb-3">
                                     <div class="input-group-prepend">
                                         <span class="input-group-text"><?php echo langHdl('tags'); ?></span>
                                     </div>
-                                    <input id="form-item-tags" type="text" class="form-control form-item-control autocomplete" data-change-ongoing="">
+                                    <input id="form-item-tags" type="text" class="form-control form-item-control autocomplete purify" data-change-ongoing="" data-field-name="tags">
                                 </div>
                                 <!-- ANYONE CAN MODIFY -->
                                 <?php
                                 if (
                                     isset($SETTINGS['anyone_can_modify']) === true
@@ -321,11 +321,11 @@ foreach ($rows as $reccord) {
                                             <div class="row">
                                                 <div class="d-inline p-2">
                                                     <?php echo langHdl('item_deleted_after_being_viewed_x_times'); ?>
                                                 </div>
                                                 <div class="d-inline p-2">
-                                                    <input type="text" class="form-control form-item-control" id="form-item-deleteAfterShown" data-change-ongoing="">
+                                                    <input type="text" class="form-control form-item-control purify" data-field-name="deleteAfterShown" id="form-item-deleteAfterShown" data-change-ongoing="">
                                                 </div>
                                             </div>
                                             <div class="row">
                                                 <div class="d-inline p-2">
                                                     <?php echo langHdl('item_deleted_after_date'); ?>
@@ -335,11 +335,11 @@ foreach ($rows as $reccord) {
                                                         <div class="input-group-prepend">
                                                             <span class="input-group-text">
                                                                 <i class="fas fa-calendar"></i>
                                                             </span>
                                                         </div>
-                                                        <input type="text" class="form-control float-right form-item-control" id="form-item-deleteAfterDate" data-change-ongoing="">
+                                                        <input type="text" class="form-control float-right form-item-control purify" data-field-name="deleteAfterDate" id="form-item-deleteAfterDate" data-change-ongoing="">
                                                     </div>
                                                 </div>
                                             </div>
                                         </div>
                                     </div>
@@ -406,10 +406,11 @@ foreach ($rows as $reccord) {
 
                             <!-- CUSTOM FIELDS -->
                             <div class="tab-pane" id="tab_4">
                                 <div id="form-item-field" class="hidden">
                                     <?php
+                                        echo "coucou ".count($_SESSION['item_fields']);
                                         if (isset($_SESSION['item_fields']) === true) {
                                             foreach ($_SESSION['item_fields'] as $category) {
                                                 echo '
                                             <div class="callout callout-info form-item-category hidden" id="form-item-category-' . $category['id'] . '">
                                                 <h5>' . $category['title'] . '</h5>
@@ -455,10 +456,16 @@ foreach ($rows as $reccord) {
                                                 }
                                                 echo '
                                                 </p>
                                             </div>';
                                             }
+                                        } else {
+                                            echo
+                                            '<div class="alert alert-info">
+                                                <h5><i class="icon fa fa-info mr-3"></i>' . langHdl('information') . '</h5>
+                                                ' . langHdl('no_fields') . '
+                                            </div>';
                                         } ?>
                                 </div>
                                 <div class="alert alert-info hidden no-item-fields">
                                     <h5><i class="icon fa fa-info mr-3"></i><?php echo langHdl('information'); ?></h5>
                                     <?php echo langHdl('no_fields'); ?>
diff --git a/sources/items.queries.php b/sources/items.queries.php
index 31dfd0bb77..f1737ffb9e 100755
--- a/sources/items.queries.php
+++ b/sources/items.queries.php
@@ -448,10 +448,11 @@ switch ($inputData['type']) {
                             && $post_anyone_can_modify === 'on') ? 1 : 0,
                         'complexity_level' => $post_complexity_level,
                         'encryption_type' => 'teampass_aes',
                         'fa_icon' => $post_fa_icon,
                         'item_key' => uniqidReal(50),
+                        'created_at' => time(),
                     )
                 );
                 $newID = DB::insertId();
 
                 // Create sharekeys for users
@@ -1192,10 +1193,11 @@ switch ($inputData['type']) {
                         'anyone_can_modify' => (int) $post_anyone_can_modify,
                         'complexity_level' => (int) $post_complexity_level,
                         'encryption_type' => TP_ENCRYPTION_NAME,
                         'perso' => in_array($inputData['folderId'], $_SESSION['personal_folders']) === true ? 1 : 0,
                         'fa_icon' => $post_fa_icon,
+                        'updated_at' => time(),
                     ),
                     'id=%i',
                     $inputData['itemId']
                 );
 
@@ -2028,10 +2030,11 @@ switch ($inputData['type']) {
             // reaffect pw
             $originalRecord['pw'] = $cryptedStuff['encrypted'];
 
             // generate the query to update the new record with the previous values
             $aSet = array();
+            $aSet['created_at'] = time();
             foreach ($originalRecord as $key => $value) {
                 if ($key === 'id_tree') {
                     $aSet['id_tree'] = $post_dest_id;
                 } elseif ($key === 'label') {
                     $aSet[$key] = $post_new_label;
@@ -2639,10 +2642,11 @@ switch ($inputData['type']) {
             // statistics
             DB::update(
                 prefixTable('items'),
                 array(
                     'viewed_no' => $dataItem['viewed_no'] + 1,
+                    'updated_at' => time(),
                 ),
                 'id = %i',
                 $inputData['id']
             );
             $arrData['viewed_no'] = $dataItem['viewed_no'] + 1;
@@ -2791,10 +2795,11 @@ switch ($inputData['type']) {
                         // make inactive object
                         DB::update(
                             prefixTable('items'),
                             array(
                                 'inactif' => 1,
+                                'deleted_at' => time(),
                             ),
                             'id = %i',
                             $inputData['id']
                         );
 
@@ -3167,31 +3172,14 @@ switch ($inputData['type']) {
             $inputData['data'],
             'decode'
         );
 
         // Prepare POST variables
-        $inputData['label'] = filter_var($dataReceived['label'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
         $inputData['folderId'] = (int) filter_var($dataReceived['folder_id'], FILTER_SANITIZE_NUMBER_INT);
         $inputData['itemId'] = (int) filter_var($dataReceived['item_id'], FILTER_SANITIZE_NUMBER_INT);
         $post_access_level = (int) filter_var($dataReceived['access_level'], FILTER_SANITIZE_NUMBER_INT);
 
-        // perform a check in case of Read-Only user creating an item in his PF
-        if (($_SESSION['user_read_only'] === true
-                && in_array($inputData['label'], $_SESSION['personal_folders']) === false)
-            || (int) $post_access_level <= 20
-        ) {
-            echo (string) prepareExchangedData(
-                $SETTINGS['cpassman_dir'],
-                array(
-                    'error' => true,
-                    'message' => langHdl('error_not_allowed_to'),
-                ),
-                'encode'
-            );
-            break;
-        }
-
         // Check that user can access this item
         $granted = accessToItemIsGranted($inputData['itemId'], $SETTINGS);
         if ($granted !== true) {
             echo (string) prepareExchangedData(
                 $SETTINGS['cpassman_dir'],
@@ -3215,10 +3203,11 @@ switch ($inputData['type']) {
         // delete item consists in disabling it
         DB::update(
             prefixTable('items'),
             array(
                 'inactif' => '1',
+                'deleted_at' => time(),
             ),
             'id = %i',
             $inputData['itemId']
         );
 
@@ -3865,10 +3854,11 @@ $SETTINGS['cpassman_dir'],$inputData['data'], 'decode');
                     if ((int) $record['perso'] !== 1 && (int) $folder_is_personal === 1) {
                         DB::update(
                             prefixTable('items'),
                             array(
                                 'perso' => 1,
+                                'updated_at' => time(),
                             ),
                             'id=%i',
                             $record['id']
                         );
                         $record['perso'] = 1;
@@ -4740,10 +4730,11 @@ $SETTINGS['cpassman_dir'],$returnValues, 'encode');
             // Just update is needed. Item key is the same
             DB::update(
                 prefixTable('items'),
                 array(
                     'id_tree' => $inputData['folderId'],
+                    'updated_at' => time(),
                 ),
                 'id=%i',
                 $inputData['itemId']
             );
             // ---
@@ -4799,10 +4790,11 @@ $SETTINGS['cpassman_dir'],$returnValues, 'encode');
             DB::update(
                 prefixTable('items'),
                 array(
                     'id_tree' => $inputData['folderId'],
                     'perso' => 1,
+                    'updated_at' => time(),
                 ),
                 'id=%i',
                 $inputData['itemId']
             );
             // ---
@@ -4812,10 +4804,11 @@ $SETTINGS['cpassman_dir'],$returnValues, 'encode');
             // just update is needed. Item key is the same
             DB::update(
                 prefixTable('items'),
                 array(
                     'id_tree' => $inputData['folderId'],
+                    'updated_at' => time(),
                 ),
                 'id=%i',
                 $inputData['itemId']
             );
             // ---
@@ -4939,10 +4932,11 @@ $SETTINGS['cpassman_dir'],$returnValues, 'encode');
             DB::update(
                 prefixTable('items'),
                 array(
                     'id_tree' => $inputData['folderId'],
                     'perso' => 0,
+                    'updated_at' => time(),
                 ),
                 'id=%i',
                 $inputData['itemId']
             );
         }
@@ -5058,10 +5052,11 @@ $SETTINGS['cpassman_dir'],$returnValues, 'encode');
                     // just update is needed. Item key is the same
                     DB::update(
                         prefixTable('items'),
                         array(
                             'id_tree' => $inputData['folderId'],
+                            'updated_at' => time(),
                         ),
                         'id = %i',
                         $item_id
                     );
                     // ---
@@ -5121,10 +5116,11 @@ $SETTINGS['cpassman_dir'],$returnValues, 'encode');
                     DB::update(
                         prefixTable('items'),
                         array(
                             'id_tree' => $inputData['folderId'],
                             'perso' => 1,
+                            'updated_at' => time(),
                         ),
                         'id = %i',
                         $item_id
                     );
                     // ---
@@ -5138,10 +5134,11 @@ $SETTINGS['cpassman_dir'],$returnValues, 'encode');
                     // just update is needed. Item key is the same
                     DB::update(
                         prefixTable('items'),
                         array(
                             'id_tree' => $inputData['folderId'],
+                            'updated_at' => time(),
                         ),
                         'id = %i',
                         $item_id
                     );
                     // ---
@@ -5269,10 +5266,11 @@ $SETTINGS['cpassman_dir'],$returnValues, 'encode');
                     DB::update(
                         prefixTable('items'),
                         array(
                             'id_tree' => $inputData['folderId'],
                             'perso' => 0,
+                            'updated_at' => time(),
                         ),
                         'id=%i',
                         $item_id
                     );
                 }
@@ -5367,11 +5365,11 @@ $SETTINGS['cpassman_dir'],
                 // Check that user can access this folder
                 if (
                     in_array($dataSource['id_tree'], $_SESSION['groupes_visibles']) === false
                 ) {
                     echo (string) prepareExchangedData(
-$SETTINGS['cpassman_dir'],
+                        $SETTINGS['cpassman_dir'],
                         array(
                             'error' => true,
                             'message' => langHdl('error_not_allowed_to'),
                         ),
                         'encode'
@@ -5382,10 +5380,11 @@ $SETTINGS['cpassman_dir'],
                 // delete item consists in disabling it
                 DB::update(
                     prefixTable('items'),
                     array(
                         'inactif' => '1',
+                        'deleted_at' => time(),
                     ),
                     'id = %i',
                     $item_id
                 );
 

Vulnerable code:             typeof $(element).data('purify-text') !== undefined ? $(element).data('purify-text') : ''
            arrFields[$(element).data('field')] = purifiedField;
    // ---->
    // OPERATION - 20230604_1 - generate key for item_key
    // Get items to treat
    $rows = mysqli_query(
        "SELECT id FROM ".$pre."items
        WHERE item_key = '-1'
        ORDER BY id
        LIMIT ".$post_nb.";"
    // Handle error on query
    if (!$rows) {
        echo '[{"finish":"1" , "error":"'.mysqli_error($db_link).'"}]';
        exit();
    // Get total of items to treat
    $total = mysqli_num_rows($rows);
    // Loop on items and update for requested ones
    if ((int) $total > 0) {
        while ($row = mysqli_fetch_array($rows, MYSQLI_ASSOC)) {
            // Gererate a key and update
                "UPDATE `".$pre."items`
                SET `item_key` = '".uniqidReal(50)."'
                WHERE `id` = ".$row['id'].";"
            if (mysqli_error($db_link)) {
                echo '[{"finish":"1", "next":"", "error":"MySQL Error! '.addslashes(mysqli_error($db_link)).'"}]';
                exit();
            }
    // Manage end of operation
    if ($total === 0) {
        $finish = 1;
    } else {
        $finish = 0;
    }
    // ----<
// Close connection
mysqli_close($db_link);

// Return back
echo '[{"finish":"'.$finish.'" , "next":"", "error":"", "total":"'.$total.'"}]';
//
// populate created_at, updated_at and deleted_at fields in USERS table
//---<END 3.0.9
}
            'label': DOMPurify.sanitize($('#form-item-copy-new-label').val()),

            let formLabel = fieldDomPurifier('#form-item-label', false, false, false),
                formDescription = $('#form-item-description').summernote('code') !== "<p><br></p>" ? fieldDomPurifier('#form-item-description', true, false, false, $('#form-item-description').summernote('code')) : '',
                formEmail = fieldDomPurifier('#form-item-email'),
                formTags = fieldDomPurifier('#form-item-tags'),
                formUrl = fieldDomPurifier('#form-item-url'),
                formIcon = fieldDomPurifier('#form-item-icon');
            if (formLabel === false || formDescription === false || formEmail === false || formTags === false || formUrl === false || formIcon === false) {
                // Label is empty
                toastr.remove();
                toastr.warning(
                    'XSS attempt detected. Field has been emptied.',
                    'Error', {
                        timeOut: 5000,
                        progressBar: true
                    }
                );
            if (formLabel === '') {
            } else if (formTags !== '' &&
                reg.test(formTags)
                    'description': formDescription,
                    'email': formEmail,
                    'label': formLabel,
                    'login': DOMPurify.sanitize($('#form-item-login').val()),
                    'tags': formTags,
                    'to_be_deleted_after_date': ($('#form-item-deleteAfterDate').length !== 0 &&
                        $('#form-item-deleteAfterDate').val() !== '') ? $('#form-item-deleteAfterDate').val() : '',
                    'to_be_deleted_after_x_views': ($('#form-item-deleteAfterShown').length !== 0 &&
                            $('#form-item-deleteAfterShown').val() !== '' && $('#form-item-deleteAfterShown').val() >= 1) ?
                        parseInt($('#form-item-deleteAfterShown').val()) : '',
                    'url': formUrl,
                    'fa_icon': formIcon,
                                    <input id="form-item-label" type="text" class="form-control form-item-control" data-change-ongoing="" data-field-name="label">
                                    <div id="form-item-description" class="form-item-control w-100 clear-me-html" data-field-name="description" data-change-ongoing=""></div>
                                    <input id="form-item-login" type="text" class="form-control form-item-control" data-field-name="login" data-change-ongoing="">
                                    <input id="form-item-email" type="email" class="form-control form-item-control" data-field-name="email" data-change-ongoing="">
                                    <input id="form-item-url" type="url" class="form-control form-item-control" data-field-name="url" data-change-ongoing="">
                                    <input id="form-item-icon" type="text" class="form-control form-item-control" data-field-name="icon" data-change-ongoing="">
                                    <input id="form-item-tags" type="text" class="form-control form-item-control autocomplete" data-change-ongoing="">
                                                    <input type="text" class="form-control form-item-control" id="form-item-deleteAfterShown" data-change-ongoing="">
                                                        <input type="text" class="form-control float-right form-item-control" id="form-item-deleteAfterDate" data-change-ongoing="">
        $inputData['label'] = filter_var($dataReceived['label'], FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        // perform a check in case of Read-Only user creating an item in his PF
        if (($_SESSION['user_read_only'] === true
                && in_array($inputData['label'], $_SESSION['personal_folders']) === false)
            || (int) $post_access_level <= 20
        ) {
            echo (string) prepareExchangedData(
                $SETTINGS['cpassman_dir'],
                array(
                    'error' => true,
                    'message' => langHdl('error_not_allowed_to'),
                ),
                'encode'
            );
            break;
        }

$SETTINGS['cpassman_dir'],
Fixed code:             .replaceAll('&#x3C;', '<')
            .replaceAll('&#60;', '<')
            .replaceAll('&#x3E;', '>')
            .replaceAll('&#62;', '>')
            .replaceAll('&#38;', '&')
            .replaceAll('&#x26;', '&')
            .replaceAll('&#34;;', '"')
            .replaceAll('&#x22;', '"')
            typeof $(element).data('purify-text') !== 'undefined' ? $(element).data('purify-text') : ''
            if (typeof $(element).data('field') !== 'undefined') {
                arrFields[$(element).data('field')] = purifiedField;
            } else if (typeof $(element).data('field-name') !== 'undefined') {
                arrFields[$(element).data('field-name')] = purifiedField;
            }
                            `created_at` varchar(30) NULL,
                            `updated_at` varchar(30) NULL,
                            `deleted_at` varchar(30) NULL,
    if ($post_operation === '20230604_1') {
        // ---->
        // OPERATION - 20230604_1 - generate key for item_key

        // Get items to treat
        $rows = mysqli_query(
            $db_link,
            "SELECT id FROM ".$pre."items
            WHERE item_key = '-1'
            ORDER BY id
            LIMIT ".$post_nb.";"
        );
        // Handle error on query
        if (!$rows) {
            echo '[{"finish":"1" , "error":"'.mysqli_error($db_link).'"}]';
            exit();
        }

        // Get total of items to treat
        $total = mysqli_num_rows($rows);

        // Loop on items and update for requested ones
        if ((int) $total > 0) {
            while ($row = mysqli_fetch_array($rows, MYSQLI_ASSOC)) {
                // Gererate a key and update
                mysqli_query(
                    $db_link,
                    "UPDATE `".$pre."items`
                    SET `item_key` = '".uniqidReal(50)."'
                    WHERE `id` = ".$row['id'].";"
                );
                if (mysqli_error($db_link)) {
                    echo '[{"finish":"1", "next":"", "error":"MySQL Error! '.addslashes(mysqli_error($db_link)).'"}]';
                    exit();
                }
            }
        }

        // Manage end of operation
        if ($total === 0) {
            $finish = 1;
        } else {
            $finish = 0;
        }
        // ----<
    } elseif ($post_operation === 'populateItemsTable_CreatedAt') {
        $finish = populateItemsTable_CreatedAt($pre, $post_nb);
    } elseif ($post_operation === 'populateItemsTable_UpdatedAt') {
        $finish = populateItemsTable_UpdatedAt($pre);
    } elseif ($post_operation === 'populateItemsTable_DeletedAt') {
        $finish = populateItemsTable_DeletedAt($pre);
    }
}
// Close connection
mysqli_close($db_link);


// Return back
echo '[{"finish":"'.$finish.'" , "next":"", "error":"", "total":"'.$total.'"}]';



function populateItemsTable_CreatedAt($pre, $post_nb)
{
    global $db_link;
    // loop on items - created_at
    $items = mysqli_query(
        "select i.id as id, ls.date as datetime
        from `" . $pre . "items` as i
        inner join `" . $pre . "log_items` as ls on ls.id_item = i.id
        WHERE ls.action = 'at_creation' AND i.created_at IS NULL
        LIMIT " . $post_nb.";"
    while ($item = mysqli_fetch_assoc($items)) {
        if (empty((string) $item['datetime']) === false && is_null($item['datetime']) === false) {
            // update created_at field
            mysqli_query(
                $db_link,
                "UPDATE `" . $pre . "items` SET created_at = '".$item['datetime']."' WHERE id = ".$item['id']
            );
        }
    // Is it finished?
    $remainingItems = mysqli_num_rows(
        mysqli_query(
            $db_link,
            "SELECT * FROM `" . $pre . "items` WHERE created_at IS NULL"
        )
    );
    return $remainingItems > 0 ? 0 : 1;
}
function populateItemsTable_UpdatedAt($pre)
{
    global $db_link;
    // loop on items - updated_at
    $items = mysqli_query(
        $db_link,
        "select i.id as id, (select date from " . $pre . "log_items where action = 'at_modification' and id_item=id order by date DESC limit 1) as datetime 
        from `" . $pre . "items` as i;"
    );
    while ($item = mysqli_fetch_assoc($items)) {
        if (is_null($item['datetime']) === false) {
            // update updated_at field
                "UPDATE `" . $pre . "items` SET updated_at = '".$item['datetime']."' WHERE id = ".$item['id']
    return 1;
function populateItemsTable_DeletedAt($pre)
{
    global $db_link;
    // loop on items - deleted_at
    $items = mysqli_query(
        $db_link,
        "select i.id as id, (select date from " . $pre . "log_items where action = 'at_deleted' and id_item=id order by date DESC limit 1) as datetime
        from `" . $pre . "items` as i;"
    );
    while ($item = mysqli_fetch_assoc($items)) {
        if (is_null($item['datetime']) === false) {
            // update updated_at field
            mysqli_query(
                $db_link,
                "UPDATE `" . $pre . "items` SET deleted_at = '".$item['datetime']."' WHERE id = ".$item['id']
            );
        }
    }
    return 1;
}
//---------------------------------------------------------------------

//---------------------------------------------------------------------
//---------------------------------------------------------------------
//---------------------------------------------------------------------
//---------------------------------------------------------------------
//---------------------------------------------------------------------
// populate created_at, updated_at and deleted_at fields in USERS table
//---<END 3.0.9
//---------------------------------------------------------------------
//--->BEGIN 3.0.10
// Add field created_at to ITEMS table
$res = addColumnIfNotExist(
    $pre . 'items',
    'created_at',
    "varchar(30) NULL;"
);
if ($res === false) {
    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field created_at to table ITEMS! ' . mysqli_error($db_link) . '!"}]';
    mysqli_close($db_link);
    exit();
}

// Add field updated_at to ITEMS table
$res = addColumnIfNotExist(
    $pre . 'items',
    'updated_at',
    "varchar(30) NULL;"
);
if ($res === false) {
    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field updated_at to table ITEMS! ' . mysqli_error($db_link) . '!"}]';
    mysqli_close($db_link);
    exit();
}

// Add field deleted_at to ITEMS table
$res = addColumnIfNotExist(
    $pre . 'items',
    'deleted_at',
    "varchar(30) NULL;"
);
if ($res === false) {
    echo '[{"finish":"1", "msg":"", "error":"An error appears when adding field deleted_at to table ITEMS! ' . mysqli_error($db_link) . '!"}]';
    mysqli_close($db_link);
    exit();
}
/*
// populate created_at, updated_at and deleted_at fields in ITEMS table
$tmp = mysqli_num_rows(mysqli_query($db_link, "SELECT * FROM `" . $pre . "items` WHERE created_at IS NOT NULL"));
if (intval($tmp) === 0) {
    populateItemsTable($pre);
}*/
//---<END 3.0.10

//---------------------------------------------------------------------
}
    array('upgrade_operations.php', 'populateItemsTable_CreatedAt'),
    array('upgrade_operations.php', 'populateItemsTable_UpdatedAt'),
    array('upgrade_operations.php', 'populateItemsTable_DeletedAt'),



                // Prepare datePicker
                $('#form-item-deleteAfterDate, .datepicker').datepicker({
                    format: '<?php echo str_replace(['Y', 'M'], ['yyyy', 'mm'], $SETTINGS['date_format']); ?>',
                    todayHighlight: true,
                    todayBtn: true,
                    language: '<?php echo isset($_SESSION['user_language_code']) === true ? $_SESSION['user_language_code'] : 'en'; ?>'
                });
        
            purifyRes = fieldDomPurifierLoop('#form-item .purify');
            if (purifyRes.purifyStop === true) {
                // if purify failed, stop
            if (purifyRes.arrFields['label'] === '') {
            } else if (purifyRes.arrFields['tags'] !== '' && reg.test(purifyRes.arrFields['tags'])
                    'description': $('#form-item-description').summernote('code'),
                    'email': purifyRes.arrFields['email'],
                    'label': purifyRes.arrFields['label'],
                    'login': purifyRes.arrFields['login'],
                    'tags': purifyRes.arrFields['tags'],
                    'to_be_deleted_after_date': purifyRes.arrFields['deleteAfterDate'] !== '' ? purifyRes.arrFields['deleteAfterDate'] : '',
                    'to_be_deleted_after_x_views': parseInt(purifyRes.arrFields['deleteAfterShown']) > 0 ? parseInt(purifyRes.arrFields['deleteAfterShown']) : '',
                    'url': purifyRes.arrFields['url'],
                    'fa_icon': purifyRes.arrFields['icon'],
                                    <input id="form-item-label" type="text" class="form-control form-item-control purify" data-change-ongoing="" data-field-name="label">
                                    <div id="form-item-description" class="form-item-control w-100 clear-me-html purify purifyHtml" data-field-name="description" data-change-ongoing=""></div>
                                    <input id="form-item-login" type="text" class="form-control form-item-control purify" data-field-name="login" data-change-ongoing="">
                                    <input id="form-item-email" type="email" class="form-control form-item-control purify" data-field-name="email" data-change-ongoing="">
                                    <input id="form-item-url" type="url" class="form-control form-item-control purify" data-field-name="url" data-change-ongoing="">
                                    <input id="form-item-icon" type="text" class="form-control form-item-control " data-field-name="icon" data-change-ongoing="">
                                    <input id="form-item-tags" type="text" class="form-control form-item-control autocomplete purify" data-change-ongoing="" data-field-name="tags">
                                                    <input type="text" class="form-control form-item-control purify" data-field-name="deleteAfterShown" id="form-item-deleteAfterShown" data-change-ongoing="">
                                                        <input type="text" class="form-control float-right form-item-control purify" data-field-name="deleteAfterDate" id="form-item-deleteAfterDate" data-change-ongoing="">
                                        echo "coucou ".count($_SESSION['item_fields']);
                                        } else {
                                            echo
                                            '<div class="alert alert-info">
                                                <h5><i class="icon fa fa-info mr-3"></i>' . langHdl('information') . '</h5>
                                                ' . langHdl('no_fields') . '
                                            </div>';
                        'created_at' => time(),
                        'updated_at' => time(),
            $aSet['created_at'] = time();
                    'updated_at' => time(),
                                'deleted_at' => time(),
                'deleted_at' => time(),
                                'updated_at' => time(),
                    'updated_at' => time(),
                    'updated_at' => time(),
                    'updated_at' => time(),
                    'updated_at' => time(),
                            'updated_at' => time(),
                            'updated_at' => time(),
                            'updated_at' => time(),
                            'updated_at' => time(),
                        $SETTINGS['cpassman_dir'],
                        'deleted_at' => time(),