Compare commits

...

70 Commits

Author SHA1 Message Date
snipe
93014d7f34 Better handle backups in S3
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 14:58:32 +00:00
snipe
759e3794df Small upgrader UI fixes
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 14:57:28 +00:00
snipe
c50b14763f Merge pull request #16403 from snipe/#16402_each_localization
Fixed #16402 - localize "each" string in components tab on asset view
2025-03-04 13:35:08 +00:00
snipe
369a68fe57 Fixed #16402 - Localize “each” in string
Signed-off-by: snipe <snipe@snipe.net>
2025-03-04 13:33:04 +00:00
snipe
83855d44d0 Merge pull request #16251 from Godmartinz/Audit_Checkin_warning_fix
refactors audit notification to mail, adds test, adds  alerts check to scheduler
2025-03-04 12:51:42 +00:00
snipe
6f847294ed Merge pull request #15911 from Fiala06/patch-1
Fixed duplicate entries preventing LDAP sync from continuing
2025-03-04 12:49:54 +00:00
snipe
d556d1c6e7 Merge pull request #16150 from Godmartinz/add-translations-to-settings
Adds Translation strings to General and Branding Settings
2025-03-04 12:48:21 +00:00
snipe
3bb94e98f0 Merge pull request #16398 from marcusmoore/bug/sc-28535
Avoid using authenticated user's email address in email partial
2025-03-04 12:47:10 +00:00
Marcus Moore
8f5f6f3502 Avoid using authenticated user's email address in email partial 2025-03-03 16:28:08 -08:00
snipe
b3792bfa00 Merge pull request #16396 from marcusmoore/chore/migrate-checkbox-helpers-pt10
Replace calls to Form::checkbox pt10
2025-03-03 20:52:54 +00:00
Marcus Moore
40f7257723 Replace call to Form::checkbox 2025-03-03 12:44:50 -08:00
snipe
8486256142 Merge pull request #16381 from marcusmoore/chore/migrate-checkbox-helpers-pt9
Replace calls to Form::checkbox pt9
2025-03-03 20:28:21 +00:00
snipe
7f36750e33 Merge branch 'develop' of https://github.com/snipe/snipe-it into develop 2025-03-03 19:54:14 +00:00
snipe
de046db106 Add @azmcnutt as a contributor 2025-03-03 19:53:30 +00:00
snipe
eb1d27a5bc Merge pull request #16379 from azmcnutt/feature/settings_ldap_invert_active_flag
Feature/settings ldap invert active flag
2025-03-03 19:52:57 +00:00
Godfrey M
cc0b9f404a merged develop, fix conflicts 2025-02-27 15:38:31 -08:00
Marcus Moore
70332696c6 Fix test by passing in required properties 2025-02-27 15:23:17 -08:00
Marcus Moore
7a9b5d61b0 Replace another Form::checkbox 2025-02-27 13:25:32 -08:00
Marcus Moore
5876259893 Replace another Form::checkbox 2025-02-27 13:19:26 -08:00
Marcus Moore
8755c54edc Replace Form::checkbox 2025-02-27 13:13:39 -08:00
Marcus Moore
014f3b7652 Cast to boolean 2025-02-27 13:12:01 -08:00
Marcus Moore
3a2579b205 WIP: replace Form::checkbox 2025-02-27 13:08:02 -08:00
James M
149474bfe3 Update general.php
FIX: Spelling error
2025-02-27 12:42:47 -07:00
James M
b2b768dede Merge branch 'snipe:develop' into develop 2025-02-27 12:25:07 -07:00
snipe
a9ed9e2a7f Merge pull request #16378 from snipe/wrap_pdf_table_results
Wrap long text in PDF export in tables
2025-02-27 19:06:34 +00:00
snipe
ce8523b00a Fixed wrapping
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 18:58:18 +00:00
snipe
7076a68d35 Wrap table results in PDF
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 18:38:10 +00:00
James M
112112d258 Feat: #14926 LDAP Active Flag - Add config option to make False = Enable 2025-02-27 10:52:12 -07:00
snipe
3928c8afe9 Merge pull request #16376 from uberbrady/improve_safety_csv_charset_detection
Add some safeties around the charset-detection and transliteration
2025-02-27 16:26:44 +00:00
snipe
23ce54e80c Make sure we’re saving the last_login in 2FA auths
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 16:17:59 +00:00
Brady Wetherington
646e3e8df5 Complete failed-transliteration test, clean up error, new translation string 2025-02-27 16:10:56 +00:00
snipe
30c4e9dbf7 Use formatter for created_at on unaccepted assets
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 15:48:24 +00:00
snipe
27fc30a881 Nicer button layout on unaccepted assets
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 15:43:53 +00:00
snipe
8ac7cda4ee Merge pull request #16366 from marcusmoore/chore/migrate-checkbox-helpers-pt7
Replace calls to Form::checkbox pt7
2025-02-27 15:01:31 +00:00
snipe
6f04d314a8 Merge pull request #16367 from marcusmoore/chore/migrate-checkbox-helpers-pt8
Replace calls to Form::checkbox pt8
2025-02-27 15:01:19 +00:00
snipe
1051b1d16d Merge pull request #16375 from snipe/fixes_16371_name_not_included_in_reminder_emails
Fixed #16371 - incorrect count and missing name in acceptance reminder email
2025-02-27 15:00:04 +00:00
snipe
115bb94704 Merge pull request #16156 from marcusmoore/acceptance-reminder-subject
Added "Reminder" to subject line of follow up asset checkout emails
2025-02-27 14:55:59 +00:00
snipe
25807cc62f Fixed constructor
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 14:22:48 +00:00
snipe
cd1d1b2d3e Fixed count
Signed-off-by: snipe <snipe@snipe.net>
2025-02-27 14:22:40 +00:00
Brady Wetherington
6dcd3bfd30 Add some safeties around the charset-detection and transliteration 2025-02-27 13:44:31 +00:00
Marcus Moore
a0dc056da8 Replace Form::checkbox on label settings page 2025-02-26 16:33:07 -08:00
Marcus Moore
27aeb518ff Replace Form::checkbox on general settings page 2025-02-26 16:18:00 -08:00
Marcus Moore
245a16c377 Replace Form::checkbox on branding settings page 2025-02-26 15:37:07 -08:00
Marcus Moore
de3c1d159f Replace Form::checkbox on branding settings page 2025-02-26 15:35:17 -08:00
Marcus Moore
e88bba51bb Merge branch 'develop' into acceptance-reminder-subject 2025-02-24 11:55:50 -08:00
Marcus Moore
f97211f6cd Remove unused language string 2025-02-24 11:45:58 -08:00
Marcus Moore
027c2b3627 Change subject to "You have Unaccepted Assets." 2025-02-24 11:45:23 -08:00
Godfrey M
4c43a06eee add overdue asset to test 2025-02-13 11:19:49 -08:00
Godfrey M
25c8449e86 remove unused 2025-02-13 11:14:18 -08:00
Godfrey M
13e1f4a127 adds mailable 2025-02-13 11:09:39 -08:00
Godfrey M
ed96fd766c refactors audit notification to mail, adds test, ads check to scheduler 2025-02-13 11:08:53 -08:00
Marcus Moore
7cbb3f7e07 Add assertion 2025-01-30 11:48:55 -08:00
Marcus Moore
7e9c564d0b Simplify test 2025-01-30 11:47:43 -08:00
Marcus Moore
fc88b2487f Extract method 2025-01-30 11:44:37 -08:00
Marcus Moore
e94ee48f74 Extract helper 2025-01-30 10:37:11 -08:00
Marcus Moore
6a4a5d1380 Add translation 2025-01-30 10:35:31 -08:00
Marcus Moore
ab9e9b66d2 Reduce complexity 2025-01-29 16:27:18 -08:00
Marcus Moore
c15c338ffd Merge if/else 2025-01-29 16:25:37 -08:00
Marcus Moore
d1197d015c Add another case scenario 2025-01-29 16:24:43 -08:00
Marcus Moore
ce31ce477e Inline additional variables 2025-01-29 16:16:47 -08:00
Marcus Moore
78f9292555 Inline variable 2025-01-29 16:15:27 -08:00
Marcus Moore
4e7c6bd2cf Fix relationship 2025-01-29 16:14:09 -08:00
Marcus Moore
70aed45bfe Improve naming 2025-01-29 15:56:20 -08:00
Marcus Moore
e2805f4033 Add "Reminder" to subject line 2025-01-29 15:36:45 -08:00
Marcus Moore
d254a40e0a Scaffold tests 2025-01-29 15:21:10 -08:00
Marcus Moore
fdcb891cbb Improve test case 2025-01-29 15:20:56 -08:00
Godfrey M
16d322d70e fix translation 2025-01-29 10:38:30 -08:00
Godfrey M
2163312997 adds translations for branding and general settings 2025-01-29 10:35:26 -08:00
Godfrey M
0dfb71cfe5 added some translations to branding and general setrting 2025-01-28 11:56:10 -08:00
Fiala06
bdb0e6c2a3 Update LdapSync.php
Fix for duplicate entries preventing the sync from continuing. 

  SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '13178-6' for key 'PRIMARY' (Connection: mysql, SQL: insert into users_groups (group_id, user_id) values (6, 13178))
