] * * @see UsersController::getDatatable() method that generates the JSON response * @since [v1.0] * * @return View * * @throws AuthorizationException */ public function index() { $this->authorize('index', User::class); return view('users/index'); } /** * Returns a view that displays the user creation form. * * @author [A. Gianotto] [] * * @since [v1.0] * * @return View * * @throws AuthorizationException */ public function create(Request $request) { $this->authorize('create', User::class); $groups = Group::orderBy('name', 'asc')->pluck('name', 'id'); $userGroups = collect(); if ($request->old('groups')) { $userGroups = Group::whereIn('id', $request->old('groups'))->pluck('name', 'id'); } $permissions = config('permissions'); $userPermissions = Helper::selectedPermissionsArray($permissions, $request->old('permissions', [])); $permissions = $this->filterDisplayable($permissions); $user = new User; return view('users/edit', compact('groups', 'userGroups', 'permissions', 'userPermissions')) ->with('user', $user); } /** * Validate and store the new user data, or return an error. * * @author [A. Gianotto] [] * * @since [v1.0] * * @return RedirectResponse * * @throws AuthorizationException */ public function store(SaveUserRequest $request) { $this->authorize('create', User::class); $authenticatedUser = auth()->user(); $user = new User; // Username, email, and password need to be handled specially because the need to respect config values on an edit. $user->email = trim($request->input('email')); $user->username = trim($request->input('username')); $user->display_name = $request->input('display_name'); if ($request->filled('password')) { $user->password = bcrypt($request->input('password')); } $user->first_name = $request->input('first_name'); $user->last_name = $request->input('last_name'); $user->locale = $request->input('locale'); $user->employee_num = $request->input('employee_num'); $user->activated = $request->input('activated', 0); $user->jobtitle = $request->input('jobtitle'); $user->phone = $request->input('phone'); $user->mobile = $request->input('mobile'); $user->location_id = $request->input('location_id', null); $user->department_id = $request->input('department_id', null); $companyIds = array_filter(array_map('intval', (array) ($request->input('company_ids') ?? ($request->filled('company_id') ? [$request->input('company_id')] : [])))); $user->manager_id = $request->input('manager_id', null); $user->notes = $request->input('notes'); $user->address = $request->input('address', null); $user->city = $request->input('city', null); $user->state = $request->input('state', null); $user->country = $request->input('country', null); $user->zip = $request->input('zip', null); $user->remote = $request->input('remote', 0); $user->website = $request->input('website', null); $user->created_by = auth()->id(); $user->start_date = $request->input('start_date', null); $user->end_date = $request->input('end_date', null); $user->autoassign_licenses = $request->input('autoassign_licenses', 0); $user->permissions = json_encode(PreserveUnauthorizedPrivilegedPermissionsAction::run( requestedPermissions: NormalizePermissionsPayloadAction::run($request->input('permission')), authenticatedUser: $authenticatedUser, )); // we have to invoke the form request here to handle image uploads app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); if ($request->input('redirect_option') === 'back') { session()->put(['redirect_option' => 'index']); } else { session()->put(['redirect_option' => $request->input('redirect_option')]); } if ($user->save()) { $user->syncCompaniesWithLogging(Company::getIdsForCurrentUser($companyIds)); if (($user->activated == '1') && ($user->email != '') && ($request->input('send_welcome') == '1')) { try { $user->notify(new WelcomeNotification($user)); } catch (\Exception $e) { Log::warning('Could not send welcome notification for user: '.$e->getMessage()); } } if (auth()->user()->isSuperUser() && auth()->user()->can('editableOnDemo')) { $user->groups()->sync($request->input('groups')); } return Helper::getRedirectOption($request, $user->id, 'Users') ->with('success', trans('admin/users/message.success.create')); } return redirect()->back()->withInput()->withErrors($user->getErrors()); } private function filterDisplayable($permissions) { $output = null; foreach ($permissions as $key => $permission) { $output[$key] = array_filter($permission, function ($p) { return $p['display'] === true; }); } return $output; } /** * Returns a view that displays the edit user form * * @author [A. Gianotto] [] * * @since [v1.0] * * @param $permissions * @return View|RedirectResponse * * @internal param int $id * * @throws AuthorizationException */ public function edit(User $user) { $this->authorize('update', $user); session()->put('url.intended', url()->previous()); $user = User::with(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed()->find($user->id); if ($user) { if ($user->trashed()) { return redirect()->route('users.show', $user->id); } $permissions = config('permissions'); $groups = Group::orderBy('name', 'asc')->pluck('name', 'id'); $userGroups = $user->groups()->pluck('name', 'id'); $user->permissions = $user->decodePermissions(); $userPermissions = Helper::selectedPermissionsArray($permissions, $user->permissions); $permissions = $this->filterDisplayable($permissions); return view('users/edit', compact('user', 'groups', 'userGroups', 'permissions', 'userPermissions'))->with('item', $user); } } /** * Validate and save edited user data from edit form. * * @author [A. Gianotto] [] * * @since [v1.0] * * @param int $id * @return RedirectResponse * * @throws AuthorizationException */ public function update(SaveUserRequest $request, User $user) { $this->authorize('update', $user); $authenticatedUser = auth()->user(); // This is a janky hack to prevent people from changing admin demo user data on the public demo. // The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder. // Thanks, jerks. You are why we can't have nice things. - snipe if ((($user->id == 1) || ($user->id == 2)) && (config('app.lock_passwords'))) { return redirect()->route('users.index')->with('error', trans('general.permission_denied_superuser_demo')); } // We need to reverse the UI specific logic for our // permissions here before we update the user. $permissions = $request->input('permissions', []); app('request')->request->set('permissions', $permissions); $user->load(['assets', 'assets.model', 'consumables', 'accessories', 'licenses', 'userloc'])->withTrashed(); $this->authorize('update', $user); $orig_permissions_array = NormalizePermissionsPayloadAction::run($user->decodePermissions()); // Update the user fields $user->first_name = $request->input('first_name'); $user->last_name = $request->input('last_name'); $user->display_name = $request->input('display_name'); $user->two_factor_optin = $request->input('two_factor_optin') ?: 0; $user->locale = $request->input('locale'); $user->employee_num = $request->input('employee_num'); $user->jobtitle = $request->input('jobtitle', null); $user->phone = $request->input('phone'); $user->mobile = $request->input('mobile'); $user->location_id = $request->input('location_id', null); $companyIds = array_filter(array_map('intval', (array) ($request->input('company_ids') ?? ($request->filled('company_id') ? [$request->input('company_id')] : [])))); $user->manager_id = $request->input('manager_id', null); $user->notes = $request->input('notes'); $user->department_id = $request->input('department_id', null); $user->address = $request->input('address', null); $user->city = $request->input('city', null); $user->state = $request->input('state', null); $user->country = $request->input('country', null); $user->zip = $request->input('zip', null); $user->remote = $request->input('remote', 0); $user->vip = $request->input('vip', 0); $user->website = $request->input('website', null); $user->start_date = $request->input('start_date', null); $user->end_date = $request->input('end_date', null); $user->autoassign_licenses = $request->input('autoassign_licenses', 0); // Set this here so that we can overwrite it later if the user is an admin or superadmin $user->activated = $request->input('activated', auth()->user()->is($user) ? 1 : $user->activated); // Update the location of any assets checked out to this user Asset::where('assigned_type', User::class) ->where('assigned_to', $user->id) ->update(['location_id' => $request->input('location_id', null)]); // check for permissions related fields and only set them if the user has permission to edit them if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) { $user->username = trim($request->input('username')); $user->email = trim($request->input('email')); $user->activated = $request->input('activated', $request->user()->is($user) ? 1 : 0); // Do we want to update the user password? if ($request->filled('password')) { $user->password = bcrypt($request->input('password')); } if ($request->has('permission')) { $user->permissions = json_encode(PreserveUnauthorizedPrivilegedPermissionsAction::run( requestedPermissions: NormalizePermissionsPayloadAction::run($request->input('permission')), authenticatedUser: $authenticatedUser, originalPermissions: $orig_permissions_array, targetUser: $user, )); } // Only save groups if the user is a superuser if (auth()->user()->isSuperUser()) { $user->groups()->sync($request->input('groups')); } } // Update the location of any assets checked out to this user Asset::where('assigned_type', User::class) ->where('assigned_to', $user->id) ->update(['location_id' => $user->location_id]); // Handle uploaded avatar app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); session()->put(['redirect_option' => $request->input('redirect_option')]); if ($user->save()) { $user->syncCompaniesWithLogging(Company::getIdsForCurrentUser($companyIds)); // Redirect to the user page return Helper::getRedirectOption($request, $user->id, 'Users') ->with('success', trans('admin/users/message.success.update')); } return redirect()->back()->withInput()->withErrors($user->getErrors()); } /** * Delete a user * * @author [A. Gianotto] [] * * @since [v1.0] * * @param int $id * @return RedirectResponse * * @throws AuthorizationException */ public function destroy(DeleteUserRequest $request, $id) { $this->authorize('delete', User::class); if ($user = User::find($id)) { $this->authorize('delete', $user); if (auth()->user()->can('canEditAuthFields', $user) && auth()->user()->can('editableOnDemo')) { if ($user->delete()) { return redirect()->route('users.index')->with('success', trans('admin/users/message.success.delete')); } } return redirect()->route('users.index')->with('error', trans('admin/users/message.cannot_delete')); } return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found')); } /** * Restore a deleted user * * @author [A. Gianotto] [] * * @since [v1.0] * * @param int $id * @return RedirectResponse * * @throws AuthorizationException */ public function getRestore(User $user) { $this->authorize('delete', $user); if ($user->deleted_at == '') { return redirect()->back()->with('error', trans('general.not_deleted', ['item_type' => trans('general.user')])); } if ($user->restore()) { $logaction = new Actionlog; $logaction->item_type = User::class; $logaction->item_id = $user->id; $logaction->created_at = date('Y-m-d H:i:s'); $logaction->created_by = auth()->id(); $logaction->logaction('restore'); // Redirect them to the deleted page if there are more, otherwise the section index $deleted_users = User::onlyTrashed()->count(); if ($deleted_users > 0) { return redirect()->back()->with('success', trans('admin/users/message.success.restored')); } return redirect()->route('users.index')->with('success', trans('admin/users/message.success.restored')); } // Check validation to make sure we're not restoring a user with the same username as an existing user return redirect()->back()->with('error', trans('general.could_not_restore', ['item_type' => trans('general.user'), 'error' => $user->getErrors()->first()])); } /** * Return a view with user detail * * @author [A. Gianotto] [] * * @since [v1.0] * * @param int $userId * @return View * * @throws AuthorizationException */ public function show(User $user) { // Make sure the user can view users at all $this->authorize('view', $user); $user = User::with([ 'consumables', 'accessories', 'licenses', 'userloc', 'groups', ]) ->withTrashed() ->find($user->id); // Make sure they can view this particular user $this->authorize('view', $user); return view('users/view', [ 'user' => $user, 'settings' => Setting::getSettings(), 'effectivePermissionsBySection' => $user->getEffectivePermissionsBySection(), ]); } /** * Return a view containing a pre-populated new user form, * populated with some fields from an existing user. * * @author [A. Gianotto] [] * * @since [v1.0] * * @param int $id * @return View * * @throws AuthorizationException */ public function getClone(Request $request, User $user) { $this->authorize('create', $user); // We need to reverse the UI specific logic for our // permissions here before we update the user. $permissions = $request->input('permissions', []); app('request')->request->set('permissions', $permissions); $user_to_clone = User::with('userloc', 'companies')->withTrashed()->find($user->id); // Make sure they can view this particular user $this->authorize('view', $user_to_clone); if ($user_to_clone) { $user = clone $user_to_clone; // Blank out some fields $user->first_name = ''; $user->last_name = ''; $user->email = substr($user->email, ($pos = strpos($user->email, '@')) !== false ? $pos : 0); $user->id = null; $user->username = null; $user->avatar = null; // Get this user's groups $userGroups = $user_to_clone->groups()->pluck('name', 'id'); // Get all the available permissions $permissions = config('permissions'); $clonedPermissions = $user_to_clone->decodePermissions(); $userPermissions = Helper::selectedPermissionsArray($permissions, $clonedPermissions); // Show the page return view('users/edit', compact('permissions', 'userPermissions')) ->with('user', $user) ->with('groups', Group::pluck('name', 'id')) ->with('userGroups', $userGroups) ->with('cloned_model', $user_to_clone) ->with('item', $user); } } /** * Exports users to CSV * * @author [A. Gianotto] [] * * @since [v3.5] * * @return StreamedResponse * * @throws AuthorizationException */ public function getExportUserCsv() { $this->authorize('view', User::class); $this->disableDebugbar(); $response = new StreamedResponse(function () { // Open output stream $handle = fopen('php://output', 'w'); $headers = [ // strtolower to prevent Excel from trying to open it as a SYLK file strtolower(trans('general.id')), trans('admin/companies/table.title'), trans('admin/users/table.title'), trans('general.employee_number'), trans('admin/users/table.first_name'), trans('admin/users/table.last_name'), trans('admin/users/table.name'), trans('admin/users/table.display_name'), trans('admin/users/table.username'), trans('admin/users/table.email'), trans('admin/users/table.phone'), trans('admin/users/table.mobile'), trans('general.website'), trans('general.address'), trans('general.city'), trans('general.state'), trans('general.country'), trans('general.zip'), trans('admin/users/table.manager'), trans('admin/users/table.location'), trans('general.department'), trans('general.assets'), trans('general.licenses'), trans('general.accessories'), trans('general.consumables'), trans('general.groups'), trans('general.permissions'), trans('general.notes'), trans('admin/users/table.activated'), trans('general.created_at'), trans('general.importer.vip'), trans('admin/users/general.remote'), trans('general.language'), trans('general.autoassign_licenses'), trans('general.ldap_sync'), trans('admin/users/general.two_factor_enrolled'), trans('admin/users/general.two_factor_active'), trans('admin/users/table.managed_users'), trans('admin/users/table.managed_locations'), trans('admin/users/general.department_manager'), trans('general.created_by'), trans('general.updated_at'), trans('general.start_date'), trans('general.end_date'), trans('admin/users/table.last_login'), trans('admin/licenses/table.deleted_at'), ]; fputcsv($handle, $headers); $users = User::with( 'assets', 'accessories', 'consumables', 'department.manager', 'licenses', 'manager', 'groups', 'userloc', 'companies', 'createdBy' )->withCount(['managesUsers as manages_users_count', 'managedLocations as manages_locations_count']) ->orderBy('created_at', 'DESC') ->chunk(500, function ($users) use ($handle) { $formatter = new EscapeFormula('`'); foreach ($users as $user) { $permissionstring = ''; if ($user->isSuperUser()) { $permissionstring = trans('general.superuser'); } elseif ($user->hasAccess('admin')) { $permissionstring = trans('general.admin'); } else { $permissionstring = trans('general.user'); } // Add a new row with data $values = [ $user->id, $user->companies->pluck('name')->implode('|'), $user->jobtitle, $user->employee_num, $user->first_name, $user->last_name, $user->getFullNameAttribute(), $user->getRawOriginal('display_name'), $user->username, $user->email, $user->phone, $user->mobile, $user->website, $user->address, $user->city, $user->state, $user->country, $user->zip, ($user->manager) ? $user->manager->display_name : '', ($user->userloc) ? $user->userloc->name : '', ($user->department) ? $user->department->name : '', $user->assets->count(), $user->licenses->count(), $user->accessories->count(), $user->consumables->count(), $user->groups->pluck('name')->implode(', '), $permissionstring, $user->notes, ($user->activated == '1') ? trans('general.yes') : trans('general.no'), $user->created_at, ($user->vip == '1') ? trans('general.yes') : trans('general.no'), ($user->remote == '1') ? trans('general.yes') : trans('general.no'), $user->locale, ($user->autoassign_licenses == '1') ? trans('general.yes') : trans('general.no'), ($user->ldap_import == '1') ? trans('general.yes') : trans('general.no'), ($user->two_factor_active_and_enrolled()) ? trans('general.yes') : trans('general.no'), ($user->two_factor_active()) ? trans('general.yes') : trans('general.no'), $user->manages_users_count, $user->manages_locations_count, ($user->department && $user->department->manager) ? $user->department->manager->display_name : '', ($user->createdBy) ? $user->createdBy->display_name : '', $user->updated_at, $user->start_date, $user->end_date, $user->last_login, $user->deleted_at, ]; // CSV_ESCAPE_FORMULAS is set to false in the .env if (config('app.escape_formulas') === false) { fputcsv($handle, $values); // CSV_ESCAPE_FORMULAS is set to true or is not set in the .env } else { fputcsv($handle, $formatter->escapeRecord($values)); } } }); // Close the output stream fclose($handle); }, 200, [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => 'attachment; filename="users-'.date('Y-m-d-his').'.csv"', ]); return $response; } /** * Print inventory * * @since [v1.8] * * @author Aladin Alaily */ public function printInventory($id) { $this->authorize('view', User::class); $actor = auth()->user(); $canViewLicenses = $actor->can('view', License::class); $canViewAccessories = $actor->can('view', Accessory::class); $canViewConsumables = $actor->can('view', Consumable::class); $user = User::withInventoryRelations($id, $canViewLicenses, $canViewAccessories, $canViewConsumables)->first(); $indirectItemsCount = $user?->assets?->flatMap->assignedAssets->count() + $user?->assets?->flatMap->components->count() + ($canViewLicenses ? $user?->assets?->flatMap->licenses->count() : 0) + ($canViewAccessories ? $user?->assets?->flatMap->assignedAccessories->count() : 0); if ($user) { $this->authorize('view', $user); return view('users.print') ->with('users', [$user]) ->with('indirectItemsCount', $indirectItemsCount) ->with('settings', Setting::getSettings()); } return redirect()->route('users.index')->with('error', trans('admin/users/message.user_not_found', compact('id'))); } /** * Emails user a list of assigned assets * * @author [G. Martinez] [] * * @since [v6.0.5] * * @param UsersController $id * @return RedirectResponse */ public function emailAssetList($id) { $this->authorize('view', User::class); $user = User::find($id); // Make sure they can view this particular user $this->authorize('view', $user); if ($user) { if (empty($user->email)) { return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email')); } $user->notify((new CurrentInventory($user))); return redirect()->back()->with('success', trans('admin/users/general.user_notified')); } return redirect()->back()->with('error', trans('admin/users/message.user_not_found', ['id' => $id])); } /** * Resend pending acceptance reminder email for a specific user. */ public function resendAcceptanceReminder(User $user): RedirectResponse { $this->authorize('view', $user); if (empty($user->email)) { return redirect()->back()->with('error', trans('admin/users/message.user_has_no_email')); } if ($user->activated == '0') { return redirect()->back()->with('error', trans('admin/users/message.not_activated')); } $pendingItems = $user->getAssignedItemsWithPendingAcceptance(); if ($pendingItems->isEmpty()) { return redirect()->back()->with('warning', trans('admin/users/message.error.no_pending_acceptances')); } $firstAcceptance = CheckoutAcceptance::query() ->forUser($user) ->pending() ->with('assignedTo') ->first(); if (! $firstAcceptance) { return redirect()->back()->with('warning', trans('admin/users/message.error.no_pending_acceptances')); } $mailable = new UnacceptedAssetReminderMail($firstAcceptance, $pendingItems->count()); if (! empty($user->locale)) { $mailable->locale($user->locale); } Mail::to($user->email)->send($mailable); return redirect()->back()->with('success', trans_choice('admin/users/message.success.acceptance_reminder_sent', $pendingItems->count(), ['count' => $pendingItems->count()])); } /** * Send individual password reset email * * @author A. Gianotto * * @since [v5.0.15] * * @return RedirectResponse */ public function sendPasswordReset($id) { $this->authorize('view', User::class); if (($user = User::find($id)) && ($user->activated == '1') && ($user->email != '') && ($user->ldap_import == '0')) { $credentials = ['email' => trim($user->email)]; try { Password::sendResetLink($credentials); return redirect()->back()->with('success', trans('admin/users/message.password_reset_sent', ['email' => $user->email])); } catch (\Exception $e) { return redirect()->back()->with('error', trans('general.error_sending_email')); } } return redirect()->back()->with('error', trans('general.pwd_reset_not_sent')); } }