Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions cron/dailyroutine.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
FROM people AS p
LEFT OUTER JOIN camps AS c ON c.id = p.camp_id
WHERE (NOT p.deleted OR p.deleted IS NULL) AND p.parent_id IS NULL');

// All deletions are logged with the same timestamp
$now = date('Y-m-d H:i:s');
while ($row = db_fetch($result)) {
$row['touch'] = db_value('
SELECT GREATEST(COALESCE((
Expand All @@ -48,8 +51,8 @@
$row['diff'] = $date2->diff($date1)->format('%a');

if ($row['diff'] > $row['treshold']) {
db_query('UPDATE people SET deleted = NOW() WHERE id = :id', ['id' => $row['id']]);
simpleSaveChangeHistory('people', $row['id'], 'Record deleted by daily routine');
db_query('UPDATE people SET deleted = :now WHERE id = :id', ['id' => $row['id'], 'now' => $now]);
simpleSaveChangeHistory('people', $row['id'], 'Record deleted by daily routine', $now);
db_touch('people', $row['id']);
}
}
Expand All @@ -61,8 +64,8 @@
FROM people AS p1, people AS p2
WHERE p2.parent_id = p1.id AND p1.deleted AND (NOT p2.deleted OR p2.deleted IS NULL)');
while ($row = db_fetch($result)) {
db_query('UPDATE people SET deleted = NOW() WHERE id = :id', ['id' => $row['id']]);
simpleSaveChangeHistory('people', $row['id'], 'Record deleted by daily routine because head of family/beneficiary was deleted');
db_query('UPDATE people SET deleted = :now WHERE id = :id', ['id' => $row['id'], 'now' => $now]);
simpleSaveChangeHistory('people', $row['id'], 'Record deleted by daily routine because head of family/beneficiary was deleted', $now);
db_touch('people', $row['id']);
}

Expand Down
7 changes: 4 additions & 3 deletions include/people.php
Original file line number Diff line number Diff line change
Expand Up @@ -514,14 +514,15 @@ function () use ($cmsmain, $data) {
case 'touch':
$ids = explode(',', (string) $_POST['ids']);
$userId = $_SESSION['user']['id'];
$now = date('Y-m-d H:i:s');
// Query speed optimised for 500 records from 6.2 seconds to 0.54 seconds using transaction blocks over UPDATE and bulk inserts
db_transaction(function () use ($ids, $userId) {
db_transaction(function () use ($ids, $userId, $now) {
foreach ($ids as $id) {
db_query('UPDATE people SET modified = NOW(), modified_by = :user WHERE id = :id', ['id' => $id, 'user' => $userId]);
db_query('UPDATE people SET modified = :now, modified_by = :user WHERE id = :id', ['id' => $id, 'user' => $userId, 'now' => $now]);
}
});
// Bulk insert used to insert into history table
simpleBulkSaveChangeHistory('people', $ids, 'Touched');
simpleBulkSaveChangeHistory('people', $ids, 'Touched', $now);

$success = true;
$message = 'Selected people have been touched';
Expand Down
5 changes: 3 additions & 2 deletions library/ajax/deleteprofile.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?php

db_transaction(function () {
$now = date('Y-m-d H:i:s');
// Only append .deleted suffix if the email doesn't already have it
// This prevents double-deletion if the query is somehow executed twice
// Pattern checks for .deleted.<digits> after the @ symbol (e.g., user@domain.com.deleted.123)
db_query('UPDATE cms_users SET deleted = NOW(), email = CONCAT(email,".deleted.",id) WHERE id = :id AND (NOT deleted OR deleted IS NULL) AND email NOT REGEXP "@.*\.deleted\.[0-9]+$"', ['id' => $_POST['cms_user_id']]);
db_query('UPDATE cms_users SET deleted = :now, email = CONCAT(email,".deleted.",id) WHERE id = :id AND (NOT deleted OR deleted IS NULL) AND email NOT REGEXP "@.*\.deleted\.[0-9]+$"', ['now' => $now, 'id' => $_POST['cms_user_id']]);
updateAuth0UserFromDb($_POST['cms_user_id']);
simpleSaveChangeHistory('cms_users', $_POST['cms_user_id'], 'Record deleted without undelete', $now);
});

simpleSaveChangeHistory('cms_users', $_POST['cms_user_id'], 'Record deleted without undelete');
// when a user deactive its account we need to ensure that user logged out immediately and then redirected to Auth0 login page
global $settings;
logout();
Expand Down
2 changes: 1 addition & 1 deletion library/ajax/testdbdelete.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

if ('true' == $return) {
db_transaction(function () use ($ids, $return) {
[$return, $msg, $redirect] = listRealDelete($_POST['table'], $ids);
[$return, $msg, $redirect] = listBulkRealDelete($_POST['table'], $ids);
foreach ($ids as $id) {
deleteAuth0User($id);
}
Expand Down
16 changes: 9 additions & 7 deletions library/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ function move_boxes($ids, $newlocationid, $mobile = false)
{
[$count, $action_label, $mobile_message] = db_transaction(function () use ($ids, $newlocationid) {
$count = 0;
$now = date('Y-m-d H:i:s');
foreach ($ids as $id) {
$box = db_row('
SELECT
Expand Down Expand Up @@ -311,23 +312,24 @@ function move_boxes($ids, $newlocationid, $mobile = false)
'
UPDATE stock
SET
modified = NOW(),
modified = :now,
modified_by = :user_id ,
location_id = :location
WHERE id = :id',
['location' => $newlocationid, 'id' => $id, 'user_id' => $_SESSION['user']['id']]
['location' => $newlocationid, 'id' => $id, 'now' => $now, 'user_id' => $_SESSION['user']['id']]
);

$from['int'] = $box['location_id'];
$to['int'] = $newlocationid;
simpleSaveChangeHistory('stock', $id, 'location_id', $from, $to);
simpleSaveChangeHistory('stock', $id, 'location_id', $now, $from, $to);
db_query(
'
INSERT INTO itemsout (product_id, size_id, count, movedate, from_location, to_location)
VALUES (:product_id, :size_id, :count, NOW(), :from_location, :to_location)',
VALUES (:product_id, :size_id, :count, :now, :from_location, :to_location)',
['product_id' => $box['product_id'],
'size_id' => $box['size_id'],
'count' => $box['items'],
'now' => $now,
'from_location' => $box['location_id'],
'to_location' => $newlocationid, ]
);
Expand All @@ -343,12 +345,12 @@ function move_boxes($ids, $newlocationid, $mobile = false)
UPDATE stock
SET
box_state_id = :box_state_id,
modified = NOW(),
modified = :now,
modified_by = :user_id
WHERE id = :id',
['box_state_id' => $newlocation['box_state_id'], 'id' => $id, 'user_id' => $_SESSION['user']['id']]
['box_state_id' => $newlocation['box_state_id'], 'id' => $id, 'now' => $now, 'user_id' => $_SESSION['user']['id']]
);
simpleSaveChangeHistory('stock', $id, 'box_state_id', $from, $to);
simpleSaveChangeHistory('stock', $id, 'box_state_id', $now, $from, $to);
$mobile_message .= ' and its state changed to '.$newlocation['box_state_name'];
}

Expand Down
58 changes: 20 additions & 38 deletions library/lib/list.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,28 +91,6 @@ function listBulkMove($table, $ids, $regardparent = true, $hook = '', $updatetra
return [true, $return, false, $aftermove];
}

function listRealDelete($table, $ids, $uri = false)
{
global $translate, $action;

$hasPrevent = db_fieldexists($table, 'preventdelete');
$hasTree = db_fieldexists($table, 'parent_id');
$count = 0;
foreach ($ids as $id) {
$result = db_query('DELETE FROM '.$table.' WHERE id = :id'.($hasPrevent ? ' AND NOT preventdelete' : ''), ['id' => $id]);
$count += $result->rowCount();
if ($result->rowCount()) {
simpleSaveChangeHistory($table, $id, 'Record deleted without undelete');
}
}

if ($count) {
return [true, $translate['cms_list_deletesuccess'], true];
}

return [false, $translate['cms_list_deleteerror'], false];
}

function listBulkRealDelete($table, $ids, $uri = false)
{
global $translate, $action;
Expand Down Expand Up @@ -147,6 +125,7 @@ function listDelete($table, $ids, $uri = false, $fktables = null, $saveHistory =
$hasPrevent = db_fieldexists($table, 'preventdelete');
$hasTree = db_fieldexists($table, 'parent_id');
$count = 0;
$now = date('Y-m-d H:i:s');

try {
foreach ($ids as $id) {
Expand Down Expand Up @@ -181,12 +160,12 @@ function listDelete($table, $ids, $uri = false, $fktables = null, $saveHistory =
}
}
}
$count += listDeleteAction($table, $id, 0, $hasTree);
$count += listDeleteAction($table, $id, $now, 0, $hasTree);
} else {
$result = db_query('DELETE FROM '.$table.' WHERE id = :id'.($hasPrevent ? ' AND NOT preventdelete' : ''), ['id' => $id]);
$count += $result->rowCount();
if ($result->rowCount() && $saveHistory) {
simpleSaveChangeHistory($table, $id, 'Record deleted');
simpleSaveChangeHistory($table, $id, 'Record deleted', $now);
}
}
}
Expand Down Expand Up @@ -226,28 +205,29 @@ function listDeleteMessage($table, $id, $foreignkey, $restricted)
return 'This '.$table_name[$table].' cannot be removed since '.$object_table_name[$foreignkey['TABLE_NAME']].''.$object_name.' '.$id_name.' is still active. Please edit or remove it first!';
}

function listDeleteAction($table, $id, $count = 0, $recursive = false)
function listDeleteAction($table, $id, $now, $count = 0, $recursive = false)
{
$hasPrevent = db_fieldexists($table, 'preventdelete');
// prevent deletion of deleted records
$hasDeleted = db_fieldexists($table, 'deleted');

$query = 'UPDATE '.$table.' SET deleted = NOW(), modified = NOW(), modified_by = :user_id WHERE id = :id';
$query = 'UPDATE '.$table.' SET deleted = :now, modified = :now, modified_by = :user_id WHERE id = :id';
$query .= ($hasPrevent ? ' AND NOT preventdelete' : '');
$query .= ($hasDeleted ? ' AND (NOT deleted OR deleted IS NULL)' : '');
$result = db_query($query, [
'now' => $now,
'id' => $id,
'user_id' => $_SESSION['user']['id'],
]);
$count += $result->rowCount();
if (1 === $result->rowCount()) {
simpleSaveChangeHistory($table, $id, 'Record deleted');
simpleSaveChangeHistory($table, $id, 'Record deleted', $now);
}

if ($recursive) {
$childs = db_array('SELECT id FROM '.$table.' WHERE parent_id = :id'.($hasPrevent ? ' AND NOT preventdelete' : ''), ['id' => $id]);
foreach ($childs as $child) {
$count += listDeleteAction($table, $child['id'], $count, true);
$count += listDeleteAction($table, $child['id'], $now, $count, true);
}
}

Expand All @@ -259,6 +239,7 @@ function listUndelete($table, $ids, $uri = false, $overwritehastree = false)
global $translate, $action;

$count = 0;
$now = date('Y-m-d H:i:s');

$hasDeletefield = db_fieldexists($table, 'deleted');
$hasPrevent = db_fieldexists($table, 'preventdelete');
Expand All @@ -270,7 +251,7 @@ function listUndelete($table, $ids, $uri = false, $overwritehastree = false)

foreach ($ids as $id) {
if ($hasDeletefield) {
$count += listUndeleteAction($table, $id, 0, $hasTree, db_nullable($table, 'deleted'));
$count += listUndeleteAction($table, $id, $now, 0, $hasTree, db_nullable($table, 'deleted'));
}
}

Expand All @@ -281,18 +262,18 @@ function listUndelete($table, $ids, $uri = false, $overwritehastree = false)
return [false, $translate['cms_list_undeleteerror'], false];
}

function listUnDeleteAction($table, $id, $count = 0, $recursive = false, $null = true)
function listUndeleteAction($table, $id, $now, $count = 0, $recursive = false, $null = true)
{
$result = db_query('UPDATE '.$table.' SET deleted = '.($null ? 'NULL' : '0').', modified = NOW(), modified_by = :user_id WHERE id = :id', ['id' => $id, 'user_id' => $_SESSION['user']['id']]);
$result = db_query('UPDATE '.$table.' SET deleted = '.($null ? 'NULL' : '0').', modified = :now, modified_by = :user_id WHERE id = :id', ['now' => $now, 'id' => $id, 'user_id' => $_SESSION['user']['id']]);
$count += $result->rowCount();
if ($result->rowCount()) {
simpleSaveChangeHistory($table, $id, 'Record recovered');
simpleSaveChangeHistory($table, $id, 'Record recovered', $now);
}

if ($recursive) {
$childs = db_array('SELECT id FROM '.$table.' WHERE parent_id = :id', ['id' => $id]);
foreach ($childs as $child) {
$count += listUnDeleteAction($table, $child['id'], $count, true);
$count += listUndeleteAction($table, $child['id'], $now, $count, true);
}
}

Expand Down Expand Up @@ -320,12 +301,13 @@ function listBulkUndelete($table, $ids, $uri = false, $overwritehastree = false)
return [false, $translate['cms_list_undeleteerror'], false];
}

function listBulkUnDeleteAction($table, $ids, $count = 0, $hasTree = false)
function listBulkUndeleteAction($table, $ids, $count = 0, $hasTree = false)
{
[$finalIds, $count] = db_transaction(function () use ($table, $ids, $count, $hasTree) {
$now = date('Y-m-d H:i:s');
[$finalIds, $count] = db_transaction(function () use ($table, $ids, $count, $hasTree, $now) {
$finalIds = [];
foreach ($ids as $id) {
$result = db_query('UPDATE '.$table.' SET deleted = 0, modified = NOW(), modified_by = :user_id WHERE id = :id', ['id' => $id, 'user_id' => $_SESSION['user']['id']]);
$result = db_query('UPDATE '.$table.' SET deleted = 0, modified = :now, modified_by = :user_id WHERE id = :id', ['id' => $id, 'user_id' => $_SESSION['user']['id'], 'now' => $now]);
$count += $result->rowCount();
if ($result->rowCount()) {
$finalIds[] = $id;
Expand All @@ -334,7 +316,7 @@ function listBulkUnDeleteAction($table, $ids, $count = 0, $hasTree = false)
if ($hasTree) {
$childs = db_array('SELECT id FROM '.$table.' WHERE parent_id = :id', ['id' => $id]);
foreach ($childs as $child) {
$result = db_query('UPDATE '.$table.' SET deleted = 0, modified = NOW(), modified_by = :user_id WHERE id = :id', ['id' => $child['id'], 'user_id' => $_SESSION['user']['id']]);
$result = db_query('UPDATE '.$table.' SET deleted = 0, modified = :now, modified_by = :user_id WHERE id = :id', ['id' => $child['id'], 'user_id' => $_SESSION['user']['id'], 'now' => $now]);
$count += $result->rowCount();
if ($result->rowCount()) {
$finalIds[] = $child['id'];
Expand All @@ -346,7 +328,7 @@ function listBulkUnDeleteAction($table, $ids, $count = 0, $hasTree = false)
return [$finalIds, $count];
});

simpleBulkSaveChangeHistory($table, $finalIds, 'Record recovered');
simpleBulkSaveChangeHistory($table, $finalIds, 'Record recovered', $now);

return $count;
}
Expand Down
21 changes: 13 additions & 8 deletions library/lib/tools.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,33 +286,38 @@ function utf8_decode_array($array)
return $array;
}

function simpleSaveChangeHistory($table, $record, $changes, $from = [], $to = [])
function simpleSaveChangeHistory($table, $record, $changes, $now = null, $from = [], $to = [])
{
// from and to variable must be arrays with entry 'int' or 'float'
if (!db_tableexists('history')) {
return;
}
db_query('INSERT INTO history (tablename, record_id, changes, user_id, ip, changedate, from_int, from_float, to_int, to_float) VALUES (:table,:id,:change,:user_id,:ip,NOW(), :from_int, :from_float, :to_int, :to_float)', ['table' => $table, 'id' => $record, 'change' => $changes, 'user_id' => $_SESSION['user']['id'], 'ip' => $_SERVER['REMOTE_ADDR'], 'from_int' => $from['int'], 'from_float' => $from['float'], 'to_int' => $to['int'], 'to_float' => $to['float']]);
if (null === $now) {
$now = date('Y-m-d H:i:s');
}
db_query('INSERT INTO history (tablename, record_id, changes, user_id, ip, changedate, from_int, from_float, to_int, to_float) VALUES (:table,:id,:change,:user_id,:ip,:now, :from_int, :from_float, :to_int, :to_float)', ['table' => $table, 'id' => $record, 'change' => $changes, 'user_id' => $_SESSION['user']['id'], 'ip' => $_SERVER['REMOTE_ADDR'], 'now' => $now, 'from_int' => $from['int'], 'from_float' => $from['float'], 'to_int' => $to['int'], 'to_float' => $to['float']]);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When simpleSaveChangeHistory is called without the $from and $to parameters (using the default empty arrays), accessing $from['int'], $from['float'], $to['int'], and $to['float'] at line 298 will trigger "Undefined array key" warnings in PHP 8+. Consider adding null coalescing operators to handle these cases gracefully, such as $from['int'] ?? null instead of $from['int']. This affects all calls to this function that don't provide these parameters, including the new calls added in this PR.

Copilot uses AI. Check for mistakes.
}

function simpleBulkSaveChangeHistory($table, $records, $changes, $from = [], $to = [])
function simpleBulkSaveChangeHistory($table, $records, $changes, $now = null)
{
// from and to variable must be arrays with entry 'int' or 'float'
if (!db_tableexists('history')) {
return;
}
if (null === $now) {
$now = date('Y-m-d H:i:s');
}
$query = '';
$params = [];
$params = ['now' => $now];
if (is_iterable($records)) {
for ($i = 0; $i < sizeof($records); ++$i) {
$query .= "(:table{$i},:id{$i},:change{$i},:user_id{$i},:ip{$i},NOW(), :from_int{$i}, :from_float{$i}, :to_int{$i}, :to_float{$i})";
$params = array_merge($params, ['table'.$i => $table, 'id'.$i => $records[$i], 'change'.$i => $changes, 'user_id'.$i => $_SESSION['user']['id'], 'ip'.$i => $_SERVER['REMOTE_ADDR'], 'from_int'.$i => $from['int'], 'from_float'.$i => $from['float'], 'to_int'.$i => $to['int'], 'to_float'.$i => $to['float']]);
$query .= "(:table{$i},:id{$i},:change{$i},:user_id{$i},:ip{$i},:now)";
$params = array_merge($params, ['table'.$i => $table, 'id'.$i => $records[$i], 'change'.$i => $changes, 'user_id'.$i => $_SESSION['user']['id'], 'ip'.$i => $_SERVER['REMOTE_ADDR']]);
if ($i !== sizeof($records) - 1) {
$query .= ',';
}
}
}
if (strlen($query) > 0) {
db_query("INSERT INTO history (tablename, record_id, changes, user_id, ip, changedate, from_int, from_float, to_int, to_float) VALUES {$query}", $params);
db_query("INSERT INTO history (tablename, record_id, changes, user_id, ip, changedate) VALUES {$query}", $params);
}
}
1 change: 1 addition & 0 deletions php.ini
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
date.timezone = UTC
short_open_tag = On
; the following setting was required for smarty templates in google cloud,
; but is now deprecated with php7.4. In php8.2 seems to work without it, too.
Expand Down