2024-12-02 11:07:37 -08:00
41 changed files with 569 additions and 116 deletions

View File

@@ -3289,6 +3289,15 @@
"contributions": [
"code"
]
},
{
"login": "azmcnutt",
"name": "James M",
"avatar_url": "https://avatars.githubusercontent.com/u/31522486?v=4",
"profile": "https://github.com/azmcnutt",
"contributions": [
"code"
]
}
]
}

View File

@@ -53,7 +53,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/55590532?v=4" width="110px;"/><br /><sub>squintfox</sub>](https://github.com/squintfox)<br />[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [<img src="https://avatars.githubusercontent.com/u/1380084?v=4" width="110px;"/><br /><sub>Jeff Clay</sub>](https://github.com/jeffclay)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [<img src="https://avatars.githubusercontent.com/u/52716446?v=4" width="110px;"/><br /><sub>Phil J R</sub>](https://github.com/PP-JN-RL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [<img src="https://avatars.githubusercontent.com/u/1496725?v=4" width="110px;"/><br /><sub>i_virus</sub>](https://www.corelight.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [<img src="https://avatars.githubusercontent.com/u/1020541?v=4" width="110px;"/><br /><sub>Paul Grime</sub>](https://github.com/gitgrimbo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [<img src="https://avatars.githubusercontent.com/u/922815?v=4" width="110px;"/><br /><sub>Lee Porte</sub>](https://leeporte.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [<img src="https://avatars.githubusercontent.com/u/23613427?v=4" width="110px;"/><br /><sub>BRYAN </sub>](https://github.com/bryanlopezinc)<br />[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") |
| [<img src="https://avatars.githubusercontent.com/u/64061710?v=4" width="110px;"/><br /><sub>U-H-T</sub>](https://github.com/U-H-T)<br />[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [<img src="https://avatars.githubusercontent.com/u/5395363?v=4" width="110px;"/><br /><sub>Matt Tyree</sub>](https://github.com/Tyree)<br />[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [<img src="https://avatars.githubusercontent.com/u/292081?v=4" width="110px;"/><br /><sub>Florent Bervas</sub>](http://spoontux.net)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [<img src="https://avatars.githubusercontent.com/u/4498077?v=4" width="110px;"/><br /><sub>Daniel Albertsen</sub>](https://ditscheri.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [<img src="https://avatars.githubusercontent.com/u/100710244?v=4" width="110px;"/><br /><sub>r-xyz</sub>](https://github.com/r-xyz)<br />[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [<img src="https://avatars.githubusercontent.com/u/47491036?v=4" width="110px;"/><br /><sub>Steven Mainor</sub>](https://github.com/DrekiDegga)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [<img src="https://avatars.githubusercontent.com/u/65785975?v=4" width="110px;"/><br /><sub>arne-kroeger</sub>](https://github.com/arne-kroeger)<br />[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") |
| [<img src="https://avatars.githubusercontent.com/u/167117705?v=4" width="110px;"/><br /><sub>Glukose1</sub>](https://github.com/Glukose1)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [<img src="https://avatars.githubusercontent.com/u/1197791?v=4" width="110px;"/><br /><sub>Scarzy</sub>](https://github.com/Scarzy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [<img src="https://avatars.githubusercontent.com/u/37372069?v=4" width="110px;"/><br /><sub>setpill</sub>](https://github.com/setpill)<br />[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [<img src="https://avatars.githubusercontent.com/u/3755203?v=4" width="110px;"/><br /><sub>swift2512</sub>](https://github.com/swift2512)<br />[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [<img src="https://avatars.githubusercontent.com/u/6136439?v=4" width="110px;"/><br /><sub>Darren Rainey</sub>](https://darrenraineys.co.uk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [<img src="https://avatars.githubusercontent.com/u/133033121?v=4" width="110px;"/><br /><sub>maciej-poleszczyk</sub>](https://github.com/maciej-poleszczyk)<br />[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | [<img src="https://avatars.githubusercontent.com/u/143394709?v=4" width="110px;"/><br /><sub>Sebastian Groß</sub>](https://github.com/sgross-emlix)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sgross-emlix "Code") |
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") |
| [<img src="https://avatars.githubusercontent.com/u/41107778?v=4" width="110px;"/><br /><sub>Anouar Touati</sub>](https://github.com/AnouarTouati)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AnouarTouati "Code") | [<img src="https://avatars.githubusercontent.com/u/25596663?v=4" width="110px;"/><br /><sub>aHVzY2g</sub>](https://github.com/aHVzY2g)<br />[💻](https://github.com/snipe/snipe-it/commits?author=aHVzY2g "Code") | [<img src="https://avatars.githubusercontent.com/u/13408130?v=4" width="110px;"/><br /><sub>林博仁 Buo-ren Lin</sub>](https://brlin.me)<br />[💻](https://github.com/snipe/snipe-it/commits?author=brlin-tw "Code") | [<img src="https://avatars.githubusercontent.com/u/18550946?v=4" width="110px;"/><br /><sub>Adugna Gizaw</sub>](https://orbalia.pythonanywhere.com/)<br />[🌍](#translation-addex12 "Translation") | [<img src="https://avatars.githubusercontent.com/u/760989?v=4" width="110px;"/><br /><sub>Jesse Ostrander</sub>](https://github.com/jostrander)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jostrander "Code") | [<img src="https://avatars.githubusercontent.com/u/31522486?v=4" width="110px;"/><br /><sub>James M</sub>](https://github.com/azmcnutt)<br />[💻](https://github.com/snipe/snipe-it/commits?author=azmcnutt "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -361,9 +361,15 @@ class LdapSync extends Command
// (Specifically, we don't handle a value of '0.0' correctly)
$raw_value = @$results[$i][$ldap_map["active_flag"]][0];
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$boolean_cast = (bool) $raw_value;
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
if (Setting::getSettings()->ldap_invert_active_flag === 1) {
// Because ldap_active_flag is set, if filter_var is true or boolean_cast is true, then user is suspended
$user->activated = !($filter_var ?? $boolean_cast);
}else{
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
}
} elseif (array_key_exists('useraccountcontrol', $results[$i])) {
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
@@ -428,8 +434,12 @@ class LdapSync extends Command
$item['note'] = $item['createorupdate'];
$item['status'] = 'success';
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
$user->groups()->attach($ldap_default_group);
// Check if the relationship already exists
if (!$user->groups()->where('group_id', $ldap_default_group)->exists()) {
$user->groups()->attach($ldap_default_group);
}
}
//updates assets location based on user's location
if ($user->wasChanged('location_id')) {
foreach ($user->assets as $asset) {

View File

@@ -70,23 +70,26 @@ class SendAcceptanceReminder extends Command
// The [0] is weird, but it allows for the item_count to work and grabs the appropriate info for each user.
// Collapsing and flattening the collection doesn't work above.
$acceptance = $unacceptedAssetGroup[0]['acceptance'];
$locale = $acceptance->assignedTo?->locale;
$email = $acceptance->assignedTo?->email;
if(!$email){
$no_email_list[] = [
'id' => $acceptance->assignedTo->id,
'name' => $acceptance->assignedTo->present()->fullName(),
'id' => $acceptance->assignedTo?->id,
'name' => $acceptance->assignedTo?->present()->fullName(),
];
} else {
$count++;
}
$item_count = $unacceptedAssetGroup->count();
if ($locale && $email) {
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count))->locale($locale));
} elseif ($email) {
Mail::to($email)->send((new UnacceptedAssetReminderMail($acceptance, $item_count)));
}
$count++;
}
$this->info($count.' users notified.');

