Merge pull request #426 from LDAPAccountManager/feature/408-multi-edit-combine-actions

Feature/408 multi edit combine actions
This commit is contained in:
gruberroland 2025-04-03 07:53:48 +02:00 committed by GitHub
commit 37b0f15379
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 68 additions and 84 deletions

View file

@ -1,6 +1,7 @@
June 2025 9.2 June 2025 9.2
- TAK support added - TAK support added
- Active Directory: allow to restore deleted entries in tree view (415) - Active Directory: allow to restore deleted entries in tree view (415)
- Multi-edit tool: change operations are combined by DN to allow e.g. adding object classes with required attributes (408)
- Fixed bugs: - Fixed bugs:
-> Unix: profile editor for users not working (418) -> Unix: profile editor for users not working (418)
-> Custom fields: problems with deleting facsimileTelephoneNumber (419) -> Custom fields: problems with deleting facsimileTelephoneNumber (419)

View file

@ -9,7 +9,7 @@ use function LAM\TYPES\getScopeFromTypeId;
/* /*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2003 - 2024 Roland Gruber Copyright (C) 2003 - 2025 Roland Gruber
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -1608,18 +1608,14 @@ class accountContainer {
} }
// find deleted attributes (in $orig but no longer in $attributes) // find deleted attributes (in $orig but no longer in $attributes)
foreach ($orig as $name => $value) { foreach ($orig as $name => $value) {
if (!isset($attributes[$name]) || (count($attributes[$name]) === 0)) { if (!isset($attributes[$name])) {
$toModify[$name] = []; $toModify[$name] = [];
} }
} }
// find changed attributes // find changed attributes
foreach ($attributes as $name => $value) { foreach ($attributes as $name => $value) {
// new attributes // new/changed attributes
if (!isset($orig[$name])) { if (!isset($orig[$name]) || !areArrayContentsEqual($value, $orig[$name])) {
$toModify[$name] = $value;
}
// changed attributes
elseif (!areArrayContentsEqual($value, $orig[$name])) {
$toModify[$name] = $value; $toModify[$name] = $value;
} }
// unchanged attributes // unchanged attributes

View file

@ -26,7 +26,7 @@ use LamTemporaryFilesManager;
/* /*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2013 - 2023 Roland Gruber Copyright (C) 2013 - 2025 Roland Gruber
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -346,38 +346,64 @@ function readLDAPData(): array {
*/ */
function generateActions(): array { function generateActions(): array {
$actions = []; $actions = [];
foreach ($_SESSION['multiEdit_status']['entries'] as $entry) { foreach ($_SESSION['multiEdit_status']['entries'] as $oldEntry) {
$dn = $entry['dn']; $dn = $oldEntry['dn'];
$newEntry = $oldEntry;
foreach ($_SESSION['multiEdit_operations'] as $op) { foreach ($_SESSION['multiEdit_operations'] as $op) {
$opType = $op[0]; $opType = $op[0];
$attr = $op[1]; $attr = $op[1];
$val = replaceWildcards($op[2], $entry); $val = replaceWildcards($op[2], $oldEntry);
switch ($opType) { switch ($opType) {
case ADD: case ADD:
if (empty($entry[$attr]) || !in_array_ignore_case($val, $entry[$attr])) { if (empty($oldEntry[$attr]) || !in_array_ignore_case($val, $oldEntry[$attr])) {
$actions[] = [ADD, $dn, $attr, $val]; $newEntry[$attr][] = $val;
} }
break; break;
case MOD: case MOD:
if (empty($entry[$attr])) { if (empty($oldEntry[$attr]) || !in_array_ignore_case($val, $oldEntry[$attr])) {
// attribute not yet exists, add it // attribute not yet exists, add it
$actions[] = [ADD, $dn, $attr, $val]; $newEntry[$attr] = [$val];
}
elseif (!empty($entry[$attr]) && !in_array_ignore_case($val, $entry[$attr])) {
// attribute exists and value is not included, replace old values
$actions[] = [MOD, $dn, $attr, $val];
} }
break; break;
case DEL: case DEL:
if (empty($val) && !empty($entry[$attr])) { if (empty($val) && !empty($oldEntry[$attr])) {
$actions[] = [DEL, $dn, $attr, null]; unset($newEntry[$attr]);
} }
elseif (!empty($val) && isset($entry[$attr]) && in_array($val, $entry[$attr])) { elseif (!empty($val) && isset($oldEntry[$attr]) && in_array($val, $oldEntry[$attr])) {
$actions[] = [DEL, $dn, $attr, $val]; $newEntry[$attr] = array_delete([$val], $newEntry[$attr]);
} }
break; break;
} }
} }
unset($oldEntry['dn']);
unset($newEntry['dn']);
// cleanup
foreach ($newEntry as $name => &$values) {
// remove empty values
$values = array_values($values);
for ($i = 0; $i < count($values); $i++) {
if ($values[$i] === '') {
unset($values[$i]);
}
}
$values = array_values($values);
// remove empty list of values
if (count($values) === 0) {
unset($newEntry[$name]);
}
}
// find deleted attributes (in $oldEntry but no longer in $newEntry)
foreach ($oldEntry as $name => $value) {
if (!isset($newEntry[$name])) {
$actions[$dn][$name] = [];
}
}
// find changed attributes
foreach ($newEntry as $name => $value) {
if (!isset($oldEntry[$name]) || !areArrayContentsEqual($value, $oldEntry[$name])) {
$actions[$dn][$name] = $value;
}
}
} }
// save actions // save actions
$_SESSION['multiEdit_status']['actions'] = $actions; $_SESSION['multiEdit_status']['actions'] = $actions;
@ -399,46 +425,24 @@ function dryRun(): array {
$ldif = '# LDAP Account Manager' . $pro . ' ' . LAMVersion() . "\n\nversion: 1\n\n"; $ldif = '# LDAP Account Manager' . $pro . ' ' . LAMVersion() . "\n\nversion: 1\n\n";
$log = ''; $log = '';
// fill LDIF and log file // fill LDIF and log file
$lastDN = ''; foreach ($_SESSION['multiEdit_status']['actions'] as $dn => $changes) {
foreach ($_SESSION['multiEdit_status']['actions'] as $action) { $log .= $dn . "\r\n";
$opType = $action[0];
$dn = $action[1];
$attr = $action[2];
$val = $action[3];
if ($lastDN != $dn) {
if ($lastDN != '') {
$log .= "\r\n";
}
$lastDN = $dn;
$log .= $dn . "\r\n";
}
if ($lastDN != '') {
$ldif .= "\n";
}
$ldif .= 'dn: ' . $dn . "\n"; $ldif .= 'dn: ' . $dn . "\n";
$ldif .= 'changetype: modify' . "\n"; $ldif .= 'changetype: modify' . "\n";
switch ($opType) { $isFirstChange = true;
case ADD: foreach ($changes as $attr => $values) {
$log .= '+' . $attr . '=' . $val . "\r\n"; $log .= '* ' . $attr . '=' . implode(', ', $values) . "\r\n";
$ldif .= 'add: ' . $attr . "\n"; if (!$isFirstChange) {
$ldif .= $attr . ': ' . $val . "\n"; $ldif .= "-\n";
break; }
case DEL: $ldif .= 'replace: ' . $attr . "\n";
$ldif .= 'delete: ' . $attr . "\n"; foreach ($values as $value) {
if (empty($val)) { $ldif .= $attr . ': ' . $value . "\n";
$log .= '-' . $attr . "\r\n"; }
} $isFirstChange = false;
else {
$log .= '-' . $attr . '=' . $val . "\r\n";
$ldif .= $attr . ': ' . $val . "\n";
}
break;
case MOD:
$log .= '*' . $attr . '=' . $val . "\r\n";
$ldif .= 'replace: ' . $attr . "\n";
$ldif .= $attr . ': ' . $val . "\n";
break;
} }
$ldif .= "\n";
$log .= "\r\n";
} }
// build meta HTML // build meta HTML
$container = new htmlTable(); $container = new htmlTable();
@ -498,6 +502,7 @@ function doModify(): array {
// initial action index // initial action index
if (!isset($_SESSION['multiEdit_status']['index'])) { if (!isset($_SESSION['multiEdit_status']['index'])) {
$_SESSION['multiEdit_status']['index'] = 0; $_SESSION['multiEdit_status']['index'] = 0;
$_SESSION['multiEdit_status']['dnList'] = array_keys($_SESSION['multiEdit_status']['actions']);
} }
// initial content // initial content
if (!isset($_SESSION['multiEdit_status']['modContent'])) { if (!isset($_SESSION['multiEdit_status']['modContent'])) {
@ -505,31 +510,12 @@ function doModify(): array {
} }
// run 10 modifications in each call // run 10 modifications in each call
$localCount = 0; $localCount = 0;
while (($localCount < 10) && ($_SESSION['multiEdit_status']['index'] < count($_SESSION['multiEdit_status']['actions']))) { while (($localCount < 10) && ($_SESSION['multiEdit_status']['index'] < count($_SESSION['multiEdit_status']['dnList']))) {
$action = $_SESSION['multiEdit_status']['actions'][$_SESSION['multiEdit_status']['index']]; $dn = $_SESSION['multiEdit_status']['dnList'][$_SESSION['multiEdit_status']['index']];
$opType = $action[0]; $changes = $_SESSION['multiEdit_status']['actions'][$dn];
$dn = $action[1];
$attr = $action[2];
$val = $action[3];
$_SESSION['multiEdit_status']['modContent'] .= htmlspecialchars($dn) . "<br>"; $_SESSION['multiEdit_status']['modContent'] .= htmlspecialchars($dn) . "<br>";
// run LDAP commands // run LDAP commands
$success = false; $success = ldap_modify($_SESSION['ldap']->server(), $dn, $changes);
switch ($opType) {
case ADD:
$success = ldap_mod_add($_SESSION['ldap']->server(), $dn, [$attr => [$val]]);
break;
case DEL:
if (empty($val)) {
$success = ldap_modify($_SESSION['ldap']->server(), $dn, [$attr => []]);
}
else {
$success = ldap_mod_del($_SESSION['ldap']->server(), $dn, [$attr => [$val]]);
}
break;
case MOD:
$success = ldap_modify($_SESSION['ldap']->server(), $dn, [$attr => [$val]]);
break;
}
if (!$success || isset($_REQUEST['multiEdit_error'])) { if (!$success || isset($_REQUEST['multiEdit_error'])) {
$msg = new htmlStatusMessage('ERROR', getDefaultLDAPErrorString($_SESSION['ldap']->server())); $msg = new htmlStatusMessage('ERROR', getDefaultLDAPErrorString($_SESSION['ldap']->server()));
$_SESSION['multiEdit_status']['modContent'] .= getMessageHTML($msg); $_SESSION['multiEdit_status']['modContent'] .= getMessageHTML($msg);

View file

@ -27,6 +27,7 @@ parameters:
- '#Call to an undefined method object::.*#' - '#Call to an undefined method object::.*#'
- '#Parameter \#2 \$string of function explode expects string, .* given.#' - '#Parameter \#2 \$string of function explode expects string, .* given.#'
- '#Parameter \#2 \$result of function ldap_.* expects LDAP\\Result, array\|LDAP\\Result given.#' - '#Parameter \#2 \$result of function ldap_.* expects LDAP\\Result, array\|LDAP\\Result given.#'
- '#Cannot access an offset on mixed.#'
- '#Cannot access offset .* on mixed.#' - '#Cannot access offset .* on mixed.#'
- '#Cannot access offset .* on array\|int.#' - '#Cannot access offset .* on array\|int.#'
- '#Cannot access an offset on array\|Countable.#' - '#Cannot access an offset on array\|Countable.#'