Fix company syncing in bulk editing users

If the target user belongs to [A, B, C] and the acting admin belongs to [B, C], only B and C get detached. Company A — which the acting admin can't see — is left untouched.
This commit is contained in:
snipe
2026-06-03 10:36:41 +01:00
parent 5e5bd7a17d
commit a161fa8519
2 changed files with 245 additions and 2 deletions
@@ -175,7 +175,15 @@ class BulkUsersController extends Controller
->conditionallyAddItem('start_date')
->conditionallyAddItem('end_date')
->conditionallyAddItem('city')
->conditionallyAddItem('autoassign_licenses');
->conditionallyAddItem('autoassign_licenses')
->conditionallyAddItem('phone')
->conditionallyAddItem('jobtitle')
->conditionallyAddItem('address')
->conditionallyAddItem('state')
->conditionallyAddItem('country')
->conditionallyAddItem('zip')
->conditionallyAddItem('website')
->conditionallyAddItem('notes');
// If the manager_id is one of the users being updated, generate a warning.
if (array_search($request->input('manager_id'), $user_raw_array)) {
@@ -220,6 +228,46 @@ class BulkUsersController extends Controller
$this->update_array['display_name'] = null;
}
if ($request->input('null_city') == '1') {
$this->update_array['city'] = null;
}
if ($request->input('null_phone') == '1') {
$this->update_array['phone'] = null;
}
if ($request->input('null_jobtitle') == '1') {
$this->update_array['jobtitle'] = null;
}
if ($request->input('null_employee_num') == '1') {
$this->update_array['employee_num'] = null;
}
if ($request->input('null_address') == '1') {
$this->update_array['address'] = null;
}
if ($request->input('null_state') == '1') {
$this->update_array['state'] = null;
}
if ($request->input('null_country') == '1') {
$this->update_array['country'] = null;
}
if ($request->input('null_zip') == '1') {
$this->update_array['zip'] = null;
}
if ($request->input('null_website') == '1') {
$this->update_array['website'] = null;
}
if ($request->input('null_notes') == '1') {
$this->update_array['notes'] = null;
}
if (! $manager_conflict) {
$this->conditionallyAddItem('manager_id');
}
@@ -245,7 +293,15 @@ class BulkUsersController extends Controller
User::whereIn('id', $user_raw_array)->where('id', '!=', auth()->id())
->update(['company_id' => $scalarCompanyId]);
foreach ($users as $user) {
$user->companies()->sync($allowedIds);
if ($clearCompanies && ! auth()->user()->isSuperUser() && Company::isFullMultipleCompanySupportEnabled()) {
// Non-superusers can only detach companies they belong to; sync([]) would
// also wipe memberships for companies outside their scope.
$user->companies()->detach(Company::getIdsForCurrentUser(
$user->companies()->pluck('companies.id')->toArray()
));
} else {
$user->companies()->sync($allowedIds);
}
}
}
@@ -260,6 +316,11 @@ class BulkUsersController extends Controller
if ($request->filled('ldap_import')) {
$authFieldUpdate['ldap_import'] = $request->input('ldap_import');
}
if ($request->filled('email')) {
$authFieldUpdate['email'] = $request->input('email');
} elseif ($request->input('null_email') == '1') {
$authFieldUpdate['email'] = null;
}
if (! empty($authFieldUpdate)) {
$user->update($authFieldUpdate);
}
+182
View File
@@ -117,6 +117,152 @@
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_city" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.city'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- State -->
<div class="form-group{{ $errors->has('state') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="state">{{ trans('general.state') }}</label>
<div class="col-md-4">
<input class="form-control" type="text" name="state" id="state" aria-label="state" maxlength="191" />
{!! $errors->first('state', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_state" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.state'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Country -->
<div class="form-group{{ $errors->has('country') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="country">{{ trans('general.country') }}</label>
<div class="col-md-4">
<x-input.country-select name="country" :selected="old('country', '')" />
{!! $errors->first('country', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_country" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.country'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Zip -->
<div class="form-group{{ $errors->has('zip') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="zip">{{ trans('general.zip') }}</label>
<div class="col-md-4">
<input class="form-control" type="text" name="zip" id="zip" aria-label="zip" maxlength="10" />
{!! $errors->first('zip', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_zip" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.zip'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Address -->
<div class="form-group{{ $errors->has('address') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="address">{{ trans('general.address') }}</label>
<div class="col-md-4">
<input class="form-control" type="text" name="address" id="address" aria-label="address" maxlength="191" />
{!! $errors->first('address', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_address" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.address'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Phone -->
<div class="form-group{{ $errors->has('phone') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="phone">{{ trans('admin/users/table.phone') }}</label>
<div class="col-md-4">
<input class="form-control" type="text" name="phone" id="phone" aria-label="phone" maxlength="191" />
{!! $errors->first('phone', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_phone" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('admin/users/table.phone'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Job Title -->
<div class="form-group{{ $errors->has('jobtitle') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="jobtitle">{{ trans('admin/users/table.title') }}</label>
<div class="col-md-4">
<input class="form-control" type="text" name="jobtitle" id="jobtitle" aria-label="jobtitle" maxlength="191" />
{!! $errors->first('jobtitle', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_jobtitle" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('admin/users/table.title'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Employee Number (clear only employee numbers are unique per user) -->
<div class="form-group">
<label class="col-md-3 control-label">{{ trans('general.employee_number') }}</label>
<div class=" col-md-9">
<label class="form-control">
<input type="checkbox" name="null_employee_num" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.employee_number'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Website -->
<div class="form-group{{ $errors->has('website') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="website">{{ trans('general.website') }}</label>
<div class="col-md-4">
<input class="form-control" type="url" name="website" id="website" aria-label="website" maxlength="191" />
{!! $errors->first('website', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_website" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.website'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- remote -->
<div class="form-group">
<div class="col-sm-3 control-label">
@@ -210,6 +356,24 @@
</div> <!--/form-group-->
<!-- Email (auth-sensitive: only applied to users the acting user can edit) -->
<div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="email">{{ trans('admin/users/table.email') }}</label>
<div class="col-md-4">
<input class="form-control" type="email" name="email" id="email" aria-label="email" maxlength="191" />
{!! $errors->first('email', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_email" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('admin/users/table.email'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
<!-- Groups -->
<div class="form-group{{ $errors->has('groups') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="groups"> {{ trans('general.groups') }}</label>
@@ -290,6 +454,24 @@
</div>
<!-- Notes -->
<div class="form-group{{ $errors->has('notes') ? ' has-error' : '' }}">
<label class="col-md-3 control-label" for="notes">{{ trans('general.notes') }}</label>
<div class="col-md-6">
<textarea class="form-control" rows="4" id="notes" name="notes" aria-label="notes"></textarea>
{!! $errors->first('notes', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</div>
</div>
<div class="form-group">
<div class=" col-md-9 col-md-offset-3">
<label class="form-control">
<input type="checkbox" name="null_notes" value="1" />
{{ trans_choice('general.set_users_field_to_null', count($users), ['field' => trans('general.notes'), 'user_count' => count($users)]) }}
</label>
</div>
</div>
@foreach ($users as $user)
<input type="hidden" name="ids[{{ $user->id }}]" value="{{ $user->id }}">
@endforeach