View File

@@ -2,13 +2,12 @@
namespace App\Console\Commands;
use App\Mail\SendUpcomingAuditMail;
use App\Models\Asset;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
use App\Notifications\SendUpcomingAuditNotification;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class SendUpcomingAuditReport extends Command
{
@@ -48,19 +47,19 @@ class SendUpcomingAuditReport extends Command
$today = Carbon::now();
$interval_date = $today->copy()->addDays($interval);
$assets = Asset::whereNull('deleted_at')->DueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count().' assets must be audited in on or before '.$interval_date.' is deadline');
$assets = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'desc')->get();
$this->info($assets->count() . ' assets must be audited in on or before ' . $interval_date . ' is deadline');
if (($assets) && ($assets->count() > 0) && ($settings->alert_email != '')) {
if ((count($assets) !== 0) && ($assets->count() > 0) && ($settings->alert_email != '')) {
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))->map(function ($item) {
return new AlertRecipient($item);
});
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item))
->all();
$this->info('Sending Admin SendUpcomingAuditNotification to: '.$settings->alert_email);
\Notification::send($recipients, new SendUpcomingAuditNotification($assets, $settings->audit_warning_days));
$this->info('Sending Admin SendUpcomingAuditNotification to: ' . $settings->alert_email);
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets, $settings->audit_warning_days));
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Console;
use App\Console\Commands\ImportLocations;
use App\Console\Commands\ReEncodeCustomFieldNames;
use App\Console\Commands\RestoreDeletedUsers;
use App\Models\Setting;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -18,12 +19,14 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
if(Setting::getSettings()->alerts_enabled === 1) {
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
}
$schedule->command('snipeit:backup')->weekly();
$schedule->command('backup:clean')->daily();
$schedule->command('snipeit:upcoming-audits')->daily();
$schedule->command('auth:clear-resets')->everyFifteenMinutes();
$schedule->command('saml:clear_expired_nonces')->weekly();
}

View File

@@ -66,25 +66,41 @@ class ImportController extends Controller
if (! ini_get('auto_detect_line_endings')) {
ini_set('auto_detect_line_endings', '1');
}
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
$reader = null;
if (strcasecmp($encoding, 'UTF-8') != 0) {
$transliterated = iconv($encoding, 'UTF-8', $file_contents);
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
if ($tmpresults !== false) {
if (function_exists('iconv')) {
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
\Log::warning("Discovered encoding: $encoding in uploaded CSV");
$reader = null;
if (strcasecmp($encoding, 'UTF-8') != 0) {
$transliterated = false;
try {
$transliterated = iconv(strtoupper($encoding), 'UTF-8', $file_contents);
} catch (\Exception $e) {
$transliterated = false; //blank out the partially-decoded string
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.transliterate_failure', ["encoding" => $encoding])
),
422
);
}
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
$transliterated = null; //save on memory?
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
if ($tmpresults !== false) {
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
}
}
}
}
$file_contents = null; //try to save on memory, I guess?
}
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
$file_contents = null; //try to save on memory, I guess?
try {
$import->header_row = $reader->fetchOne(0);

View File

@@ -206,6 +206,7 @@ class LoginController extends Controller
$user->password = bcrypt($request->input('password'));
}
$user->last_login = \Carbon::now();
$user->email = $ldap_attr['email'];
$user->first_name = $ldap_attr['firstname'];
$user->last_name = $ldap_attr['lastname']; //FIXME (or TODO?) - do we need to map additional fields that we now support? E.g. country, phone, etc.
@@ -432,6 +433,7 @@ class LoginController extends Controller
if (Google2FA::verifyKey($user->two_factor_secret, $secret)) {
$user->two_factor_enrolled = 1;
$user->last_login = \Carbon::now();
$user->saveQuietly();
$request->session()->put('2fa_authed', $user->id);

View File

@@ -1175,18 +1175,13 @@ class ReportsController extends Controller
}
$email = $assetItem->assignedTo?->email;
$locale = $assetItem->assignedTo?->locale;
// Only send notification if assigned
if ($locale && $email) {
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note))->locale($locale));
} elseif ($email) {
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)));
}
if ($email == ''){
if (is_null($email) || $email === '') {
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.no_email'));
}
Mail::to($email)->send((new CheckoutAssetMail($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note, firstTimeSending: false))->locale($locale));
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
}

1
app/Http/Controllers/SettingsController.php Executable file → Normal file
View File

@@ -851,6 +851,7 @@ class SettingsController extends Controller
$setting->ldap_auth_filter_query = $request->input('ldap_auth_filter_query');
$setting->ldap_version = $request->input('ldap_version', 3);
$setting->ldap_active_flag = $request->input('ldap_active_flag');
$setting->ldap_invert_active_flag = $request->input('ldap_invert_active_flag');
$setting->ldap_emp_num = $request->input('ldap_emp_num');
$setting->ldap_email = $request->input('ldap_email');
$setting->ldap_manager = $request->input('ldap_manager');

View File

