diff --git a/setup/class_setupStepMigrate.inc b/setup/class_setupStepMigrate.inc index dc71532be62eb0bfed6cbca001f72866bfb7402b..13ee1bc0518bdec9ee7875a096e3bfe971812144 100644 --- a/setup/class_setupStepMigrate.inc +++ b/setup/class_setupStepMigrate.inc @@ -184,10 +184,10 @@ class setupStepMigrate extends setupStep var $rootOC_details = []; /* Entries needing migration */ - var $orgUnits_toMigrate = []; - var $accounts_toMigrate = []; - var $outsideUsers_toMigrate = []; - var $outsideGroups_toMigrate = []; + protected $orgUnits_toMigrate = []; + protected $accounts_toMigrate = []; + protected $outsideUsers_toMigrate = []; + protected $outsideGroups_toMigrate = []; /* check for multiple use of same uidNumber */ var $check_uidNumber = []; @@ -532,7 +532,7 @@ class setupStepMigrate extends setupStep /* Remember old list of invisible users, to be able to set * the 'html checked' status for the checkboxes again */ - $old = $this->accounts_toMigrate; + $old = $this->accounts_toMigrate; $this->accounts_toMigrate = []; /* Get all invisible users */ @@ -547,8 +547,9 @@ class setupStepMigrate extends setupStep '(!(objectClass=inetOrgPerson))'. '(uid=*)'. '(!(uid=*$))'. + '(!(dc:dn:=addressbook))'. ')', - ['sn','givenName','cn','uid'] + ['objectClass','sn','givenName','cn','uid'] ); if (!$res) { throw new CheckFailedException( @@ -556,20 +557,29 @@ class setupStepMigrate extends setupStep _('Possibly the "root object" is missing.') ); } - $sizeLimitHit = $ldap->hitSizeLimit(); + $sizeLimitHit = $ldap->hitSizeLimit(); + $accountsCount = $ldap->count(); while ($attrs = $ldap->fetch()) { - if (!preg_match('/,dc=addressbook,/', $attrs['dn'])) { - $attrs['checked'] = FALSE; - $attrs['before'] = ""; - $attrs['after'] = ""; - - /* Set objects to selected, that were selected before reload */ - if (isset($old[base64_encode($attrs['dn'])])) { - $attrs['checked'] = $old[base64_encode($attrs['dn'])]['checked']; - } - $this->accounts_toMigrate[base64_encode($attrs['dn'])] = $attrs; + $base = preg_replace('/^[^,]+,/', '', $attrs['dn']); + + /* Build groupid depending on base and objectClasses */ + $groupid = md5($base.implode('', $attrs['objectClass'])); + + if (!isset($this->accounts_toMigrate[$groupid])) { + $this->accounts_toMigrate[$groupid] = [ + /* Set objects to selected, that were selected before reload */ + 'checked' => ($old[$groupid]['checked'] ?? FALSE), + 'objects' => [], + 'base' => $base, + 'classes' => $attrs['objectClass'], + ]; } + + $attrs['before'] = ''; + $attrs['after'] = ''; + + $this->accounts_toMigrate[$groupid]['objects'][base64_encode($attrs['dn'])] = $attrs; } if (count($this->accounts_toMigrate) == 0) { @@ -584,7 +594,7 @@ class setupStepMigrate extends setupStep } else { $message = sprintf( _('Found %d user(s) that will not be visible in FusionDirectory or which are incomplete.'), - count($this->accounts_toMigrate) + $accountsCount ); } throw new CheckFailedException( @@ -661,46 +671,55 @@ class setupStepMigrate extends setupStep foreach ($this->$var as $key => &$entry) { $entry['checked'] = isset($_POST['migrate_'.$key]); if ($entry['checked']) { - /* Get old objectClasses */ - $ldap->cat($entry['dn'], array_merge(['objectClass'], array_keys($mandatory))); - $attrs = $ldap->fetch(); - - /* Create new objectClass array */ - $new_attrs = []; - $new_attrs['objectClass'] = $oc; - for ($i = 0; $i < $attrs['objectClass']['count']; $i++) { - if (!in_array_ics($attrs['objectClass'][$i], $new_attrs['objectClass'])) { - $new_attrs['objectClass'][] = $attrs['objectClass'][$i]; - } + if (isset($entry['objects'])) { + $objects =& $entry['objects']; + } else { + $objects = [&$entry]; } + foreach ($objects as &$object) { + /* Get old objectClasses */ + $ldap->cat($object['dn'], array_merge(['objectClass'], array_keys($mandatory))); + $attrs = $ldap->fetch(); + + /* Create new objectClass array */ + $new_attrs = []; + $new_attrs['objectClass'] = $oc; + for ($i = 0; $i < $attrs['objectClass']['count']; $i++) { + if (!in_array_ics($attrs['objectClass'][$i], $new_attrs['objectClass'])) { + $new_attrs['objectClass'][] = $attrs['objectClass'][$i]; + } + } - /* Append mandatories if missing */ - foreach ($mandatory as $name => $value) { - if (!isset($attrs[$name])) { - $new_attrs[$name] = $value; + /* Append mandatories if missing */ + foreach ($mandatory as $name => $value) { + if (!isset($attrs[$name])) { + $new_attrs[$name] = $value; + } } - } - /* Set info attributes for current object, - * or write changes to the ldap database - */ - if ($only_ldif) { - $entry['before'] = $this->array_to_ldif($attrs); - $entry['after'] = $this->array_to_ldif($new_attrs); - } else { - $ldap->cd($attrs['dn']); - if (!$ldap->modify($new_attrs)) { - msg_dialog::display( - _('Migration error'), - sprintf( - _('Cannot migrate entry "%s":').'<br/><br/><i>%s</i>', - $attrs['dn'], $ldap->get_error() - ), - ERROR_DIALOG - ); - return FALSE; + /* Set info attributes for current object, + * or write changes to the ldap database + */ + if ($only_ldif) { + $object['before'] = $this->array_to_ldif($attrs); + $object['after'] = $this->array_to_ldif($new_attrs); + } else { + $ldap->cd($attrs['dn']); + if (!$ldap->modify($new_attrs)) { + msg_dialog::display( + _('Migration error'), + sprintf( + _('Cannot migrate entry "%s":').'<br/><br/><i>%s</i>', + $attrs['dn'], $ldap->get_error() + ), + ERROR_DIALOG + ); + return FALSE; + } } } + unset($object); + unset($objects); } } unset($entry); @@ -967,13 +986,13 @@ class setupStepMigrate extends setupStep /* Search for users outside the people ou */ function check_outsideUsers (&$checkobj) { - $sizeLimitHit = $this->check_outsideObjects_generic($checkobj, '(&(objectClass=inetOrgPerson)(!(uid=*$)))', 'userRDN'); + list($sizeLimitHit,$count) = $this->check_outsideObjects_generic($checkobj, '(&(objectClass=inetOrgPerson)(!(uid=*$)))', 'userRDN'); - if (count($this->outsideUsers_toMigrate) !== 0) { + if ($count > 0) { if ($sizeLimitHit) { $message = sprintf(_('Found more than %d user(s) outside the configured tree "%s".'), static::$objectNumberLimit, trim(get_ou('userRDN'))); } else { - $message = sprintf(_('Found %d user(s) outside the configured tree "%s".'), count($this->outsideUsers_toMigrate), trim(get_ou('userRDN'))); + $message = sprintf(_('Found %d user(s) outside the configured tree "%s".'), $count, trim(get_ou('userRDN'))); } throw new CheckFailedException( "<div style='color:#F0A500'>"._("Warning")."</div>", @@ -998,11 +1017,12 @@ class setupStepMigrate extends setupStep $this->$var = []; $objects_ou = trim(get_ou($ou)); $cookie = ''; + $count = 0; do { if (version_compare(PHP_VERSION, '7.3.0') >= 0) { /* 7.3 and newer, use pagination control */ - $res = $ldap->search($filter, ['dn'], 'subtree', + $res = $ldap->search($filter, ['dn','objectClass'], 'subtree', [['oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => ['size' => 500, 'cookie' => $cookie]]] ); if (!$res) { @@ -1034,7 +1054,7 @@ class setupStepMigrate extends setupStep } else { /* fallback to search without pagination */ $ldap->set_size_limit(static::$objectNumberLimit); - $res = $ldap->search($filter, ['dn']); + $res = $ldap->search($filter, ['dn','objectClass']); if (!$res) { throw new CheckFailedException( _('LDAP query failed'), @@ -1050,17 +1070,29 @@ class setupStepMigrate extends setupStep } while ($attrs = $ldap->fetch()) { - $object_base = preg_replace('/^[^,]+,'.preg_quote($objects_ou, '/').'/i', '', $attrs['dn']); + $object_base = preg_replace('/^[^,]+,'.preg_quote($objects_ou, '/').'/i', '', $attrs['dn']); + $base = preg_replace('/^[^,]+,/', '', $attrs['dn']); /* Check if entry is not an addressbook only object * and verify that he is in a valid department */ if (!preg_match('/dc=addressbook,/', $object_base) && !in_array($object_base, $config->getDepartmentList())) { - $attrs['checked'] = FALSE; - $attrs['ldif'] = ''; - $this->$var[base64_encode($attrs['dn'])] = $attrs; - if (count($this->$var) >= static::$objectNumberLimit) { + /* Build groupid depending on base and objectClasses */ + $groupid = md5($base.implode('', $attrs['objectClass'])); + + if (!isset($this->$var[$groupid])) { + $this->$var[$groupid] = [ + 'checked' => FALSE, + 'objects' => [], + 'base' => $base, + 'classes' => $attrs['objectClass'], + ]; + } + + $attrs['ldif'] = ''; + $this->$var[$groupid]['objects'][base64_encode($attrs['dn'])] = $attrs; + if (++$count >= static::$objectNumberLimit) { $sizeLimitHit = TRUE; break 2; } @@ -1069,7 +1101,7 @@ class setupStepMigrate extends setupStep // Empty cookie means last page } while (!empty($cookie)); - return $sizeLimitHit; + return [$sizeLimitHit, $count]; } function check_outsideUsers_migrate (&$checkobj) @@ -1117,25 +1149,27 @@ class setupStepMigrate extends setupStep $var = $checkobj->name.'_toMigrate'; foreach ($this->$var as $b_dn => &$entry) { $entry['checked'] = isset($_POST['migrate_'.$b_dn]); - $entry['ldif'] = ''; if ($entry['checked']) { - $dn = base64_decode($b_dn); - $d_dn = preg_replace('/,.*$/', ','.$destination_dep, $dn); - if ($only_ldif) { - $entry['ldif'] = _('Entry will be moved from').":<br/>\t".htmlentities($dn, ENT_COMPAT, 'UTF-8').'<br/>'._('to').":<br/>\t".htmlentities($d_dn, ENT_COMPAT, 'UTF-8'); - - /* Check if there are references to this object */ - $ldap->search('(&(member='.ldap_escape_f($dn).')(|(objectClass=gosaGroupOfNames)(objectClass=groupOfNames)))', ['dn']); - $refs = ''; - while ($attrs = $ldap->fetch()) { - $ref_dn = $attrs['dn']; - $refs .= "<br/>\t".$ref_dn; - } - if (!empty($refs)) { - $entry['ldif'] .= '<br/><br/><i>'._('The following references will be updated').':</i>'.$refs; + foreach ($entry['objects'] as &$object) { + $dn = $object['dn']; + $d_dn = preg_replace('/,.*$/', ','.$destination_dep, $dn); + if ($only_ldif) { + $object['ldif'] = sprintf(_("Entry will be moved from:<br/>\t%s<br/>to:<br/>\t%s"), htmlentities($dn, ENT_COMPAT, 'UTF-8'), htmlentities($d_dn, ENT_COMPAT, 'UTF-8')); + + /* Check if there are references to this object */ + $ldap->search('(&(member='.ldap_escape_f($dn).')(|(objectClass=gosaGroupOfNames)(objectClass=groupOfNames)))', ['dn']); + $refs = ''; + while ($attrs = $ldap->fetch()) { + $ref_dn = $attrs['dn']; + $refs .= "<br/>\t".$ref_dn; + } + if (!empty($refs)) { + $entry['ldif'] .= '<br/><br/><i>'._('The following references will be updated').':</i>'.$refs; + } + } else { + $object['ldif'] = ''; + $this->move($dn, $d_dn); } - } else { - $this->move($dn, $d_dn); } } } @@ -1147,13 +1181,13 @@ class setupStepMigrate extends setupStep /* Search for groups outside the group ou */ function check_outsideGroups (&$checkobj) { - $sizeLimitHit = $this->check_outsideObjects_generic($checkobj, '(objectClass=posixGroup)', 'groupRDN'); + list($sizeLimitHit,$count) = $this->check_outsideObjects_generic($checkobj, '(objectClass=posixGroup)', 'groupRDN'); - if (count($this->outsideGroups_toMigrate) !== 0) { + if ($count > 0) { if ($sizeLimitHit) { $message = sprintf(_('Found more than %d groups outside the configured tree "%s".'), static::$objectNumberLimit, trim(get_ou('groupRDN'))); } else { - $message = sprintf(_('Found %d groups outside the configured tree "%s".'), count($this->outsideGroups_toMigrate), trim(get_ou('groupRDN'))); + $message = sprintf(_('Found %d groups outside the configured tree "%s".'), $count, trim(get_ou('groupRDN'))); } throw new CheckFailedException( "<div style='color:#F0A500'>"._("Warning")."</div>", diff --git a/setup/setup_migrate_accounts.tpl b/setup/setup_migrate_accounts.tpl index 2971f43f90c64be00478b0afa7f221bb850fdeac..8b74b8e544104df86307e9c5b4754bea4dc571d1 100644 --- a/setup/setup_migrate_accounts.tpl +++ b/setup/setup_migrate_accounts.tpl @@ -21,7 +21,42 @@ {/if} {foreach from=$infos.entries item=entry key=key} - {if $entry.checked} + {if isset($entry.objects)} + <input type="checkbox" name="migrate_{$key}" id="migrate_{$key}" + {if $entry.checked}checked="checked"{/if} /> + <label for="migrate_{$key}">{$entry.base}</label> + <ul> + {foreach from=$entry.objects item=object} + <li>{$object.dn}</li> + {if $entry.checked} + {if !empty($object.after)} + <div class="step2-entry-container-info"> + {t}Current{/t} + <div style="padding-left:20px;"> +<pre> +dn: {$object.dn} +{$object.before} +</pre> + </div> + {t}After migration{/t} + <div style="padding-left:20px;"> +<pre> +dn: {$object.dn} +{$object.after} +</pre> + </div> + </div> + {elseif !empty($object.ldif)} + <div class="step2-entry-container-info"> + <div style="padding-left:20px;"> + <pre>{$object.ldif}</pre> + </div> + </div> + {/if} + {/if} + {/foreach} + </ul> + {elseif $entry.checked} <input type="checkbox" name="migrate_{$key}" checked="checked" id="migrate_{$key}"/> <label for="migrate_{$key}">{$entry.dn}</label> {if !empty($entry.after)}