@@ -12,11 +12,11 @@ class CategoryEditForm extends Component
public $originalSendCheckInEmailValue;
public $requireAcceptance;
public bool $requireAcceptance;
public $sendCheckInEmail;
public bool $sendCheckInEmail;
public $useDefaultEula;
public bool $useDefaultEula;
public function mount()
{

View File

@@ -20,10 +20,12 @@ class CheckoutAssetMail extends Mailable
{
use Queueable, SerializesModels;
private bool $firstTimeSending;
/**
* Create a new message instance.
*/
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note)
public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note, bool $firstTimeSending = true)
{
$this->item = $asset;
$this->admin = $checkedOutBy;
@@ -36,6 +38,8 @@ class CheckoutAssetMail extends Mailable
$this->last_checkout = '';
$this->expected_checkin = '';
$this->firstTimeSending = $firstTimeSending;
if ($this->item->last_checkout) {
$this->last_checkout = Helper::getFormattedDateObject($this->item->last_checkout, 'date',
false);
@@ -56,7 +60,7 @@ class CheckoutAssetMail extends Mailable
return new Envelope(
from: $from,
subject: trans('mail.Asset_Checkout_Notification'),
subject: $this->getSubject(),
);
}
@@ -107,4 +111,13 @@ class CheckoutAssetMail extends Mailable
{
return [];
}
private function getSubject(): string
{
if ($this->firstTimeSending) {
return trans('mail.Asset_Checkout_Notification');
}
return trans('mail.unaccepted_asset_reminder');
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class SendUpcomingAuditMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct($params, $threshold)
{
$this->assets = $params;
$this->threshold = $threshold;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$from = new Address(config('mail.from.address'), config('mail.from.name'));
return new Envelope(
from: $from,
subject: trans_choice('mail.upcoming-audits', $this->assets->count(), ['count' => $this->assets->count(), 'threshold' => $this->threshold]),
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
markdown: 'notifications.markdown.upcoming-audits',
with: [
'assets' => $this->assets,
'threshold' => $this->threshold,
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -19,9 +19,10 @@ class UnacceptedAssetReminderMail extends Mailable
*/
public function __construct($checkout_info, $count)
{
$this->count = $count;
$this->target = $checkout_info['acceptance']?->assignedTo;
$this->acceptance = $checkout_info['acceptance'];
$this->target = $checkout_info?->assignedTo;
$this->acceptance = $checkout_info;
}
/**

View File

@@ -90,12 +90,6 @@ class AssetMaintenancesPresenter extends Presenter
'searchable' => true,
'sortable' => true,
'title' => trans('admin/asset_maintenances/form.asset_maintenance_type'),
], [
'field' => 'title',
'searchable' => true,
'sortable' => true,
'switchable' => false,
'title' => trans('admin/asset_maintenances/form.title'),
], [
'field' => 'start_date',
'searchable' => true,

View File

@@ -105,4 +105,17 @@ $config = [
// (by default, the PUBLIC_FILESYSTEM DISK is 'local_public', in the public/uploads directory)
$config['disks']['public'] = $config['disks'][env('PUBLIC_FILESYSTEM_DISK','local_public')];
// Handle the 'backup' disk for S3 consideration
if (env('PRIVATE_FILESYSTEM_DISK') == 's3_private') {
$config['disks']['backup']['driver'] = 's3';
$config['disks']['backup']['key'] = $config['disks']['s3_private']['key'];
$config['disks']['backup']['secret'] = $config['disks']['s3_private']['secret'];
$config['disks']['backup']['region'] = $config['disks']['s3_private']['region'];
$config['disks']['backup']['bucket'] = $config['disks']['s3_private']['bucket'];
$config['disks']['backup']['url'] = $config['disks']['s3_private']['url'];
$config['disks']['backup']['root'] = $config['disks']['s3_private']['root'];
$config['disks']['backup']['visibility'] = 'private';
}
return $config;

View File

@@ -27,6 +27,10 @@ class CheckoutAcceptanceFactory extends Factory
public function configure(): static
{
return $this->afterCreating(function (CheckoutAcceptance $acceptance) {
if ($acceptance->checkoutable instanceof Asset) {
$this->createdAssociatedActionLogEntry($acceptance);
}
if ($acceptance->checkoutable instanceof Asset && $acceptance->assignedTo instanceof User) {
$acceptance->checkoutable->update([
'assigned_to' => $acceptance->assigned_to_id,
@@ -51,4 +55,23 @@ class CheckoutAcceptanceFactory extends Factory
'declined_at' => null,
]);
}
public function accepted()
{
return $this->state([
'accepted_at' => now()->subDay(),
'declined_at' => null,
]);
}
private function createdAssociatedActionLogEntry(CheckoutAcceptance $acceptance): void
{
$acceptance->checkoutable->assetlog()->create([
'action_type' => 'checkout',
'target_id' => $acceptance->assigned_to_id,
'target_type' => get_class($acceptance->assignedTo),
'item_id' => $acceptance->checkoutable_id,
'item_type' => $acceptance->checkoutable_type,
]);
}
}

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('settings', function (Blueprint $table) {
$table->boolean('ldap_invert_active_flag')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn('ldap_invert_active_flag');
});
}
};

View File

@@ -13,4 +13,8 @@ return [
'no_depreciations_warning' => '<strong>Warning: </strong>
You do not currently have any depreciations set up.
Please set up at least one depreciation to view the depreciation report.',
'depreciation_method' => 'Depreciation Method',
'linear_depreciation' => 'Linear (Default)',
'half_1' => 'Half-year convention, always applied',
'half_2' => 'Half-year convention, applied with condition',
];

View File

@@ -66,6 +66,7 @@ return [
'file_already_deleted' => 'The file selected was already deleted',
'header_row_has_malformed_characters' => 'One or more attributes in the header row contain malformed UTF-8 characters',
'content_row_has_malformed_characters' => 'One or more attributes in the first row of content contain malformed UTF-8 characters',
'transliterate_failure' => 'Transliteration from :encoding to UTF-8 failed due to invalid characters in input'
],

View File

@@ -4,6 +4,7 @@ return [
'info' => 'Select the options you want for your asset report.',
'deleted_user' => 'Deleted user',
'send_reminder' => 'Send reminder',
'cannot_send_reminder' => 'User has been deleted or does not have an email address so cannot receive a reminder',
'reminder_sent' => 'Reminder sent',
'acceptance_deleted' => 'Acceptance request deleted',
'acceptance_request' => 'Acceptance request',

View File

@@ -42,6 +42,7 @@ return [
'confirm_purge' => 'Confirm Purge',
'confirm_purge_help' => 'Enter the text "DELETE" in the box below to purge your deleted records. This action cannot be undone and will PERMANENTLY delete all soft-deleted items and users. (You should make a backup first, just to be safe.)',
'custom_css' => 'Custom CSS',
'custom_css_placeholder' => 'Add your custom CSS',
'custom_css_help' => 'Enter any custom CSS overrides you would like to use. Do not include the &lt;style&gt;&lt;/style&gt; tags.',
'custom_forgot_pass_url' => 'Custom Password Reset URL',
'custom_forgot_pass_url_help' => 'This replaces the built-in forgotten password URL on the login screen, useful to direct people to internal or hosted LDAP password reset functionality. It will effectively disable local user forgotten password functionality.',
@@ -69,6 +70,7 @@ return [
'favicon_size' => 'Favicons should be square images, 16x16 pixels.',
'footer_text' => 'Additional Footer Text ',
'footer_text_help' => 'This text will appear in the right-side footer. Links are allowed using <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>. Line breaks, headers, images, etc may result in unpredictable results.',
'footer_text_placeholder' => 'Optional footer text',
'general_settings' => 'General Settings',
'general_settings_help' => 'Default EULA and more',
'generate_backup' => 'Generate Backup',
@@ -118,6 +120,8 @@ return [
'ldap_version' => 'LDAP Version',
'ldap_active_flag' => 'LDAP Active Flag',
'ldap_activated_flag_help' => 'This value is used to determine whether a synced user can login to Snipe-IT. <strong>It does not affect the ability to check items in or out to them</strong>, and should be the <strong>attribute name</strong> within your AD/LDAP, <strong>not the value</strong>. <br><br>If this field is set to a field name that does not exist in your AD/LDAP, or the value in the AD/LDAP field is set to <code>0</code> or <code>false</code>, <strong>user login will be disabled</strong>. If the value in the AD/LDAP field is set to <code>1</code> or <code>true</code> or <em>any other text</em> means the user can log in. When the field is blank in your AD, we respect the <code>userAccountControl</code> attribute, which usually allows non-suspended users to log in.',
'ldap_invert_active_flag' => 'LDAP Invert Active Flag',
'ldap_invert_active_flag_help' => 'If enabled: when the value returned by LDAP Active Flag is <code>0</code> or <code>false</code> the user account will be active.',
'ldap_emp_num' => 'LDAP Employee Number',
'ldap_email' => 'LDAP Email',
'ldap_test' => 'Test LDAP',
@@ -132,6 +136,7 @@ return [
'login_user_agent' => 'User Agent',
'login_help' => 'List of attempted logins',
'login_note' => 'Login Note',
'login_note_placeholder' => "If you do not have a login or have found a device belonging to this company, please call technical support at 888-555-1212. Thank you.",
'login_note_help' => 'Optionally include a few sentences on your login screen, for example to assist people who have found a lost or stolen device. This field accepts <a href="https://help.github.com/articles/github-flavored-markdown/">Github flavored markdown</a>',
'login_remote_user_text' => 'Remote User login options',
'login_remote_user_enabled_text' => 'Enable Login with Remote User Header',
@@ -345,6 +350,8 @@ return [
'setup_migration_create_user' => 'Next: Create User',
'ldap_settings_link' => 'LDAP Settings Page',
'slack_test' => 'Test <i class="fab fa-slack"></i> Integration',
'status_label_name' => 'Status Label Name',
'super_admin_only' => 'Super Admin Only',
'label2_enable' => 'New Label Engine',
'label2_enable_help' => 'Switch to the new label engine. <b>Note: You will need to save this setting before setting others.</b>',
'label2_template' => 'Template',
@@ -378,6 +385,7 @@ return [
'database_driver' => 'Database Driver',
'bs_table_storage' => 'Table Storage',
'timezone' => 'Timezone',
'test_mail' => 'Test Mail',
'profile_edit' => 'Edit Profile',
'profile_edit_help' => 'Allow users to edit their own profiles.',
'default_avatar' => 'Upload custom default avatar',
@@ -387,6 +395,15 @@ return [
'due_checkin_days' => 'Due For Checkin Warning',
'due_checkin_days_help' => 'How many days before the expected checkin of an asset should it be listed in the "Due for checkin" page?',
'no_groups' => 'No groups have been created yet. Visit <code>Admin Settings > Permission Groups</code> to add one.',
'text' => 'Text',
'logo_option_types' => [
'text' => 'Text',
'logo' => 'Logo',
'logo_and_text' => 'Logo and Text',
],
/* Keywords for settings overview help */

View File

@@ -599,5 +599,6 @@ return [
'generic_model_not_found' => 'That :model was not found or you do not have permission to access it',
'deleted_models' => 'Deleted Asset Models',
'deleted_users' => 'Deleted Users',
'cost_each' => ':amount each',
];

View File

@@ -26,9 +26,9 @@
<livewire:category-edit-form
:default-eula-text="$snipeSettings->default_eula_text"
:eula-text="old('eula_text', $item->eula_text)"
:require-acceptance="old('require_acceptance', $item->require_acceptance)"
:send-check-in-email="old('checkin_email', $item->checkin_email)"
:use-default-eula="old('use_default_eula', $item->use_default_eula)"
:require-acceptance="(bool) old('require_acceptance', $item->require_acceptance)"
:send-check-in-email="(bool) old('checkin_email', $item->checkin_email)"
:use-default-eula="(bool) old('use_default_eula', $item->use_default_eula)"
/>
@include ('partials.forms.edit.image-upload', ['image_path' => app('categories_upload_path')])

View File

@@ -1211,7 +1211,7 @@
<a href="{{ route('components.show', $component->id) }}">{{ $component->name }}</a>
</td>
<td>{{ $component->pivot->assigned_qty }}</td>
<td>{{ Helper::formatCurrencyOutput($component->purchase_cost) }} each</td>
<td>{{ trans('general.cost_each', ['amount' => Helper::formatCurrencyOutput($component->purchase_cost)]) }} </td>
<td>{{ $component->serial }}</td>
<td>
<a href="{{ route('components.checkin.show', $component->pivot->id) }}" class="btn btn-sm bg-purple hidden-print" data-tooltip="true">{{ trans('general.checkin') }}</a>

View File

@@ -23,12 +23,25 @@
<div class="col-md-9 col-md-offset-3">
@if ($defaultEulaText!='')
<label class="form-control">
{{ Form::checkbox('use_default_eula', '1', $useDefaultEula, ['wire:model.live' => 'useDefaultEula', 'aria-label'=>'use_default_eula']) }}
<input
type="checkbox"
name="use_default_eula"
value="1"
wire:model.live="useDefaultEula"
aria-label="use_default_eula"
/>
<span>{!! trans('admin/categories/general.use_default_eula') !!}</span>
</label>
@else
<label class="form-control form-control--disabled">
{{ Form::checkbox('use_default_eula', '0', $useDefaultEula, ['wire:model.live' => 'useDefaultEula', 'class'=>'disabled','disabled' => 'disabled', 'aria-label'=>'use_default_eula']) }}
<input
type="checkbox"
name="use_default_eula"
value="0"
wire:model.live="useDefaultEula"
aria-label="use_default_eula"
disabled
/>
<span>{!! trans('admin/categories/general.use_default_eula_disabled') !!}</span>
</label>
@endif
@@ -39,7 +52,13 @@
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
{{ Form::checkbox('require_acceptance', '1', $requireAcceptance, ['wire:model.live' => 'requireAcceptance', 'aria-label'=>'require_acceptance']) }}
<input
type="checkbox"
name="require_acceptance"
value="1"
wire:model.live="requireAcceptance"
aria-label="require_acceptance"
/>
{{ trans('admin/categories/general.require_acceptance') }}
</label>
</div>
@@ -49,7 +68,14 @@
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
{{ Form::checkbox('checkin_email', '1', $sendCheckInEmail, ['wire:model.live' => 'sendCheckInEmail', 'aria-label'=>'checkin_email', 'disabled' => $this->sendCheckInEmailDisabled]) }}
<input
type="checkbox"
name="checkin_email"
value="1"
wire:model.live="sendCheckInEmail"
aria-label="checkin_email"
@disabled($this->sendCheckInEmailDisabled)
/>
{{ trans('admin/categories/general.checkin_email') }}
</label>
@if ($this->shouldDisplayEmailMessage)

View File

@@ -10,8 +10,15 @@
<div class="col-md-3">
@if ($fieldset_id)
<label class="form-control">
{{ Form::checkbox('add_default_values', 1, old('add_default_values', $add_default_values), ['data-livewire-component' => $this->getId(), 'id' => 'add_default_values', 'wire:model.live' => 'add_default_values', 'disabled' => $this->fields->isEmpty()]) }}
<input
type="checkbox"
name="add_default_values"
value="1"
id="add_default_values"
wire:model.live="add_default_values"
data-livewire-component="{{ $this->getId() }}"
@disabled($this->fields->isEmpty())
/>
{{ trans('admin/models/general.add_default_values') }}
</label>
@endif

View File

@@ -29,7 +29,16 @@
data_export_options = $(this).attr('data-export-options');
export_options = data_export_options ? JSON.parse(data_export_options) : {};
export_options['htmlContent'] = false; // this is already the default; but let's be explicit about it
export_options['jspdf']= {"orientation": "l"};
export_options['jspdf'] = {
"orientation": "l",
"autotable": {
"styles": {
overflow: 'linebreak'
},
tableWidth: 'wrap'
}
};
// tableWidth: 'wrap',
// the following callback method is necessary to prevent XSS vulnerabilities
// (this is taken from Bootstrap Tables's default wrapper around jQuery Table Export)
export_options['onCellHtmlData'] = function (cell, rowIndex, colIndex, htmlData) {

View File

@@ -1,7 +1,7 @@
<div class="form-group {{ $errors->has('email') ? ' has-error' : '' }}">
<label for="email" class="col-md-3 col-xs-12 control-label">{{ trans('admin/suppliers/table.email') }}</label>
<div class="col-md-8 col-xs-12">
<input type="text" name="email" id="email" value="{{ old('email', ($item->email ?? $user->email)) }}" class="form-control" maxlength="191" style="width:100%; display:flex;">
<input type="text" name="email" id="email" value="{{ old('email', $item->email) }}" class="form-control" maxlength="191" style="width:100%; display:flex;">
{!! $errors->first('email', '<span class="alert-msg" aria-hidden="true"><i class="fas fa-times" aria-hidden="true"></i> :message</span>') !!}
</div>
</div>
</div>

View File

@@ -68,7 +68,7 @@
@foreach ($assetsForReport as $item)
@if ($item['assetItem'])
<tr @if($item['acceptance']->trashed()) style="text-decoration: line-through" @endif>
<td>{{ $item['acceptance']->created_at }}</td>
<td>{{ Helper::getFormattedDateObject($item['acceptance']->created_at, 'datetime', false) }}</td>
<td>{{ ($item['assetItem']->company) ? $item['assetItem']->company->name : '' }}</td>
<td>{!! $item['assetItem']->model->category->present()->nameUrl() !!}</td>
<td>{!! $item['assetItem']->present()->modelUrl() !!}</td>
@@ -79,13 +79,18 @@
<nobr>
@if(!$item['acceptance']->trashed())
<form method="post" class="white-space: nowrap;" action="{{ route('reports/unaccepted_assets_sent_reminder') }}">
@if ($item['acceptance']->assignedTo)
@if (($item['acceptance']->assignedTo) && ($item['acceptance']->assignedTo->email))
@csrf
<input type="hidden" name="acceptance_id" value="{{ $item['acceptance']->id }}">
<button class="btn btn-sm btn-warning" data-tooltip="true" data-title="{{ trans('admin/reports/general.send_reminder') }}">
<i class="fa fa-repeat" aria-hidden="true"></i>
</button>
@else
<span data-tooltip="true" data-title="{{ trans('admin/reports/general.cannot_send_reminder') }}">
<a class="btn btn-sm btn-warning disabled" href="#">
<i class="fa fa-repeat" aria-hidden="true"></i>
</a>
</span>
@endif
<a href="{{ route('reports/unaccepted_assets_delete', ['acceptanceId' => $item['acceptance']->id]) }}" class="btn btn-sm btn-danger delete-asset" data-tooltip="true" data-toggle="modal" data-content="{{ trans('general.delete_confirm', ['item' =>trans('admin/reports/general.acceptance_request')]) }}" data-title="{{ trans('general.delete') }}" onClick="return false;"><i class="fa fa-trash"></i></a>
</form>

View File

@@ -68,7 +68,9 @@
</div>
</div>
@php
$optionTypes = trans('admin/settings/general.logo_option_types');
@endphp
<!-- Branding -->
<div class="form-group {{ $errors->has('brand') ? 'error' : '' }}">
@@ -76,8 +78,13 @@
<label for="brand">{{ trans('admin/settings/general.web_brand') }}</label>
</div>
<div class="col-md-9">
{!! Form::select('brand', array('1'=>'Text','2'=>'Logo','3'=>'Logo + Text'), old('brand', $setting->brand), array('class' => 'form-control select2', 'style'=>'width: 150px ;')) !!}
{!! $errors->first('brand', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
<select name="brand" id="brand" class="form-control select2 minimumResultsForSearch" style="width: 150px;">
@foreach($optionTypes as $value => $label)
<option value="{{ $value }}" {{ old('brand', $setting->brand) == $value ? 'selected' : '' }}>
{{ $label }}
</option>
@endforeach
</select>
</div>
</div>
@@ -135,7 +142,7 @@
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
{{ Form::checkbox('restore_default_avatar', '1', old('restore_default_avatar', $setting->restore_default_avatar)) }}
<input type="checkbox" name="restore_default_avatar" value="1" @checked(old('restore_default_avatar', $setting->restore_default_avatar)) />
<span>{!! trans('admin/settings/general.restore_default_avatar', ['default_avatar'=> Storage::disk('public')->url('default.png')]) !!}</span>
</label>
<p class="help-block">
@@ -152,7 +159,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('load_remote', '1', old('load_remote', $setting->load_remote)) }}
<input type="checkbox" name="load_remote" value="1" @checked(old('load_remote', $setting->load_remote)) />
{{ trans('general.yes') }}
{!! $errors->first('load_remote', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</label>
@@ -172,7 +179,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('logo_print_assets', '1', old('logo_print_assets', $setting->logo_print_assets),array('aria-label'=>'logo_print_assets')) }}
<input type="checkbox" name="logo_print_assets" value="1" @checked(old('logo_print_assets', $setting->logo_print_assets)) aria-label="logo_print_assets"/>
{{ trans('admin/settings/general.logo_print_assets_help') }}
</label>
@@ -187,7 +194,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('show_url_in_emails', '1', old('show_url_in_emails', $setting->show_url_in_emails),array('aria-label'=>'show_url_in_emails')) }}
<input type="checkbox" name="show_url_in_emails" value="1" @checked(old('show_url_in_emails', $setting->show_url_in_emails)) aria-label="show_url_in_emails" />
{{ trans('general.yes') }}
</label>
<p class="help-block">{{ trans('admin/settings/general.show_url_in_emails_help_text') }}</p>
@@ -228,7 +235,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('allow_user_skin', '1', old('allow_user_skin', $setting->allow_user_skin)) }}
<input type="checkbox" name="allow_user_skin" value="1" @checked(old('allow_user_skin', $setting->allow_user_skin))/>
{{ trans('general.yes') }}
</label>
<p class="help-block">{{ trans('admin/settings/general.allow_user_skin_help_text') }}</p>
@@ -272,10 +279,10 @@
</div>
<div class="col-md-9">
@if (config('app.lock_passwords')===true)
{!! Form::select('support_footer', array('on'=>'Enabled','off'=>'Disabled','admin'=>'Superadmin Only'), old('support_footer', $setting->support_footer), ['class' => 'form-control select2 disabled', 'style'=>'width: 150px ;', 'disabled' => 'disabled']) !!}
{!! Form::select('support_footer', array('on'=>trans('admin/settings/general.enabled'),'off'=>trans('admin/settings/general.two_factor_disabled'),'admin'=>trans('admin/settings/general.super_admin_only')), old('support_footer', $setting->support_footer), ['class' => 'form-control select2 disabled', 'style'=>'width: 150px ;', 'disabled' => 'disabled']) !!}
<p class="text-warning"><i class="fas fa-lock"></i> {{ trans('general.feature_disabled') }}</p>
@else
{!! Form::select('support_footer', array('on'=>'Enabled','off'=>'Disabled','admin'=>'Superadmin Only'), old('support_footer', $setting->support_footer), array('class' => 'form-control select2', 'style'=>'width: 150px ;')) !!}
{!! Form::select('support_footer', array('on'=>trans('admin/settings/general.enabled'),'off'=>trans('admin/settings/general.two_factor_disabled'),'admin'=>trans('admin/settings/general.super_admin_only')), old('support_footer', $setting->support_footer), array('class' => 'form-control select2', 'style'=>'width: 150px ;')) !!}
@endif
@@ -291,10 +298,10 @@
</div>
<div class="col-md-9">
@if (config('app.lock_passwords')===true)
{!! Form::select('version_footer', array('on'=>'Enabled','off'=>'Disabled','admin'=>'Superadmin Only'), old('version_footer', $setting->version_footer), ['class' => 'form-control select2 disabled', 'style'=>'width: 150px ;', 'disabled' => 'disabled']) !!}
{!! Form::select('version_footer', array('on'=>trans('admin/settings/general.enabled'),'off'=>trans('admin/settings/general.two_factor_disabled'),'admin'=>trans('admin/settings/general.super_admin_only')), old('version_footer', $setting->version_footer), ['class' => 'form-control select2 disabled', 'style'=>'width: 150px ;', 'disabled' => 'disabled']) !!}
<p class="text-warning"><i class="fas fa-lock"></i> {{ trans('general.feature_disabled') }}</p>
@else
{!! Form::select('version_footer', array('on'=>'Enabled','off'=>'Disabled','admin'=>'Superadmin Only'), old('version_footer', $setting->version_footer), array('class' => 'form-control select2', 'style'=>'width: 150px ;')) !!}
{!! Form::select('version_footer', array('on'=>trans('admin/settings/general.enabled'),'off'=>trans('admin/settings/general.two_factor_disabled'),'admin'=>trans('admin/settings/general.super_admin_only')), old('version_footer', $setting->version_footer), array('class' => 'form-control select2', 'style'=>'width: 150px ;')) !!}
@endif
<p class="help-block">{{ trans('admin/settings/general.version_footer_help') }}</p>

View File

@@ -45,7 +45,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('full_multiple_companies_support', '1', old('full_multiple_companies_support', $setting->full_multiple_companies_support),array('aria-label'=>'full_multiple_companies_support')) }}
<input type="checkbox" name="full_multiple_companies_support" value="1" @checked(old('full_multiple_companies_support', $setting->full_multiple_companies_support)) aria-label="full_multiple_companies_support" />
{{ trans('admin/settings/general.full_multiple_companies_support_text') }}
</label>
{!! $errors->first('full_multiple_companies_support', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
@@ -64,7 +64,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('require_accept_signature', '1', old('require_accept_signature', $setting->require_accept_signature)) }}
<input type="checkbox" name="require_accept_signature" value="1" @checked(old('require_accept_signature', $setting->require_accept_signature)) />
{{ trans('general.yes') }}
</label>
{!! $errors->first('require_accept_signature', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
@@ -136,7 +136,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('show_images_in_email', '1', old('show_images_in_email', $setting->show_images_in_email)) }}
<input type="checkbox" name="show_images_in_email" value="1" @checked(old('show_images_in_email', $setting->show_images_in_email)) />
{{ trans('general.yes') }}
{!! $errors->first('show_images_in_email', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</label>
@@ -152,7 +152,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('unique_serial', '1', old('unique_serial', $setting->unique_serial),array('class' => 'minimal')) }}
<input type="checkbox" name="unique_serial" value="1" @checked(old('unique_serial', $setting->unique_serial)) />
{{ trans('general.yes') }}
{!! $errors->first('unique_serial', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
</label>
@@ -245,11 +245,11 @@
<div class="col-md-9">
@if (config('app.lock_passwords'))
<textarea class="form-control disabled" name="login_note" placeholder="If you do not have a login or have found a device belonging to this company, please call technical support at 888-555-1212. Thank you." rows="2" aria-label="login_note" readonly>{{ old('login_note', $setting->login_note) }}</textarea>
<textarea class="form-control disabled" name="login_note" placeholder="{{trans('admin/settings/general.login_note_placeholder')}}" rows="2" aria-label="login_note" readonly>{{ old('login_note', $setting->login_note) }}</textarea>
{!! $errors->first('login_note', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
<p class="text-warning"><i class="fas fa-lock"></i> {{ trans('general.feature_disabled') }}</p>
@else
<textarea class="form-control" name="login_note" aria-label="login_note" placeholder="If you do not have a login or have found a device belonging to this company, please call technical support at 888-555-1212. Thank you." rows="2">{{ old('login_note', $setting->login_note) }}</textarea>
<textarea class="form-control" name="login_note" aria-label="login_note" placeholder="{{trans('admin/settings/general.login_note_placeholder')}}" rows="2">{{ old('login_note', $setting->login_note) }}</textarea>
{!! $errors->first('login_note', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
@endif
<p class="help-block">{!! trans('admin/settings/general.login_note_help') !!}</p>
@@ -259,7 +259,7 @@
<!-- Mail test -->
<div class="form-group">
<div class="col-md-3">
<label for="login_note">Test Mail</label>
<label for="login_note">{{trans('admin/settings/general.test_mail')}}</label>
</div>
<div class="col-md-9" id="mailtestrow">
<a class="btn btn-default btn-sm pull-left" id="mailtest" style="margin-right: 10px;">
@@ -311,7 +311,7 @@
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('show_archived_in_list', '1', old('show_archived_in_list', $setting->show_archived_in_list),array('aria-label'=>'show_archived_in_list')) }}
<input type="checkbox" name="show_archived_in_list" value="1" @checked(old('show_archived_in_list', $setting->show_archived_in_list)) aria-label="show_archived_in_list" />
{{ trans('admin/settings/general.show_archived_in_list_text') }}
</label>
{!! $errors->first('show_archived_in_list', '<span class="alert-msg" aria-hidden="true">:message</span>') !!}
@@ -325,7 +325,7 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('show_assigned_assets', '1', old('show_assigned_assets', $setting->show_assigned_assets),array('class' => 'minimal')) }}
<input type="checkbox" name="show_assigned_assets" value="1" @checked(old('show_assigned_assets', $setting->show_assigned_assets)) />
{{ trans('general.yes') }}
</label>
<p class="help-block">{{ trans('admin/settings/general.show_assigned_assets_help') }}</p>
@@ -340,15 +340,19 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('show_in_model_list[]', 'image', old('show_in_model_list', $snipeSettings->modellistCheckedValue('image')),array('class' => 'minimal', 'aria-label'=>'show_in_model_list' )) }} {{ trans('general.image') }}
<input type="checkbox" name="show_in_model_list[]" value="image" @checked(old('show_in_model_list', $snipeSettings->modellistCheckedValue('image'))) aria-label="show_in_model_list"/>
{{ trans('general.image') }}
</label>
<label class="form-control">
{{ Form::checkbox('show_in_model_list[]', 'category', old('show_in_model_list', $snipeSettings->modellistCheckedValue('category')),array('class' => 'minimal', 'aria-label'=>'show_in_model_list' )) }} {{ trans('general.category') }}
<input type="checkbox" name="show_in_model_list[]" value="category" @checked(old('show_in_model_list', $snipeSettings->modellistCheckedValue('category'))) aria-label="show_in_model_list"/>
{{ trans('general.category') }}
</label>
<label class="form-control">
{{ Form::checkbox('show_in_model_list[]', 'manufacturer', old('show_in_model_list', $snipeSettings->modellistCheckedValue('manufacturer')),array('class' => 'minimal', 'aria-label'=>'show_in_model_list' )) }} {{ trans('general.manufacturer') }} </label>
<input type="checkbox" name="show_in_model_list[]" value="manufacturer" @checked(old('show_in_model_list', $snipeSettings->modellistCheckedValue('manufacturer'))) aria-label="show_in_model_list"/>
{{ trans('general.manufacturer') }} </label>
<label class="form-control">
{{ Form::checkbox('show_in_model_list[]', 'model_number', old('show_in_model_list', $snipeSettings->modellistCheckedValue('model_number')),array('class' => 'minimal', 'aria-label'=>'show_in_model_list' )) }} {{ trans('general.model_no') }}
<input type="checkbox" name="show_in_model_list[]" value="model_number" @checked(old('show_in_model_list', $snipeSettings->modellistCheckedValue('model_number'))) aria-label="show_in_model_list"/>
{{ trans('general.model_no') }}
</label>
</div>
</div>
@@ -370,13 +374,13 @@
<!-- Depreciation method -->
<div class="form-group {{ $errors->has('depreciation_method') ? 'error' : '' }}">
<div class="col-md-3">
<label for="depreciation_method">{{ trans('Depreciation method') }}</label>
<label for="depreciation_method">{{ trans('admin/depreciations/general.depreciation_method') }}</label>
</div>
<div class="col-md-9">
{{ Form::select('depreciation_method', array(
'default' => 'Linear (default)',
'half_1' => 'Half-year convention, always applied',
'half_2' => 'Half-year convention, applied with condition',
'default' => trans('admin/depreciations/general.linear_depreciation'),
'half_1' => trans('admin/depreciations/general.half_1'),
'half_2' => trans('admin/depreciations/general.half_2'),
), old('username_format', $setting->depreciation_method), ['class' =>'select2', 'style' => 'width: 80%']) }}
</div>
</div>

View File

@@ -144,7 +144,7 @@
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
{{ Form::checkbox('alt_barcode_enabled', '1', old('alt_barcode_enabled', $setting->alt_barcode_enabled),array( 'aria-label'=>'alt_barcode_enabled')) }}
<input type="checkbox" name="alt_barcode_enabled" value="1" @checked(old('alt_barcode_enabled', $setting->alt_barcode_enabled)) aria-label="alt_barcode_enabled"/>
{{ trans('admin/settings/general.display_alt_barcode') }}
</label>
</div>
@@ -189,7 +189,7 @@
<div class="form-group">
<div class="col-md-9 col-md-offset-3">
<label class="form-control">
{{ Form::checkbox('qr_code', '1', old('qr_code', $setting->qr_code),array('aria-label'=>'qr_code')) }}
<input type="checkbox" name="qr_code" value="1" @checked(old('qr_code', $setting->qr_code)) aria-label="qr_code" />
{{ trans('admin/settings/general.display_qr') }}
</label>
</div>
@@ -480,23 +480,23 @@
</div>
<div class="col-md-9">
<label class="form-control">
{{ Form::checkbox('labels_display_name', '1', old('labels_display_name', $setting->labels_display_name),['class' => 'minimal', 'aria-label'=>'labels_display_name']) }}
<input type="checkbox" name="labels_display_name" value="1" @checked(old('labels_display_name', $setting->labels_display_name)) aria-label="labels_display_name" />
{{ trans('admin/hardware/form.name') }}
</label>
<label class="form-control">
{{ Form::checkbox('labels_display_serial', '1', old('labels_display_serial', $setting->labels_display_serial),['class' => 'minimal', 'aria-label'=>'labels_display_serial']) }}
<input type="checkbox" name="labels_display_serial" value="1" @checked(old('labels_display_serial', $setting->labels_display_serial)) aria-label="labels_display_serial" />
{{ trans('admin/hardware/form.serial') }}
</label>
<label class="form-control">
{{ Form::checkbox('labels_display_tag', '1', old('labels_display_tag', $setting->labels_display_tag),['class' => 'minimal', 'aria-label'=>'labels_display_tag']) }}
<input type="checkbox" name="labels_display_tag" value="1" @checked(old('labels_display_tag', $setting->labels_display_tag)) aria-label="labels_display_tag" />
{{ trans('admin/hardware/form.tag') }}
</label>
<label class="form-control">
{{ Form::checkbox('labels_display_model', '1', old('labels_display_model', $setting->labels_display_model),['class' => 'minimal', 'aria-label'=>'labels_display_model']) }}
<input type="checkbox" name="labels_display_model" value="1" @checked(old('labels_display_model', $setting->labels_display_model)) aria-label="labels_display_model" />
{{ trans('admin/hardware/form.model') }}
</label>
<label class="form-control">
{{ Form::checkbox('labels_display_company_name', '1', old('labels_display_company_name', $setting->labels_display_company_name),['class' => 'minimal', 'aria-label'=>'labels_display_company_name']) }}
<input type="checkbox" name="labels_display_company_name" value="1" @checked(old('labels_display_company_name', $setting->labels_display_company_name)) aria-label="labels_display_company_name"/>
{{ trans('admin/companies/table.name') }}
</label>
</div> <!--/.col-md-9-->

View File

@@ -554,6 +554,32 @@
</div>
</div>
<!-- LDAP invert active flag -->
<div class="form-group">
<div class="col-md-3">
{{ Form::label('ldap_invert_active_flag', trans('admin/settings/general.ldap_invert_active_flag')) }}
</div>
<div class="col-md-8">
<label class="form-control">
<input type="checkbox" name="ldap_invert_active_flag" value="1" id="ldap_invert_active_flag" @checked(old('ldap_invert_active_flag', $setting->ldap_invert_active_flag)) />
<p class="help-block">{!! trans('admin/settings/general.ldap_invert_active_flag_help') !!}</p>
</label>
@error('ldap_invert_active_flag')
<span class="alert-msg">
<x-icon type="x" />
{{ $message }}
</span>
@enderror
@if (config('app.lock_passwords')===true)
<p class="text-warning">
<x-icon type="locked" />
{{ trans('general.feature_disabled') }}
</p>
@endif
</div>
</div>
<!-- LDAP emp number -->
<div class="form-group {{ $errors->has('ldap_emp_num') ? 'error' : '' }}">
<div class="col-md-3">

View File

@@ -45,4 +45,24 @@ class ImportTest extends TestCase
]);
$this->assertEquals($evil_string, $results->json()['files'][0]['first_row'][0]);
}
public function testStoreInternationalAssetMisparse(): void
{
$evil_maker = function ($arr) {
$results = '';
foreach ($arr as $thing) {
$results .= chr($thing);
}
return $results;
};
// 0xC0 makes it 'not unicode', and 0xFF makes it 'likely WINDOWS-1251', and 0x98 at the end makes it 'not-valid-Windows-1251'
$evil_content = $evil_maker([0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x98]);
$this->actingAsForApi(User::factory()->superuser()->create());
$results = $this->post(route('api.imports.store'), ['files' => [UploadedFile::fake()->createWithContent("myname.csv", $evil_content)]])
->assertStatus(422)
->assertStatusMessageIs('error')
->assertMessagesAre(trans('admin/hardware/message.import.transliterate_failure', ["encoding" => "windows-1251"]));
}
}

View File

@@ -10,7 +10,10 @@ class CategoryEditFormTest extends TestCase
{
public function testTheComponentCanRender()
{
Livewire::test(CategoryEditForm::class)->assertStatus(200);
Livewire::test(CategoryEditForm::class, [
'sendCheckInEmail' => true,
'useDefaultEula' => true,
])->assertStatus(200);
}
public function testSendEmailCheckboxIsCheckedOnLoadWhenSendEmailIsExistingSetting()

View File

@@ -0,0 +1,108 @@
<?php
namespace Tests\Feature\Notifications\Email;
use App\Mail\CheckoutAssetMail;
use App\Models\CheckoutAcceptance;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class AssetAcceptanceReminderTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Mail::fake();
}
public function testMustHavePermissionToSendReminder()
{
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
$userWithoutPermission = User::factory()->create();
$this->actingAs($userWithoutPermission)
->post($this->routeFor($checkoutAcceptance))
->assertForbidden();
Mail::assertNotSent(CheckoutAssetMail::class);
}
public function testReminderNotSentIfAcceptanceDoesNotExist()
{
$this->actingAs(User::factory()->canViewReports()->create())
->post(route('reports/unaccepted_assets_sent_reminder', [
'acceptance_id' => 999999,
]));
Mail::assertNotSent(CheckoutAssetMail::class);
}
public function testReminderNotSentIfAcceptanceAlreadyAccepted()
{
$checkoutAcceptanceAlreadyAccepted = CheckoutAcceptance::factory()->accepted()->create();
$this->actingAs(User::factory()->canViewReports()->create())
->post($this->routeFor($checkoutAcceptanceAlreadyAccepted));
Mail::assertNotSent(CheckoutAssetMail::class);
}
public static function CheckoutAcceptancesToUsersWithoutEmailAddresses()
{
yield 'User with null email address' => [
function () {
return CheckoutAcceptance::factory()
->pending()
->forAssignedTo(['email' => null])
->create();
}
];
yield 'User with empty string email address' => [
function () {
return CheckoutAcceptance::factory()
->pending()
->forAssignedTo(['email' => ''])
->create();
}
];
}
#[DataProvider('CheckoutAcceptancesToUsersWithoutEmailAddresses')]
public function testUserWithoutEmailAddressHandledGracefully($callback)
{
$checkoutAcceptance = $callback();
$this->actingAs(User::factory()->canViewReports()->create())
->post($this->routeFor($checkoutAcceptance))
// check we didn't crash...
->assertRedirect();
Mail::assertNotSent(CheckoutAssetMail::class);
}
public function testReminderIsSentToUser()
{
$checkoutAcceptance = CheckoutAcceptance::factory()->pending()->create();
$this->actingAs(User::factory()->canViewReports()->create())
->post($this->routeFor($checkoutAcceptance))
->assertRedirect(route('reports/unaccepted_assets'));
Mail::assertSent(CheckoutAssetMail::class, 1);
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) use ($checkoutAcceptance) {
return $mail->hasTo($checkoutAcceptance->assignedTo->email)
&& $mail->hasSubject(trans('mail.unaccepted_asset_reminder'));
});
}
private function routeFor(CheckoutAcceptance $checkoutAcceptance): string
{
return route('reports/unaccepted_assets_sent_reminder', [
'acceptance_id' => $checkoutAcceptance->id,
]);
}
}

View File

@@ -4,6 +4,7 @@ namespace Tests\Feature\Notifications\Email;
use App\Mail\ExpiringAssetsMail;
use App\Mail\ExpiringLicenseMail;
use App\Mail\SendUpcomingAuditMail;
use App\Models\Asset;
use App\Models\License;
use App\Models\Setting;
@@ -88,4 +89,38 @@ class ExpiringAlertsNotificationTest extends TestCase
return $mail->licenses->contains($expiredLicense) || $mail->licenses->contains($notExpiringLicense);
});
}
public function testAuditWarningThresholdEmailNotification()
{
$this->markIncompleteIfSqlite();
Mail::fake();
$this->settings->enableAlertEmail('admin@example.com');
$this->settings->setAuditWarningDays(15);
$alert_email = Setting::first()->alert_email;
$upcomingAuditableAsset = Asset::factory()->create([
'next_audit_date' => now()->addDays(14)->format('Y-m-d'),
'deleted_at' => null,
]);
$overDueForAuditableAsset = Asset::factory()->create([
'next_audit_date' => now()->subDays(1)->format('Y-m-d'),
'deleted_at' => null,
]);
$notAuditableAsset = Asset::factory()->create([
'next_audit_date' => now()->addDays(30)->format('Y-m-d'),
'deleted_at' => null,
]);
$this->artisan('snipeit:upcoming-audits')->assertExitCode(0);
Mail::assertSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $upcomingAuditableAsset, $overDueForAuditableAsset) {
return $mail->hasTo($alert_email) && ($mail->assets->contains($upcomingAuditableAsset) && $mail->assets->contains($overDueForAuditableAsset));
});
Mail::assertNotSent(SendUpcomingAuditMail::class, function($mail) use ($alert_email, $notAuditableAsset) {
return $mail->hasTo($alert_email) && $mail->assets->contains($notAuditableAsset);
});
}
}

View File

@@ -32,6 +32,12 @@ class Settings
'alert_threshold' => $days,
]);
}
public function setAuditWarningDays(int $days): Settings
{
return $this->update([
'audit_warning_days' => $days,
]);
}
public function disableAlertEmail(): Settings
{
return $this->update([

View File

@@ -7,9 +7,7 @@ use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
use Carbon\Carbon;
use App\Notifications\CheckoutAssetNotification;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
class NotificationTest extends TestCase
@@ -33,8 +31,8 @@ class NotificationTest extends TestCase
Mail::fake();
$asset->checkOut($user, $admin->id);
Mail::assertSent(CheckoutAssetMail::class, function ($mail) use ($user) {
return $mail->hasTo($user->email);
Mail::assertSent(CheckoutAssetMail::class, function (CheckoutAssetMail $mail) use ($user) {
return $mail->hasTo($user->email) && $mail->hasSubject(trans('mail.Asset_Checkout_Notification'));
});
}
public function testDefaultEulaIsSentWhenSetInCategory()

View File

@@ -261,7 +261,7 @@ if ($env_bad !='') {
if(!$skip_php_checks){
echo "\n\e[95m--------------------------------------------------------\n";
echo "STEP 2: Checking PHP requirements: (Required PHP >=". $php_min_works. " - <".$php_max_wontwork.") \e[39m\n";
echo "STEP 2: Checking PHP requirements: (Required PHP >=". $php_min_works. " - <".$php_max_wontwork.")\n";
echo "--------------------------------------------------------\e[39m\n\n";
if ((version_compare(phpversion(), $php_min_works, '>=')) && (version_compare(phpversion(), $php_max_wontwork, '<'))) {
@@ -540,7 +540,7 @@ echo "--------------------------------------------------------\e[39m\n\n";
exec('php artisan down', $down_results, $return_code);
echo '-- ' . implode("\n", $down_results) . "\n";
if ($return_code > 0) {
die("Something went wrong with downing your site. This can't be good. Please investigate the error. Aborting!n\n");
die("Something went wrong with downing your site. This can't be good. Please investigate the error. Aborting!\n\n");
}
unset($return_code);