diff --git a/.all-contributorsrc b/.all-contributorsrc index 8ea582b9b43e..d6c82c2976e6 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3217,6 +3217,24 @@ "contributions": [ "bug" ] + }, + { + "login": "DarrenRainey", + "name": "Darren Rainey", + "avatar_url": "https://avatars.githubusercontent.com/u/6136439?v=4", + "profile": "https://darrenraineys.co.uk", + "contributions": [ + "code" + ] + }, + { + "login": "maciej-poleszczyk", + "name": "maciej-poleszczyk", + "avatar_url": "https://avatars.githubusercontent.com/u/133033121?v=4", + "profile": "https://github.com/maciej-poleszczyk", + "contributions": [ + "code" + ] } ] } diff --git a/.env.testing.example b/.env.testing.example index 26211f95c3a4..6090cd910f3c 100644 --- a/.env.testing.example +++ b/.env.testing.example @@ -17,3 +17,5 @@ DB_PORT=3306 DB_DATABASE=null DB_USERNAME=null DB_PASSWORD=null + +MAIL_FROM_ADDR=you@example.com diff --git a/.github/workflows/tests-sqlite.yml b/.github/workflows/tests-sqlite.yml index 8bf0115169fb..220194314fc8 100644 --- a/.github/workflows/tests-sqlite.yml +++ b/.github/workflows/tests-sqlite.yml @@ -43,6 +43,9 @@ jobs: cp -v .env.testing.example .env cp -v .env.testing.example .env.testing + - name: Create database file + run: touch database/database.sqlite + - name: Install Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist @@ -57,5 +60,5 @@ jobs: - name: Execute tests (Unit and Feature tests) via PHPUnit env: - DB_CONNECTION: sqlite_testing + DB_CONNECTION: sqlite run: php artisan test diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f821c1f17f81..f9fcd3e5d41c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -52,7 +52,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken | [
bilias](https://github.com/bilias)
[💻](https://github.com/snipe/snipe-it/commits?author=bilias "Code") | [
coach1988](https://github.com/coach1988)
[💻](https://github.com/snipe/snipe-it/commits?author=coach1988 "Code") | [
MrM](https://github.com/mauro-miatello)
[💻](https://github.com/snipe/snipe-it/commits?author=mauro-miatello "Code") | [
koiakoia](https://github.com/koiakoia)
[💻](https://github.com/snipe/snipe-it/commits?author=koiakoia "Code") | [
Mustafa Online](https://github.com/mustafa-online)
[💻](https://github.com/snipe/snipe-it/commits?author=mustafa-online "Code") | [
franceslui](https://github.com/franceslui)
[💻](https://github.com/snipe/snipe-it/commits?author=franceslui "Code") | [
Q4kK](https://github.com/Q4kK)
[💻](https://github.com/snipe/snipe-it/commits?author=Q4kK "Code") | | [
squintfox](https://github.com/squintfox)
[💻](https://github.com/snipe/snipe-it/commits?author=squintfox "Code") | [
Jeff Clay](https://github.com/jeffclay)
[💻](https://github.com/snipe/snipe-it/commits?author=jeffclay "Code") | [
Phil J R](https://github.com/PP-JN-RL)
[💻](https://github.com/snipe/snipe-it/commits?author=PP-JN-RL "Code") | [
i_virus](https://www.corelight.com/)
[💻](https://github.com/snipe/snipe-it/commits?author=chandanchowdhury "Code") | [
Paul Grime](https://github.com/gitgrimbo)
[💻](https://github.com/snipe/snipe-it/commits?author=gitgrimbo "Code") | [
Lee Porte](https://leeporte.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=LeePorte "Code") | [
BRYAN ](https://github.com/bryanlopezinc)
[💻](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Code") [⚠️](https://github.com/snipe/snipe-it/commits?author=bryanlopezinc "Tests") | | [
U-H-T](https://github.com/U-H-T)
[💻](https://github.com/snipe/snipe-it/commits?author=U-H-T "Code") | [
Matt Tyree](https://github.com/Tyree)
[📖](https://github.com/snipe/snipe-it/commits?author=Tyree "Documentation") | [
Florent Bervas](http://spoontux.net)
[💻](https://github.com/snipe/snipe-it/commits?author=FlorentDotMe "Code") | [
Daniel Albertsen](https://ditscheri.com)
[💻](https://github.com/snipe/snipe-it/commits?author=dbakan "Code") | [
r-xyz](https://github.com/r-xyz)
[💻](https://github.com/snipe/snipe-it/commits?author=r-xyz "Code") | [
Steven Mainor](https://github.com/DrekiDegga)
[💻](https://github.com/snipe/snipe-it/commits?author=DrekiDegga "Code") | [
arne-kroeger](https://github.com/arne-kroeger)
[💻](https://github.com/snipe/snipe-it/commits?author=arne-kroeger "Code") | -| [
Glukose1](https://github.com/Glukose1)
[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [
Scarzy](https://github.com/Scarzy)
[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [
setpill](https://github.com/setpill)
[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [
swift2512](https://github.com/swift2512)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | +| [
Glukose1](https://github.com/Glukose1)
[💻](https://github.com/snipe/snipe-it/commits?author=Glukose1 "Code") | [
Scarzy](https://github.com/Scarzy)
[💻](https://github.com/snipe/snipe-it/commits?author=Scarzy "Code") | [
setpill](https://github.com/setpill)
[💻](https://github.com/snipe/snipe-it/commits?author=setpill "Code") | [
swift2512](https://github.com/swift2512)
[🐛](https://github.com/snipe/snipe-it/issues?q=author%3Aswift2512 "Bug reports") | [
Darren Rainey](https://darrenraineys.co.uk)
[💻](https://github.com/snipe/snipe-it/commits?author=DarrenRainey "Code") | [
maciej-poleszczyk](https://github.com/maciej-poleszczyk)
[💻](https://github.com/snipe/snipe-it/commits?author=maciej-poleszczyk "Code") | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! diff --git a/README.md b/README.md index e0f4154f6cc8..778469a490fa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![snipe-it-by-grok](https://github.com/snipe/snipe-it/assets/197404/b515673b-c7c8-4d9a-80f5-9fa58829a602) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml) +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://app.codacy.com/gh/snipe/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/snipe/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/snipe/snipe-it/actions/workflows/tests.yml) [![All Contributors](https://img.shields.io/badge/all_contributors-331-orange.svg?style=flat-square)](#contributing) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) ## Snipe-IT - Open Source Asset Management System @@ -14,6 +14,21 @@ Snipe-IT is actively developed and we [release quite frequently](https://github. > [!TIP] > __This is web-based software__. This means there is no executable file (aka no .exe files), and it must be run on a web server and accessed through a web browser. It runs on any Mac OSX, any flavor of Linux, as well as Windows, and we have a [Docker image](https://snipe-it.readme.io/docs/docker) available if that's what you're into. +----- + +### Table of Contents +* [Installation](#installation) +* [User's Manual](#users-manual) +* [Bug Reports & Feature Requests](#bug-reports--feature-requests) +* [Security](#security) +* [Upgrading](#upgrading) +* [Translations!](#translations-) +* [Libraries, Modules & Related Projects](#libraries-modules--related-projects) +* [Join the Community!](#join-the-community) +* [Contributing](#contributing) +* [Announcement List](#announcement-list) + + ----- ### Installation @@ -22,8 +37,6 @@ For instructions on installing and configuring Snipe-IT on your server, check ou If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help. - - ----- ### User's Manual For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.io/docs/overview). @@ -35,20 +48,21 @@ Feel free to check out the [GitHub Issues for this project](https://github.com/s > [!IMPORTANT] > **PLEASE see the [Getting Help Guidelines](https://snipe-it.readme.io/docs/getting-help) and [Common Issues](https://snipe-it.readme.io/docs/common-issues) before opening a ticket, and be sure to complete all of the questions in the Github Issue template to help us to help you as quickly as possible.** -> + ----- -### Upgrading +### Security -Please see the [upgrading documentation](https://snipe-it.readme.io/docs/upgrading) for instructions on upgrading Snipe-IT. +> [!IMPORTANT] +> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.** +----- ------- -### Announcement List -To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important. +### Upgrading ------- +Please see the [upgrading documentation](https://snipe-it.readme.io/docs/upgrading) for instructions on upgrading Snipe-IT. +------ ### Translations! Please see the [translations documentation](https://snipe-it.readme.io/docs/translations) for information about available languages and how to add translations to Snipe-IT. @@ -82,23 +96,33 @@ Since the release of the JSON REST API, several third-party developers have been ----- +### Join the Community! + +- **[Join our Discord](https://discord.gg/yZFtShAcKk)!** It’s full of great people. We even wrote about it [here](https://grokstar.dev/culture/2024/06/the-unlikely-rise-of-discord-as-a-support-channel/)! +- **Follow us on Bluesky** at [@snipeitapp.com](https://bsky.app/profile/snipeitapp.com) +- **Follow us on Mastodon** at [hachyderm.io/@grokability](https://hachyderm.io/@grokability) +- **Follow our blog** at [Grokstar.Dev](https://grokstar.dev) +- **Subscribe here** on Github for notifications about new releases. (We recommend selecting "Releases" only for mosrt users - this repo can get noisy.) + +----- + ### Contributing -Please refrain from submitting issues or pull requests generated by fully-automated tools. Maintainers reserve the right, at their sole discretion, to close such submissions and to block any account responsible for them. +**Please refrain from submitting issues or pull requests generated by fully-automated tools. Maintainers reserve the right, at their sole discretion, to close such submissions and to block any account responsible for them.** -Ideally, contributions should follow from a human-to-human discussion in the form of an issue. +Contributions should follow from a human-to-human discussion in the form of an issue for the best chances of being merged into the core project. (Sometimes we might already be working on that feature, sometimes we've decided against ) Please see the complete documentation on [contributing and developing for Snipe-IT](https://snipe-it.readme.io/docs/contributing-overview). -Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. +This project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. The ERD is available [online here](https://drawsql.app/templates/snipe-it). -[Here is a list](CONTRIBUTORS.md) of the wonderful people that have contributed to the Snipe-IT. +Be sure to check out all of the [amazing people](CONTRIBUTORS.md) that have contributed to Snipe-IT over the years! ------ +------ +### Announcement List + +To be notified of important news (such as new releases, security advisories, etc), [sign up for our list](http://eepurl.com/XyZKz). We'll never sell or give away your info, and we'll only email you when it's important. -### Security -> [!IMPORTANT] -> **To report a security vulnerability, please email security@snipeitapp.com instead of using the issue tracker.** diff --git a/app/Console/Commands/LdapSync.php b/app/Console/Commands/LdapSync.php index 9f4281bd4648..2338cebf750e 100644 --- a/app/Console/Commands/LdapSync.php +++ b/app/Console/Commands/LdapSync.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Models\Asset; use App\Models\Department; use App\Models\Group; use Illuminate\Console\Command; @@ -322,22 +323,29 @@ public function handle() ] ]; } - + + $add_manager_to_cache = true; if ($ldap_manager["count"] > 0) { - - // Get the Manager's username - // PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array. - $ldapManagerUsername = $ldap_manager[0][$ldap_map["username"]][0]; - - // Get User from Manager username. - $ldap_manager = User::where('username', $ldapManagerUsername)->first(); - - if ($ldap_manager && isset($ldap_manager->id)) { - // Link user to manager id. - $user->manager_id = $ldap_manager->id; + try { + // Get the Manager's username + // PHP LDAP returns every LDAP attribute as an array, and 90% of the time it's an array of just one item. But, hey, it's an array. + $ldapManagerUsername = $ldap_manager[0][$ldap_map["username"]][0]; + + // Get User from Manager username. + $ldap_manager = User::where('username', $ldapManagerUsername)->first(); + + if ($ldap_manager && isset($ldap_manager->id)) { + // Link user to manager id. + $user->manager_id = $ldap_manager->id; + } + } catch (\Exception $e) { + $add_manager_to_cache = false; + \Log::warning('Handling ldap manager ' . $item['manager'] . ' caused an exception: ' . $e->getMessage() . '. Continuing synchronization.'); } } - $manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed' + if ($add_manager_to_cache) { + $manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed' + } } } @@ -418,6 +426,8 @@ public function handle() if ($item['createorupdate'] === 'created' && $ldap_default_group) { $user->groups()->attach($ldap_default_group); } + //updates assets location based on user's location + Asset::where('assigned_to', '=', $user->id)->update(['location_id' => $user->location_id]); } else { foreach ($user->getErrors()->getMessages() as $key => $err) { diff --git a/app/Console/Commands/ObjectImportCommand.php b/app/Console/Commands/ObjectImportCommand.php index 8370e7c0508c..a1202ded8959 100644 --- a/app/Console/Commands/ObjectImportCommand.php +++ b/app/Console/Commands/ObjectImportCommand.php @@ -6,6 +6,7 @@ use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Illuminate\Support\Facades\Log; +use Symfony\Component\Console\Helper\ProgressIndicator; ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M')); @@ -29,6 +30,11 @@ class ObjectImportCommand extends Command */ protected $description = 'Import Items from CSV'; + /** + * The progress indicator instance. + */ + protected ProgressIndicator $progressIndicator; + /** * Create a new command instance. * @@ -39,8 +45,6 @@ public function __construct() parent::__construct(); } - private $bar; - /** * Execute the console command. * @@ -48,6 +52,8 @@ public function __construct() */ public function handle() { + $this->progressIndicator = new ProgressIndicator($this->output); + $filename = $this->argument('filename'); $class = title_case($this->option('item-type')); $classString = "App\\Importer\\{$class}Importer"; @@ -61,46 +67,25 @@ public function handle() // This $logFile/useFiles() bit is currently broken, so commenting it out for now // $logFile = $this->option('logfile'); // Log::useFiles($logFile); - $this->comment('======= Importing Items from '.$filename.' ========='); - $importer->import(); + $this->progressIndicator->start('======= Importing Items from '.$filename.' ========='); - $this->bar = null; + $importer->import(); - if (! empty($this->errors)) { - $this->comment('The following Errors were encountered.'); - foreach ($this->errors as $asset => $error) { - $this->comment('Error: Item: '.$asset.' failed validation: '.json_encode($error)); - } - } else { - $this->comment('All Items imported successfully!'); - } - $this->comment(''); + $this->progressIndicator->finish('Import finished.'); } - public function errorCallback($item, $field, $errorString) + public function errorCallback($item, $field, $error) { - $this->errors[$item->name][$field] = $errorString; + $this->output->write("\x0D\x1B[2K"); + + $this->warn('Error: Item: '.$item->name.' failed validation: '.json_encode($error)); } - public function progress($count) + public function progress($importedItemsCount) { - if (! $this->bar) { - $this->bar = $this->output->createProgressBar($count); - } - static $index = 0; - $index++; - if ($index < $count) { - $this->bar->advance(); - } else { - $this->bar->finish(); - } + $this->progressIndicator->advance(); } - // Tracks the current item for error messages - private $updating; - // An array of errors encountered while parsing - private $errors; - /** * Log a message to file, configurable by the --log-file parameter. * If a warning message is passed, we'll spit it to the console as well. diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index 18e149b57d83..95a344dce9e7 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -16,6 +16,7 @@ use Illuminate\Contracts\Encryption\DecryptException; use Carbon\Carbon; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; use Intervention\Image\ImageManagerStatic as Image; use Illuminate\Support\Facades\Session; @@ -708,6 +709,28 @@ public static function generateRandomString($length = 10) return $randomString; } + /** + * A method to be used to handle deprecations notifications, currently handling MS Teams. more can be added when needed. + * + * + * @author [Godfrey Martinez] + * @since [v7.0.14] + * @return array + */ + public static function deprecationCheck() : array { + // The check and message that the user is still using the deprecated version + $deprecations = [ + 'ms_teams_deprecated' => array( + 'check' => !Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'), + 'message' => 'The Microsoft Teams webhook URL being used will be deprecated Jan 31st, 2025. Change webhook endpoint'), + ]; + + // if item of concern is being used and its being used with the deprecated values return the notification array. + if(Setting::getSettings()->webhook_selected === 'microsoft' && $deprecations['ms_teams_deprecated']['check']) { + return $deprecations; + } + return []; + } /** * This nasty little method gets the low inventory info for the @@ -1123,6 +1146,7 @@ public static function filetype_icon($filename) 'png' => 'far fa-image', 'webp' => 'far fa-image', 'avif' => 'far fa-image', + 'svg' => 'fas fa-vector-square', // word 'doc' => 'far fa-file-word', 'docx' => 'far fa-file-word', @@ -1135,7 +1159,7 @@ public static function filetype_icon($filename) //Text 'txt' => 'far fa-file-alt', 'rtf' => 'far fa-file-alt', - 'xml' => 'far fa-file-alt', + 'xml' => 'fas fa-code', // Misc 'pdf' => 'far fa-file-pdf', 'lic' => 'far fa-save', @@ -1148,41 +1172,7 @@ public static function filetype_icon($filename) return 'far fa-file'; } - public static function show_file_inline($filename) - { - $extension = substr(strrchr($filename, '.'), 1); - - if ($extension) { - switch ($extension) { - case 'jpg': - case 'jpeg': - case 'gif': - case 'png': - case 'webp': - case 'avif': - return true; - break; - default: - return false; - } - } - - return false; - } - /** - * Generate a random encrypted password. - * - * @author Wes Hulette - * - * @since 5.0.0 - * - * @return string - */ - public static function generateEncyrptedPassword(): string - { - return bcrypt(self::generateUnencryptedPassword()); - } /** * Get a random unencrypted password. diff --git a/app/Helpers/StorageHelper.php b/app/Helpers/StorageHelper.php index 2cdab1d66c40..47700f913ac5 100644 --- a/app/Helpers/StorageHelper.php +++ b/app/Helpers/StorageHelper.php @@ -7,6 +7,7 @@ use Illuminate\Http\RedirectResponse; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\StreamedResponse; +use Illuminate\Contracts\Filesystem\FileNotFoundException; class StorageHelper { public static function downloader($filename, $disk = 'default') : BinaryFileResponse | RedirectResponse | StreamedResponse @@ -25,4 +26,64 @@ public static function downloader($filename, $disk = 'default') : BinaryFileResp return Storage::disk($disk)->download($filename); } } + + + /** + * This determines the file types that should be allowed inline and checks their fileinfo extension + * to determine that they are safe to display inline. + * + * @author [ + * @since v7.0.14 + * @param $file_with_path + * @return bool + */ + public static function allowSafeInline($file_with_path) { + + $allowed_inline = [ + 'pdf', + 'svg', + 'jpg', + 'gif', + 'svg', + 'avif', + 'webp', + 'png', + ]; + + + // The file exists and is allowed to be displayed inline + if (Storage::exists($file_with_path) && (in_array(pathinfo($file_with_path, PATHINFO_EXTENSION), $allowed_inline))) { + return true; + } + return false; + + } + + /** + * Decide whether to show the file inline or download it. + */ + public static function showOrDownloadFile($file, $filename) { + + $headers = []; + + if (request('inline') == 'true') { + + $headers = [ + 'Content-Disposition' => 'inline', + ]; + + // This is NOT allowed as inline - force it to be displayed as text in the browser + if (self::allowSafeInline($file) != true) { + $headers = array_merge($headers, ['Content-Type' => 'text/plain']); + } + } + + // Everything else seems okay, but the file doesn't exist on the server. + if (Storage::missing($file)) { + throw new FileNotFoundException(); + } + + return Storage::download($file, $filename, $headers); + + } } diff --git a/app/Http/Controllers/Accessories/AccessoriesFilesController.php b/app/Http/Controllers/Accessories/AccessoriesFilesController.php index b63c202d3001..ebc1e4b8e0d8 100644 --- a/app/Http/Controllers/Accessories/AccessoriesFilesController.php +++ b/app/Http/Controllers/Accessories/AccessoriesFilesController.php @@ -106,50 +106,29 @@ public function destroy($accessoryId = null, $fileId = null) : RedirectResponse * @param int $accessoryId * @param int $fileId */ - public function show($accessoryId = null, $fileId = null, $download = true) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse + public function show($accessoryId = null, $fileId = null) : View | RedirectResponse | Response | BinaryFileResponse | StreamedResponse { - Log::debug('Private filesystem is: '.config('filesystems.default')); - $accessory = Accessory::find($accessoryId); - - // the accessory is valid - if (isset($accessory->id)) { + if ($accessory = Accessory::find($accessoryId)) { $this->authorize('view', $accessory); $this->authorize('accessories.files', $accessory); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) { - return redirect()->route('accessories.index')->with('error', trans('admin/users/message.log_record_not_found')); - } - - $file = 'private_uploads/accessories/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $accessory->id)->find($fileId)) { + $file = 'private_uploads/accessories/'.$log->filename; - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.file_not_found')); } + } + return redirect()->route('accessories.show', ['accessory' => $accessory])->with('error', trans('general.log_record_not_found')); - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); - } - } } - return redirect()->route('accessories.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); + return redirect()->route('accessories.index')->with('error', trans('general.file_not_found')); } } diff --git a/app/Http/Controllers/Account/AcceptanceController.php b/app/Http/Controllers/Account/AcceptanceController.php index e29fa7c63b08..278d7e208106 100644 --- a/app/Http/Controllers/Account/AcceptanceController.php +++ b/app/Http/Controllers/Account/AcceptanceController.php @@ -338,4 +338,5 @@ public function store(Request $request, $id) : RedirectResponse return redirect()->to('account/accept')->with('success', $return_msg); } + } diff --git a/app/Http/Controllers/ActionlogController.php b/app/Http/Controllers/ActionlogController.php index f143c4b73bb9..f2580c96fc6f 100644 --- a/app/Http/Controllers/ActionlogController.php +++ b/app/Http/Controllers/ActionlogController.php @@ -37,10 +37,16 @@ public function displaySig($filename) : RedirectResponse | Response | bool } } - public function getStoredEula($filename) : Response | BinaryFileResponse + public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse { $this->authorize('view', \App\Models\Asset::class); $file = config('app.private_uploads').'/eula-pdfs/'.$filename; - return response()->download($file); + + if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) { + return response()->download($file); + } + + return redirect()->back()->with('error', trans('general.file_does_not_exist')); + } } diff --git a/app/Http/Controllers/Api/AssetModelsController.php b/app/Http/Controllers/Api/AssetModelsController.php index e1ae0c12d3ca..33a00d1d3f52 100644 --- a/app/Http/Controllers/Api/AssetModelsController.php +++ b/app/Http/Controllers/Api/AssetModelsController.php @@ -56,20 +56,21 @@ public function index(Request $request) : JsonResponse | array 'models.id', 'models.image', 'models.name', - 'model_number', - 'min_amt', - 'eol', - 'requestable', + 'models.model_number', + 'models.min_amt', + 'models.eol', + 'models.created_by', + 'models.requestable', 'models.notes', 'models.created_at', - 'category_id', - 'manufacturer_id', - 'depreciation_id', - 'fieldset_id', + 'models.category_id', + 'models.manufacturer_id', + 'models.depreciation_id', + 'models.fieldset_id', 'models.deleted_at', 'models.updated_at', ]) - ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues','adminuser') + ->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser') ->withCount('assets as assets_count'); if ($request->input('status')=='deleted') { @@ -95,7 +96,7 @@ public function index(Request $request) : JsonResponse | array $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'models.created_at'; - switch ($sort) { + switch ($request->input('sort')) { case 'manufacturer': $assetmodels->OrderManufacturer($order); break; @@ -105,6 +106,9 @@ public function index(Request $request) : JsonResponse | array case 'fieldset': $assetmodels->OrderFieldset($order); break; + case 'created_by': + $assetmodels->OrderByCreatedByName($order); + break; default: $assetmodels->orderBy($sort, $order); break; diff --git a/app/Http/Controllers/Api/AssetsController.php b/app/Http/Controllers/Api/AssetsController.php index d4a103be3732..6e60cbada836 100644 --- a/app/Http/Controllers/Api/AssetsController.php +++ b/app/Http/Controllers/Api/AssetsController.php @@ -33,6 +33,8 @@ use App\Http\Requests\ImageUploadRequest; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route; +use App\View\Label; +use Illuminate\Support\Facades\Storage; /** @@ -80,10 +82,10 @@ public function index(Request $request, $action = null, $upcoming_status = null) $this->authorize('reports.view'); } else { $transformer = 'App\Http\Transformers\AssetsTransformer'; - $this->authorize('index', Asset::class); + $this->authorize('index', Asset::class); } - - + + $settings = Setting::getSettings(); $allowed_columns = [ @@ -126,8 +128,19 @@ public function index(Request $request, $action = null, $upcoming_status = null) } $assets = Asset::select('assets.*') - ->with('location', 'assetstatus', 'company', 'defaultLoc','assignedTo', 'adminuser','model.depreciation', - 'model.category', 'model.manufacturer', 'model.fieldset','supplier'); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users. + ->with( + 'location', + 'assetstatus', + 'company', + 'defaultLoc', + 'assignedTo', + 'adminuser', + 'model.depreciation', + 'model.category', + 'model.manufacturer', + 'model.fieldset', + 'supplier' + ); //it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users. if ($filter_non_deprecable_assets) { @@ -159,8 +172,8 @@ public function index(Request $request, $action = null, $upcoming_status = null) * Handle due and overdue audits and checkin dates */ switch ($action) { - // Audit (singular) is left over from earlier legacy APIs - case 'audits' : + // Audit (singular) is left over from earlier legacy APIs + case 'audits': switch ($upcoming_status) { case 'due': $assets->DueForAudit($settings); @@ -187,7 +200,7 @@ public function index(Request $request, $action = null, $upcoming_status = null) break; } break; - } + } /** * End handling due and overdue audits and checkin dates @@ -265,7 +278,6 @@ public function index(Request $request, $action = null, $upcoming_status = null) $join->on('status_alias.id', '=', 'assets.status_id'); }); } - } @@ -345,7 +357,7 @@ public function index(Request $request, $action = null, $upcoming_status = null) $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc'; - + switch ($sort_override) { case 'model': $assets->OrderModels($order); @@ -399,7 +411,6 @@ public function index(Request $request, $action = null, $upcoming_status = null) } else { $assets->orderBy($sort_override, $order); } - } else { $assets->orderBy($column_sort, $order); } @@ -413,11 +424,11 @@ public function index(Request $request, $action = null, $upcoming_status = null) $total = $assets->count(); $assets = $assets->skip($offset)->take($limit)->get(); - + /** * Include additional associated relationships - */ + */ if ($request->input('components')) { $assets->loadMissing(['components' => function ($query) { $query->orderBy('created_at', 'desc'); @@ -441,7 +452,7 @@ public function index(Request $request, $action = null, $upcoming_status = null) * @since [v4.2.1] * @author [A. Gianotto] [] */ - public function showByTag(Request $request, $tag) : JsonResponse | array + public function showByTag(Request $request, $tag): JsonResponse | array { $this->authorize('index', Asset::class); $assets = Asset::where('asset_tag', $tag)->with('assetstatus')->with('assignedTo'); @@ -463,12 +474,10 @@ public function showByTag(Request $request, $tag) : JsonResponse | array } else { return (new AssetsTransformer)->transformAssets($assets, $assets->count()); } - } // If there are 0 results, return the "no such asset" response return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); - } /** @@ -479,7 +488,7 @@ public function showByTag(Request $request, $tag) : JsonResponse | array * @since [v4.2.1] * @return \Illuminate\Http\JsonResponse */ - public function showBySerial(Request $request, $serial) : JsonResponse | array + public function showBySerial(Request $request, $serial): JsonResponse | array { $this->authorize('index', Asset::class); $assets = Asset::where('serial', $serial)->with('assetstatus')->with('assignedTo'); @@ -488,14 +497,13 @@ public function showBySerial(Request $request, $serial) : JsonResponse | array if ($request->input('deleted', 'false') == 'true') { $assets = $assets->withTrashed(); } - + if (($assets = $assets->get()) && ($assets->count()) > 0) { - return (new AssetsTransformer)->transformAssets($assets, $assets->count()); + return (new AssetsTransformer)->transformAssets($assets, $assets->count()); } // If there are 0 results, return the "no such asset" response return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); - } /** @@ -506,20 +514,20 @@ public function showBySerial(Request $request, $serial) : JsonResponse | array * @since [v4.0] * @return \Illuminate\Http\JsonResponse */ - public function show(Request $request, $id) : JsonResponse | array + public function show(Request $request, $id): JsonResponse | array { if ($asset = Asset::with('assetstatus') ->with('assignedTo')->withTrashed() - ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->find($id)) { + ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->find($id) + ) { $this->authorize('view', $asset); - return (new AssetsTransformer)->transformAsset($asset, $request->input('components') ); + return (new AssetsTransformer)->transformAsset($asset, $request->input('components')); } return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); - } - public function licenses(Request $request, $id) : array + public function licenses(Request $request, $id): array { $this->authorize('view', Asset::class); $this->authorize('view', License::class); @@ -527,7 +535,7 @@ public function licenses(Request $request, $id) : array $licenses = $asset->licenses()->get(); return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count()); - } + } /** @@ -537,7 +545,7 @@ public function licenses(Request $request, $id) : array * @since [v4.0.16] * @see \App\Http\Transformers\SelectlistTransformer */ - public function selectlist(Request $request) : array + public function selectlist(Request $request): array { $assets = Asset::select([ @@ -548,7 +556,7 @@ public function selectlist(Request $request) : array 'assets.assigned_to', 'assets.assigned_type', 'assets.status_id', - ])->with('model', 'assetstatus', 'assignedTo')->NotArchived(); + ])->with('model', 'assetstatus', 'assignedTo')->NotArchived(); if ($request->filled('assetStatusType') && $request->input('assetStatusType') === 'RTD') { $assets = $assets->RTD(); @@ -570,12 +578,12 @@ public function selectlist(Request $request) : array $asset->use_text = $asset->present()->fullName; if (($asset->checkedOutToUser()) && ($asset->assigned)) { - $asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute(); + $asset->use_text .= ' → ' . $asset->assigned->getFullNameAttribute(); } if ($asset->assetstatus->getStatuslabelType() == 'pending') { - $asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')'; + $asset->use_text .= '(' . $asset->assetstatus->getStatuslabelType() . ')'; } $asset->use_image = ($asset->getImageUrl()) ? $asset->getImageUrl() : null; @@ -601,12 +609,12 @@ public function store(StoreAssetRequest $request): JsonResponse $asset->created_by = auth()->id(); /** - * this is here just legacy reasons. Api\AssetController - * used image_source once to allow encoded image uploads. - */ + * this is here just legacy reasons. Api\AssetController + * used image_source once to allow encoded image uploads. + */ if ($request->has('image_source')) { $request->offsetSet('image', $request->offsetGet('image_source')); - } + } $asset = $request->handleImages($asset); @@ -623,9 +631,9 @@ public function store(StoreAssetRequest $request): JsonResponse // If input value is null, use custom field's default value if ($field_val == null) { - Log::debug('Field value for '.$field->db_column.' is null'); + Log::debug('Field value for ' . $field->db_column . ' is null'); $field_val = $field->defaultValue($request->get('model_id')); - Log::debug('Use the default fieldset value of '.$field->defaultValue($request->get('model_id'))); + Log::debug('Use the default fieldset value of ' . $field->defaultValue($request->get('model_id'))); } // if the field is set to encrypted, make sure we encrypt the value @@ -643,7 +651,7 @@ public function store(StoreAssetRequest $request): JsonResponse } } if ($field->element == 'checkbox') { - if(is_array($field_val)) { + if (is_array($field_val)) { $field_val = implode(',', $field_val); } } @@ -671,7 +679,9 @@ public function store(StoreAssetRequest $request): JsonResponse return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.create.success'))); - return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success'))); + // below is what we want the _eventual_ return to look like - in a more standardized format. + // return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.create.success'))); + } return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200); @@ -702,64 +712,64 @@ public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse } /** - * this is here just legacy reasons. Api\AssetController - * used image_source once to allow encoded image uploads. - */ + * this is here just legacy reasons. Api\AssetController + * used image_source once to allow encoded image uploads. + */ if ($request->has('image_source')) { $request->offsetSet('image', $request->offsetGet('image_source')); } $asset = $request->handleImages($asset); $model = $asset->model; - - // Update custom fields - $problems_updating_encrypted_custom_fields = false; - if (($model) && (isset($model->fieldset))) { - foreach ($model->fieldset->fields as $field) { - $field_val = $request->input($field->db_column, null); - - if ($request->has($field->db_column)) { - if ($field->element == 'checkbox') { - if(is_array($field_val)) { - $field_val = implode(',', $field_val); - } + + // Update custom fields + $problems_updating_encrypted_custom_fields = false; + if (($model) && (isset($model->fieldset))) { + foreach ($model->fieldset->fields as $field) { + $field_val = $request->input($field->db_column, null); + + if ($request->has($field->db_column)) { + if ($field->element == 'checkbox') { + if (is_array($field_val)) { + $field_val = implode(',', $field_val); } - if ($field->field_encrypted == '1') { - if (Gate::allows('assets.view.encrypted_custom_fields')) { - $field_val = Crypt::encrypt($field_val); - } else { - $problems_updating_encrypted_custom_fields = true; - continue; - } + } + if ($field->field_encrypted == '1') { + if (Gate::allows('assets.view.encrypted_custom_fields')) { + $field_val = Crypt::encrypt($field_val); + } else { + $problems_updating_encrypted_custom_fields = true; + continue; } - $asset->{$field->db_column} = $field_val; } + $asset->{$field->db_column} = $field_val; } } - if ($asset->save()) { - if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { - $location = $target->location_id; - } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) { - $location = $target->location_id; - - Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id) - ->update(['location_id' => $target->location_id]); - } elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) { - $location = $target->id; - } + } + if ($asset->save()) { + if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { + $location = $target->location_id; + } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) { + $location = $target->location_id; + + Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id) + ->update(['location_id' => $target->location_id]); + } elseif (($request->filled('assigned_location')) && ($target = Location::find($request->get('assigned_location')))) { + $location = $target->id; + } - if (isset($target)) { - $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location); - } + if (isset($target)) { + $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), '', 'Checked out on asset update', e($request->get('name')), $location); + } - if ($asset->image) { - $asset->image = $asset->getImageUrl(); - } + if ($asset->image) { + $asset->image = $asset->getImageUrl(); + } if ($problems_updating_encrypted_custom_fields) { - return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.encrypted_warning'))); + return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.encrypted_warning'))); } else { - return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success'))); + return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($asset), trans('admin/hardware/message.update.success'))); } } return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200); @@ -773,7 +783,7 @@ public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse * @param int $assetId * @since [v4.0] */ - public function destroy($id) : JsonResponse + public function destroy($id): JsonResponse { $this->authorize('delete', Asset::class); @@ -799,7 +809,7 @@ public function destroy($id) : JsonResponse return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); } - + /** * Restore a soft-deleted asset. @@ -808,7 +818,7 @@ public function destroy($id) : JsonResponse * @param int $assetId * @since [v5.1.18] */ - public function restore(Request $request, $assetId = null) : JsonResponse + public function restore(Request $request, $assetId = null): JsonResponse { if ($asset = Asset::withTrashed()->find($assetId)) { @@ -827,7 +837,6 @@ public function restore(Request $request, $assetId = null) : JsonResponse } return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200); - } /** @@ -837,7 +846,7 @@ public function restore(Request $request, $assetId = null) : JsonResponse * @param string $tag * @since [v6.0.5] */ - public function checkoutByTag(AssetCheckoutRequest $request, $tag) : JsonResponse + public function checkoutByTag(AssetCheckoutRequest $request, $tag): JsonResponse { if ($asset = Asset::where('asset_tag', $tag)->first()) { return $this->checkout($request, $asset->id); @@ -852,13 +861,13 @@ public function checkoutByTag(AssetCheckoutRequest $request, $tag) : JsonRespons * @param int $assetId * @since [v4.0] */ - public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonResponse + public function checkout(AssetCheckoutRequest $request, $asset_id): JsonResponse { $this->authorize('checkout', Asset::class); $asset = Asset::findOrFail($asset_id); if (! $asset->availableForCheckout()) { - return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available'))); + return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.not_available'))); } $this->authorize('checkout', $asset); @@ -875,14 +884,12 @@ public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonRespons $asset->location_id = ($target) ? $target->id : ''; $error_payload['target_id'] = $request->input('assigned_location'); $error_payload['target_type'] = 'location'; - } elseif (request('checkout_to_type') == 'asset') { $target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset')); // Override with the asset's location_id if it has one $asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : ''; $error_payload['target_id'] = $request->input('assigned_asset'); $error_payload['target_type'] = 'asset'; - } elseif (request('checkout_to_type') == 'user') { // Fetch the target and set the asset's new location_id $target = User::find(request('assigned_user')); @@ -896,7 +903,7 @@ public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonRespons } if (! isset($target)) { - return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset '.e($asset->asset_tag).' is invalid - '.$error_payload['target_type'].' does not exist.')); + return response()->json(Helper::formatStandardApiResponse('error', $error_payload, 'Checkout target for asset ' . e($asset->asset_tag) . ' is invalid - ' . $error_payload['target_type'] . ' does not exist.')); } $checkout_at = request('checkout_at', date('Y-m-d H:i:s')); @@ -910,15 +917,15 @@ public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonRespons // TODO: Follow up here. WTF. Commented out for now. -// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) { -// $asset->location_id = $target->rtd_location_id; -// } + // if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) { + // $asset->location_id = $target->rtd_location_id; + // } if ($asset->checkOut($target, auth()->user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) { - return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success'))); + return response()->json(Helper::formatStandardApiResponse('success', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.success'))); } - return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error'))); + return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.error'))); } @@ -929,7 +936,7 @@ public function checkout(AssetCheckoutRequest $request, $asset_id) : JsonRespons * @param int $assetId * @since [v4.0] */ - public function checkin(Request $request, $asset_id) : JsonResponse + public function checkin(Request $request, $asset_id): JsonResponse { $asset = Asset::with('model')->findOrFail($asset_id); $this->authorize('checkin', $asset); @@ -937,7 +944,7 @@ public function checkin(Request $request, $asset_id) : JsonResponse $target = $asset->assignedTo; if (is_null($target)) { return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset_tag'=> e($asset->asset_tag), + 'asset_tag' => e($asset->asset_tag), 'model' => e($asset->model->name), 'model_number' => e($asset->model->model_number) ], trans('admin/hardware/message.checkin.already_checked_in'))); @@ -960,7 +967,7 @@ public function checkin(Request $request, $asset_id) : JsonResponse if ($request->filled('location_id')) { $asset->location_id = $request->input('location_id'); - if ($request->input('update_default_location')){ + if ($request->input('update_default_location')) { $asset->rtd_location_id = $request->input('location_id'); } } @@ -968,8 +975,8 @@ public function checkin(Request $request, $asset_id) : JsonResponse if ($request->filled('status_id')) { $asset->status_id = $request->input('status_id'); } - - $checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at').' '. date('H:i:s') : date('Y-m-d H:i:s'); + + $checkin_at = $request->filled('checkin_at') ? $request->input('checkin_at') . ' ' . date('H:i:s') : date('Y-m-d H:i:s'); $originalValues = $asset->getRawOriginal(); if (($request->filled('checkin_at')) && ($request->get('checkin_at') != date('Y-m-d'))) { @@ -987,7 +994,8 @@ public function checkin(Request $request, $asset_id) : JsonResponse [Asset::class], function (Builder $query) use ($asset) { $query->where('id', $asset->id); - }) + } + ) ->get() ->map(function ($acceptance) { $acceptance->delete(); @@ -997,13 +1005,13 @@ function (Builder $query) use ($asset) { event(new CheckoutableCheckedIn($asset, $target, auth()->user(), $request->input('note'), $checkin_at, $originalValues)); return response()->json(Helper::formatStandardApiResponse('success', [ - 'asset_tag'=> e($asset->asset_tag), + 'asset_tag' => e($asset->asset_tag), 'model' => e($asset->model->name), 'model_number' => e($asset->model->model_number) ], trans('admin/hardware/message.checkin.success'))); } - return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error'))); + return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkin.error'))); } /** @@ -1012,7 +1020,7 @@ function (Builder $query) use ($asset) { * @author [A. Janes] [] * @since [v6.0] */ - public function checkinByTag(Request $request, $tag = null) : JsonResponse + public function checkinByTag(Request $request, $tag = null): JsonResponse { $this->authorize('checkin', Asset::class); if (null == $tag && null !== ($request->input('asset_tag'))) { @@ -1025,8 +1033,8 @@ public function checkinByTag(Request $request, $tag = null) : JsonResponse } return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset'=> e($tag) - ], 'Asset with tag '.e($tag).' not found')); + 'asset' => e($tag) + ], 'Asset with tag ' . e($tag) . ' not found')); } @@ -1037,7 +1045,7 @@ public function checkinByTag(Request $request, $tag = null) : JsonResponse * @param int $id * @since [v4.0] */ - public function audit(Request $request) : JsonResponse + public function audit(Request $request): JsonResponse { $this->authorize('audit', Asset::class); @@ -1048,8 +1056,8 @@ public function audit(Request $request) : JsonResponse // No tag passed - return an error if (!$request->filled('asset_tag')) { return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset_tag'=> '', - 'error'=> trans('admin/hardware/message.no_tag'), + 'asset_tag' => '', + 'error' => trans('admin/hardware/message.no_tag'), ], trans('admin/hardware/message.no_tag')), 200); } @@ -1097,28 +1105,25 @@ public function audit(Request $request) : JsonResponse $asset->logAudit(request('note'), request('location_id')); return response()->json(Helper::formatStandardApiResponse('success', [ - 'asset_tag'=> e($asset->asset_tag), - 'note'=> e($request->input('note')), + 'asset_tag' => e($asset->asset_tag), + 'note' => e($request->input('note')), 'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date), ], trans('admin/hardware/message.audit.success'))); } // Asset failed validation or was not able to be saved return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset_tag'=> e($asset->asset_tag), - 'error'=> $asset->getErrors()->first(), + 'asset_tag' => e($asset->asset_tag), + 'error' => $asset->getErrors()->first(), ], trans('admin/hardware/message.audit.error', ['error' => $asset->getErrors()->first()])), 200); - } // No matching asset for the asset tag that was passed. return response()->json(Helper::formatStandardApiResponse('error', [ - 'asset_tag'=> e($request->input('asset_tag')), - 'error'=> trans('admin/hardware/message.audit.error'), + 'asset_tag' => e($request->input('asset_tag')), + 'error' => trans('admin/hardware/message.audit.error'), ], trans('admin/hardware/message.audit.error', ['error' => trans('admin/hardware/message.does_not_exist')])), 200); - - } @@ -1129,7 +1134,7 @@ public function audit(Request $request) : JsonResponse * @author [A. Gianotto] [] * @since [v4.0] */ - public function requestable(Request $request) : JsonResponse | array + public function requestable(Request $request): JsonResponse | array { $this->authorize('viewRequestable', Asset::class); @@ -1150,8 +1155,18 @@ public function requestable(Request $request) : JsonResponse | array } $assets = Asset::select('assets.*') - ->with('location', 'assetstatus', 'assetlog', 'company','assignedTo', - 'model.category', 'model.manufacturer', 'model.fieldset', 'supplier', 'requests'); + ->with( + 'location', + 'assetstatus', + 'assetlog', + 'company', + 'assignedTo', + 'model.category', + 'model.manufacturer', + 'model.fieldset', + 'supplier', + 'requests' + ); @@ -1159,7 +1174,7 @@ public function requestable(Request $request) : JsonResponse | array if ($request->filled('search')) { $assets->TextSearch($request->input('search')); } - + // Search custom fields by column name foreach ($all_custom_fields as $field) { if ($request->filled($field->db_column_name())) { @@ -1200,4 +1215,89 @@ public function requestable(Request $request) : JsonResponse | array return (new AssetsTransformer)->transformRequestedAssets($assets, $total); } + + /** + * Generate asset labels by tag + * + * @author [Nebelkreis] [https://github.com/NebelKreis] + * + * @param Request $request Contains asset_tags array of asset tags to generate labels for + * @return JsonResponse Returns base64 encoded PDF on success, error message on failure + */ + public function getLabels(Request $request): JsonResponse + { + try { + $this->authorize('view', Asset::class); + + // Validate that asset tags were provided in the request + if (!$request->filled('asset_tags')) { + return response()->json(Helper::formatStandardApiResponse('error', null, + trans('admin/hardware/message.no_assets_selected')), 400); + } + + // Convert asset tags from request into collection and fetch matching assets + $asset_tags = collect($request->input('asset_tags')); + $assets = Asset::whereIn('asset_tag', $asset_tags)->get(); + + // Return error if no assets were found for the provided tags + if ($assets->isEmpty()) { + return response()->json(Helper::formatStandardApiResponse('error', null, + trans('admin/hardware/message.does_not_exist')), 404); + } + + try { + $settings = Setting::getSettings(); + + // Check if logo file exists in storage and disable logo if not found + // This prevents errors when trying to include a non-existent logo in the PDF + $settings->label_logo = ($original_logo = $settings->label_logo) && !Storage::disk('public')->exists('/' . $original_logo) ? null : $settings->label_logo; + + + $label = new Label(); + + if (!$label) { + throw new \Exception('Label object could not be created'); + } + + // Configure label with assets and settings + // bulkedit=false and count=0 are default values for label generation + $label = $label->with('assets', $assets) + ->with('settings', $settings) + ->with('bulkedit', false) + ->with('count', 0); + + // Generate PDF using callback function + // The callback captures the PDF content in $pdf_content variable + $pdf_content = ''; + $label->render(function($pdf) use (&$pdf_content) { + $pdf_content = $pdf->Output('', 'S'); + return $pdf; + }); + + // Verify PDF was generated successfully + if (empty($pdf_content)) { + throw new \Exception('PDF content is empty'); + } + + $encoded_content = base64_encode($pdf_content); + + return response()->json(Helper::formatStandardApiResponse('success', [ + 'pdf' => $encoded_content + ], trans('admin/hardware/message.labels_generated'))); + + } catch (\Exception $e) { + return response()->json(Helper::formatStandardApiResponse('error', [ + 'error_message' => $e->getMessage(), + 'error_line' => $e->getLine(), + 'error_file' => $e->getFile() + ], trans('admin/hardware/message.error_generating_labels')), 500); + } + } catch (\Exception $e) { + return response()->json(Helper::formatStandardApiResponse('error', [ + 'error_message' => $e->getMessage(), + 'error_line' => $e->getLine(), + 'error_file' => $e->getFile() + ], $e->getMessage()), 500); + } + } } diff --git a/app/Http/Controllers/Api/ComponentsController.php b/app/Http/Controllers/Api/ComponentsController.php index 561e13c9cd3b..0f594f5e75d1 100644 --- a/app/Http/Controllers/Api/ComponentsController.php +++ b/app/Http/Controllers/Api/ComponentsController.php @@ -38,6 +38,7 @@ public function index(Request $request) : JsonResponse | array 'name', 'min_amt', 'order_number', + 'model_number', 'serial', 'purchase_date', 'purchase_cost', @@ -47,7 +48,7 @@ public function index(Request $request) : JsonResponse | array ]; $components = Component::select('components.*') - ->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser'); + ->with('company', 'location', 'category', 'assets', 'supplier', 'adminuser', 'manufacturer'); if ($request->filled('search')) { $components = $components->TextSearch($request->input('search')); @@ -69,6 +70,14 @@ public function index(Request $request) : JsonResponse | array $components->where('supplier_id', '=', $request->input('supplier_id')); } + if ($request->filled('manufacturer_id')) { + $components->where('manufacturer_id', '=', $request->input('manufacturer_id')); + } + + if ($request->filled('model_number')) { + $components->where('model_number', '=', $request->input('model_number')); + } + if ($request->filled('location_id')) { $components->where('location_id', '=', $request->input('location_id')); } @@ -98,6 +107,9 @@ public function index(Request $request) : JsonResponse | array case 'supplier': $components = $components->OrderSupplier($order); break; + case 'manufacturer': + $components = $components->OrderManufacturer($order); + break; case 'created_by': $components = $components->OrderByCreatedBy($order); break; @@ -297,9 +309,7 @@ public function checkout(Request $request, $componentId) : JsonResponse public function checkin(Request $request, $component_asset_id) : JsonResponse { if ($component_assets = DB::table('components_assets')->find($component_asset_id)) { - if (is_null($component = Component::find($component_assets->component_id))) { - return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.not_found'))); } @@ -307,17 +317,13 @@ public function checkin(Request $request, $component_asset_id) : JsonResponse $max_to_checkin = $component_assets->assigned_qty; - if ($max_to_checkin > 1) { - - $validator = Validator::make($request->all(), [ - "checkin_qty" => "required|numeric|between:1,$max_to_checkin" - ]); - - if ($validator->fails()) { - return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and '.$max_to_checkin)); - } + $validator = Validator::make($request->all(), [ + "checkin_qty" => "required|numeric|between:1,$max_to_checkin" + ]); + + if ($validator->fails()) { + return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and ' . $max_to_checkin)); } - // Validation passed, so let's figure out what we have to do here. $qty_remaining_in_checkout = ($component_assets->assigned_qty - (int)$request->input('checkin_qty', 1)); @@ -327,28 +333,23 @@ public function checkin(Request $request, $component_asset_id) : JsonResponse $component_assets->assigned_qty = $qty_remaining_in_checkout; Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id); - - DB::table('components_assets')->where('id', - $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]); + + DB::table('components_assets')->where('id', $component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]); // If the checked-in qty is exactly the same as the assigned_qty, // we can simply delete the associated components_assets record - if ($qty_remaining_in_checkout == 0) { + if ($qty_remaining_in_checkout === 0) { DB::table('components_assets')->where('id', '=', $component_asset_id)->delete(); } - $asset = Asset::find($component_assets->asset_id); event(new CheckoutableCheckedIn($component, $asset, auth()->user(), $request->input('note'), Carbon::now())); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success'))); - } return response()->json(Helper::formatStandardApiResponse('error', null, 'No matching checkouts for that component join record')); - - } } diff --git a/app/Http/Controllers/Api/ConsumablesController.php b/app/Http/Controllers/Api/ConsumablesController.php index 8e7f3217207c..7ff676c7bef3 100644 --- a/app/Http/Controllers/Api/ConsumablesController.php +++ b/app/Http/Controllers/Api/ConsumablesController.php @@ -258,6 +258,8 @@ public function checkout(Request $request, $id) : JsonResponse $this->authorize('checkout', $consumable); + $consumable->checkout_qty = $request->input('checkout_qty', 1); + // Make sure there is at least one available to checkout if ($consumable->numRemaining() <= 0) { return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable'))); @@ -268,6 +270,12 @@ public function checkout(Request $request, $id) : JsonResponse return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.invalid_item_category_single', ['type' => trans('general.consumable')]))); } + // Make sure there is at least one available to checkout + if ($consumable->numRemaining() <= 0 || $consumable->checkout_qty > $consumable->numRemaining()) { + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.checkout.unavailable', ['requested' => $consumable->checkout_qty, 'remaining' => $consumable->numRemaining() ]))); + } + + // Check if the user exists - @TODO: this should probably be handled via validation, not here?? if (!$user = User::find($request->input('assigned_to'))) { @@ -278,7 +286,8 @@ public function checkout(Request $request, $id) : JsonResponse // Update the consumable data $consumable->assigned_to = $request->input('assigned_to'); - $consumable->users()->attach($consumable->id, + for ($i = 0; $i < $consumable->checkout_qty; $i++) { + $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, 'created_by' => $user->id, @@ -286,6 +295,8 @@ public function checkout(Request $request, $id) : JsonResponse 'note' => $request->input('note'), ] ); + } + event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note'))); diff --git a/app/Http/Controllers/Api/ImportController.php b/app/Http/Controllers/Api/ImportController.php index 13f43012174a..ebf8b550b2c0 100644 --- a/app/Http/Controllers/Api/ImportController.php +++ b/app/Http/Controllers/Api/ImportController.php @@ -28,8 +28,7 @@ class ImportController extends Controller public function index() : JsonResponse | array { $this->authorize('import'); - $imports = Import::latest()->get(); - + $imports = Import::with('adminuser')->latest()->get(); return (new ImportsTransformer)->transformImports($imports); } @@ -133,7 +132,7 @@ public function store() : JsonResponse } $import->filesize = filesize($path.'/'.$file_name); - + $import->created_by = auth()->id(); $import->save(); $results[] = $import; } @@ -177,6 +176,9 @@ public function process(ItemImportRequest $request, $import_id) : JsonResponse case 'asset': $redirectTo = 'hardware.index'; break; + case 'assetModel': + $redirectTo = 'models.index'; + break; case 'accessory': $redirectTo = 'accessories.index'; break; diff --git a/app/Http/Controllers/Api/ManufacturersController.php b/app/Http/Controllers/Api/ManufacturersController.php index f111ef6c83e6..f716fbbf7f75 100644 --- a/app/Http/Controllers/Api/ManufacturersController.php +++ b/app/Http/Controllers/Api/ManufacturersController.php @@ -60,7 +60,8 @@ public function index(Request $request) : JsonResponse | array ->withCount('assets as assets_count') ->withCount('licenses as licenses_count') ->withCount('consumables as consumables_count') - ->withCount('accessories as accessories_count'); + ->withCount('accessories as accessories_count') + ->withCount('components as components_count'); if ($request->input('deleted') == 'true') { $manufacturers->onlyTrashed(); diff --git a/app/Http/Controllers/Api/ReportsController.php b/app/Http/Controllers/Api/ReportsController.php index 566911dd271c..494c75104f6d 100644 --- a/app/Http/Controllers/Api/ReportsController.php +++ b/app/Http/Controllers/Api/ReportsController.php @@ -45,7 +45,7 @@ public function index(Request $request) : JsonResponse | array } if ($request->filled('action_type')) { - $actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type'))->orderBy('created_at', 'desc'); + $actionlogs = $actionlogs->where('action_type', '=', $request->input('action_type')); } if ($request->filled('created_by')) { @@ -53,15 +53,16 @@ public function index(Request $request) : JsonResponse | array } if ($request->filled('action_source')) { - $actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source'))->orderBy('created_at', 'desc'); + $actionlogs = $actionlogs->where('action_source', '=', $request->input('action_source')); } - + if ($request->filled('remote_ip')) { - $actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip'))->orderBy('created_at', 'desc'); + $actionlogs = $actionlogs->where('remote_ip', '=', $request->input('remote_ip')); } + if ($request->filled('uploads')) { - $actionlogs = $actionlogs->whereNotNull('filename')->orderBy('created_at', 'desc'); + $actionlogs = $actionlogs->whereNotNull('filename'); } $allowed_columns = [ @@ -74,6 +75,8 @@ public function index(Request $request) : JsonResponse | array 'note', 'remote_ip', 'user_agent', + 'target_type', + 'item_type', 'action_source', 'action_date', ]; @@ -91,7 +94,7 @@ public function index(Request $request) : JsonResponse | array $actionlogs->OrderByCreatedBy($order); break; default: - $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at'; + $sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'action_logs.created_at'; $actionlogs = $actionlogs->orderBy($sort, $order); break; } diff --git a/app/Http/Controllers/Api/StatuslabelsController.php b/app/Http/Controllers/Api/StatuslabelsController.php index 754ebf73231f..7e4851ff5ae1 100644 --- a/app/Http/Controllers/Api/StatuslabelsController.php +++ b/app/Http/Controllers/Api/StatuslabelsController.php @@ -95,7 +95,8 @@ public function store(Request $request) : JsonResponse $request->except('deployable', 'pending', 'archived'); if (! $request->filled('type')) { - return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']]), 500); + + return response()->json(Helper::formatStandardApiResponse('error', null, ['type' => ['Status label type is required.']])); } $statuslabel = new Statuslabel; diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index a9c8c26f14ca..61aad1073445 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -283,6 +283,7 @@ public function index(Request $request) : array 'autoassign_licenses', 'website', 'locale', + 'notes', ]; $sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'first_name'; @@ -480,10 +481,11 @@ public function update(SaveUserRequest $request, User $user): JsonResponse $user->permissions = $permissions_array; } - // 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)]); - + if($request->has('location_id')) { + // 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)]); + } app('App\Http\Requests\ImageUploadRequest')->handleImages($user, 600, 'image', 'avatars', 'avatar'); if ($user->save()) { diff --git a/app/Http/Controllers/Assets/AssetFilesController.php b/app/Http/Controllers/Assets/AssetFilesController.php index b5a04759bbee..d15055c4b2fb 100644 --- a/app/Http/Controllers/Assets/AssetFilesController.php +++ b/app/Http/Controllers/Assets/AssetFilesController.php @@ -61,43 +61,30 @@ public function store(UploadFileRequest $request, $assetId = null) : RedirectRes */ public function show($assetId = null, $fileId = null) : View | RedirectResponse | Response | StreamedResponse | BinaryFileResponse { - $asset = Asset::find($assetId); - // the asset is valid - if (isset($asset->id)) { - $this->authorize('view', $asset); - - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } - - $file = 'private_uploads/assets/'.$log->filename; + if ($asset = Asset::find($assetId)) { - if ($log->action_type == 'audit') { - $file = 'private_uploads/audits/'.$log->filename; - } + $this->authorize('view', $asset); - if (! Storage::exists($file)) { - return response('File '.$file.' not found on server', 404) - ->header('Content-Type', 'text/plain'); - } + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $asset->id)->find($fileId)) { + $file = 'private_uploads/assets/'.$log->filename; - if (request('inline') == 'true') { + if ($log->action_type == 'audit') { + $file = 'private_uploads/audits/'.$log->filename; + } - $headers = [ - 'Content-Disposition' => 'inline', - ]; + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.file_not_found')); + } - return Storage::download($file, $log->filename, $headers); } - return StorageHelper::downloader($file); + return redirect()->route('hardware.show', ['hardware' => $asset])->with('error', trans('general.log_record_not_found')); } - // Prepare the error message - $error = trans('admin/hardware/message.does_not_exist', ['id' => $fileId]); - // Redirect to the hardware management page - return redirect()->route('hardware.index')->with('error', $error); + return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist')); + } /** diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index dda54f4c8d39..52eb751a8939 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -17,7 +17,6 @@ use App\Models\Setting; use App\Models\Statuslabel; use App\Models\User; -use Illuminate\Support\Facades\Auth; use App\View\Label; use Carbon\Carbon; use Illuminate\Support\Facades\DB; @@ -112,8 +111,10 @@ public function store(ImageUploadRequest $request) : RedirectResponse $settings = Setting::getSettings(); - $success = false; + $successes = []; + $failures = []; $serials = $request->input('serials'); + $asset = null; for ($a = 1; $a <= count($asset_tags); $a++) { $asset = new Asset(); @@ -200,20 +201,35 @@ public function store(ImageUploadRequest $request) : RedirectResponse $asset->checkOut($target, auth()->user(), date('Y-m-d H:i:s'), $request->input('expected_checkin', null), 'Checked out on asset creation', $request->get('name'), $location); } - $success = true; - + $successes[] = " $asset->id]) . "' style='color: white;'>" . e($asset->asset_tag) . ""; + + } else { + $failures[] = join(",", $asset->getErrors()->all()); } } session()->put(['redirect_option' => $request->get('redirect_option'), 'checkout_to_type' => $request->get('checkout_to_type')]); - if ($success) { + if ($successes) { + if ($failures) { + //some succeeded, some failed + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) //FIXME - not tested + ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)])) + ->with('warning', trans_choice('admin/hardware/message.create.partial_failure', $failures, ['failures' => join("; ", $failures)])); + } else { + if (count($successes) == 1) { + //the most common case, keeping it so we don't have to make every use of that translation string be trans_choice'ed + //and re-translated + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)])); + } else { + //multi-success + return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) + ->with('success-unescaped', trans_choice('admin/hardware/message.create.multi_success_linked', $successes, ['links' => join(", ", $successes)])); + } + } - return redirect()->to(Helper::getRedirectOption($request, $asset->id, 'Assets')) - ->with('success-unescaped', trans('admin/hardware/message.create.success_linked', ['link' => route('hardware.show', ['hardware' => $asset->id]), 'id', 'tag' => e($asset->asset_tag)])); - - } return redirect()->back()->withInput()->withErrors($asset->getErrors()); diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php index c27cfe3e0c69..93f7255c0bf1 100644 --- a/app/Http/Controllers/Assets/BulkAssetsController.php +++ b/app/Http/Controllers/Assets/BulkAssetsController.php @@ -245,10 +245,12 @@ public function update(Request $request) : RedirectResponse || ($request->filled('status_id')) || ($request->filled('model_id')) || ($request->filled('next_audit_date')) + || ($request->filled('asset_eol_date')) || ($request->filled('null_name')) || ($request->filled('null_purchase_date')) || ($request->filled('null_expected_checkin_date')) || ($request->filled('null_next_audit_date')) + || ($request->filled('null_asset_eol_date')) || ($request->anyFilled($custom_field_columns)) ) { @@ -271,7 +273,8 @@ public function update(Request $request) : RedirectResponse ->conditionallyAddItem('requestable') ->conditionallyAddItem('supplier_id') ->conditionallyAddItem('warranty_months') - ->conditionallyAddItem('next_audit_date'); + ->conditionallyAddItem('next_audit_date') + ->conditionallyAddItem('asset_eol_date'); foreach ($custom_field_columns as $key => $custom_field_column) { $this->conditionallyAddItem($custom_field_column); } @@ -316,6 +319,17 @@ public function update(Request $request) : RedirectResponse $this->update_array['next_audit_date'] = null; } + if ($request->input('null_asset_eol_date')=='1') { + $this->update_array['asset_eol_date'] = null; + + // If they are nulling the EOL date to allow it to calculate, set eol explicit to 0 + if ($request->input('calc_eol')=='1') { + $this->update_array['eol_explicit'] = 0; + } + } + + + if ($request->filled('purchase_cost')) { $this->update_array['purchase_cost'] = $request->input('purchase_cost'); } diff --git a/app/Http/Controllers/Components/ComponentsController.php b/app/Http/Controllers/Components/ComponentsController.php index 430984767e77..62dc25cf1d32 100644 --- a/app/Http/Controllers/Components/ComponentsController.php +++ b/app/Http/Controllers/Components/ComponentsController.php @@ -73,6 +73,8 @@ public function store(ImageUploadRequest $request) $component->name = $request->input('name'); $component->category_id = $request->input('category_id'); $component->supplier_id = $request->input('supplier_id'); + $component->manufacturer_id = $request->input('manufacturer_id'); + $component->model_number = $request->input('model_number'); $component->location_id = $request->input('location_id'); $component->company_id = Company::getIdForCurrentUser($request->input('company_id')); $component->order_number = $request->input('order_number', null); @@ -150,6 +152,8 @@ public function update(ImageUploadRequest $request, $componentId = null) $component->name = $request->input('name'); $component->category_id = $request->input('category_id'); $component->supplier_id = $request->input('supplier_id'); + $component->manufacturer_id = $request->input('manufacturer_id'); + $component->model_number = $request->input('model_number'); $component->location_id = $request->input('location_id'); $component->company_id = Company::getIdForCurrentUser($request->input('company_id')); $component->order_number = $request->input('order_number'); @@ -189,7 +193,7 @@ public function destroy($componentId) $this->authorize('delete', $component); // Remove the image if one exists - if (Storage::disk('public')->exists('components/'.$component->image)) { + if ($component->image && Storage::disk('public')->exists('components/' . $component->image)) { try { Storage::disk('public')->delete('components/'.$component->image); } catch (\Exception $e) { diff --git a/app/Http/Controllers/Components/ComponentsFilesController.php b/app/Http/Controllers/Components/ComponentsFilesController.php index a7d42bb0729e..83468a0b10b4 100644 --- a/app/Http/Controllers/Components/ComponentsFilesController.php +++ b/app/Http/Controllers/Components/ComponentsFilesController.php @@ -112,40 +112,25 @@ public function destroy($componentId = null, $fileId = null) public function show($componentId = null, $fileId = null) { Log::debug('Private filesystem is: '.config('filesystems.default')); - $component = Component::find($componentId); + // the component is valid - if (isset($component->id)) { + if ($component = Component::find($componentId)) { $this->authorize('view', $component); $this->authorize('components.files', $component); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } - - $file = 'private_uploads/components/'.$log->filename; + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $component->id)->find($fileId)) { - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); + $file = 'private_uploads/components/'.$log->filename; - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.file_not_found')); } - - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); - } } + return redirect()->route('components.show', ['component' => $component])->with('error', trans('general.log_record_not_found')); + } return redirect()->route('components.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php index 3bf202733a1b..e08da4122972 100644 --- a/app/Http/Controllers/Consumables/ConsumableCheckoutController.php +++ b/app/Http/Controllers/Consumables/ConsumableCheckoutController.php @@ -70,7 +70,7 @@ public function store(Request $request, $consumableId) $this->authorize('checkout', $consumable); // If the quantity is not present in the request or is not a positive integer, set it to 1 - $quantity = $request->input('qty'); + $quantity = $request->input('checkout_qty'); if (!isset($quantity) || !ctype_digit((string)$quantity) || $quantity <= 0) { $quantity = 1; } @@ -92,7 +92,7 @@ public function store(Request $request, $consumableId) // Update the consumable data $consumable->assigned_to = e($request->input('assigned_to')); - for($i = 0; $i < $quantity; $i++){ + for ($i = 0; $i < $quantity; $i++){ $consumable->users()->attach($consumable->id, [ 'consumable_id' => $consumable->id, 'created_by' => $admin_user->id, @@ -100,6 +100,8 @@ public function store(Request $request, $consumableId) 'note' => $request->input('note'), ]); } + + $consumable->checkout_qty = $quantity; event(new CheckoutableCheckedOut($consumable, $user, auth()->user(), $request->input('note'))); $request->request->add(['checkout_to_type' => 'user']); diff --git a/app/Http/Controllers/Consumables/ConsumablesFilesController.php b/app/Http/Controllers/Consumables/ConsumablesFilesController.php index 35a4ae841ec3..054fdc0b81a9 100644 --- a/app/Http/Controllers/Consumables/ConsumablesFilesController.php +++ b/app/Http/Controllers/Consumables/ConsumablesFilesController.php @@ -104,7 +104,6 @@ public function destroy($consumableId = null, $fileId = null) * @since [v1.4] * @param int $consumableId * @param int $fileId - * @return \Symfony\Consumable\HttpFoundation\Response * @throws \Illuminate\Auth\Access\AuthorizationException */ public function show($consumableId = null, $fileId = null) @@ -116,36 +115,18 @@ public function show($consumableId = null, $fileId = null) $this->authorize('view', $consumable); $this->authorize('consumables.files', $consumable); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } - - $file = 'private_uploads/consumables/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('FILE DOES NOT EXISTS for '.$file); - Log::debug('URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download($file, $log->filename, $headers); - } - + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $consumable->id)->find($fileId)) { + $file = 'private_uploads/consumables/'.$log->filename; - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.file_not_found')); } } + // The log record doesn't exist somehow + return redirect()->route('consumables.show', ['consumable' => $consumable])->with('error', trans('general.log_record_not_found')); + } return redirect()->route('consumables.index')->with('error', trans('general.file_does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/Licenses/LicenseFilesController.php b/app/Http/Controllers/Licenses/LicenseFilesController.php index fa18e8cf4868..6ab3cb7703aa 100644 --- a/app/Http/Controllers/Licenses/LicenseFilesController.php +++ b/app/Http/Controllers/Licenses/LicenseFilesController.php @@ -112,37 +112,19 @@ public function show($licenseId = null, $fileId = null, $download = true) $this->authorize('view', $license); $this->authorize('licenses.files', $license); - if (! $log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) { - return response('No matching record for that asset/file', 500) - ->header('Content-Type', 'text/plain'); - } - - $file = 'private_uploads/licenses/'.$log->filename; - - if (Storage::missing($file)) { - Log::debug('NOT EXISTS for '.$file); - Log::debug('NOT EXISTS URL should be '.Storage::url($file)); - - return response('File '.$file.' ('.Storage::url($file).') not found on server', 404) - ->header('Content-Type', 'text/plain'); - } else { - - if (request('inline') == 'true') { + if ($log = Actionlog::whereNotNull('filename')->where('item_id', $license->id)->find($fileId)) { + $file = 'private_uploads/licenses/'.$log->filename; - $headers = [ - 'Content-Disposition' => 'inline', - ]; - - return Storage::download($file, $log->filename, $headers); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.file_not_found')); } + } - // We have to override the URL stuff here, since local defaults in Laravel's Flysystem - // won't work, as they're not accessible via the web - if (config('filesystems.default') == 'local') { // TODO - is there any way to fix this at the StorageHelper layer? - return StorageHelper::downloader($file); + // The log record doesn't exist somehow + return redirect()->route('licenses.show', ['licenses' => $license])->with('error', trans('general.log_record_not_found')); - } - } } return redirect()->route('licenses.index')->with('error', trans('admin/licenses/message.does_not_exist', ['id' => $fileId])); diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 105dac6350f4..0d4bb936b2c2 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -259,7 +259,7 @@ public function postActivityReport(Request $request) : StreamedResponse $executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']; Log::debug('Added headers: '.$executionTime); - $actionlogs = Actionlog::with('item', 'user', 'target', 'location') + $actionlogs = Actionlog::with('item', 'user', 'target', 'location', 'adminuser') ->orderBy('created_at', 'DESC') ->chunk(20, function ($actionlogs) use ($handle) { $executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']; @@ -286,7 +286,7 @@ public function postActivityReport(Request $request) : StreamedResponse $row = [ $actionlog->created_at, - ($actionlog->admin) ? e($actionlog->admin->getFullNameAttribute()) : '', + ($actionlog->adminuser) ? e($actionlog->adminuser->getFullNameAttribute()) : '', $actionlog->present()->actionType(), e($actionlog->itemType()), ($actionlog->itemType() == 'user') ? $actionlog->filename : $item_name, diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index aa773d9eea5f..2186a3fc76f6 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -334,6 +334,8 @@ public function postSettings(Request $request) : RedirectResponse $setting->depreciation_method = $request->input('depreciation_method'); $setting->dash_chart_type = $request->input('dash_chart_type'); $setting->profile_edit = $request->input('profile_edit', 0); + $setting->require_checkinout_notes = $request->input('require_checkinout_notes', 0); + if ($request->input('per_page') != '') { $setting->per_page = $request->input('per_page'); diff --git a/app/Http/Controllers/Users/BulkUsersController.php b/app/Http/Controllers/Users/BulkUsersController.php index fbf08c9820b0..5541f7a15d42 100644 --- a/app/Http/Controllers/Users/BulkUsersController.php +++ b/app/Http/Controllers/Users/BulkUsersController.php @@ -323,7 +323,7 @@ protected function logItemCheckinAndDelete($items, $itemType) $logAction->item_type = $itemType; $logAction->target_id = $item->assigned_to; $logAction->target_type = User::class; - $logAction->created_at = auth()->id(); + $logAction->created_by = auth()->id(); $logAction->note = 'Bulk checkin items'; $logAction->logaction('checkin from'); } diff --git a/app/Http/Controllers/Users/UserFilesController.php b/app/Http/Controllers/Users/UserFilesController.php index 9e5f322c03eb..e99bfe298f78 100644 --- a/app/Http/Controllers/Users/UserFilesController.php +++ b/app/Http/Controllers/Users/UserFilesController.php @@ -7,9 +7,6 @@ use App\Http\Requests\UploadFileRequest; use App\Models\Actionlog; use App\Models\User; -use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Input; -use Illuminate\Support\Facades\Response; use Symfony\Component\HttpFoundation\JsonResponse; use Illuminate\Support\Facades\Storage; @@ -116,31 +113,30 @@ public function destroy($userId = null, $fileId = null) public function show($userId = null, $fileId = null) { + if (empty($fileId)) { return redirect()->route('users.show')->with('error', 'Invalid file request'); } - $user = User::find($userId); - - // the license is valid - if (isset($user->id)) { + if ($user = User::find($userId)) { $this->authorize('view', $user); if ($log = Actionlog::whereNotNull('filename')->where('item_id', $user->id)->find($fileId)) { + $file = 'private_uploads/users/'.$log->filename; - // Display the file inline - if (request('inline') == 'true') { - $headers = [ - 'Content-Disposition' => 'inline', - ]; - return Storage::download('private_uploads/users/'.$log->filename, $log->filename, $headers); + try { + return StorageHelper::showOrDownloadFile($file, $log->filename); + } catch (\Exception $e) { + return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.file_not_found')); } - - return Storage::download('private_uploads/users/'.$log->filename); } - return redirect()->route('users.index')->with('error', trans('admin/users/message.log_record_not_found')); + // The log record doesn't exist somehow + return redirect()->route('users.show', ['user' => $user])->with('error', trans('general.log_record_not_found')); + + + return redirect()->back()->with('error', trans('general.file_not_found')); } // Redirect to the user management page if the user doesn't exist diff --git a/app/Http/Controllers/Users/UsersController.php b/app/Http/Controllers/Users/UsersController.php index 051db1f4efcf..397bfd16d8b7 100755 --- a/app/Http/Controllers/Users/UsersController.php +++ b/app/Http/Controllers/Users/UsersController.php @@ -288,33 +288,31 @@ public function update(SaveUserRequest $request, User $user) $user->password = bcrypt($request->input('password')); } - // 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]); - $permissions_array = $request->input('permission'); - + $permissions_array = $request->input('permission'); - // Strip out the superuser permission if the user isn't a superadmin - if (! auth()->user()->isSuperUser()) { - unset($permissions_array['superuser']); - $permissions_array['superuser'] = $orig_superuser; - } + // Strip out the superuser permission if the user isn't a superadmin + if (! auth()->user()->isSuperUser()) { + unset($permissions_array['superuser']); + $permissions_array['superuser'] = $orig_superuser; + } - $user->permissions = json_encode($permissions_array); + $user->permissions = json_encode($permissions_array); - // Handle uploaded avatar - app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); - session()->put(['redirect_option' => $request->get('redirect_option')]); + // Handle uploaded avatar + app(ImageUploadRequest::class)->handleImages($user, 600, 'avatar', 'avatars', 'avatar'); + session()->put(['redirect_option' => $request->get('redirect_option')]); - if ($user->save()) { - // Redirect to the user page - return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users')) - ->with('success', trans('admin/users/message.success.update')); - } - return redirect()->back()->withInput()->withErrors($user->getErrors()); + if ($user->save()) { + // Redirect to the user page + return redirect()->to(Helper::getRedirectOption($request, $user->id, 'Users')) + ->with('success', trans('admin/users/message.success.update')); + } + return redirect()->back()->withInput()->withErrors($user->getErrors()); } /** diff --git a/app/Http/Requests/AssetCheckinRequest.php b/app/Http/Requests/AssetCheckinRequest.php index 116b8b39f22a..8980cea095b4 100644 --- a/app/Http/Requests/AssetCheckinRequest.php +++ b/app/Http/Requests/AssetCheckinRequest.php @@ -21,9 +21,14 @@ public function authorize() */ public function rules() { - return [ + $settings = \App\Models\Setting::getSettings(); - ]; + $rules = []; + + if($settings->require_checkinout_notes) { + $rules['note'] = 'string|required'; + } + return $rules; } public function response(array $errors) diff --git a/app/Http/Requests/AssetCheckoutRequest.php b/app/Http/Requests/AssetCheckoutRequest.php index f48a7d5e534f..c7975a62406d 100644 --- a/app/Http/Requests/AssetCheckoutRequest.php +++ b/app/Http/Requests/AssetCheckoutRequest.php @@ -21,10 +21,12 @@ public function authorize() */ public function rules() { + $settings = \App\Models\Setting::getSettings(); + $rules = [ - 'assigned_user' => 'required_without_all:assigned_asset,assigned_location', - 'assigned_asset' => 'required_without_all:assigned_user,assigned_location', - 'assigned_location' => 'required_without_all:assigned_user,assigned_asset', + 'assigned_user' => 'integer|required_without_all:assigned_asset,assigned_location', + 'assigned_asset' => 'integer|required_without_all:assigned_user,assigned_location', + 'assigned_location' => 'integer|required_without_all:assigned_user,assigned_asset', 'status_id' => 'exists:status_labels,id,deployable,1', 'checkout_to_type' => 'required|in:asset,location,user', 'checkout_at' => [ @@ -35,7 +37,11 @@ public function rules() 'nullable', 'date' ], - ]; + ]; + + if($settings->require_checkinout_notes) { + $rules['note'] = 'required|string'; + } return $rules; } diff --git a/app/Http/Requests/ItemImportRequest.php b/app/Http/Requests/ItemImportRequest.php index a6dc0ad7e5e2..59afdbe09f44 100644 --- a/app/Http/Requests/ItemImportRequest.php +++ b/app/Http/Requests/ItemImportRequest.php @@ -38,10 +38,11 @@ public function import(Import $import) $filename = config('app.private_uploads').'/imports/'.$import->file_path; $import->import_type = $this->input('import-type'); - $class = title_case($import->import_type); + $class = ucfirst($import->import_type); $classString = "App\\Importer\\{$class}Importer"; $importer = new $classString($filename); $import->field_map = request('column-mappings'); + $import->created_by = auth()->id(); $import->save(); $fieldMappings = []; @@ -60,7 +61,7 @@ public function import(Import $import) $fieldMappings = array_change_key_case(array_flip($import->field_map), CASE_LOWER); } $importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback']) - ->setUserId(auth()->id()) + ->setCreatedBy(auth()->id()) ->setUpdating($this->get('import-update')) ->setShouldNotify($this->get('send-welcome')) ->setUsernameFormat('firstname.lastname') diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php index 26d01051b4ae..66179ac739c4 100644 --- a/app/Http/Requests/StoreAssetRequest.php +++ b/app/Http/Requests/StoreAssetRequest.php @@ -26,12 +26,19 @@ public function authorize(): bool public function prepareForValidation(): void { + // Guard against users passing in an array for company_id instead of an integer. + // If the company_id is not an integer then we simply use what was + // provided to be caught by model level validation later. + // The use of is_numeric accounts for 1 and '1'. + $idForCurrentUser = is_numeric($this->company_id) + ? Company::getIdForCurrentUser($this->company_id) + : $this->company_id; + $this->parseLastAuditDate(); $this->merge([ 'asset_tag' => $this->asset_tag ?? Asset::autoincrement_asset(), - 'company_id' => Company::getIdForCurrentUser($this->company_id), - 'assigned_to' => $assigned_to ?? null, + 'company_id' => $idForCurrentUser, ]); } diff --git a/app/Http/Requests/StoreNotificationSettings.php b/app/Http/Requests/StoreNotificationSettings.php index db7e8a0fe214..13ce5478ed41 100644 --- a/app/Http/Requests/StoreNotificationSettings.php +++ b/app/Http/Requests/StoreNotificationSettings.php @@ -25,7 +25,7 @@ public function rules(): array { return [ 'alert_email' => 'email_array|nullable', - 'admin_cc_email' => 'email|nullable', + 'admin_cc_email' => 'email_array|nullable', 'alert_threshold' => 'numeric|nullable|gt:0', 'alert_interval' => 'numeric|nullable|gt:0', 'audit_warning_days' => 'numeric|nullable|gt:0', diff --git a/app/Http/Requests/Traits/MayContainCustomFields.php b/app/Http/Requests/Traits/MayContainCustomFields.php index 9a7f85e3a268..bbdf62893d06 100644 --- a/app/Http/Requests/Traits/MayContainCustomFields.php +++ b/app/Http/Requests/Traits/MayContainCustomFields.php @@ -23,7 +23,7 @@ public function withValidator($validator) return str_starts_with($attributes, '_snipeit_'); }); // if there are custom fields, find the one's that don't exist on the model's fieldset and add an error to the validator's error bag - if (count($request_fields) > 0) { + if (count($request_fields) > 0 && $validator->errors()->isEmpty()) { $request_fields->diff($asset_model?->fieldset?->fields?->pluck('db_column')) ->each(function ($request_field_name) use ($request_fields, $validator) { if (CustomField::where('db_column', $request_field_name)->exists()) { diff --git a/app/Http/Transformers/ActionlogsTransformer.php b/app/Http/Transformers/ActionlogsTransformer.php index 3fe05c687b61..4e6341c8f33f 100644 --- a/app/Http/Transformers/ActionlogsTransformer.php +++ b/app/Http/Transformers/ActionlogsTransformer.php @@ -160,7 +160,6 @@ public function transformActionlog (Actionlog $actionlog, $settings = null) [ 'url' => $file_url, 'filename' => $actionlog->filename, - 'inlineable' => (bool) Helper::show_file_inline($actionlog->filename), ] : null, 'item' => ($actionlog->item) ? [ diff --git a/app/Http/Transformers/AssetModelsTransformer.php b/app/Http/Transformers/AssetModelsTransformer.php index a7b1c6a49f3f..dab21d9773f8 100644 --- a/app/Http/Transformers/AssetModelsTransformer.php +++ b/app/Http/Transformers/AssetModelsTransformer.php @@ -65,6 +65,10 @@ public function transformAssetModel(AssetModel $assetmodel) 'eol' => ($assetmodel->eol > 0) ? $assetmodel->eol.' months' : 'None', 'requestable' => ($assetmodel->requestable == '1') ? true : false, 'notes' => Helper::parseEscapedMarkedownInline($assetmodel->notes), + 'created_by' => ($assetmodel->adminuser) ? [ + 'id' => (int) $assetmodel->adminuser->id, + 'name'=> e($assetmodel->adminuser->present()->fullName()), + ] : null, 'created_at' => Helper::getFormattedDateObject($assetmodel->created_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($assetmodel->updated_at, 'datetime'), 'deleted_at' => Helper::getFormattedDateObject($assetmodel->deleted_at, 'datetime'), diff --git a/app/Http/Transformers/ComponentsTransformer.php b/app/Http/Transformers/ComponentsTransformer.php index 70572c949415..f98edd6e3fb1 100644 --- a/app/Http/Transformers/ComponentsTransformer.php +++ b/app/Http/Transformers/ComponentsTransformer.php @@ -38,6 +38,8 @@ public function transformComponent(Component $component) 'name' => e($component->category->name), ] : null, 'supplier' => ($component->supplier) ? ['id' => $component->supplier->id, 'name'=> e($component->supplier->name)] : null, + 'manufacturer' => ($component->manufacturer) ? ['id' => $component->manufacturer->id, 'name'=> e($component->manufacturer->name)] : null, + 'model_number' => ($component->model_number) ? e($component->model_number) : null, 'order_number' => e($component->order_number), 'purchase_date' => Helper::getFormattedDateObject($component->purchase_date, 'date'), 'purchase_cost' => Helper::formatCurrencyOutput($component->purchase_cost), diff --git a/app/Http/Transformers/ManufacturersTransformer.php b/app/Http/Transformers/ManufacturersTransformer.php index e08aaa743671..d6954c1d2407 100644 --- a/app/Http/Transformers/ManufacturersTransformer.php +++ b/app/Http/Transformers/ManufacturersTransformer.php @@ -36,6 +36,7 @@ public function transformManufacturer(Manufacturer $manufacturer = null) 'licenses_count' => (int) $manufacturer->licenses_count, 'consumables_count' => (int) $manufacturer->consumables_count, 'accessories_count' => (int) $manufacturer->accessories_count, + 'components_count' => (int) $manufacturer->components_count, 'created_by' => ($manufacturer->adminuser) ? [ 'id' => (int) $manufacturer->adminuser->id, 'name'=> e($manufacturer->adminuser->present()->fullName()), diff --git a/app/Importer/AccessoryImporter.php b/app/Importer/AccessoryImporter.php index eb17c5acadc0..bc9c1909ff42 100644 --- a/app/Importer/AccessoryImporter.php +++ b/app/Importer/AccessoryImporter.php @@ -42,6 +42,7 @@ public function createAccessoryIfNotExists($row) } $this->log('No Matching Accessory, Creating a new one'); $accessory = new Accessory(); + $accessory->created_by = auth()->id(); $this->item['model_number'] = $this->findCsvMatch($row, "model_number"); $this->item['min_amt'] = $this->findCsvMatch($row, "min_amt"); $accessory->fill($this->sanitizeItemForStoring($accessory)); diff --git a/app/Importer/AssetModelImporter.php b/app/Importer/AssetModelImporter.php new file mode 100644 index 000000000000..7cfd8a530dbb --- /dev/null +++ b/app/Importer/AssetModelImporter.php @@ -0,0 +1,174 @@ +createAssetModelIfNotExists($row); + } + + /** + * Create a model if a duplicate does not exist. + * @todo Investigate how this should interact with Importer::createModelIfNotExists + * + * @author A. Gianotto + * @since 6.1.0 + * @param array $row + */ + public function createAssetModelIfNotExists(array $row) + { + + $editingAssetModel = false; + $assetModel = AssetModel::where('name', '=', $this->findCsvMatch($row, 'name'))->first(); + + if ($assetModel) { + if (! $this->updating) { + $this->log('A matching Model '.$this->item['name'].' already exists'); + return; + } + + $this->log('Updating Model'); + $editingAssetModel = true; + } else { + $this->log('No Matching Model, Create a new one'); + $assetModel = new AssetModel(); + } + + // Pull the records from the CSV to determine their values + $this->item['name'] = trim($this->findCsvMatch($row, 'name')); + $this->item['category'] = trim($this->findCsvMatch($row, 'category')); + $this->item['manufacturer'] = trim($this->findCsvMatch($row, 'manufacturer')); + $this->item['min_amt'] = trim($this->findCsvMatch($row, 'min_amt')); + $this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number')); + $this->item['eol'] = trim($this->findCsvMatch($row, 'eol')); + $this->item['notes'] = trim($this->findCsvMatch($row, 'notes')); + $this->item['fieldset'] = trim($this->findCsvMatch($row, 'fieldset')); + $this->item['depreciation'] = trim($this->findCsvMatch($row, 'depreciation')); + $this->item['requestable'] = trim(($this->fetchHumanBoolean($this->findCsvMatch($row, 'requestable'))) == 1) ? 1 : 0; + + if (!empty($this->item['category'])) { + if ($category = $this->createOrFetchCategory($this->item['category'])) { + $this->item['category_id'] = $category; + } + } + if (!empty($this->item['manufacturer'])) { + if ($manufacturer = $this->createOrFetchManufacturer($this->item['manufacturer'])) { + $this->item['manufacturer_id'] = $manufacturer; + } + } + + if (!empty($this->item['depreciation'])) { + if ($depreciation = $this->fetchDepreciation($this->item['depreciation'])) { + $this->item['depreciation_id'] = $depreciation; + } + } + + if (!empty($this->item['fieldset'])) { + if ($fieldset = $this->createOrFetchCustomFieldset($this->item['fieldset'])) { + $this->item['fieldset_id'] = $fieldset; + } + } + + Log::debug('Item array is: '); + Log::debug(print_r($this->item, true)); + + + if ($editingAssetModel) { + Log::debug('Updating existing model'); + $assetModel->update($this->sanitizeItemForUpdating($assetModel)); + } else { + Log::debug('Creating model'); + $assetModel->fill($this->sanitizeItemForStoring($assetModel)); + $assetModel->created_by = auth()->id(); + } + + if ($assetModel->save()) { + $this->log('AssetModel '.$assetModel->name.' created or updated from CSV import'); + return $assetModel; + + } else { + $this->log($assetModel->getErrors()->first()); + $this->addErrorToBag($assetModel, $assetModel->getErrors()->keys()[0], $assetModel->getErrors()->first()); + return $assetModel->getErrors(); + } + + } + + + /** + * Fetch an existing depreciation, or create new if it doesn't exist. + * + * We only do a fetch vs create here since Depreciations have additional fields required + * and cannot be created without them (months, for example.)) + * + * @author A. Gianotto + * @since 7.1.3 + * @param $depreciation_name string + * @return int id of depreciation created/found + */ + public function fetchDepreciation($depreciation_name) : ?int + { + if ($depreciation_name != '') { + + if ($depreciation = Depreciation::where('name', '=', $depreciation_name)->first()) { + $this->log('A matching Depreciation '.$depreciation_name.' already exists'); + return $depreciation->id; + } + } + + return null; + } + + /** + * Fetch an existing fieldset, or create new if it doesn't exist + * + * @author A. Gianotto + * @since 7.1.3 + * @param $fieldset_name string + * @return int id of fieldset created/found + */ + public function createOrFetchCustomFieldset($fieldset_name) : ?int + { + if ($fieldset_name != '') { + $fieldset = CustomFieldset::where('name', '=', $fieldset_name)->first(); + + if ($fieldset) { + $this->log('A matching fieldset '.$fieldset_name.' already exists'); + return $fieldset->id; + } + + $fieldset = new CustomFieldset(); + $fieldset->name = $fieldset_name; + + if ($fieldset->save()) { + $this->log('Fieldset '.$fieldset_name.' was created'); + + return $fieldset->id; + } + $this->logError($fieldset, 'Fieldset'); + } + + return null; + } +} \ No newline at end of file diff --git a/app/Importer/ComponentImporter.php b/app/Importer/ComponentImporter.php index 9687ec4f1791..3979ba499d29 100644 --- a/app/Importer/ComponentImporter.php +++ b/app/Importer/ComponentImporter.php @@ -47,6 +47,7 @@ public function createComponentIfNotExists() } $this->log('No matching component, creating one'); $component = new Component; + $component->created_by = auth()->id(); $component->fill($this->sanitizeItemForStoring($component)); // This sets an attribute on the Loggable trait for the action log @@ -58,7 +59,7 @@ public function createComponentIfNotExists() if (isset($this->item['asset_tag']) && ($asset = Asset::where('asset_tag', $this->item['asset_tag'])->first())) { $component->assets()->attach($component->id, [ 'component_id' => $component->id, - 'created_by' => $this->created_by, + 'created_by' => auth()->id(), 'created_at' => date('Y-m-d H:i:s'), 'assigned_qty' => 1, // Only assign the first one to the asset 'asset_id' => $asset->id, diff --git a/app/Importer/ConsumableImporter.php b/app/Importer/ConsumableImporter.php index 9e7019b0861e..10ffaaedfb5a 100644 --- a/app/Importer/ConsumableImporter.php +++ b/app/Importer/ConsumableImporter.php @@ -41,6 +41,7 @@ public function createConsumableIfNotExists($row) } $this->log('No matching consumable, creating one'); $consumable = new Consumable(); + $consumable->created_by = auth()->id(); $this->item['model_number'] = trim($this->findCsvMatch($row, 'model_number')); $this->item['item_no'] = trim($this->findCsvMatch($row, 'item_number')); $this->item['min_amt'] = trim($this->findCsvMatch($row, "min_amt")); diff --git a/app/Importer/Importer.php b/app/Importer/Importer.php index 47de5add4c2c..907c8b72c55c 100644 --- a/app/Importer/Importer.php +++ b/app/Importer/Importer.php @@ -21,7 +21,6 @@ abstract class Importer * Id of User performing import * @var */ - protected $created_by; /** * Are we updating items in the import @@ -149,17 +148,23 @@ public function import() { $headerRow = $this->csv->fetchOne(); $this->csv->setHeaderOffset(0); //explicitly sets the CSV document header record - $results = $this->normalizeInputArray($this->csv->getRecords($headerRow)); $this->populateCustomFields($headerRow); - DB::transaction(function () use (&$results) { + DB::transaction(function () use ($headerRow) { + $importedItemsCount = 0; Model::unguard(); - $resultsCount = count($results); - foreach ($results as $row) { + + foreach ($this->csv->getRecords($headerRow) as $row) { + //Lowercase header values to ensure we're comparing values properly. + $row = array_change_key_case($row, CASE_LOWER); + $this->handle($row); + + $importedItemsCount++; + if ($this->progressCallback) { - call_user_func($this->progressCallback, $resultsCount); + call_user_func($this->progressCallback, $importedItemsCount); } $this->log('------------- Action Summary ----------------'); @@ -237,22 +242,6 @@ public function lookupCustomKey($key) return $key; } - /** - * Used to lowercase header values to ensure we're comparing values properly. - * - * @param $results - * @return array - */ - public function normalizeInputArray($results) - { - $newArray = []; - foreach ($results as $index => $arrayToNormalize) { - $newArray[$index] = array_change_key_case($arrayToNormalize); - } - - return $newArray; - } - /** * Figure out the fieldname of the custom field * @@ -374,6 +363,7 @@ protected function createOrFetchUser($row, $type = 'user') // No luck finding a user on username or first name, let's create one. $user = new User; + $user->first_name = $user_array['first_name']; $user->last_name = $user_array['last_name']; $user->username = $user_array['username']; @@ -417,7 +407,7 @@ protected function findUserByNumber($user_name) * * @return self */ - public function setUserId($created_by) + public function setCreatedBy($created_by) { $this->created_by = $created_by; @@ -503,6 +493,16 @@ public function setUsernameFormat($usernameFormat) public function fetchHumanBoolean($value) { + $true = [ + 'yes', + 'y', + 'true', + ]; + + if (in_array(strtolower($value), $true)) { + return 1; + } + return (int) filter_var($value, FILTER_VALIDATE_BOOLEAN); } @@ -539,6 +539,7 @@ public function createOrFetchDepartment($user_department_name) return null; } + /** * Fetch an existing manager * diff --git a/app/Importer/ItemImporter.php b/app/Importer/ItemImporter.php index 360618f4f0a3..6b1d647136fc 100644 --- a/app/Importer/ItemImporter.php +++ b/app/Importer/ItemImporter.php @@ -94,7 +94,7 @@ protected function handle($row) $this->item['qty'] = $this->findCsvMatch($row, 'quantity'); $this->item['requestable'] = $this->findCsvMatch($row, 'requestable'); - $this->item['created_by'] = $this->created_by; + $this->item['created_by'] = auth()->id(); $this->item['serial'] = $this->findCsvMatch($row, 'serial'); // NO need to call this method if we're running the user import. // TODO: Merge these methods. @@ -113,7 +113,7 @@ protected function handle($row) protected function determineCheckout($row) { // Locations don't get checked out to anyone/anything - if (get_class($this) == LocationImporter::class) { + if ((get_class($this) == LocationImporter::class) || (get_class($this) == AssetModelImporter::class)) { return; } @@ -249,6 +249,7 @@ public function createOrFetchAssetModel(array $row) $this->log('No Matching Model, Creating a new one'); $asset_model = new AssetModel(); + $asset_model->created_by = auth()->id(); $item = $this->sanitizeItemForStoring($asset_model, $editingModel); $item['name'] = $asset_model_name; $item['model_number'] = $asset_modelNumber; @@ -256,11 +257,8 @@ public function createOrFetchAssetModel(array $row) $item['category_id'] = $this->createOrFetchCategory($asset_model_category); $asset_model->fill($item); - //$asset_model = AssetModel::firstOrNew($item); $item = null; - - if ($asset_model->save()) { $this->log('Asset Model '.$asset_model_name.' with model number '.$asset_modelNumber.' was created'); @@ -287,21 +285,28 @@ public function createOrFetchCategory($asset_category) $classname = class_basename(get_class($this)); $item_type = strtolower(substr($classname, 0, strpos($classname, 'Importer'))); + // If we're importing asset models only (without attached assets), override the category type to asset + if ($item_type == 'assetmodel') { + $item_type = 'asset'; + } + if (empty($asset_category)) { $asset_category = 'Unnamed Category'; } + + $category = Category::where(['name' => $asset_category, 'category_type' => $item_type])->first(); - if ($category) { - $this->log('A matching category: '.$asset_category.' already exists'); + if ($category) { + $this->log('A matching category: '.$category->name.' already exists'); return $category->id; } $category = new Category(); + $category->created_by = auth()->id(); $category->name = $asset_category; $category->category_type = $item_type; - $category->created_by = $this->created_by; if ($category->save()) { $this->log('Category '.$asset_category.' was created'); @@ -330,6 +335,7 @@ public function createOrFetchCompany($asset_company_name) return $company->id; } $company = new Company(); + $company->created_by = auth()->id(); $company->name = $asset_company_name; if ($company->save()) { @@ -386,6 +392,7 @@ public function createOrFetchStatusLabel($asset_statuslabel_name) } $this->log('Creating a new status'); $status = new Statuslabel(); + $status->created_by = auth()->id(); $status->name = trim($asset_statuslabel_name); $status->deployable = 1; @@ -425,7 +432,7 @@ public function createOrFetchManufacturer($item_manufacturer) //Otherwise create a manufacturer. $manufacturer = new Manufacturer(); $manufacturer->name = trim($item_manufacturer); - $manufacturer->created_by = $this->created_by; + $manufacturer->created_by = auth()->id(); if ($manufacturer->save()) { $this->log('Manufacturer '.$manufacturer->name.' was created'); @@ -466,7 +473,7 @@ public function createOrFetchLocation($asset_location) $location->city = ''; $location->state = ''; $location->country = ''; - $location->created_by = $this->created_by; + $location->created_by = auth()->id(); if ($location->save()) { $this->log('Location '.$asset_location.' was created'); @@ -502,7 +509,7 @@ public function createOrFetchSupplier($item_supplier) $supplier = new Supplier(); $supplier->name = $item_supplier; - $supplier->created_by = $this->created_by; + $supplier->created_by = auth()->id(); if ($supplier->save()) { $this->log('Supplier '.$item_supplier.' was created'); diff --git a/app/Importer/LicenseImporter.php b/app/Importer/LicenseImporter.php index 3f7bb9f85ceb..0dc7475478b8 100644 --- a/app/Importer/LicenseImporter.php +++ b/app/Importer/LicenseImporter.php @@ -84,6 +84,7 @@ public function createLicenseIfNotExists(array $row) $license->update($this->sanitizeItemForUpdating($license)); } else { $license->fill($this->sanitizeItemForStoring($license)); + $license->created_by = auth()->id(); } // This sets an attribute on the Loggable trait for the action log diff --git a/app/Importer/LocationImporter.php b/app/Importer/LocationImporter.php index b3ef59d24829..a9a515223449 100644 --- a/app/Importer/LocationImporter.php +++ b/app/Importer/LocationImporter.php @@ -51,6 +51,7 @@ public function createLocationIfNotExists(array $row) } else { $this->log('No Matching Location, Create a new one'); $location = new Location; + $location->created_by = auth()->id(); } // Pull the records from the CSV to determine their values @@ -65,7 +66,6 @@ public function createLocationIfNotExists(array $row) $this->item['ldap_ou'] = trim($this->findCsvMatch($row, 'ldap_ou')); $this->item['manager'] = trim($this->findCsvMatch($row, 'manager')); $this->item['manager_username'] = trim($this->findCsvMatch($row, 'manager_username')); - $this->item['created_by'] = auth()->id(); if ($this->findCsvMatch($row, 'parent_location')) { $this->item['parent_id'] = $this->createOrFetchLocation(trim($this->findCsvMatch($row, 'parent_location'))); diff --git a/app/Importer/UserImporter.php b/app/Importer/UserImporter.php index 036bf15c9a16..77317b3d09db 100644 --- a/app/Importer/UserImporter.php +++ b/app/Importer/UserImporter.php @@ -114,6 +114,7 @@ public function createUserIfNotExists(array $row) $this->log('No matching user, creating one'); $user = new User(); + $user->created_by = auth()->id(); $user->fill($this->sanitizeItemForStoring($user)); if ($user->save()) { diff --git a/app/Listeners/CheckoutableListener.php b/app/Listeners/CheckoutableListener.php index eb6b73809420..145abb168733 100644 --- a/app/Listeners/CheckoutableListener.php +++ b/app/Listeners/CheckoutableListener.php @@ -3,13 +3,20 @@ namespace App\Listeners; use App\Events\CheckoutableCheckedOut; +use App\Mail\CheckinAccessoryMail; +use App\Mail\CheckinLicenseMail; +use App\Mail\CheckoutAccessoryMail; +use App\Mail\CheckoutAssetMail; +use App\Mail\CheckinAssetMail; +use App\Mail\CheckoutConsumableMail; +use App\Mail\CheckoutLicenseMail; use App\Models\Accessory; use App\Models\Asset; use App\Models\CheckoutAcceptance; use App\Models\Component; use App\Models\Consumable; use App\Models\LicenseSeat; -use App\Models\Recipients\AdminRecipient; +use App\Models\Location; use App\Models\Setting; use App\Models\User; use App\Notifications\CheckinAccessoryNotification; @@ -20,9 +27,12 @@ use App\Notifications\CheckoutConsumableNotification; use App\Notifications\CheckoutLicenseSeatNotification; use GuzzleHttp\Exception\ClientException; +use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Notification; use Exception; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; +use Osama\LaravelTeamsNotification\TeamsNotification; class CheckoutableListener { @@ -43,33 +53,57 @@ public function onCheckedOut($event) /** * Make a checkout acceptance and attach it in the notification */ + $settings = Setting::getSettings(); $acceptance = $this->getCheckoutAcceptance($event); - $notifiables = $this->getNotifiables($event); + $adminCcEmailsArray = []; + if($settings->admin_cc_email !== '') { + $adminCcEmail = $settings->admin_cc_email; + $adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail)); + } + $ccEmails = array_filter($adminCcEmailsArray); + $mailable = $this->getCheckoutMailType($event, $acceptance); + $notifiable = $this->getNotifiables($event); + + if (!$event->checkedOutTo->locale){ + $mailable->locale($event->checkedOutTo->locale); + } // Send email notifications try { - foreach ($notifiables as $notifiable) { - if ($notifiable instanceof User && $notifiable->email != '') { - if (! $event->checkedOutTo->locale){ - Notification::locale(Setting::getSettings()->locale)->send($notifiable, $this->getCheckoutNotification($event, $acceptance)); - } - else { - Notification::send($notifiable, $this->getCheckoutNotification($event, $acceptance)); + /** + * Send an email if any of the following conditions are met: + * 1. The asset requires acceptance + * 2. The item has a EULA + * 3. The item should send an email at check-in/check-out + */ + + if ($event->checkoutable->requireAcceptance() || $event->checkoutable->getEula() || + $this->checkoutableShouldSendEmail($event)) { + Log::info('Sending checkout email, Locale: ' . ($event->checkedOutTo->locale ?? 'default')); + if (!empty($notifiable)) { + Mail::to($notifiable)->cc($ccEmails)->send($mailable); + } elseif (!empty($ccEmails)) { + Mail::cc($ccEmails)->send($mailable); } + Log::info('Checkout Mail sent.'); } - } - - // Send Webhook notification - if ($this->shouldSendWebhookNotification()) { - // Slack doesn't include the URL in its messaging format, so this is needed to hit the endpoint - if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general') { - Notification::route('slack', Setting::getSettings()->webhook_endpoint) - ->notify($this->getCheckoutNotification($event, $acceptance)); - } else { - Notification::route(Setting::getSettings()->webhook_selected, Setting::getSettings()->webhook_endpoint) - ->notify($this->getCheckoutNotification($event, $acceptance)); + } catch (ClientException $e) { + Log::debug("Exception caught during checkout email: " . $e->getMessage()); + } catch (Exception $e) { + Log::debug("Exception caught during checkout email: " . $e->getMessage()); + } +// Send Webhook notification + try{ + if ($this->shouldSendWebhookNotification()) { + if ($this->newMicrosoftTeamsWebhookEnabled()) { + $message = $this->getCheckoutNotification($event)->toMicrosoftTeams(); + $notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint); + $notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams + } else { + Notification::route(Setting::getSettings()->webhook_selected, Setting::getSettings()->webhook_endpoint) + ->notify($this->getCheckoutNotification($event, $acceptance)); + } } - } } catch (ClientException $e) { Log::debug("Exception caught during checkout notification: " . $e->getMessage()); } catch (Exception $e) { @@ -103,34 +137,57 @@ public function onCheckedIn($event) } } } + $settings = Setting::getSettings(); + $adminCcEmailsArray = []; - $notifiables = $this->getNotifiables($event); + if($settings->admin_cc_email !== '') { + $adminCcEmail = $settings->admin_cc_email; + $adminCcEmailsArray = array_map('trim', explode(',', $adminCcEmail)); + } + $ccEmails = array_filter($adminCcEmailsArray); + $mailable = $this->getCheckinMailType($event); + $notifiable = $this->getNotifiables($event); + if (!$event->checkedOutTo->locale){ + $mailable->locale($event->checkedOutTo->locale); + } // Send email notifications try { - foreach ($notifiables as $notifiable) { - if ($notifiable instanceof User && $notifiable->email != '') { - if (! $event->checkedOutTo->locale){ - Notification::locale(Setting::getSettings()->locale)->send($notifiable, $this->getCheckoutNotification($event, $acceptance)); - } - else { - Notification::send($notifiable, $this->getCheckinNotification($event)); + /** + * Send an email if any of the following conditions are met: + * 1. The asset requires acceptance + * 2. The item has a EULA + * 3. The item should send an email at check-in/check-out + */ + if ($event->checkoutable->requireAcceptance() || $event->checkoutable->getEula() || + $this->checkoutableShouldSendEmail($event)) { + Log::info('Sending checkin email, Locale: ' . ($event->checkedOutTo->locale ?? 'default')); + if (!empty($notifiable)) { + Mail::to($notifiable)->cc($ccEmails)->send($mailable); + } elseif (!empty($ccEmails)){ + Mail::cc($ccEmails)->send($mailable); } + Log::info('Checkin Mail sent.'); } - } - // Send Webhook notification + } catch (ClientException $e) { + Log::debug("Exception caught during checkin email: " . $e->getMessage()); + } catch (Exception $e) { + Log::debug("Exception caught during checkin email: " . $e->getMessage()); + } + + // Send Webhook notification + try { if ($this->shouldSendWebhookNotification()) { - // Slack doesn't include the URL in its messaging format, so this is needed to hit the endpoint - if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general') { - Notification::route('slack', Setting::getSettings()->webhook_endpoint) - ->notify($this->getCheckinNotification($event)); - } else { - Notification::route(Setting::getSettings()->webhook_selected, Setting::getSettings()->webhook_endpoint) - ->notify($this->getCheckinNotification($event)); + if ($this->newMicrosoftTeamsWebhookEnabled()) { + $message = $this->getCheckinNotification($event)->toMicrosoftTeams(); + $notification = new TeamsNotification(Setting::getSettings()->webhook_endpoint); + $notification->success()->sendMessage($message[0], $message[1]); // Send the message to Microsoft Teams + } else { + Notification::route(Setting::getSettings()->webhook_selected, Setting::getSettings()->webhook_endpoint) + ->notify($this->getCheckinNotification($event)); + } } - } - } catch (ClientException $e) { - Log::warning("Exception caught during checkout notification: " . $e->getMessage()); + Log::warning("Exception caught during checkin notification: " . $e->getMessage()); } catch (Exception $e) { Log::warning("Exception caught during checkin notification: " . $e->getMessage()); } @@ -159,33 +216,6 @@ private function getCheckoutAcceptance($event) return $acceptance; } - /** - * Gets the entities to be notified of the passed event - * - * @param Event $event - * @return Collection - */ - private function getNotifiables($event) - { - $notifiables = collect(); - - /** - * Notify who checked out the item as long as the model can route notifications - */ - if (method_exists($event->checkedOutTo, 'routeNotificationFor')) { - $notifiables->push($event->checkedOutTo); - } - - /** - * Notify Admin users if the settings is activated - */ - if ((Setting::getSettings()) && (Setting::getSettings()->admin_cc_email != '')) { - $notifiables->push(new AdminRecipient()); - } - - return $notifiables; - } - /** * Get the appropriate notification for the event * @@ -234,7 +264,7 @@ private function getCheckoutNotification($event, $acceptance = null) break; case Consumable::class: $notificationClass = CheckoutConsumableNotification::class; - break; + break; case LicenseSeat::class: $notificationClass = CheckoutLicenseSeatNotification::class; break; @@ -243,6 +273,43 @@ private function getCheckoutNotification($event, $acceptance = null) return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note); } + private function getCheckoutMailType($event, $acceptance){ + $lookup = [ + Accessory::class => CheckoutAccessoryMail::class, + Asset::class => CheckoutAssetMail::class, + LicenseSeat::class => CheckoutLicenseMail::class, + Consumable::class => CheckoutConsumableMail::class, + ]; + $mailable= $lookup[get_class($event->checkoutable)]; + + return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $event->note, $acceptance); + + } + private function getCheckinMailType($event){ + $lookup = [ + Accessory::class => CheckinAccessoryMail::class, + Asset::class => CheckinAssetMail::class, + LicenseSeat::class => CheckinLicenseMail::class, + ]; + + $mailable= $lookup[get_class($event->checkoutable)]; + + return new $mailable($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); + + } + private function getNotifiables($event){ + + if($event->checkedOutTo instanceof Asset){ + $event->checkedOutTo->load('assignedTo'); + return $event->checkedOutTo->assignedto?->email ?? ''; + } + else if($event->checkedOutTo instanceof Location) { + return $event->checkedOutTo->manager?->email ?? ''; + } + else{ + return $event->checkedOutTo?->email ?? ''; + } + } /** * Register the listeners for the subscriber. @@ -271,4 +338,17 @@ private function shouldSendWebhookNotification(): bool { return Setting::getSettings() && Setting::getSettings()->webhook_endpoint; } + + private function checkoutableShouldSendEmail($event): bool + { + if($event->checkoutable instanceof LicenseSeat){ + return $event->checkoutable->license->checkin_email(); + } + return (method_exists($event->checkoutable, 'checkin_email') && $event->checkoutable->checkin_email()); + } + + private function newMicrosoftTeamsWebhookEnabled(): bool + { + return Setting::getSettings()->webhook_selected === 'microsoft' && Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows'); + } } diff --git a/app/Livewire/CustomFieldSetDefaultValuesForModel.php b/app/Livewire/CustomFieldSetDefaultValuesForModel.php index 0ca733eb2459..f45b62ce1637 100644 --- a/app/Livewire/CustomFieldSetDefaultValuesForModel.php +++ b/app/Livewire/CustomFieldSetDefaultValuesForModel.php @@ -81,6 +81,12 @@ private function populatedSelectedValuesArray(): void { $this->fields->each(function ($field) { $this->selectedValues[$field->db_column] = $this->getSelectedValueForField($field); + + // if the element is a checkbox and the value was just sent to null, make it + // an array since Livewire can't bind to non-array values for checkboxes. + if ($field->element === 'checkbox' && is_null($this->selectedValues[$field->db_column])) { + $this->selectedValues[$field->db_column] = []; + } }); } diff --git a/app/Livewire/Importer.php b/app/Livewire/Importer.php index 3c6f7990ef7e..75b707b8ad24 100644 --- a/app/Livewire/Importer.php +++ b/app/Livewire/Importer.php @@ -73,6 +73,9 @@ private function getColumns($type) case 'asset': $results = $this->assets_fields; break; + case 'assetModel': + $results = $this->assetmodels_fields; + break; case 'accessory': $results = $this->accessories_fields; break; @@ -82,6 +85,9 @@ private function getColumns($type) case 'component': $results = $this->components_fields; break; + case 'consumable': + $results = $this->consumables_fields; + break; case 'license': $results = $this->licenses_fields; break; @@ -91,10 +97,14 @@ private function getColumns($type) case 'location': $results = $this->locations_fields; break; + case 'user': + $results = $this->users_fields; + break; default: $results = []; } asort($results, SORT_FLAG_CASE | SORT_STRING); + if ($type == "asset") { // add Custom Fields after a horizontal line $results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’"; @@ -107,6 +117,7 @@ private function getColumns($type) public function updatingTypeOfImport($type) { + // go through each header, find a matching field to try and map it to. foreach ($this->headerRow as $i => $header) { // do we have something mapped already? @@ -152,13 +163,14 @@ public function mount() { $this->authorize('import'); $this->importTypes = [ - 'asset' => trans('general.assets'), - 'accessory' => trans('general.accessories'), - 'consumable' => trans('general.consumables'), - 'component' => trans('general.components'), - 'license' => trans('general.licenses'), - 'user' => trans('general.users'), - 'location' => trans('general.locations'), + 'accessory' => trans('general.accessories'), + 'asset' => trans('general.assets'), + 'assetModel' => trans('general.asset_models'), + 'component' => trans('general.components'), + 'consumable' => trans('general.consumables'), + 'license' => trans('general.licenses'), + 'location' => trans('general.locations'), + 'user' => trans('general.users'), ]; /** @@ -196,7 +208,6 @@ public function mount() 'supplier' => trans('general.supplier'), 'purchase_cost' => trans('general.purchase_cost'), 'purchase_date' => trans('general.purchase_date'), - 'purchase_order' => trans('admin/licenses/form.purchase_order'), 'asset_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/general.asset')]), 'model_notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]), 'manufacturer' => trans('general.manufacturer'), @@ -332,6 +343,19 @@ public function mount() 'parent_location' => trans('admin/locations/table.parent'), ]; + $this->assetmodels_fields = [ + 'item_name' => trans('general.item_name_var', ['item' => trans('general.asset_model')]), + 'category' => trans('general.category'), + 'manufacturer' => trans('general.manufacturer'), + 'model_number' => trans('general.model_no'), + 'notes' => trans('general.item_notes', ['item' => trans('admin/hardware/form.model')]), + 'min_amt' => trans('mail.min_QTY'), + 'fieldset' => trans('admin/models/general.fieldset'), + 'eol' => trans('general.eol'), + 'requestable' => trans('admin/models/general.requestable'), + + ]; + // "real fieldnames" to a list of aliases for that field $this->aliases_fields = [ 'item_name' => @@ -360,6 +384,23 @@ public function mount() 'eol date', 'asset eol date', ], + 'eol' => + [ + 'eol', + 'EOL', + 'eol months', + ], + 'depreciation' => + [ + 'Depreciation', + 'depreciation', + ], + 'requestable' => + [ + 'requestable', + 'Requestable', + ], + 'gravatar' => [ 'gravatar', @@ -504,7 +545,6 @@ public function selectFile($id) if (!$this->activeFile) { $this->message = trans('admin/hardware/message.import.file_missing'); $this->message_type = 'danger'; - return; } @@ -519,6 +559,8 @@ public function selectFile($id) $this->field_map[] = null; // re-inject the 'nulls' if a file was imported with some 'Do Not Import' settings } } + + $this->file_id = $id; $this->import_errors = null; $this->statusText = null; diff --git a/app/Livewire/SlackSettingsForm.php b/app/Livewire/SlackSettingsForm.php index 45b8b7b41e61..909b2223add1 100644 --- a/app/Livewire/SlackSettingsForm.php +++ b/app/Livewire/SlackSettingsForm.php @@ -4,10 +4,11 @@ use GuzzleHttp\Client; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Str; use Livewire\Component; use App\Models\Setting; use App\Helpers\Helper; - +use Osama\LaravelTeamsNotification\TeamsNotification; class SlackSettingsForm extends Component { public $webhook_endpoint; @@ -19,6 +20,7 @@ class SlackSettingsForm extends Component public $webhook_placeholder; public $webhook_icon; public $webhook_selected; + public $teams_webhook_deprecated; public array $webhook_text; public Setting $setting; @@ -62,7 +64,7 @@ public function mount() { "name" => trans('admin/settings/general.ms_teams'), "icon" => "fa-brands fa-microsoft", "placeholder" => "https://abcd.webhook.office.com/webhookb2/XXXXXXX", - "link" => "https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=dotnet#create-incoming-webhooks-1", + "link" => "https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498", "test" => "msTeamTestWebhook" ), ]; @@ -79,15 +81,17 @@ public function mount() { $this->webhook_channel = $this->setting->webhook_channel; $this->webhook_botname = $this->setting->webhook_botname; $this->webhook_options = $this->setting->webhook_selected; - if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){ + $this->teams_webhook_deprecated = !Str::contains($this->webhook_endpoint, 'workflows'); + if($this->webhook_selected === 'microsoft' || $this->webhook_selected === 'google'){ $this->webhook_channel = '#NA'; } - if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){ $this->isDisabled= ''; } - + if($this->webhook_selected === 'microsoft' && $this->teams_webhook_deprecated) { + session()->flash('warning', 'The selected Microsoft Teams webhook URL will be deprecated Jan 31st, 2025. Please use a workflow URL. Microsofts Documentation on creating a workflow can be found here.'); + } } public function updated($field) { @@ -109,7 +113,11 @@ public function updatedWebhookSelected() { if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){ $this->webhook_channel = '#NA'; } + } + public function updatedwebhookEndpoint() + { + $this->teams_webhook_deprecated = !Str::contains($this->webhook_endpoint, 'workflows'); } private function isButtonDisabled() { @@ -126,7 +134,9 @@ private function isButtonDisabled() { public function render() { $this->isButtonDisabled(); + return view('livewire.slack-settings-form'); + } public function testWebhook(){ @@ -236,20 +246,32 @@ public function googleWebhookTest(){ } public function msTeamTestWebhook(){ - $payload = - [ - "@type" => "MessageCard", - "@context" => "http://schema.org/extensions", - "summary" => trans('mail.snipe_webhook_summary'), - "title" => trans('mail.snipe_webhook_test'), - "text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]), - ]; + try { - try { - $response = Http::withHeaders([ - 'content-type' => 'applications/json', - ])->post($this->webhook_endpoint, - $payload)->throw(); + if($this->teams_webhook_deprecated){ + //will use the deprecated webhook format + $payload = + [ + "@type" => "MessageCard", + "@context" => "http://schema.org/extensions", + "summary" => trans('mail.snipe_webhook_summary'), + "title" => trans('mail.snipe_webhook_test'), + "text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]), + ]; + $response = Http::withHeaders([ + 'content-type' => 'applications/json', + ])->post($this->webhook_endpoint, + $payload)->throw(); + } + else { + $notification = new TeamsNotification($this->webhook_endpoint); + $message = trans('general.webhook_test_msg', ['app' => $this->webhook_name]); + $notification->success()->sendMessage($message); + + $response = Http::withHeaders([ + 'content-type' => 'applications/json', + ])->post($this->webhook_endpoint); + } if(($response->getStatusCode() == 302)||($response->getStatusCode() == 301)){ return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint])); diff --git a/app/Mail/CheckinAccessoryMail.php b/app/Mail/CheckinAccessoryMail.php new file mode 100644 index 000000000000..cddd1bfab982 --- /dev/null +++ b/app/Mail/CheckinAccessoryMail.php @@ -0,0 +1,70 @@ +item = $accessory; + $this->target = $checkedOutTo; + $this->admin = $checkedInby; + $this->note = $note; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address')); + + return new Envelope( + from: $from, + subject: trans('mail.Accessory_Checkin_Notification'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'mail.markdown.checkin-accessory', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckinAssetMail.php b/app/Mail/CheckinAssetMail.php new file mode 100644 index 000000000000..d348100eba86 --- /dev/null +++ b/app/Mail/CheckinAssetMail.php @@ -0,0 +1,93 @@ +target = $checkedOutTo; + $this->item = $asset; + $this->admin = $checkedInBy; + $this->note = $note; + + $this->settings = Setting::getSettings(); + $this->expected_checkin = ''; + + if ($this->item->expected_checkin) { + $this->expected_checkin = Helper::getFormattedDateObject($this->item->expected_checkin, 'date', + false); + } + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address')); + + return new Envelope( + from: $from, + subject: trans('mail.Asset_Checkin_Notification'), + ); + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return Content + */ + public function content(): Content + { + $this->item->load('assetstatus'); + $fields = []; + + // Check if the item has custom fields associated with it + if (($this->item->model) && ($this->item->model->fieldset)) { + $fields = $this->item->model->fieldset->fields; + } + + return new Content( + markdown: 'mail.markdown.checkin-asset', + with: [ + 'item' => $this->item, + 'status' => $this->item->assetstatus?->name, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'fields' => $fields, + 'expected_checkin' => $this->expected_checkin, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckinLicenseMail.php b/app/Mail/CheckinLicenseMail.php new file mode 100644 index 000000000000..8ebc946c0029 --- /dev/null +++ b/app/Mail/CheckinLicenseMail.php @@ -0,0 +1,71 @@ +target = $checkedOutTo; + $this->item = $licenseSeat; + $this->admin = $checkedInBy; + $this->note = $note; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address')); + + return new Envelope( + from: $from, + subject: trans('mail.License_Checkin_Notification'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + return new Content( + markdown: 'mail.markdown.checkin-license', + with: [ + 'license_seat' => $this->item, + 'license' => $this->item->license, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutAccessoryMail.php b/app/Mail/CheckoutAccessoryMail.php new file mode 100644 index 000000000000..9d7d3dba051f --- /dev/null +++ b/app/Mail/CheckoutAccessoryMail.php @@ -0,0 +1,82 @@ +item = $accessory; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->checkout_qty = $accessory->checkout_qty; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address')); + + return new Envelope( + from: $from, + subject: (trans('mail.Accessory_Checkout_Notification')), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + Log::debug($this->item->getImageUrl()); + $eula = $this->item->getEula(); + $req_accept = $this->item->requireAcceptance(); + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + + return new Content( + markdown: 'mail.markdown.checkout-accessory', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + 'checkout_qty' => $this->checkout_qty, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutAssetMail.php b/app/Mail/CheckoutAssetMail.php new file mode 100644 index 000000000000..d5c5e23faebd --- /dev/null +++ b/app/Mail/CheckoutAssetMail.php @@ -0,0 +1,110 @@ +item = $asset; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + + $this->settings = Setting::getSettings(); + + $this->last_checkout = ''; + $this->expected_checkin = ''; + + if ($this->item->last_checkout) { + $this->last_checkout = Helper::getFormattedDateObject($this->item->last_checkout, 'date', + false); + } + + if ($this->item->expected_checkin) { + $this->expected_checkin = Helper::getFormattedDateObject($this->item->expected_checkin, 'date', + false); + } + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address')); + + return new Envelope( + from: $from, + subject: trans('mail.Asset_Checkout_Notification'), + ); + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return Content + */ + public function content(): Content + { + $this->item->load('assetstatus'); + $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; + $req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; + $fields = []; + + // Check if the item has custom fields associated with it + if (($this->item->model) && ($this->item->model->fieldset)) { + $fields = $this->item->model->fieldset->fields; + } + + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + + return new Content( + markdown: 'mail.markdown.checkout-asset', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'status' => $this->item->assetstatus?->name, + 'note' => $this->note, + 'target' => $this->target, + 'fields' => $fields, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + 'last_checkout' => $this->last_checkout, + 'expected_checkin' => $this->expected_checkin, + ], + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutConsumableMail.php b/app/Mail/CheckoutConsumableMail.php new file mode 100644 index 000000000000..ec68125ef36f --- /dev/null +++ b/app/Mail/CheckoutConsumableMail.php @@ -0,0 +1,84 @@ +item = $consumable; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + $this->qty = $consumable->checkout_qty; + + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address')); + + return new Envelope( + from: $from, + subject: trans('mail.Confirm_consumable_delivery'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + + $eula = $this->item->getEula(); + $req_accept = $this->item->requireAcceptance(); + + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + + return new Content( + markdown: 'mail.markdown.checkout-consumable', + with: [ + 'item' => $this->item, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + 'qty' => $this->qty, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Mail/CheckoutLicenseMail.php b/app/Mail/CheckoutLicenseMail.php new file mode 100644 index 000000000000..80b226d9bf01 --- /dev/null +++ b/app/Mail/CheckoutLicenseMail.php @@ -0,0 +1,80 @@ +item = $licenseSeat; + $this->admin = $checkedOutBy; + $this->note = $note; + $this->target = $checkedOutTo; + $this->acceptance = $acceptance; + + $this->settings = Setting::getSettings(); + } + + /** + * Get the message envelope. + */ + public function envelope(): Envelope + { + $from = new Address(config('mail.from.address')); + + return new Envelope( + from: $from, + subject: trans('mail.Confirm_license_delivery'), + ); + } + + /** + * Get the message content definition. + */ + public function content(): Content + { + $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; + $req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; + + $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); + return new Content( + markdown: 'mail.markdown.checkout-license', + with: [ + 'license_seat' => $this->item, + 'license' => $this->item->license, + 'admin' => $this->admin, + 'note' => $this->note, + 'target' => $this->target, + 'eula' => $eula, + 'req_accept' => $req_accept, + 'accept_url' => $accept_url, + ] + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/Actionlog.php b/app/Models/Actionlog.php index 0831352b87bb..008c5b11462c 100755 --- a/app/Models/Actionlog.php +++ b/app/Models/Actionlog.php @@ -55,6 +55,8 @@ class Actionlog extends SnipeModel 'created_by', 'remote_ip', 'user_agent', + 'item_type', + 'target_type', 'action_source' ]; @@ -64,10 +66,10 @@ class Actionlog extends SnipeModel * @var array */ protected $searchableRelations = [ - 'company' => ['name'], - 'adminuser' => ['first_name','last_name','username', 'email'], - 'user' => ['first_name','last_name','username', 'email'], - 'assets' => ['asset_tag','name'], + 'company' => ['name'], + 'adminuser' => ['first_name','last_name','username', 'email'], + 'user' => ['first_name','last_name','username', 'email'], + 'assets' => ['asset_tag','name'], ]; /** diff --git a/app/Models/Asset.php b/app/Models/Asset.php index ce8b870eb2e0..862b99436252 100644 --- a/app/Models/Asset.php +++ b/app/Models/Asset.php @@ -119,7 +119,8 @@ public function declinedCheckout(User $declinedBy, $signature) 'byod' => ['nullable', 'boolean'], 'order_number' => ['nullable', 'string', 'max:191'], 'notes' => ['nullable', 'string', 'max:65535'], - 'assigned_to' => ['nullable', 'integer'], + 'assigned_to' => ['nullable', 'integer', 'required_with:assigned_type'], + 'assigned_type' => ['nullable', 'required_with:assigned_to', 'in:'.User::class.",".Location::class.",".Asset::class], 'requestable' => ['nullable', 'boolean'], 'assigned_user' => ['nullable', 'exists:users,id,deleted_at,NULL'], 'assigned_location' => ['nullable', 'exists:locations,id,deleted_at,NULL'], diff --git a/app/Models/AssetModel.php b/app/Models/AssetModel.php index 0c8f8e7b3c4b..02b5df40d13b 100755 --- a/app/Models/AssetModel.php +++ b/app/Models/AssetModel.php @@ -68,6 +68,7 @@ class AssetModel extends SnipeModel 'model_number', 'name', 'notes', + 'requestable', ]; use Searchable; @@ -328,4 +329,14 @@ public function scopeOrderFieldset($query, $order) { return $query->leftJoin('custom_fieldsets', 'models.fieldset_id', '=', 'custom_fieldsets.id')->orderBy('custom_fieldsets.name', $order); } + + /** + * Query builder scope to order on created_by name + * + */ + public function scopeOrderByCreatedByName($query, $order) + { + return $query->leftJoin('users as admin_sort', 'models.created_by', '=', 'admin_sort.id')->select('models.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); + } + } diff --git a/app/Models/Company.php b/app/Models/Company.php index 171d559542e4..8886da77f6c0 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -116,7 +116,7 @@ public static function getIdForCurrentUser($unescaped_input) if ($current_user->company_id != null) { return $current_user->company_id; } else { - return static::getIdFromInput($unescaped_input); + return null; } } } diff --git a/app/Models/Component.php b/app/Models/Component.php index 761c76f09715..fb77bf082412 100644 --- a/app/Models/Component.php +++ b/app/Models/Component.php @@ -38,6 +38,7 @@ class Component extends SnipeModel 'min_amt' => 'integer|min:0|nullable', 'purchase_date' => 'date_format:Y-m-d|nullable', 'purchase_cost' => 'numeric|nullable|gte:0|max:9999999999999', + 'manufacturer_id' => 'integer|exists:manufacturers,id|nullable', ]; /** @@ -60,6 +61,8 @@ class Component extends SnipeModel 'company_id', 'supplier_id', 'location_id', + 'manufacturer_id', + 'model_number', 'name', 'purchase_cost', 'purchase_date', @@ -77,7 +80,15 @@ class Component extends SnipeModel * * @var array */ - protected $searchableAttributes = ['name', 'order_number', 'serial', 'purchase_cost', 'purchase_date', 'notes']; + protected $searchableAttributes = [ + 'name', + 'order_number', + 'serial', + 'purchase_cost', + 'purchase_date', + 'notes', + 'model_number', + ]; /** * The relations and their attributes that should be included when searching the model. @@ -89,6 +100,7 @@ class Component extends SnipeModel 'company' => ['name'], 'location' => ['name'], 'supplier' => ['name'], + 'manufacturer' => ['name'], ]; @@ -183,6 +195,19 @@ public function supplier() return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id'); } + + /** + * Establishes the item -> manufacturer relationship + * + * @author [A. Gianotto] [] + * @since [v3.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function manufacturer() + { + return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id'); + } + /** * Establishes the component -> action logs relationship * @@ -311,6 +336,19 @@ public function scopeOrderSupplier($query, $order) return $query->leftJoin('suppliers', 'components.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order); } + /** + * Query builder scope to order on manufacturer + * + * @param \Illuminate\Database\Query\Builder $query Query builder instance + * @param text $order Order + * + * @return \Illuminate\Database\Query\Builder Modified query builder + */ + public function scopeOrderManufacturer($query, $order) + { + return $query->leftJoin('manufacturers', 'components.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order); + } + public function scopeOrderByCreatedBy($query, $order) { return $query->leftJoin('users as admin_sort', 'components.created_by', '=', 'admin_sort.id')->select('components.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); diff --git a/app/Models/CustomFieldset.php b/app/Models/CustomFieldset.php index 71be28e8a3ae..d6bd7a1bef9d 100644 --- a/app/Models/CustomFieldset.php +++ b/app/Models/CustomFieldset.php @@ -2,6 +2,8 @@ namespace App\Models; +use App\Rules\AlphaEncrypted; +use App\Rules\NumericEncrypted; use Gate; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -95,6 +97,19 @@ public function validation_rules() array_push($rule, $field->attributes['format']); $rules[$field->db_column_name()] = $rule; + + // these are to replace the standard 'numeric' and 'alpha' rules if the custom field is also encrypted. + // the values need to be decrypted first, because encrypted strings are alphanumeric + if ($field->format === 'NUMERIC' && $field->field_encrypted) { + $numericKey = array_search('numeric', $rules[$field->db_column_name()]); + $rules[$field->db_column_name()][$numericKey] = new NumericEncrypted; + } + + if ($field->format === 'ALPHA' && $field->field_encrypted) { + $alphaKey = array_search('alpha', $rules[$field->db_column_name()]); + $rules[$field->db_column_name()][$alphaKey] = new AlphaEncrypted; + } + // add not_array to rules for all fields but checkboxes if ($field->element != 'checkbox') { $rules[$field->db_column_name()][] = 'not_array'; diff --git a/app/Models/Import.php b/app/Models/Import.php index 052612a197ca..d824a3840cb4 100644 --- a/app/Models/Import.php +++ b/app/Models/Import.php @@ -14,4 +14,16 @@ class Import extends Model 'first_row' => 'array', 'field_map' => 'json', ]; + + /** + * Establishes the license -> admin user relationship + * + * @author A. Gianotto + * @since [v2.0] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function adminuser() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } } diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php index 9b56012f7a08..9f5fa735e4d1 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_1933081.php @@ -49,14 +49,7 @@ public function write($pdf, $record) { ); $currentX += $barcodeSize + self::BARCODE_MARGIN; $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; - } else { - static::writeText( - $pdf, $record->get('tag'), - $pa->x1, $pa->y2 - self::TAG_SIZE, - 'freesans', 'b', self::TAG_SIZE, 'R', - $usableWidth, self::TAG_SIZE, true, 0 - ); - } + } if ($record->has('title')) { static::writeText( diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php index e1305bd068a4..117486a8e567 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_2112283.php @@ -49,13 +49,6 @@ public function write($pdf, $record) { ); $currentX += $barcodeSize + self::BARCODE_MARGIN; $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; - } else { - static::writeText( - $pdf, $record->get('tag'), - $pa->x1, $pa->y2 - self::TAG_SIZE, - 'freesans', 'b', self::TAG_SIZE, 'R', - $usableWidth, self::TAG_SIZE, true, 0 - ); } if ($record->has('title')) { diff --git a/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php b/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php index d5f0e8d12229..ed8074547be2 100644 --- a/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php +++ b/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php @@ -50,13 +50,6 @@ public function write($pdf, $record) { ); $currentX += $barcodeSize + self::BARCODE_MARGIN; $usableWidth -= $barcodeSize + self::BARCODE_MARGIN; - } else { - static::writeText( - $pdf, $record->get('tag'), - $pa->x1, $pa->y2 - self::TAG_SIZE, - 'freemono', 'b', self::TAG_SIZE, 'R', - $usableWidth, self::TAG_SIZE, true, 0 - ); } if ($record->has('title')) { diff --git a/app/Models/License.php b/app/Models/License.php index 4923072f070d..0997c1e57b99 100755 --- a/app/Models/License.php +++ b/app/Models/License.php @@ -184,7 +184,7 @@ public static function adjustSeatCount($license, $oldSeats, $newSeats) $logAction->item_type = self::class; $logAction->item_id = $license->id; $logAction->created_by = auth()->id() ?: 1; // We don't have an id while running the importer from CLI. - $logAction->note = "deleted ${change} seats"; + $logAction->note = "deleted {$change} seats"; $logAction->target_id = null; $logAction->logaction('delete seats'); @@ -216,7 +216,7 @@ public static function adjustSeatCount($license, $oldSeats, $newSeats) $logAction->item_type = self::class; $logAction->item_id = $license->id; $logAction->created_by = auth()->id() ?: 1; // Importer. - $logAction->note = "added ${change} seats"; + $logAction->note = "added {$change} seats"; $logAction->target_id = null; $logAction->logaction('add seats'); } @@ -743,4 +743,4 @@ public function scopeOrderByCreatedBy($query, $order) { return $query->leftJoin('users as admin_sort', 'licenses.created_by', '=', 'admin_sort.id')->select('licenses.*')->orderBy('admin_sort.first_name', $order)->orderBy('admin_sort.last_name', $order); } -} \ No newline at end of file +} diff --git a/app/Models/Manufacturer.php b/app/Models/Manufacturer.php index 6e72b3a2bb5f..1b31f496d3b8 100755 --- a/app/Models/Manufacturer.php +++ b/app/Models/Manufacturer.php @@ -78,6 +78,7 @@ public function isDeletable() && (($this->licenses_count ?? $this->licenses()->count()) === 0) && (($this->consumables_count ?? $this->consumables()->count()) === 0) && (($this->accessories_count ?? $this->accessories()->count()) === 0) + && (($this->components_count ?? $this->components()->count()) === 0) && ($this->deleted_at == ''); } @@ -106,6 +107,10 @@ public function consumables() return $this->hasMany(\App\Models\Consumable::class, 'manufacturer_id'); } + public function components() + { + return $this->hasMany(\App\Models\Component::class, 'manufacturer_id'); + } public function adminuser() { diff --git a/app/Models/Recipients/AdminRecipient.php b/app/Models/Recipients/AdminRecipient.php index 433bd002094a..90e39d4ee53e 100644 --- a/app/Models/Recipients/AdminRecipient.php +++ b/app/Models/Recipients/AdminRecipient.php @@ -6,9 +6,15 @@ class AdminRecipient extends Recipient { + + protected $email; public function __construct() { $settings = Setting::getSettings(); $this->email = trim($settings->admin_cc_email); } + + public function getEmail(){ + return $this->email; + } } diff --git a/app/Models/Setting.php b/app/Models/Setting.php index 6f585b95f8fa..232285fbdb32 100755 --- a/app/Models/Setting.php +++ b/app/Models/Setting.php @@ -70,6 +70,7 @@ class Setting extends Model protected $casts = [ 'label2_asset_logo' => 'boolean', + 'require_checkinout_notes' => 'boolean', ]; /** diff --git a/app/Notifications/CheckinAccessoryNotification.php b/app/Notifications/CheckinAccessoryNotification.php index 7e033f1870bd..28e6c054f7ad 100644 --- a/app/Notifications/CheckinAccessoryNotification.php +++ b/app/Notifications/CheckinAccessoryNotification.php @@ -6,9 +6,11 @@ use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -55,22 +57,9 @@ public function via() } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { - $notifyBy[] = 'slack'; + $notifyBy[] = SlackWebhookChannel::class; } - /** - * Only send notifications to users that have email addresses - */ - if ($this->target instanceof User && $this->target->email != '') { - Log::debug('The target is a user'); - - if ($this->item->checkin_email()) { - $notifyBy[] = 'mail'; - } - } - - Log::debug('checkin_email on this category is '.$this->item->checkin_email()); - return $notifyBy; } @@ -103,18 +92,29 @@ public function toMicrosoftTeams() $admin = $this->admin; $item = $this->item; $note = $this->note; + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('Accessory_Checkin_Notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') + ->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '') + ->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) + ->fact(trans('mail.notes'), $note ?: ''); + } - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->addStartGroupToSection('activityTitle') - ->title(trans('Accessory_Checkin_Notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '') - ->fact(trans('mail.Accessory_Checkin_Notification')." by ", $admin->present()->fullName()) - ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) - ->fact(trans('mail.notes'), $note ?: ''); + $message = trans('mail.Accessory_Checkin_Notification'); + $details = [ + trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name), + trans('mail.checked_into') => $item->location->name ? $item->location->name : '', + trans('mail.Accessory_Checkin_Notification'). ' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining')=> $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + return array($message, $details); } public function toGoogleChat() { @@ -142,24 +142,4 @@ public function toGoogleChat() ); } - - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - Log::debug('to email called'); - - return (new MailMessage)->markdown('notifications.markdown.checkin-accessory', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - ]) - ->subject(trans('mail.Accessory_Checkin_Notification')); - } } diff --git a/app/Notifications/CheckinAssetNotification.php b/app/Notifications/CheckinAssetNotification.php index 77cd6d9b5a87..fa4780c1fd92 100644 --- a/app/Notifications/CheckinAssetNotification.php +++ b/app/Notifications/CheckinAssetNotification.php @@ -7,9 +7,11 @@ use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -50,7 +52,6 @@ public function __construct(Asset $asset, $checkedOutTo, User $checkedInBy, $not */ public function via() { - $notifyBy = []; if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { $notifyBy[] = GoogleChatChannel::class; @@ -62,15 +63,7 @@ public function via() } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { Log::debug('use webhook'); - $notifyBy[] = 'slack'; - } - - /** - * Only send checkin notifications to users if the category - * has the corresponding checkbox checked. - */ - if ($this->item->checkin_email() && $this->target instanceof User && $this->target->email != '') { - $notifyBy[] = 'mail'; + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; @@ -106,16 +99,30 @@ public function toMicrosoftTeams() $item = $this->item; $note = $this->note; - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->title(trans('mail.Asset_Checkin_Notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') - ->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '') - ->fact(trans('mail.Asset_Checkin_Notification')." by ", $admin->present()->fullName()) - ->fact(trans('admin/hardware/form.status'), $item->assetstatus->name) - ->fact(trans('mail.notes'), $note ?: ''); + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->title(trans('mail.Asset_Checkin_Notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') + ->fact(trans('mail.checked_into'), $item->location->name ? $item->location->name : '') + ->fact(trans('mail.Asset_Checkin_Notification') . " by ", $admin->present()->fullName()) + ->fact(trans('admin/hardware/form.status'), $item->assetstatus->name) + ->fact(trans('mail.notes'), $note ?: ''); + } + + + $message = trans('mail.Asset_Checkin_Notification'); + $details = [ + trans('mail.asset') => htmlspecialchars_decode($item->present()->name), + trans('mail.checked_into') => $item->location->name ? $item->location->name : '', + trans('mail.Asset_Checkin_Notification')." by " => $admin->present()->fullName(), + trans('admin/hardware/form.status') => $item->assetstatus->name, + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); } public function toGoogleChat() { @@ -142,35 +149,5 @@ public function toGoogleChat() ) ) ); - - } - - /** - * Get the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - $fields = []; - - // Check if the item has custom fields associated with it - if (($this->item->model) && ($this->item->model->fieldset)) { - $fields = $this->item->model->fieldset->fields; - } - - $message = (new MailMessage)->markdown('notifications.markdown.checkin-asset', - [ - 'item' => $this->item, - 'status' => $this->item->assetstatus?->name, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - 'fields' => $fields, - 'expected_checkin' => $this->expected_checkin, - ]) - ->subject(trans('mail.Asset_Checkin_Notification')); - - return $message; } } diff --git a/app/Notifications/CheckinLicenseSeatNotification.php b/app/Notifications/CheckinLicenseSeatNotification.php index 289e63a16247..1cb8706e6716 100644 --- a/app/Notifications/CheckinLicenseSeatNotification.php +++ b/app/Notifications/CheckinLicenseSeatNotification.php @@ -6,9 +6,11 @@ use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -58,15 +60,7 @@ public function via() } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { - $notifyBy[] = 'slack'; - } - - /** - * Only send checkin notifications to users if the category - * has the corresponding checkbox checked. - */ - if ($this->item->checkin_email() && $this->target instanceof User && $this->target->email != '') { - $notifyBy[] = 'mail'; + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; @@ -109,18 +103,30 @@ public function toMicrosoftTeams() $admin = $this->admin; $item = $this->item; $note = $this->note; + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.License_Checkin_Notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'header') + ->fact(trans('mail.License_Checkin_Notification')." by ", $admin->present()->fullName() ?: 'CLI tool') + ->fact(trans('mail.checkedin_from'), $target->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) + ->fact(trans('mail.notes'), $note ?: ''); + } - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->addStartGroupToSection('activityTitle') - ->title(trans('mail.License_Checkin_Notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'header') - ->fact(trans('mail.License_Checkin_Notification')." by ", $admin->present()->fullName() ?: 'CLI tool') - ->fact(trans('mail.checkedin_from'), $target->present()->fullName()) - ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) - ->fact(trans('mail.notes'), $note ?: ''); + $message = trans('mail.License_Checkin_Notification'); + $details = [ + trans('mail.checkedin_from')=> $target->present()->fullName(), + trans('mail.license_for') => htmlspecialchars_decode($item->present()->name), + trans('mail.License_Checkin_Notification')." by " => $admin->present()->fullName() ?: 'CLI tool', + trans('admin/consumables/general.remaining') => $item->availCount()->count(), + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); } public function toGoogleChat() { @@ -149,23 +155,4 @@ public function toGoogleChat() ); } - - - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - return (new MailMessage)->markdown('notifications.markdown.checkin-license', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - ]) - ->subject(trans('mail.License_Checkin_Notification')); - } } diff --git a/app/Notifications/CheckoutAccessoryNotification.php b/app/Notifications/CheckoutAccessoryNotification.php index 721ba7f6a4b5..116a5ac29ff9 100644 --- a/app/Notifications/CheckoutAccessoryNotification.php +++ b/app/Notifications/CheckoutAccessoryNotification.php @@ -9,6 +9,7 @@ use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -120,6 +121,7 @@ public function toMicrosoftTeams() $item = $this->item; $note = $this->note; + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { return MicrosoftTeamsMessage::create() ->to($this->settings->webhook_endpoint) ->type('success') @@ -133,7 +135,19 @@ public function toMicrosoftTeams() ->fact(trans('mail.Accessory_Checkout_Notification') . " by ", $admin->present()->fullName()) ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) ->fact(trans('mail.notes'), $note ?: ''); + } + $message = trans('mail.Accessory_Checkout_Notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->name, + trans('mail.accessory_name') => htmlspecialchars_decode($item->present()->name), + trans('general.qty') => $this->checkout_qty, + trans('mail.checkedout_from') => $item->location->name ? $item->location->name : '', + trans('mail.Accessory_Checkout_Notification'). ' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining')=> $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + return array($message, $details); } public function toGoogleChat() { diff --git a/app/Notifications/CheckoutAssetNotification.php b/app/Notifications/CheckoutAssetNotification.php index b14796fb8c0f..61499e62f2e6 100644 --- a/app/Notifications/CheckoutAssetNotification.php +++ b/app/Notifications/CheckoutAssetNotification.php @@ -8,9 +8,10 @@ use App\Models\User; use Exception; use Illuminate\Bus\Queueable; -use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\Enums\Icon; use NotificationChannels\GoogleChat\Enums\ImageStyle; @@ -21,6 +22,9 @@ use NotificationChannels\MicrosoftTeams\MicrosoftTeamsChannel; use NotificationChannels\MicrosoftTeams\MicrosoftTeamsMessage; use Illuminate\Support\Facades\Log; +use Osama\LaravelTeamsNotification\Logging\TeamsLoggingChannel; +use Osama\LaravelTeamsNotification\TeamsNotification; + class CheckoutAssetNotification extends Notification { use Queueable; @@ -32,14 +36,11 @@ class CheckoutAssetNotification extends Notification */ public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $acceptance, $note) { + $this->settings = Setting::getSettings(); $this->item = $asset; $this->admin = $checkedOutBy; $this->note = $note; $this->target = $checkedOutTo; - $this->acceptance = $acceptance; - - $this->settings = Setting::getSettings(); - $this->last_checkout = ''; $this->expected_checkin = ''; @@ -53,7 +54,6 @@ public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $ac false); } } - /** * Get the notification's delivery channels. * @@ -62,61 +62,34 @@ public function __construct(Asset $asset, $checkedOutTo, User $checkedOutBy, $ac public function via() { $notifyBy = []; - if (Setting::getSettings()->webhook_selected == 'google' && Setting::getSettings()->webhook_endpoint) { + + if (Setting::getSettings()->webhook_selected === 'google' && Setting::getSettings()->webhook_endpoint) { $notifyBy[] = GoogleChatChannel::class; } - if (Setting::getSettings()->webhook_selected == 'microsoft' && Setting::getSettings()->webhook_endpoint) { + if (Setting::getSettings()->webhook_selected === 'microsoft' && Setting::getSettings()->webhook_endpoint) { $notifyBy[] = MicrosoftTeamsChannel::class; } - if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { + if (Setting::getSettings()->webhook_selected === 'slack' || Setting::getSettings()->webhook_selected === 'general' ) { Log::debug('use webhook'); - $notifyBy[] = 'slack'; - } - - /** - * Only send notifications to users that have email addresses - */ - if ($this->target instanceof User && $this->target->email != '') { - - /** - * Send an email if the asset requires acceptance, - * so the user can accept or decline the asset - */ - if ($this->item->requireAcceptance()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if the item has a EULA, since the user should always receive it - */ - if ($this->item->getEula()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if an email should be sent at checkin/checkout - */ - if ($this->item->checkin_email()) { - $notifyBy[1] = 'mail'; - } + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; } - public function toSlack() + public function toSlack() :SlackMessage { $target = $this->target; $admin = $this->admin; $item = $this->item; $note = $this->note; - $botname = ($this->settings->webhook_botname) ? $this->settings->webhook_botname : 'Snipe-Bot'; + $botname = ($this->settings->webhook_botname) ?: 'Snipe-Bot'; $channel = ($this->settings->webhook_channel) ? $this->settings->webhook_channel : ''; $fields = [ @@ -124,7 +97,7 @@ public function toSlack() 'By' => '<'.$admin->present()->viewUrl().'|'.$admin->present()->fullName().'>', ]; - if (($this->expected_checkin) && ($this->expected_checkin != '')) { + if (($this->expected_checkin) && ($this->expected_checkin !== '')) { $fields['Expected Checkin'] = $this->expected_checkin; } @@ -138,6 +111,7 @@ public function toSlack() ->content($note); }); } + public function toMicrosoftTeams() { $target = $this->target; @@ -145,17 +119,26 @@ public function toMicrosoftTeams() $item = $this->item; $note = $this->note; - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->title(trans('mail.Asset_Checkout_Notification')) - ->addStartGroupToSection('activityText') - ->fact(trans('mail.assigned_to'), $target->present()->name) - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') - ->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->present()->fullName()) - ->fact(trans('mail.notes'), $note ?: ''); - + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->title(trans('mail.Asset_Checkout_Notification')) + ->addStartGroupToSection('activityText') + ->fact(trans('mail.assigned_to'), $target->present()->name) + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityText') + ->fact(trans('mail.Asset_Checkout_Notification') . " by ", $admin->present()->fullName()) + ->fact(trans('mail.notes'), $note ?: ''); + } + $message = trans('mail.Asset_Checkout_Notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->name, + trans('mail.asset') => htmlspecialchars_decode($item->present()->name), + trans('mail.Asset_Checkout_Notification'). ' by' => $admin->present()->fullName(), + trans('mail.notes') => $note ?: '', + ]; + return array($message, $details); } public function toGoogleChat() { @@ -184,42 +167,4 @@ public function toGoogleChat() ); } - - /** - * Get the mail representation of the notification. - * - * @param mixed $notifiable - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { $this->item->load('assetstatus'); - $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; - $req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; - $fields = []; - - // Check if the item has custom fields associated with it - if (($this->item->model) && ($this->item->model->fieldset)) { - $fields = $this->item->model->fieldset->fields; - } - - $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); - - $message = (new MailMessage)->markdown('notifications.markdown.checkout-asset', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'status' => $this->item->assetstatus?->name, - 'note' => $this->note, - 'target' => $this->target, - 'fields' => $fields, - 'eula' => $eula, - 'req_accept' => $req_accept, - 'accept_url' => $accept_url, - 'last_checkout' => $this->last_checkout, - 'expected_checkin' => $this->expected_checkin, - ]) - ->subject(trans('mail.Confirm_asset_delivery')); - - return $message; - } } diff --git a/app/Notifications/CheckoutConsumableNotification.php b/app/Notifications/CheckoutConsumableNotification.php index 6746795f2c63..ba7c5646abe6 100644 --- a/app/Notifications/CheckoutConsumableNotification.php +++ b/app/Notifications/CheckoutConsumableNotification.php @@ -6,9 +6,11 @@ use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -38,6 +40,7 @@ public function __construct(Consumable $consumable, $checkedOutTo, User $checked $this->note = $note; $this->target = $checkedOutTo; $this->acceptance = $acceptance; + $this->qty = $consumable->checkout_qty; $this->settings = Setting::getSettings(); } @@ -61,35 +64,7 @@ public function via() } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { - $notifyBy[] = 'slack'; - } - - /** - * Only send notifications to users that have email addresses - */ - if ($this->target instanceof User && $this->target->email != '') { - - /** - * Send an email if the asset requires acceptance, - * so the user can accept or decline the asset - */ - if ($this->item->requireAcceptance()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if the item has a EULA, since the user should always receive it - */ - if ($this->item->getEula()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if an email should be sent at checkin/checkout - */ - if ((method_exists($this->item, 'checkin_email')) && ($this->item->checkin_email())) { - $notifyBy[1] = 'mail'; - } + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; @@ -126,17 +101,30 @@ public function toMicrosoftTeams() $item = $this->item; $note = $this->note; - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->addStartGroupToSection('activityTitle') - ->title(trans('mail.Consumable_checkout_notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->present()->fullName()) - ->fact(trans('mail.assigned_to'), $target->present()->fullName()) - ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) - ->fact(trans('mail.notes'), $note ?: ''); + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.Consumable_checkout_notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') + ->fact(trans('mail.Consumable_checkout_notification')." by ", $admin->present()->fullName()) + ->fact(trans('mail.assigned_to'), $target->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->numRemaining()) + ->fact(trans('mail.notes'), $note ?: ''); + } + + $message = trans('mail.Consumable_checkout_notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->fullName(), + trans('mail.item') => htmlspecialchars_decode($item->present()->name), + trans('mail.Consumable_checkout_notification').' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining') => $item->numRemaining(), + trans('mail.notes') => $note ?: '', + ]; + + return array($message, $details); } public function toGoogleChat() { @@ -165,30 +153,4 @@ public function toGoogleChat() ); } - - /** - * Get the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - Log::debug($this->item->getImageUrl()); - $eula = $this->item->getEula(); - $req_accept = $this->item->requireAcceptance(); - - $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); - - return (new MailMessage)->markdown('notifications.markdown.checkout-consumable', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - 'eula' => $eula, - 'req_accept' => $req_accept, - 'accept_url' => $accept_url, - ]) - ->subject(trans('mail.Confirm_consumable_delivery')); - } } diff --git a/app/Notifications/CheckoutLicenseSeatNotification.php b/app/Notifications/CheckoutLicenseSeatNotification.php index 8e0273c66e7d..1aed0d200409 100644 --- a/app/Notifications/CheckoutLicenseSeatNotification.php +++ b/app/Notifications/CheckoutLicenseSeatNotification.php @@ -6,9 +6,11 @@ use App\Models\Setting; use App\Models\User; use Illuminate\Bus\Queueable; +use Illuminate\Notifications\Channels\SlackWebhookChannel; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Notification; +use Illuminate\Support\Str; use NotificationChannels\GoogleChat\Card; use NotificationChannels\GoogleChat\GoogleChatChannel; use NotificationChannels\GoogleChat\GoogleChatMessage; @@ -60,35 +62,7 @@ public function via() } if (Setting::getSettings()->webhook_selected == 'slack' || Setting::getSettings()->webhook_selected == 'general' ) { - $notifyBy[] = 'slack'; - } - - /** - * Only send notifications to users that have email addresses - */ - if ($this->target instanceof User && $this->target->email != '') { - - /** - * Send an email if the asset requires acceptance, - * so the user can accept or decline the asset - */ - if ($this->item->requireAcceptance()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if the item has a EULA, since the user should always receive it - */ - if ($this->item->getEula()) { - $notifyBy[1] = 'mail'; - } - - /** - * Send an email if an email should be sent at checkin/checkout - */ - if ($this->item->checkin_email()) { - $notifyBy[1] = 'mail'; - } + $notifyBy[] = SlackWebhookChannel::class; } return $notifyBy; @@ -125,17 +99,29 @@ public function toMicrosoftTeams() $item = $this->item; $note = $this->note; - return MicrosoftTeamsMessage::create() - ->to($this->settings->webhook_endpoint) - ->type('success') - ->addStartGroupToSection('activityTitle') - ->title(trans('mail.License_Checkout_Notification')) - ->addStartGroupToSection('activityText') - ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') - ->fact(trans('mail.License_Checkout_Notification')." by ", $admin->present()->fullName()) - ->fact(trans('mail.assigned_to'), $target->present()->fullName()) - ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) - ->fact(trans('mail.notes'), $note ?: ''); + if(!Str::contains(Setting::getSettings()->webhook_endpoint, 'workflows')) { + return MicrosoftTeamsMessage::create() + ->to($this->settings->webhook_endpoint) + ->type('success') + ->addStartGroupToSection('activityTitle') + ->title(trans('mail.License_Checkout_Notification')) + ->addStartGroupToSection('activityText') + ->fact(htmlspecialchars_decode($item->present()->name), '', 'activityTitle') + ->fact(trans('mail.License_Checkout_Notification')." by ", $admin->present()->fullName()) + ->fact(trans('mail.assigned_to'), $target->present()->fullName()) + ->fact(trans('admin/consumables/general.remaining'), $item->availCount()->count()) + ->fact(trans('mail.notes'), $note ?: ''); + } + + $message = trans('mail.License_Checkout_Notification'); + $details = [ + trans('mail.assigned_to') => $target->present()->fullName(), + trans('mail.license_for') => htmlspecialchars_decode($item->present()->name), + trans('mail.License_Checkout_Notification').' by' => $admin->present()->fullName(), + trans('admin/consumables/general.remaining') => $item->availCount()->count(), + trans('mail.notes') => $note ?: '', + ]; + return array($message, $details); } public function toGoogleChat() { @@ -164,29 +150,4 @@ public function toGoogleChat() ); } - - /** - * Get the mail representation of the notification. - * - * @return \Illuminate\Notifications\Messages\MailMessage - */ - public function toMail() - { - $eula = method_exists($this->item, 'getEula') ? $this->item->getEula() : ''; - $req_accept = method_exists($this->item, 'requireAcceptance') ? $this->item->requireAcceptance() : 0; - - $accept_url = is_null($this->acceptance) ? null : route('account.accept.item', $this->acceptance); - - return (new MailMessage)->markdown('notifications.markdown.checkout-license', - [ - 'item' => $this->item, - 'admin' => $this->admin, - 'note' => $this->note, - 'target' => $this->target, - 'eula' => $eula, - 'req_accept' => $req_accept, - 'accept_url' => $accept_url, - ]) - ->subject(trans('mail.Confirm_license_delivery')); - } } diff --git a/app/Observers/AssetObserver.php b/app/Observers/AssetObserver.php index 0d01428ea8e9..a8f5f09ae8de 100644 --- a/app/Observers/AssetObserver.php +++ b/app/Observers/AssetObserver.php @@ -40,8 +40,9 @@ public function updating(Asset $asset) // If the asset isn't being checked out or audited, log the update. // (Those other actions already create log entries.) - if (($attributes['assigned_to'] == $attributesOriginal['assigned_to']) - && ($same_checkout_counter) && ($same_checkin_counter) + if (array_key_exists('assigned_to', $attributes) && array_key_exists('assigned_to', $attributesOriginal) + && ($attributes['assigned_to'] == $attributesOriginal['assigned_to']) + && ($same_checkout_counter) && ($same_checkin_counter) && ((isset( $attributes['next_audit_date']) ? $attributes['next_audit_date'] : null) == (isset($attributesOriginal['next_audit_date']) ? $attributesOriginal['next_audit_date']: null)) && ($attributes['last_checkout'] == $attributesOriginal['last_checkout']) && (!$restoring_or_deleting)) { diff --git a/app/Presenters/ActionlogPresenter.php b/app/Presenters/ActionlogPresenter.php index 37a1adbc28ae..9251ce634748 100644 --- a/app/Presenters/ActionlogPresenter.php +++ b/app/Presenters/ActionlogPresenter.php @@ -42,27 +42,27 @@ public function icon() // User related icons if ($this->itemType() == 'user') { - if ($this->actionType()=='2fa reset') { + if ($this->action_type == '2fa reset') { return 'fa-solid fa-mobile-screen'; } - if ($this->actionType()=='create new') { + if ($this->action_type == 'create new') { return 'fa-solid fa-user-plus'; } - if ($this->actionType()=='merged') { + if ($this->action_type == 'merged') { return 'fa-solid fa-people-arrows'; } - if ($this->actionType()=='delete') { + if ($this->action_type == 'delete') { return 'fa-solid fa-user-minus'; } - if ($this->actionType()=='delete') { + if ($this->action_type == 'delete') { return 'fa-solid fa-user-minus'; } - if ($this->actionType()=='update') { + if ($this->action_type == 'update') { return 'fa-solid fa-user-pen'; } @@ -70,31 +70,31 @@ public function icon() } // Everything else - if ($this->actionType()=='create new') { + if ($this->action_type == 'create new') { return 'fa-solid fa-plus'; } - if ($this->actionType()=='delete') { + if ($this->action_type == 'delete') { return 'fa-solid fa-trash'; } - if ($this->actionType()=='update') { + if ($this->action_type == 'update') { return 'fa-solid fa-pen'; } - if ($this->actionType()=='restore') { + if ($this->action_type == 'restore') { return 'fa-solid fa-trash-arrow-up'; } - if ($this->actionType()=='upload') { + if ($this->action_type == 'upload') { return 'fas fa-paperclip'; } - if ($this->actionType()=='checkout') { + if ($this->action_type == 'checkout') { return 'fa-solid fa-rotate-left'; } - if ($this->actionType()=='checkin from') { + if ($this->action_type == 'checkin from') { return 'fa-solid fa-rotate-right'; } diff --git a/app/Presenters/ComponentPresenter.php b/app/Presenters/ComponentPresenter.php index f32bb56d57f3..39a177592dcf 100644 --- a/app/Presenters/ComponentPresenter.php +++ b/app/Presenters/ComponentPresenter.php @@ -66,8 +66,20 @@ public static function dataTableLayout() 'title' => trans('general.supplier'), 'visible' => false, 'formatter' => 'suppliersLinkObjFormatter', - ], - [ + ], [ + 'field' => 'model_number', + 'searchable' => true, + 'sortable' => true, + 'title' => trans('admin/models/table.modelnumber'), + ], [ + 'field' => 'manufacturer', + 'searchable' => true, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.manufacturer'), + 'visible' => false, + 'formatter' => 'manufacturersLinkObjFormatter', + ], [ 'field' => 'qty', 'searchable' => false, 'sortable' => true, diff --git a/app/Presenters/ManufacturerPresenter.php b/app/Presenters/ManufacturerPresenter.php index ea29974f3432..dfefec2998de 100644 --- a/app/Presenters/ManufacturerPresenter.php +++ b/app/Presenters/ManufacturerPresenter.php @@ -124,8 +124,15 @@ public static function dataTableLayout() 'title' => trans('general.accessories'), 'visible' => true, 'class' => 'css-accessory', - ], - [ + ], [ + 'field' => 'components_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'title' => trans('general.components'), + 'visible' => true, + 'class' => 'css-component', + ], [ 'field' => 'created_by', 'searchable' => false, 'sortable' => true, diff --git a/app/Rules/AlphaEncrypted.php b/app/Rules/AlphaEncrypted.php new file mode 100644 index 000000000000..f4ed1d6c3225 --- /dev/null +++ b/app/Rules/AlphaEncrypted.php @@ -0,0 +1,29 @@ + $attributeName])); + } + } catch (\Exception $e) { + report($e); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/app/Rules/NumericEncrypted.php b/app/Rules/NumericEncrypted.php new file mode 100644 index 000000000000..f3cb3ba76e42 --- /dev/null +++ b/app/Rules/NumericEncrypted.php @@ -0,0 +1,31 @@ + $attributeName])); + } + } catch (\Exception $e) { + report($e->getMessage()); + $fail(trans('general.something_went_wrong')); + } + } +} diff --git a/composer.json b/composer.json index d3637c3a4b13..865878280c5c 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "intervention/image": "^2.5", "javiereguiluz/easyslugger": "^1.0", "laravel-notification-channels/google-chat": "^3.0", - "laravel-notification-channels/microsoft-teams": "^1.1", + "laravel-notification-channels/microsoft-teams": "^1.2", "laravel/framework": "^10.0", "laravel/helpers": "^1.4", "laravel/passport": "^11.0", @@ -55,6 +55,7 @@ "nunomaduro/collision": "^7.0", "okvpn/clock-lts": "^1.0", "onelogin/php-saml": "^3.4", + "osa-eg/laravel-teams-notification": "^2.1", "paragonie/constant_time_encoding": "^2.3", "paragonie/sodium_compat": "^1.19", "phpdocumentor/reflection-docblock": "^5.1", diff --git a/composer.lock b/composer.lock index 0631fc275e29..c1b641a30264 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5341bc5be02b3c33e28e46e06dd99f29", + "content-hash": "0750e3a427347b2a56a05a8b9b533d48", "packages": [ { "name": "alek13/slack", @@ -137,16 +137,16 @@ }, { "name": "aws/aws-crt-php", - "version": "v1.2.6", + "version": "v1.2.7", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "a63485b65b6b3367039306496d49737cf1995408" + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408", - "reference": "a63485b65b6b3367039306496d49737cf1995408", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", "shasum": "" }, "require": { @@ -185,22 +185,22 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" }, - "time": "2024-06-13T17:21:28+00:00" + "time": "2024-10-18T22:15:13+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.323.4", + "version": "3.326.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "e66ee025b1d169fad3c784934f56648d3eec11ae" + "reference": "5420284de9aad84e375fa8012cefd834bebfd623" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e66ee025b1d169fad3c784934f56648d3eec11ae", - "reference": "e66ee025b1d169fad3c784934f56648d3eec11ae", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5420284de9aad84e375fa8012cefd834bebfd623", + "reference": "5420284de9aad84e375fa8012cefd834bebfd623", "shasum": "" }, "require": { @@ -283,9 +283,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.323.4" + "source": "https://github.com/aws/aws-sdk-php/tree/3.326.0" }, - "time": "2024-10-09T18:10:22+00:00" + "time": "2024-11-13T19:07:44+00:00" }, { "name": "bacon/bacon-qr-code", @@ -343,16 +343,16 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.14.3", + "version": "v3.14.6", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd" + "reference": "14e4517bd49130d6119228107eb21ae47ae120ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd", - "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/14e4517bd49130d6119228107eb21ae47ae120ab", + "reference": "14e4517bd49130d6119228107eb21ae47ae120ab", "shasum": "" }, "require": { @@ -411,7 +411,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.3" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.6" }, "funding": [ { @@ -423,7 +423,7 @@ "type": "github" } ], - "time": "2024-10-02T09:17:49+00:00" + "time": "2024-10-18T13:15:12+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -924,16 +924,16 @@ }, { "name": "doctrine/dbal", - "version": "3.9.1", + "version": "3.9.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7" + "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/d7dc08f98cba352b2bab5d32c5e58f7e745c11a7", - "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", + "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", "shasum": "" }, "require": { @@ -949,7 +949,7 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.12.0", + "phpstan/phpstan": "1.12.6", "phpstan/phpstan-strict-rules": "^1.6", "phpunit/phpunit": "9.6.20", "psalm/plugin-phpunit": "0.18.4", @@ -1017,7 +1017,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.9.1" + "source": "https://github.com/doctrine/dbal/tree/3.9.3" }, "funding": [ { @@ -1033,7 +1033,7 @@ "type": "tidelift" } ], - "time": "2024-09-01T13:49:23+00:00" + "time": "2024-10-10T17:56:43+00:00" }, { "name": "doctrine/deprecations", @@ -2165,16 +2165,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -2228,7 +2228,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.3" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -2244,7 +2244,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T10:29:17+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", @@ -2682,16 +2682,16 @@ }, { "name": "laravel/framework", - "version": "v10.48.22", + "version": "v10.48.23", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "c4ea52bb044faef4a103d7dd81746c01b2ec860e" + "reference": "625269ca4881d2b50eded2045cb930960a181d98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/c4ea52bb044faef4a103d7dd81746c01b2ec860e", - "reference": "c4ea52bb044faef4a103d7dd81746c01b2ec860e", + "url": "https://api.github.com/repos/laravel/framework/zipball/625269ca4881d2b50eded2045cb930960a181d98", + "reference": "625269ca4881d2b50eded2045cb930960a181d98", "shasum": "" }, "require": { @@ -2885,7 +2885,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-09-12T15:00:09+00:00" + "time": "2024-11-12T15:39:10+00:00" }, { "name": "laravel/helpers", @@ -3082,16 +3082,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.5", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96", + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96", "shasum": "" }, "require": { @@ -3139,7 +3139,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-09-23T13:33:08+00:00" + "time": "2024-11-11T17:06:04+00:00" }, { "name": "laravel/slack-notification-channel", @@ -3739,16 +3739,16 @@ }, { "name": "league/csv", - "version": "9.17.0", + "version": "9.18.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "8cab815fb11ec93aa2f7b8a57b3daa1f1a364011" + "reference": "b02d010e4055ae992247f6ffd1e7b103ef2a0790" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/8cab815fb11ec93aa2f7b8a57b3daa1f1a364011", - "reference": "8cab815fb11ec93aa2f7b8a57b3daa1f1a364011", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/b02d010e4055ae992247f6ffd1e7b103ef2a0790", + "reference": "b02d010e4055ae992247f6ffd1e7b103ef2a0790", "shasum": "" }, "require": { @@ -3760,11 +3760,11 @@ "ext-xdebug": "*", "friendsofphp/php-cs-fixer": "^3.64.0", "phpbench/phpbench": "^1.3.1", - "phpstan/phpstan": "^1.12.5", + "phpstan/phpstan": "^1.12.6", "phpstan/phpstan-deprecation-rules": "^1.2.1", "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-strict-rules": "^1.6.1", - "phpunit/phpunit": "^10.5.16 || ^11.4.0", + "phpunit/phpunit": "^10.5.16 || ^11.4.1", "symfony/var-dumper": "^6.4.8 || ^7.1.5" }, "suggest": { @@ -3822,7 +3822,7 @@ "type": "github" } ], - "time": "2024-10-10T10:30:28+00:00" + "time": "2024-10-18T08:14:48+00:00" }, { "name": "league/event", @@ -4461,16 +4461,16 @@ }, { "name": "livewire/livewire", - "version": "v3.5.9", + "version": "v3.5.12", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "d04a229058afa76116d0e39209943a8ea3a7f888" + "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/d04a229058afa76116d0e39209943a8ea3a7f888", - "reference": "d04a229058afa76116d0e39209943a8ea3a7f888", + "url": "https://api.github.com/repos/livewire/livewire/zipball/3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d", + "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d", "shasum": "" }, "require": { @@ -4525,7 +4525,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.9" + "source": "https://github.com/livewire/livewire/tree/v3.5.12" }, "funding": [ { @@ -4533,7 +4533,7 @@ "type": "github" } ], - "time": "2024-10-01T12:40:06+00:00" + "time": "2024-10-15T19:35:06+00:00" }, { "name": "masterminds/html5", @@ -4604,16 +4604,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.23.2", + "version": "v1.23.3", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "689720d724c771ac4add859056744b7b3f2406da" + "reference": "687400043d77943ef95e8417cb44e1673ee57844" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/689720d724c771ac4add859056744b7b3f2406da", - "reference": "689720d724c771ac4add859056744b7b3f2406da", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/687400043d77943ef95e8417cb44e1673ee57844", + "reference": "687400043d77943ef95e8417cb44e1673ee57844", "shasum": "" }, "require": { @@ -4666,22 +4666,22 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.2" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.3" }, - "time": "2024-09-16T11:23:09+00:00" + "time": "2024-10-29T12:24:25+00:00" }, { "name": "monolog/monolog", - "version": "3.7.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" + "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/32e515fdc02cdafbe4593e30a9350d486b125b67", + "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67", "shasum": "" }, "require": { @@ -4701,12 +4701,14 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.5.17", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", - "ruflin/elastica": "^7", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -4757,7 +4759,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.7.0" + "source": "https://github.com/Seldaek/monolog/tree/3.8.0" }, "funding": [ { @@ -4769,7 +4771,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:40:51+00:00" + "time": "2024-11-12T13:57:08+00:00" }, { "name": "mtdowling/jmespath.php", @@ -5191,40 +5193,40 @@ }, { "name": "nunomaduro/collision", - "version": "v7.10.0", + "version": "v7.11.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "49ec67fa7b002712da8526678abd651c09f375b2" + "reference": "994ea93df5d4132f69d3f1bd74730509df6e8a05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/49ec67fa7b002712da8526678abd651c09f375b2", - "reference": "49ec67fa7b002712da8526678abd651c09f375b2", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/994ea93df5d4132f69d3f1bd74730509df6e8a05", + "reference": "994ea93df5d4132f69d3f1bd74730509df6e8a05", "shasum": "" }, "require": { - "filp/whoops": "^2.15.3", + "filp/whoops": "^2.16.0", "nunomaduro/termwind": "^1.15.1", "php": "^8.1.0", - "symfony/console": "^6.3.4" + "symfony/console": "^6.4.12" }, "conflict": { "laravel/framework": ">=11.0.0" }, "require-dev": { - "brianium/paratest": "^7.3.0", - "laravel/framework": "^10.28.0", - "laravel/pint": "^1.13.3", - "laravel/sail": "^1.25.0", - "laravel/sanctum": "^3.3.1", - "laravel/tinker": "^2.8.2", - "nunomaduro/larastan": "^2.6.4", - "orchestra/testbench-core": "^8.13.0", - "pestphp/pest": "^2.23.2", - "phpunit/phpunit": "^10.4.1", - "sebastian/environment": "^6.0.1", - "spatie/laravel-ignition": "^2.3.1" + "brianium/paratest": "^7.3.1", + "laravel/framework": "^10.48.22", + "laravel/pint": "^1.18.1", + "laravel/sail": "^1.36.0", + "laravel/sanctum": "^3.3.3", + "laravel/tinker": "^2.10.0", + "nunomaduro/larastan": "^2.9.8", + "orchestra/testbench-core": "^8.28.3", + "pestphp/pest": "^2.35.1", + "phpunit/phpunit": "^10.5.36", + "sebastian/environment": "^6.1.0", + "spatie/laravel-ignition": "^2.8.0" }, "type": "library", "extra": { @@ -5283,37 +5285,36 @@ "type": "patreon" } ], - "time": "2023-10-11T15:45:01+00:00" + "time": "2024-10-15T15:12:40+00:00" }, { "name": "nunomaduro/termwind", - "version": "v1.15.1", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc" + "reference": "dcf1ec3dfa36137b7ce41d43866644a7ab8fc257" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/8ab0b32c8caa4a2e09700ea32925441385e4a5dc", - "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dcf1ec3dfa36137b7ce41d43866644a7ab8fc257", + "reference": "dcf1ec3dfa36137b7ce41d43866644a7ab8fc257", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^8.0", - "symfony/console": "^5.3.0|^6.0.0" + "php": "^8.1", + "symfony/console": "^6.4.12" }, "require-dev": { - "ergebnis/phpstan-rules": "^1.0.", - "illuminate/console": "^8.0|^9.0", - "illuminate/support": "^8.0|^9.0", - "laravel/pint": "^1.0.0", - "pestphp/pest": "^1.21.0", - "pestphp/pest-plugin-mock": "^1.0", - "phpstan/phpstan": "^1.4.6", - "phpstan/phpstan-strict-rules": "^1.1.0", - "symfony/var-dumper": "^5.2.7|^6.0.0", + "illuminate/console": "^10.48.22", + "illuminate/support": "^10.48.22", + "laravel/pint": "^1.18.1", + "pestphp/pest": "^2", + "pestphp/pest-plugin-mock": "2.0.0", + "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^6.4.11", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -5353,7 +5354,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.15.1" + "source": "https://github.com/nunomaduro/termwind/tree/v1.16.0" }, "funding": [ { @@ -5369,7 +5370,7 @@ "type": "github" } ], - "time": "2023-02-08T01:06:31+00:00" + "time": "2024-10-15T15:27:12+00:00" }, { "name": "nyholm/psr7", @@ -5572,6 +5573,71 @@ ], "time": "2024-05-30T15:14:26+00:00" }, + { + "name": "osa-eg/laravel-teams-notification", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/osa-eg/laravel-teams-notification.git", + "reference": "76173689930aca92b5174a3b102e705279192c0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/osa-eg/laravel-teams-notification/zipball/76173689930aca92b5174a3b102e705279192c0f", + "reference": "76173689930aca92b5174a3b102e705279192c0f", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": ">=6.5", + "illuminate/support": "^5.5 || ^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0 || ^11.0", + "monolog/monolog": ">=1.0", + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Osama\\LaravelTeamsNotification\\LaravelTeamsNotificationServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Osama\\LaravelTeamsNotification\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Osama Saad", + "email": "osaad96eg@gmail.com" + } + ], + "description": "A Laravel package to send notifications to Microsoft Teams", + "keywords": [ + "Teams", + "adaptive-card", + "laravel", + "logging", + "microsoft-teams-workflow", + "notification", + "teams-connector", + "teams-webhock", + "teams-workflow", + "teams_logging" + ], + "support": { + "issues": "https://github.com/osa-eg/laravel-teams-notification/issues", + "source": "https://github.com/osa-eg/laravel-teams-notification/tree/v2.1.2" + }, + "time": "2024-09-23T05:24:48+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v2.7.0", @@ -5920,16 +5986,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.1", + "version": "5.6.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/f3558a4c23426d12bffeaab463f8a8d8b681193c", + "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c", "shasum": "" }, "require": { @@ -5938,17 +6004,17 @@ "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.5", + "mockery/mockery": "~1.3.5 || ~1.6.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.8", "phpstan/phpstan-mockery": "^1.1", "phpstan/phpstan-webmozart-assert": "^1.2", "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.13" + "psalm/phar": "^5.26" }, "type": "library", "extra": { @@ -5978,29 +6044,29 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.0" }, - "time": "2024-05-21T05:55:05+00:00" + "time": "2024-11-12T11:25:25+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -6036,9 +6102,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpoption/phpoption", @@ -6296,16 +6362,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.32.0", + "version": "1.33.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/82a311fd3690fb2bf7b64d5c98f912b3dd746140", + "reference": "82a311fd3690fb2bf7b64d5c98f912b3dd746140", "shasum": "" }, "require": { @@ -6337,9 +6403,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.33.0" }, - "time": "2024-09-26T07:23:32+00:00" + "time": "2024-10-13T11:25:22+00:00" }, { "name": "pragmarx/google2fa", @@ -7474,24 +7540,24 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v8.6.0", + "version": "v8.7.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70" + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70", - "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf", + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf", "shasum": "" }, "require": { "ext-iconv": "*", - "php": ">=5.6.20" + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.27" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -7533,22 +7599,22 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0" }, - "time": "2024-07-01T07:33:21+00:00" + "time": "2024-10-27T17:38:32+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.2", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", - "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", + "reference": "a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e", "shasum": "" }, "require": { @@ -7559,7 +7625,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.4" + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -7604,7 +7670,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.3" }, "funding": [ { @@ -7612,7 +7678,7 @@ "type": "github" } ], - "time": "2024-08-12T06:03:08+00:00" + "time": "2024-10-18T14:56:07+00:00" }, { "name": "sebastian/diff", @@ -8561,16 +8627,16 @@ }, { "name": "symfony/console", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" + "reference": "f1fc6f47283e27336e7cebb9e8946c8de7bff9bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", - "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", + "url": "https://api.github.com/repos/symfony/console/zipball/f1fc6f47283e27336e7cebb9e8946c8de7bff9bd", + "reference": "f1fc6f47283e27336e7cebb9e8946c8de7bff9bd", "shasum": "" }, "require": { @@ -8635,7 +8701,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.12" + "source": "https://github.com/symfony/console/tree/v6.4.15" }, "funding": [ { @@ -8651,7 +8717,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:15:52+00:00" + "time": "2024-11-06T14:19:14+00:00" }, { "name": "symfony/css-selector", @@ -8788,16 +8854,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.10", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "231f1b2ee80f72daa1972f7340297d67439224f0" + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/231f1b2ee80f72daa1972f7340297d67439224f0", - "reference": "231f1b2ee80f72daa1972f7340297d67439224f0", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/9e024324511eeb00983ee76b9aedc3e6ecd993d9", + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9", "shasum": "" }, "require": { @@ -8843,7 +8909,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.10" + "source": "https://github.com/symfony/error-handler/tree/v6.4.14" }, "funding": [ { @@ -8859,20 +8925,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:30:32+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", "shasum": "" }, "require": { @@ -8923,7 +8989,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" }, "funding": [ { @@ -8939,7 +9005,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -9019,16 +9085,16 @@ }, { "name": "symfony/finder", - "version": "v6.4.11", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d7eb6daf8cd7e9ac4976e9576b32042ef7253453" + "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d7eb6daf8cd7e9ac4976e9576b32042ef7253453", - "reference": "d7eb6daf8cd7e9ac4976e9576b32042ef7253453", + "url": "https://api.github.com/repos/symfony/finder/zipball/daea9eca0b08d0ed1dc9ab702a46128fd1be4958", + "reference": "daea9eca0b08d0ed1dc9ab702a46128fd1be4958", "shasum": "" }, "require": { @@ -9063,7 +9129,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.4.11" + "source": "https://github.com/symfony/finder/tree/v6.4.13" }, "funding": [ { @@ -9079,20 +9145,20 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:27:37+00:00" + "time": "2024-10-01T08:30:56+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "133ac043875f59c26c55e79cf074562127cce4d2" + "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/133ac043875f59c26c55e79cf074562127cce4d2", - "reference": "133ac043875f59c26c55e79cf074562127cce4d2", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", + "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", "shasum": "" }, "require": { @@ -9102,12 +9168,12 @@ "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.3" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.3|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/expression-language": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", @@ -9140,7 +9206,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.12" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.15" }, "funding": [ { @@ -9156,20 +9222,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:18:25+00:00" + "time": "2024-11-08T16:09:24+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "96df83d51b5f78804f70c093b97310794fd6257b" + "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/96df83d51b5f78804f70c093b97310794fd6257b", - "reference": "96df83d51b5f78804f70c093b97310794fd6257b", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b002a5b3947653c5aee3adac2a024ea615fd3ff5", + "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5", "shasum": "" }, "require": { @@ -9254,7 +9320,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.12" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.15" }, "funding": [ { @@ -9270,20 +9336,20 @@ "type": "tidelift" } ], - "time": "2024-09-21T06:02:57+00:00" + "time": "2024-11-13T13:57:37+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b6a25408c569ae2366b3f663a4edad19420a9c26" + "reference": "c2f7e0d8d7ac8fe25faccf5d8cac462805db2663" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b6a25408c569ae2366b3f663a4edad19420a9c26", - "reference": "b6a25408c569ae2366b3f663a4edad19420a9c26", + "url": "https://api.github.com/repos/symfony/mailer/zipball/c2f7e0d8d7ac8fe25faccf5d8cac462805db2663", + "reference": "c2f7e0d8d7ac8fe25faccf5d8cac462805db2663", "shasum": "" }, "require": { @@ -9334,7 +9400,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.12" + "source": "https://github.com/symfony/mailer/tree/v6.4.13" }, "funding": [ { @@ -9350,20 +9416,20 @@ "type": "tidelift" } ], - "time": "2024-09-08T12:30:05+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/mime", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "abe16ee7790b16aa525877419deb0f113953f0e1" + "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/abe16ee7790b16aa525877419deb0f113953f0e1", - "reference": "abe16ee7790b16aa525877419deb0f113953f0e1", + "url": "https://api.github.com/repos/symfony/mime/zipball/1de1cf14d99b12c7ebbb850491ec6ae3ed468855", + "reference": "1de1cf14d99b12c7ebbb850491ec6ae3ed468855", "shasum": "" }, "require": { @@ -9419,7 +9485,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.12" + "source": "https://github.com/symfony/mime/tree/v6.4.13" }, "funding": [ { @@ -9435,7 +9501,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:18:25+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/polyfill-ctype", @@ -10075,16 +10141,16 @@ }, { "name": "symfony/process", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3" + "reference": "3cb242f059c14ae08591c5c4087d1fe443564392" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3f94e5f13ff58df371a7ead461b6e8068900fbb3", - "reference": "3f94e5f13ff58df371a7ead461b6e8068900fbb3", + "url": "https://api.github.com/repos/symfony/process/zipball/3cb242f059c14ae08591c5c4087d1fe443564392", + "reference": "3cb242f059c14ae08591c5c4087d1fe443564392", "shasum": "" }, "require": { @@ -10116,7 +10182,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.12" + "source": "https://github.com/symfony/process/tree/v6.4.15" }, "funding": [ { @@ -10132,7 +10198,7 @@ "type": "tidelift" } ], - "time": "2024-09-17T12:47:12+00:00" + "time": "2024-11-06T14:19:14+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -10225,16 +10291,16 @@ }, { "name": "symfony/routing", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "a7c8036bd159486228dc9be3e846a00a0dda9f9f" + "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/a7c8036bd159486228dc9be3e846a00a0dda9f9f", - "reference": "a7c8036bd159486228dc9be3e846a00a0dda9f9f", + "url": "https://api.github.com/repos/symfony/routing/zipball/640a74250d13f9c30d5ca045b6aaaabcc8215278", + "reference": "640a74250d13f9c30d5ca045b6aaaabcc8215278", "shasum": "" }, "require": { @@ -10288,7 +10354,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.12" + "source": "https://github.com/symfony/routing/tree/v6.4.13" }, "funding": [ { @@ -10304,7 +10370,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:32:26+00:00" + "time": "2024-10-01T08:30:56+00:00" }, { "name": "symfony/service-contracts", @@ -10391,16 +10457,16 @@ }, { "name": "symfony/string", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b" + "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b", - "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b", + "url": "https://api.github.com/repos/symfony/string/zipball/73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", + "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", "shasum": "" }, "require": { @@ -10457,7 +10523,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.12" + "source": "https://github.com/symfony/string/tree/v6.4.15" }, "funding": [ { @@ -10473,20 +10539,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:15:52+00:00" + "time": "2024-11-13T13:31:12+00:00" }, { "name": "symfony/translation", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "cf8360b8352b086be620fae8342c4d96e391a489" + "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/cf8360b8352b086be620fae8342c4d96e391a489", - "reference": "cf8360b8352b086be620fae8342c4d96e391a489", + "url": "https://api.github.com/repos/symfony/translation/zipball/bee9bfabfa8b4045a66bf82520e492cddbaffa66", + "reference": "bee9bfabfa8b4045a66bf82520e492cddbaffa66", "shasum": "" }, "require": { @@ -10552,7 +10618,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.12" + "source": "https://github.com/symfony/translation/tree/v6.4.13" }, "funding": [ { @@ -10568,7 +10634,7 @@ "type": "tidelift" } ], - "time": "2024-09-16T06:02:54+00:00" + "time": "2024-09-27T18:14:25+00:00" }, { "name": "symfony/translation-contracts", @@ -10650,16 +10716,16 @@ }, { "name": "symfony/uid", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d" + "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d", - "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d", + "url": "https://api.github.com/repos/symfony/uid/zipball/18eb207f0436a993fffbdd811b5b8fa35fa5e007", + "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007", "shasum": "" }, "require": { @@ -10704,7 +10770,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.12" + "source": "https://github.com/symfony/uid/tree/v6.4.13" }, "funding": [ { @@ -10720,20 +10786,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:32:26+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.11", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "ee14c8254a480913268b1e3b1cba8045ed122694" + "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ee14c8254a480913268b1e3b1cba8045ed122694", - "reference": "ee14c8254a480913268b1e3b1cba8045ed122694", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", + "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", "shasum": "" }, "require": { @@ -10789,7 +10855,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.11" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.15" }, "funding": [ { @@ -10805,7 +10871,7 @@ "type": "tidelift" } ], - "time": "2024-08-30T16:03:21+00:00" + "time": "2024-11-08T15:28:48+00:00" }, { "name": "tecnickcom/tc-lib-barcode", @@ -10978,16 +11044,16 @@ }, { "name": "tecnickcom/tcpdf", - "version": "6.7.6", + "version": "6.7.7", "source": { "type": "git", "url": "https://github.com/tecnickcom/TCPDF.git", - "reference": "4cf1ab192e87e6916d20f93077b2bdfa96a2f848" + "reference": "cfbc0028cc23f057f2baf9e73bdc238153c22086" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/4cf1ab192e87e6916d20f93077b2bdfa96a2f848", - "reference": "4cf1ab192e87e6916d20f93077b2bdfa96a2f848", + "url": "https://api.github.com/repos/tecnickcom/TCPDF/zipball/cfbc0028cc23f057f2baf9e73bdc238153c22086", + "reference": "cfbc0028cc23f057f2baf9e73bdc238153c22086", "shasum": "" }, "require": { @@ -11038,7 +11104,7 @@ ], "support": { "issues": "https://github.com/tecnickcom/TCPDF/issues", - "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.6" + "source": "https://github.com/tecnickcom/TCPDF/tree/6.7.7" }, "funding": [ { @@ -11046,7 +11112,7 @@ "type": "custom" } ], - "time": "2024-10-06T10:54:28+00:00" + "time": "2024-10-26T12:15:02+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11856,24 +11922,24 @@ }, { "name": "cmgmyr/phploc", - "version": "8.0.3", + "version": "8.0.4", "source": { "type": "git", "url": "https://github.com/cmgmyr/phploc.git", - "reference": "e61d4729df46c5920ab61973bfa3f70f81a70b5f" + "reference": "b0c4ec71f40ef84c9893e1a7212a72e1098b90f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/e61d4729df46c5920ab61973bfa3f70f81a70b5f", - "reference": "e61d4729df46c5920ab61973bfa3f70f81a70b5f", + "url": "https://api.github.com/repos/cmgmyr/phploc/zipball/b0c4ec71f40ef84c9893e1a7212a72e1098b90f7", + "reference": "b0c4ec71f40ef84c9893e1a7212a72e1098b90f7", "shasum": "" }, "require": { "ext-dom": "*", "ext-json": "*", "php": "^7.4 || ^8.0", - "phpunit/php-file-iterator": "^3.0|^4.0", - "sebastian/cli-parser": "^1.0|^2.0" + "phpunit/php-file-iterator": "^3.0|^4.0|^5.0", + "sebastian/cli-parser": "^1.0|^2.0|^3.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", @@ -11909,7 +11975,7 @@ "homepage": "https://github.com/cmgmyr/phploc", "support": { "issues": "https://github.com/cmgmyr/phploc/issues", - "source": "https://github.com/cmgmyr/phploc/tree/8.0.3" + "source": "https://github.com/cmgmyr/phploc/tree/8.0.4" }, "funding": [ { @@ -11917,20 +11983,20 @@ "type": "github" } ], - "time": "2023-08-05T16:49:39+00:00" + "time": "2024-10-31T19:26:53+00:00" }, { "name": "composer/pcre", - "version": "3.3.1", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4" + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/63aaeac21d7e775ff9bc9d45021e1745c97521c4", - "reference": "63aaeac21d7e775ff9bc9d45021e1745c97521c4", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", "shasum": "" }, "require": { @@ -11940,8 +12006,8 @@ "phpstan/phpstan": "<1.11.10" }, "require-dev": { - "phpstan/phpstan": "^1.11.10", - "phpstan/phpstan-strict-rules": "^1.1", + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", "phpunit/phpunit": "^8 || ^9" }, "type": "library", @@ -11980,7 +12046,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.1" + "source": "https://github.com/composer/pcre/tree/3.3.2" }, "funding": [ { @@ -11996,7 +12062,7 @@ "type": "tidelift" } ], - "time": "2024-08-27T18:44:43+00:00" + "time": "2024-11-12T16:29:46+00:00" }, { "name": "composer/semver", @@ -12309,16 +12375,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.23.1", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + "reference": "a136842a532bac9ecd8a1c723852b09915d7db50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50", + "reference": "a136842a532bac9ecd8a1c723852b09915d7db50", "shasum": "" }, "require": { @@ -12366,9 +12432,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0" }, - "time": "2024-01-02T13:46:09+00:00" + "time": "2024-11-07T15:11:20+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -12753,36 +12819,39 @@ }, { "name": "larastan/larastan", - "version": "v2.9.8", + "version": "v2.9.11", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "340badd89b0eb5bddbc503a4829c08cf9a2819d7" + "reference": "54eccd35d1732b9ee4392c25aec606a6a9c521e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/340badd89b0eb5bddbc503a4829c08cf9a2819d7", - "reference": "340badd89b0eb5bddbc503a4829c08cf9a2819d7", + "url": "https://api.github.com/repos/larastan/larastan/zipball/54eccd35d1732b9ee4392c25aec606a6a9c521e7", + "reference": "54eccd35d1732b9ee4392c25aec606a6a9c521e7", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.0", - "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.16", + "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.16", "php": "^8.0.2", "phpmyadmin/sql-parser": "^5.9.0", - "phpstan/phpstan": "^1.11.2" + "phpstan/phpstan": "^1.12.5" }, "require-dev": { "doctrine/coding-standard": "^12.0", + "laravel/framework": "^9.52.16 || ^10.28.0 || ^11.16", + "mockery/mockery": "^1.5.1", "nikic/php-parser": "^4.19.1", "orchestra/canvas": "^7.11.1 || ^8.11.0 || ^9.0.2", - "orchestra/testbench": "^7.33.0 || ^8.13.0 || ^9.0.3", + "orchestra/testbench-core": "^7.33.0 || ^8.13.0 || ^9.0.9", + "phpstan/phpstan-deprecation-rules": "^1.2", "phpunit/phpunit": "^9.6.13 || ^10.5.16" }, "suggest": { @@ -12831,7 +12900,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v2.9.8" + "source": "https://github.com/larastan/larastan/tree/v2.9.11" }, "funding": [ { @@ -12851,20 +12920,20 @@ "type": "patreon" } ], - "time": "2024-07-06T17:46:02+00:00" + "time": "2024-11-11T23:11:00+00:00" }, { "name": "league/container", - "version": "4.2.2", + "version": "4.2.4", "source": { "type": "git", "url": "https://github.com/thephpleague/container.git", - "reference": "ff346319ca1ff0e78277dc2311a42107cc1aab88" + "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/ff346319ca1ff0e78277dc2311a42107cc1aab88", - "reference": "ff346319ca1ff0e78277dc2311a42107cc1aab88", + "url": "https://api.github.com/repos/thephpleague/container/zipball/7ea728b013b9a156c409c6f0fc3624071b742dec", + "reference": "7ea728b013b9a156c409c6f0fc3624071b742dec", "shasum": "" }, "require": { @@ -12925,7 +12994,7 @@ ], "support": { "issues": "https://github.com/thephpleague/container/issues", - "source": "https://github.com/thephpleague/container/tree/4.2.2" + "source": "https://github.com/thephpleague/container/tree/4.2.4" }, "funding": [ { @@ -12933,7 +13002,7 @@ "type": "github" } ], - "time": "2024-03-13T13:12:53+00:00" + "time": "2024-11-10T12:42:13+00:00" }, { "name": "mockery/mockery", @@ -13020,16 +13089,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -13068,7 +13137,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -13076,7 +13145,7 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "netresearch/jsonmapper", @@ -13131,16 +13200,16 @@ }, { "name": "nunomaduro/phpinsights", - "version": "v2.11.0", + "version": "v2.12.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/phpinsights.git", - "reference": "f476219759a61aad988641476259465c77203383" + "reference": "5c12a8d626712de6db5e6d2db52b1eb4e9596650" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/phpinsights/zipball/f476219759a61aad988641476259465c77203383", - "reference": "f476219759a61aad988641476259465c77203383", + "url": "https://api.github.com/repos/nunomaduro/phpinsights/zipball/5c12a8d626712de6db5e6d2db52b1eb4e9596650", + "reference": "5c12a8d626712de6db5e6d2db52b1eb4e9596650", "shasum": "" }, "require": { @@ -13157,7 +13226,7 @@ "php-parallel-lint/php-parallel-lint": "^1.3.2", "psr/container": "^1.0|^2.0.2", "psr/simple-cache": "^1.0|^2.0|^3.0", - "sebastian/diff": "^4.0|^5.0.3", + "sebastian/diff": "^4.0|^5.0.3|^6.0", "slevomat/coding-standard": "^8.14.1", "squizlabs/php_codesniffer": "^3.7.2", "symfony/cache": "^5.4|^6.0|^7.0", @@ -13217,7 +13286,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/phpinsights/issues", - "source": "https://github.com/nunomaduro/phpinsights/tree/v2.11.0" + "source": "https://github.com/nunomaduro/phpinsights/tree/v2.12.0" }, "funding": [ { @@ -13233,7 +13302,7 @@ "type": "github" } ], - "time": "2023-11-30T10:54:50+00:00" + "time": "2024-11-11T14:42:55+00:00" }, { "name": "phar-io/manifest", @@ -13623,16 +13692,16 @@ }, { "name": "phpmyadmin/sql-parser", - "version": "5.10.0", + "version": "5.10.1", "source": { "type": "git", "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "91d980ab76c3f152481e367f62b921adc38af451" + "reference": "b14fd66496a22d8dd7f7e2791edd9e8674422f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/91d980ab76c3f152481e367f62b921adc38af451", - "reference": "91d980ab76c3f152481e367f62b921adc38af451", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/b14fd66496a22d8dd7f7e2791edd9e8674422f17", + "reference": "b14fd66496a22d8dd7f7e2791edd9e8674422f17", "shasum": "" }, "require": { @@ -13706,20 +13775,20 @@ "type": "other" } ], - "time": "2024-08-29T20:56:34+00:00" + "time": "2024-11-10T04:10:31+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.6", + "version": "1.12.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae" + "reference": "fc463b5d0fe906dcf19689be692c65c50406a071" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae", - "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071", + "reference": "fc463b5d0fe906dcf19689be692c65c50406a071", "shasum": "" }, "require": { @@ -13764,7 +13833,7 @@ "type": "github" } ], - "time": "2024-10-06T15:03:59+00:00" + "time": "2024-11-11T15:37:09+00:00" }, { "name": "phpunit/php-code-coverage", @@ -14089,16 +14158,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.36", + "version": "10.5.38", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870" + "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870", - "reference": "aa0a8ce701ea7ee314b0dfaa8970dc94f3f8c870", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a86773b9e887a67bc53efa9da9ad6e3f2498c132", + "reference": "a86773b9e887a67bc53efa9da9ad6e3f2498c132", "shasum": "" }, "require": { @@ -14119,7 +14188,7 @@ "phpunit/php-timer": "^6.0.0", "sebastian/cli-parser": "^2.0.1", "sebastian/code-unit": "^2.0.0", - "sebastian/comparator": "^5.0.2", + "sebastian/comparator": "^5.0.3", "sebastian/diff": "^5.1.1", "sebastian/environment": "^6.1.0", "sebastian/exporter": "^5.1.2", @@ -14170,7 +14239,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.36" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.38" }, "funding": [ { @@ -14186,7 +14255,7 @@ "type": "tidelift" } ], - "time": "2024-10-08T15:36:51+00:00" + "time": "2024-10-28T13:06:21+00:00" }, { "name": "react/cache", @@ -15484,16 +15553,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.3", + "version": "3.11.0", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" + "reference": "70c08f8d20c0eb4fe56f26644dd94dae76a7f450" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", - "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/70c08f8d20c0eb4fe56f26644dd94dae76a7f450", + "reference": "70c08f8d20c0eb4fe56f26644dd94dae76a7f450", "shasum": "" }, "require": { @@ -15560,20 +15629,20 @@ "type": "open_collective" } ], - "time": "2024-09-18T10:38:58+00:00" + "time": "2024-11-12T09:53:29+00:00" }, { "name": "symfony/cache", - "version": "v6.4.12", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "a463451b7f6ac4a47b98dbfc78ec2d3560c759d8" + "reference": "36fb8aa88833708e9f29014b6f15fac051a8b613" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/a463451b7f6ac4a47b98dbfc78ec2d3560c759d8", - "reference": "a463451b7f6ac4a47b98dbfc78ec2d3560c759d8", + "url": "https://api.github.com/repos/symfony/cache/zipball/36fb8aa88833708e9f29014b6f15fac051a8b613", + "reference": "36fb8aa88833708e9f29014b6f15fac051a8b613", "shasum": "" }, "require": { @@ -15640,7 +15709,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.12" + "source": "https://github.com/symfony/cache/tree/v6.4.14" }, "funding": [ { @@ -15656,7 +15725,7 @@ "type": "tidelift" } ], - "time": "2024-09-16T16:01:33+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/cache-contracts", @@ -15810,16 +15879,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12" + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/f810e3cbdf7fdc35983968523d09f349fa9ada12", - "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "shasum": "" }, "require": { @@ -15856,7 +15925,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.12" + "source": "https://github.com/symfony/filesystem/tree/v6.4.13" }, "funding": [ { @@ -15872,20 +15941,20 @@ "type": "tidelift" } ], - "time": "2024-09-16T16:01:33+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56" + "reference": "cb4073c905cd12b8496d24ac428a9228c1750670" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", - "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", + "url": "https://api.github.com/repos/symfony/http-client/zipball/cb4073c905cd12b8496d24ac428a9228c1750670", + "reference": "cb4073c905cd12b8496d24ac428a9228c1750670", "shasum": "" }, "require": { @@ -15949,7 +16018,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.12" + "source": "https://github.com/symfony/http-client/tree/v6.4.15" }, "funding": [ { @@ -15965,7 +16034,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:21:33+00:00" + "time": "2024-11-13T13:40:18+00:00" }, { "name": "symfony/http-client-contracts", @@ -16047,16 +16116,16 @@ }, { "name": "symfony/options-resolver", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b" + "reference": "0a62a9f2504a8dd27083f89d21894ceb01cc59db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22ab9e9101ab18de37839074f8a1197f55590c1b", - "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/0a62a9f2504a8dd27083f89d21894ceb01cc59db", + "reference": "0a62a9f2504a8dd27083f89d21894ceb01cc59db", "shasum": "" }, "require": { @@ -16094,7 +16163,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.4.8" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.13" }, "funding": [ { @@ -16110,7 +16179,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/polyfill-php81", @@ -16190,16 +16259,16 @@ }, { "name": "symfony/stopwatch", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "63e069eb616049632cde9674c46957819454b8aa" + "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/63e069eb616049632cde9674c46957819454b8aa", - "reference": "63e069eb616049632cde9674c46957819454b8aa", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/2cae0a6f8d04937d02f6d19806251e2104d54f92", + "reference": "2cae0a6f8d04937d02f6d19806251e2104d54f92", "shasum": "" }, "require": { @@ -16232,7 +16301,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v6.4.8" + "source": "https://github.com/symfony/stopwatch/tree/v6.4.13" }, "funding": [ { @@ -16248,20 +16317,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.9", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "f9a060622e0d93777b7f8687ec4860191e16802e" + "reference": "0f605f72a363f8743001038a176eeb2a11223b51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f9a060622e0d93777b7f8687ec4860191e16802e", - "reference": "f9a060622e0d93777b7f8687ec4860191e16802e", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", + "reference": "0f605f72a363f8743001038a176eeb2a11223b51", "shasum": "" }, "require": { @@ -16309,7 +16378,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.9" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" }, "funding": [ { @@ -16325,7 +16394,7 @@ "type": "tidelift" } ], - "time": "2024-06-24T15:53:56+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/app.php b/config/app.php index bc74b4dd0547..39898ff4372b 100755 --- a/config/app.php +++ b/config/app.php @@ -280,7 +280,6 @@ Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, -// Illuminate\Translation\TranslationServiceProvider::class, //replaced on next line App\Providers\SnipeTranslationServiceProvider::class, //we REPLACE the default Laravel translator with our own Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, @@ -373,7 +372,7 @@ 'Image' => Intervention\Image\ImageServiceProvider::class, 'Carbon' => Carbon\Carbon::class, 'Helper' => App\Helpers\Helper::class, - // makes it much easier to use 'Helper::blah' in blades (which is where we usually use this) + 'StorageHelper' => App\Helpers\StorageHelper::class, 'Icon' => App\Helpers\IconHelper::class, 'Socialite' => Laravel\Socialite\Facades\Socialite::class, diff --git a/config/version.php b/config/version.php index e7eea2adfb07..439a7cb8d17a 100644 --- a/config/version.php +++ b/config/version.php @@ -1,10 +1,10 @@ 'v7.0.13', - 'full_app_version' => 'v7.0.13 - build 15514-gdc0949da7', - 'build_version' => '15514', + 'app_version' => 'v7.1.15', + 'full_app_version' => 'v7.1.15 - build 16052-g25bfd3e84', + 'build_version' => '16052', 'prerelease_version' => '', - 'hash_version' => 'gdc0949da7', - 'full_hash' => 'v7.0.13-265-gdc0949da7', + 'hash_version' => 'g25bfd3e84', + 'full_hash' => 'v7.1.15-105-g25bfd3e84', 'branch' => 'develop', ); \ No newline at end of file diff --git a/database/factories/ComponentFactory.php b/database/factories/ComponentFactory.php index 51942fc69458..786e78089599 100644 --- a/database/factories/ComponentFactory.php +++ b/database/factories/ComponentFactory.php @@ -7,6 +7,7 @@ use App\Models\Category; use App\Models\Company; use App\Models\Component; +use App\Models\Manufacturer; use App\Models\Consumable; use App\Models\Location; use App\Models\User; @@ -30,6 +31,7 @@ class ComponentFactory extends Factory */ public function definition() { + return [ 'name' => $this->faker->text(20), 'category_id' => Category::factory(), @@ -42,12 +44,14 @@ public function definition() 'min_amt' => $this->faker->numberBetween($min = 1, $max = 2), 'company_id' => Company::factory(), 'supplier_id' => Supplier::factory(), + 'model_number' => $this->faker->numberBetween(1000000, 50000000), ]; } public function ramCrucial4() { - return $this->state(function () { + $manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']); + return $this->state(function () use ($manufacturer) { return [ 'name' => 'Crucial 4GB DDR3L-1600 SODIMM', 'category_id' => function () { @@ -55,6 +59,7 @@ public function ramCrucial4() }, 'qty' => 10, 'min_amt' => 2, + 'manufacturer_id' => $manufacturer->id, 'location_id' => Location::factory(), ]; }); @@ -62,7 +67,8 @@ public function ramCrucial4() public function ramCrucial8() { - return $this->state(function () { + $manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']); + return $this->state(function () use ($manufacturer) { return [ 'name' => 'Crucial 8GB DDR3L-1600 SODIMM Memory for Mac', 'category_id' => function () { @@ -70,13 +76,15 @@ public function ramCrucial8() }, 'qty' => 10, 'min_amt' => 2, + 'manufacturer_id' => $manufacturer->id, ]; }); } public function ssdCrucial120() { - return $this->state(function () { + $manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']); + return $this->state(function () use ($manufacturer) { return [ 'name' => 'Crucial BX300 120GB SATA Internal SSD', 'category_id' => function () { @@ -84,13 +92,15 @@ public function ssdCrucial120() }, 'qty' => 10, 'min_amt' => 2, + 'manufacturer_id' => $manufacturer->id, ]; }); } public function ssdCrucial240() { - return $this->state(function () { + $manufacturer = Manufacturer::where('name', 'Crucial')->first() ?? Manufacturer::factory()->create(['name' => 'Crucial']); + return $this->state(function () use ($manufacturer) { return [ 'name' => 'Crucial BX300 240GB SATA Internal SSD', 'category_id' => function () { @@ -98,6 +108,7 @@ public function ssdCrucial240() }, 'qty' => 10, 'min_amt' => 2, + 'manufacturer_id' => $manufacturer->id, ]; }); } diff --git a/database/factories/ImportFactory.php b/database/factories/ImportFactory.php index 0b0f79aa44fb..11fdbe17a968 100644 --- a/database/factories/ImportFactory.php +++ b/database/factories/ImportFactory.php @@ -143,4 +143,26 @@ public function users() return $attributes; }); } + + + + /** + * Create an asset model import type. + * + * @return static + */ + public function assetmodel() + { + return $this->state(function (array $attributes) { + $fileBuilder = Importing\AssetModelsImportFileBuilder::new(); + + $attributes['name'] = "{$attributes['name']} Asset Model"; + $attributes['import_type'] = 'assetModel'; + $attributes['header_row'] = $fileBuilder->toCsv()[0]; + $attributes['first_row'] = $fileBuilder->firstRow(); + + return $attributes; + }); + } + } diff --git a/database/migrations/2023_08_01_174150_change_webhook_settings_variable_type.php b/database/migrations/2023_08_01_174150_change_webhook_settings_variable_type.php index 59c9728e2e0d..ddce33e90306 100644 --- a/database/migrations/2023_08_01_174150_change_webhook_settings_variable_type.php +++ b/database/migrations/2023_08_01_174150_change_webhook_settings_variable_type.php @@ -26,7 +26,7 @@ public function up() public function down() { Schema::table('settings', function (Blueprint $table) { - $table->varchar('webhook_endpoint')->change(); + $table->string('webhook_endpoint')->change(); }); } diff --git a/database/migrations/2024_08_01_201721_add_required_notes_setting.php b/database/migrations/2024_08_01_201721_add_required_notes_setting.php new file mode 100644 index 000000000000..38fdd38c1137 --- /dev/null +++ b/database/migrations/2024_08_01_201721_add_required_notes_setting.php @@ -0,0 +1,30 @@ +boolean('require_checkinout_notes')->nullable()->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + if (Schema::hasColumn('settings', 'require_checkinout_notes')) { + $table->dropColumn('require_checkinout_notes'); + } + }); + } +}; diff --git a/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php b/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php index a57406ce1035..3bef5948b691 100644 --- a/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php +++ b/database/migrations/2024_09_17_204302_change_user_id_to_created_by.php @@ -42,7 +42,7 @@ public function down(): void } foreach ($this->existing_table_list() as $table) { - if (Schema::hasColumn($table, 'user_id')) { + if (Schema::hasColumn($table, 'created_by')) { Schema::table($table, function (Blueprint $table) { $table->renameColumn('created_by', 'user_id'); }); diff --git a/database/migrations/2024_10_23_162301_add_manufacturer_id_model_number_to_consumables.php b/database/migrations/2024_10_23_162301_add_manufacturer_id_model_number_to_consumables.php new file mode 100644 index 000000000000..0180ac0edddf --- /dev/null +++ b/database/migrations/2024_10_23_162301_add_manufacturer_id_model_number_to_consumables.php @@ -0,0 +1,30 @@ +integer('manufacturer_id')->after('purchase_cost')->nullable()->default(null); + $table->string('model_number')->after('purchase_cost')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('components', function (Blueprint $table) { + $table->dropColumn('manufacturer_id'); + $table->dropColumn('model_number'); + }); + } +}; diff --git a/database/migrations/2024_11_06_211457_add_manager_indexes_to_location_and_user.php b/database/migrations/2024_11_06_211457_add_manager_indexes_to_location_and_user.php new file mode 100644 index 000000000000..d1a151c126ae --- /dev/null +++ b/database/migrations/2024_11_06_211457_add_manager_indexes_to_location_and_user.php @@ -0,0 +1,34 @@ +index('manager_id'); + }); + Schema::table('users', function (Blueprint $table) { + $table->index('manager_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('locations', function (Blueprint $table) { + $table->dropIndex(['manager_id']); + }); + Schema::table('users', function (Blueprint $table) { + $table->dropIndex(['manager_id']); + }); + } +}; diff --git a/database/migrations/2024_11_07_113631_improve_manager_indexes_on_users_and_locations.php b/database/migrations/2024_11_07_113631_improve_manager_indexes_on_users_and_locations.php new file mode 100644 index 000000000000..cc2963feccc6 --- /dev/null +++ b/database/migrations/2024_11_07_113631_improve_manager_indexes_on_users_and_locations.php @@ -0,0 +1,39 @@ +dropIndex(['manager_id']); + $table->index(['manager_id','deleted_at']); + }); + Schema::table('users', function (Blueprint $table) { + $table->dropIndex(['manager_id']); + $table->index(['manager_id','deleted_at']); + }); + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('locations', function (Blueprint $table) { + $table->dropIndex(['manager_id','deleted_at']); + $table->index(['manager_id']); + }); + Schema::table('users', function (Blueprint $table) { + $table->dropIndex(['manager_id','deleted_at']); + $table->index(['manager_id']); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index 5a98e56c6ce5..1cf415e096e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "bootstrap-colorpicker": "^2.5.3", "bootstrap-datepicker": "^1.10.0", "bootstrap-less": "^3.3.8", - "bootstrap-table": "1.23.2", + "bootstrap-table": "1.23.5", "canvas-confetti": "^1.9.3", "chart.js": "^2.9.4", "clipboard": "^2.0.11", @@ -23,10 +23,10 @@ "ekko-lightbox": "^5.1.1", "imagemin": "^8.0.1", "jquery-slimscroll": "^1.3.8", - "jquery-ui": "^1.14.0", + "jquery-ui": "^1.14.1", "jquery-validation": "^1.21.0", "jquery.iframe-transport": "^1.0.0", - "jspdf-autotable": "^3.8.3", + "jspdf-autotable": "^3.8.4", "less": "^4.2.0", "less-loader": "^6.0", "list.js": "^1.5.0", @@ -37,7 +37,7 @@ "signature_pad": "^4.2.0", "tableexport.jquery.plugin": "1.30.0", "tether": "^1.4.0", - "webpack": "^5.94.0" + "webpack": "^5.95.0" }, "devDependencies": { "all-contributors-cli": "^6.26.1", @@ -2105,10 +2105,28 @@ "@types/node": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/express": { "version": "4.17.21", @@ -2467,9 +2485,9 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "bin": { "acorn": "bin/acorn" }, @@ -2484,14 +2502,6 @@ "acorn": "^8" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-node": { "version": "1.8.2", "license": "Apache-2.0", @@ -3678,9 +3688,9 @@ "license": "MIT" }, "node_modules/bootstrap-table": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.23.2.tgz", - "integrity": "sha512-1IFiWFZzbKlleXgYEHdwHkX6rxlQMEx2N1tA8rJK/j08pI+NjIGnxFeXUL26yQLQ0U135eis/BX3OV1+anY25g==", + "version": "1.23.5", + "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.23.5.tgz", + "integrity": "sha512-9WByoSpJvA73gi2YYIlX6IWR74oZtBmSixul/Th8FTBtBd/kZRpbKESGTjhA3BA3AYTnfyY8Iy1KeRWPlV2GWQ==", "peerDependencies": { "jquery": "3" } @@ -3940,7 +3950,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "funding": [ { "type": "opencollective", @@ -3955,12 +3967,11 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4067,7 +4078,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001616", + "version": "1.0.30001677", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", + "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", "funding": [ { "type": "opencollective", @@ -4081,8 +4094,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/canvas-confetti": { "version": "1.9.3", @@ -5254,8 +5266,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.756", - "license": "ISC" + "version": "1.5.52", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.52.tgz", + "integrity": "sha512-xtoijJTZ+qeucLBDNztDOuQBE1ksqjvNjvqFoST3nGC7fSpqJ+X6BdTBaY5BHG+IhWWmpc6b/KfpeuEDupEPOQ==" }, "node_modules/elliptic": { "version": "6.5.5", @@ -5388,8 +5401,9 @@ "license": "MIT" }, "node_modules/escalade": { - "version": "3.1.2", - "license": "MIT", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } @@ -7052,9 +7066,9 @@ "license": "BSD-2-Clause" }, "node_modules/jquery-ui": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.14.0.tgz", - "integrity": "sha512-mPfYKBoRCf0MzaT2cyW5i3IuZ7PfTITaasO5OFLAQxrHuI+ZxruPa+4/K1OMNT8oElLWGtIxc9aRbyw20BKr8g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.14.1.tgz", + "integrity": "sha512-DhzsYH8VeIvOaxwi+B/2BCsFFT5EGjShdzOcm5DssWjtcpGWIMsn66rJciDA6jBruzNiLf1q0KvwMoX1uGNvnQ==", "dependencies": { "jquery": ">=1.12.0 <5.0.0" } @@ -7167,9 +7181,9 @@ } }, "node_modules/jspdf-autotable": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.3.tgz", - "integrity": "sha512-PQFdljBt+ijm6ZWXYxhZ54A/awV63UKcipYoA2+YGsz0BXXiXTIL/FIg+V30j7wPdSdzClfbB3qKX9UeuFylPQ==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.4.tgz", + "integrity": "sha512-rSffGoBsJYX83iTRv8Ft7FhqfgEL2nLpGAIiqruEQQ3e4r0qdLFbPUB7N9HAle0I3XgpisvyW751VHCqKUVOgQ==", "peerDependencies": { "jspdf": "^2.5.1" } @@ -8097,8 +8111,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "license": "MIT" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -8549,8 +8564,9 @@ "optional": true }, "node_modules/picocolors": { - "version": "1.0.0", - "license": "ISC" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -10703,7 +10719,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.15", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -10718,10 +10736,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -10866,17 +10883,17 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", "dependencies": { - "@types/estree": "^1.0.5", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", diff --git a/package.json b/package.json index 2b3ec19b6364..2bc87624895a 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "bootstrap-colorpicker": "^2.5.3", "bootstrap-datepicker": "^1.10.0", "bootstrap-less": "^3.3.8", - "bootstrap-table": "1.23.2", + "bootstrap-table": "1.23.5", "canvas-confetti": "^1.9.3", "chart.js": "^2.9.4", "clipboard": "^2.0.11", @@ -43,10 +43,10 @@ "ekko-lightbox": "^5.1.1", "imagemin": "^8.0.1", "jquery-slimscroll": "^1.3.8", - "jquery-ui": "^1.14.0", + "jquery-ui": "^1.14.1", "jquery-validation": "^1.21.0", "jquery.iframe-transport": "^1.0.0", - "jspdf-autotable": "^3.8.3", + "jspdf-autotable": "^3.8.4", "less": "^4.2.0", "less-loader": "^6.0", "list.js": "^1.5.0", @@ -57,6 +57,6 @@ "signature_pad": "^4.2.0", "tableexport.jquery.plugin": "1.30.0", "tether": "^1.4.0", - "webpack": "^5.94.0" + "webpack": "^5.95.0" } } diff --git a/public/css/build/app.css b/public/css/build/app.css index d35eebb0a633..83e5d5885628 100644 --- a/public/css/build/app.css +++ b/public/css/build/app.css @@ -396,7 +396,7 @@ img.navbar-brand-img, padding: 10px; background: #f4f4f4; margin-bottom: 3px; - border-left: 2px solid #e6e7e8; + border-inline: 2px solid #e6e7e8; color: #444; cursor: move; } @@ -1160,7 +1160,6 @@ th.css-component > .th-inner::before { } @media screen and (max-width: 992px) { .info-stack-container { - display: flex; flex-direction: column; } .col-md-3.col-xs-12.col-sm-push-9.info-stack { @@ -1176,6 +1175,11 @@ th.css-component > .th-inner::before { float: none; } } +@media screen and (max-width: 992px) { + .row-new-striped div { + width: 100%; + } +} @media screen and (max-width: 1318px) and (min-width: 1200px) { .admin.box { height: 170px; diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index 6e4825dc40ea..5bae690fa3e6 100644 --- a/public/css/build/overrides.css +++ b/public/css/build/overrides.css @@ -28,7 +28,7 @@ padding: 10px; background: #f4f4f4; margin-bottom: 3px; - border-left: 2px solid #e6e7e8; + border-inline: 2px solid #e6e7e8; color: #444; cursor: move; } @@ -792,7 +792,6 @@ th.css-component > .th-inner::before { } @media screen and (max-width: 992px) { .info-stack-container { - display: flex; flex-direction: column; } .col-md-3.col-xs-12.col-sm-push-9.info-stack { @@ -808,6 +807,11 @@ th.css-component > .th-inner::before { float: none; } } +@media screen and (max-width: 992px) { + .row-new-striped div { + width: 100%; + } +} @media screen and (max-width: 1318px) and (min-width: 1200px) { .admin.box { height: 170px; diff --git a/public/css/dist/all.css b/public/css/dist/all.css index 3d3cc4f98f04..ea42eb7ad282 100644 --- a/public/css/dist/all.css +++ b/public/css/dist/all.css @@ -20968,7 +20968,333 @@ hr { .ekko-lightbox{display:-ms-flexbox!important;display:flex!important;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;padding-right:0!important}.ekko-lightbox-container{position:relative}.ekko-lightbox-container>div.ekko-lightbox-item{position:absolute;top:0;left:0;bottom:0;right:0;width:100%}.ekko-lightbox iframe{width:100%;height:100%}.ekko-lightbox-nav-overlay{z-index:1;position:absolute;top:0;left:0;width:100%;height:100%;display:-ms-flexbox;display:flex}.ekko-lightbox-nav-overlay a{-ms-flex:1;flex:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;opacity:0;transition:opacity .5s;color:#fff;font-size:30px;z-index:1}.ekko-lightbox-nav-overlay a>*{-ms-flex-positive:1;flex-grow:1}.ekko-lightbox-nav-overlay a>:focus{outline:none}.ekko-lightbox-nav-overlay a span{padding:0 30px}.ekko-lightbox-nav-overlay a:last-child span{text-align:right}.ekko-lightbox-nav-overlay a:hover{text-decoration:none}.ekko-lightbox-nav-overlay a:focus{outline:none}.ekko-lightbox-nav-overlay a.disabled{cursor:default;visibility:hidden}.ekko-lightbox a:hover{opacity:1;text-decoration:none}.ekko-lightbox .modal-dialog{display:none}.ekko-lightbox .modal-footer{text-align:left}.ekko-lightbox-loader{position:absolute;top:0;left:0;bottom:0;right:0;width:100%;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}.ekko-lightbox-loader>div{width:40px;height:40px;position:relative;text-align:center}.ekko-lightbox-loader>div>div{width:100%;height:100%;border-radius:50%;background-color:#fff;opacity:.6;position:absolute;top:0;left:0;animation:a 2s infinite ease-in-out}.ekko-lightbox-loader>div>div:last-child{animation-delay:-1s}.modal-dialog .ekko-lightbox-loader>div>div{background-color:#333}@keyframes a{0%,to{transform:scale(0);-webkit-transform:scale(0)}50%{transform:scale(1);-webkit-transform:scale(1)}} /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImVra28tbGlnaHRib3guY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGVBQ0UsOEJBQXlCLEFBQXpCLHVCQUF5QixBQUN6QixzQkFBb0IsQUFBcEIsbUJBQW9CLEFBQ3BCLHFCQUF3QixBQUF4Qix1QkFBd0IsQUFDeEIseUJBQTZCLENBQzlCLEFBQ0QseUJBQ0UsaUJBQW1CLENBQ3BCLEFBQ0QsZ0RBQ0Usa0JBQW1CLEFBQ25CLE1BQU8sQUFDUCxPQUFRLEFBQ1IsU0FBVSxBQUNWLFFBQVMsQUFDVCxVQUFZLENBQ2IsQUFDRCxzQkFDRSxXQUFZLEFBQ1osV0FBYSxDQUNkLEFBQ0QsMkJBQ0UsVUFBYSxBQUNiLGtCQUFtQixBQUNuQixNQUFPLEFBQ1AsT0FBUSxBQUNSLFdBQVksQUFDWixZQUFhLEFBQ2Isb0JBQWMsQUFBZCxZQUFjLENBQ2YsQUFDRCw2QkFDRSxXQUFRLEFBQVIsT0FBUSxBQUNSLG9CQUFjLEFBQWQsYUFBYyxBQUNkLHNCQUFvQixBQUFwQixtQkFBb0IsQUFDcEIsVUFBVyxBQUNYLHVCQUF5QixBQUN6QixXQUFZLEFBQ1osZUFBZ0IsQUFDaEIsU0FBYSxDQUNkLEFBQ0QsK0JBQ0Usb0JBQWEsQUFBYixXQUFhLENBQ2QsQUFDRCxvQ0FDRSxZQUFjLENBQ2YsQUFDRCxrQ0FDRSxjQUFnQixDQUNqQixBQUNELDZDQUNFLGdCQUFrQixDQUNuQixBQUNELG1DQUNFLG9CQUFzQixDQUN2QixBQUNELG1DQUNFLFlBQWMsQ0FDZixBQUNELHNDQUNFLGVBQWdCLEFBQ2hCLGlCQUFtQixDQUNwQixBQUNELHVCQUNFLFVBQVcsQUFDWCxvQkFBc0IsQ0FDdkIsQUFDRCw2QkFDRSxZQUFjLENBQ2YsQUFDRCw2QkFDRSxlQUFpQixDQUNsQixBQUNELHNCQUNFLGtCQUFtQixBQUNuQixNQUFPLEFBQ1AsT0FBUSxBQUNSLFNBQVUsQUFDVixRQUFTLEFBQ1QsV0FBWSxBQUNaLG9CQUFjLEFBQWQsYUFBYyxBQUVkLDBCQUF1QixBQUF2QixzQkFBdUIsQUFFdkIscUJBQXdCLEFBQXhCLHVCQUF3QixBQUV4QixzQkFBb0IsQUFBcEIsa0JBQW9CLENBQ3JCLEFBQ0QsMEJBQ0UsV0FBWSxBQUNaLFlBQWEsQUFDYixrQkFBbUIsQUFDbkIsaUJBQW1CLENBQ3BCLEFBQ0QsOEJBQ0UsV0FBWSxBQUNaLFlBQWEsQUFDYixrQkFBbUIsQUFDbkIsc0JBQXVCLEFBQ3ZCLFdBQWEsQUFDYixrQkFBbUIsQUFDbkIsTUFBTyxBQUNQLE9BQVEsQUFDUixtQ0FBNkMsQ0FDOUMsQUFDRCx5Q0FDRSxtQkFBcUIsQ0FDdEIsQUFDRCw0Q0FDRSxxQkFBdUIsQ0FDeEIsQUFVRCxhQUNFLE1BRUUsbUJBQW9CLEFBQ3BCLDBCQUE0QixDQUM3QixBQUNELElBQ0UsbUJBQW9CLEFBQ3BCLDBCQUE0QixDQUM3QixDQUNGIiwiZmlsZSI6ImVra28tbGlnaHRib3guY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLmVra28tbGlnaHRib3gge1xuICBkaXNwbGF5OiBmbGV4ICFpbXBvcnRhbnQ7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICBwYWRkaW5nLXJpZ2h0OiAwcHghaW1wb3J0YW50O1xufVxuLmVra28tbGlnaHRib3gtY29udGFpbmVyIHtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xufVxuLmVra28tbGlnaHRib3gtY29udGFpbmVyID4gZGl2LmVra28tbGlnaHRib3gtaXRlbSB7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgdG9wOiAwO1xuICBsZWZ0OiAwO1xuICBib3R0b206IDA7XG4gIHJpZ2h0OiAwO1xuICB3aWR0aDogMTAwJTtcbn1cbi5la2tvLWxpZ2h0Ym94IGlmcmFtZSB7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDEwMCU7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSB7XG4gIHotaW5kZXg6IDEwMDtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDEwMCU7XG4gIGRpc3BsYXk6IGZsZXg7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSBhIHtcbiAgZmxleDogMTtcbiAgZGlzcGxheTogZmxleDtcbiAgYWxpZ24taXRlbXM6IGNlbnRlcjtcbiAgb3BhY2l0eTogMDtcbiAgdHJhbnNpdGlvbjogb3BhY2l0eSAwLjVzO1xuICBjb2xvcjogI2ZmZjtcbiAgZm9udC1zaXplOiAzMHB4O1xuICB6LWluZGV4OiAxMDA7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSBhID4gKiB7XG4gIGZsZXgtZ3JvdzogMTtcbn1cbi5la2tvLWxpZ2h0Ym94LW5hdi1vdmVybGF5IGEgPiAqOmZvY3VzIHtcbiAgb3V0bGluZTogbm9uZTtcbn1cbi5la2tvLWxpZ2h0Ym94LW5hdi1vdmVybGF5IGEgc3BhbiB7XG4gIHBhZGRpbmc6IDAgMzBweDtcbn1cbi5la2tvLWxpZ2h0Ym94LW5hdi1vdmVybGF5IGE6bGFzdC1jaGlsZCBzcGFuIHtcbiAgdGV4dC1hbGlnbjogcmlnaHQ7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSBhOmhvdmVyIHtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xufVxuLmVra28tbGlnaHRib3gtbmF2LW92ZXJsYXkgYTpmb2N1cyB7XG4gIG91dGxpbmU6IG5vbmU7XG59XG4uZWtrby1saWdodGJveC1uYXYtb3ZlcmxheSBhLmRpc2FibGVkIHtcbiAgY3Vyc29yOiBkZWZhdWx0O1xuICB2aXNpYmlsaXR5OiBoaWRkZW47XG59XG4uZWtrby1saWdodGJveCBhOmhvdmVyIHtcbiAgb3BhY2l0eTogMTtcbiAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xufVxuLmVra28tbGlnaHRib3ggLm1vZGFsLWRpYWxvZyB7XG4gIGRpc3BsYXk6IG5vbmU7XG59XG4uZWtrby1saWdodGJveCAubW9kYWwtZm9vdGVyIHtcbiAgdGV4dC1hbGlnbjogbGVmdDtcbn1cbi5la2tvLWxpZ2h0Ym94LWxvYWRlciB7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgdG9wOiAwO1xuICBsZWZ0OiAwO1xuICBib3R0b206IDA7XG4gIHJpZ2h0OiAwO1xuICB3aWR0aDogMTAwJTtcbiAgZGlzcGxheTogZmxleDtcbiAgLyogZXN0YWJsaXNoIGZsZXggY29udGFpbmVyICovXG4gIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47XG4gIC8qIG1ha2UgbWFpbiBheGlzIHZlcnRpY2FsICovXG4gIGp1c3RpZnktY29udGVudDogY2VudGVyO1xuICAvKiBjZW50ZXIgaXRlbXMgdmVydGljYWxseSwgaW4gdGhpcyBjYXNlICovXG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG59XG4uZWtrby1saWdodGJveC1sb2FkZXIgPiBkaXYge1xuICB3aWR0aDogNDBweDtcbiAgaGVpZ2h0OiA0MHB4O1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIHRleHQtYWxpZ246IGNlbnRlcjtcbn1cbi5la2tvLWxpZ2h0Ym94LWxvYWRlciA+IGRpdiA+IGRpdiB7XG4gIHdpZHRoOiAxMDAlO1xuICBoZWlnaHQ6IDEwMCU7XG4gIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjtcbiAgb3BhY2l0eTogMC42O1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgbGVmdDogMDtcbiAgYW5pbWF0aW9uOiBzay1ib3VuY2UgMnMgaW5maW5pdGUgZWFzZS1pbi1vdXQ7XG59XG4uZWtrby1saWdodGJveC1sb2FkZXIgPiBkaXYgPiBkaXY6bGFzdC1jaGlsZCB7XG4gIGFuaW1hdGlvbi1kZWxheTogLTFzO1xufVxuLm1vZGFsLWRpYWxvZyAuZWtrby1saWdodGJveC1sb2FkZXIgPiBkaXYgPiBkaXYge1xuICBiYWNrZ3JvdW5kLWNvbG9yOiAjMzMzO1xufVxuQC13ZWJraXQta2V5ZnJhbWVzIHNrLWJvdW5jZSB7XG4gIDAlLFxuICAxMDAlIHtcbiAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMCk7XG4gIH1cbiAgNTAlIHtcbiAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMSk7XG4gIH1cbn1cbkBrZXlmcmFtZXMgc2stYm91bmNlIHtcbiAgMCUsXG4gIDEwMCUge1xuICAgIHRyYW5zZm9ybTogc2NhbGUoMCk7XG4gICAgLXdlYmtpdC10cmFuc2Zvcm06IHNjYWxlKDApO1xuICB9XG4gIDUwJSB7XG4gICAgdHJhbnNmb3JtOiBzY2FsZSgxKTtcbiAgICAtd2Via2l0LXRyYW5zZm9ybTogc2NhbGUoMSk7XG4gIH1cbn1cbiJdfQ== */ -.bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .search,.bootstrap-table .fixed-table-toolbar .columns{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px !important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.4286}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0 !important}.bootstrap-table .fixed-table-container .table th,.bootstrap-table .fixed-table-container .table td{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table thead th,.bootstrap-table .fixed-table-container .table tfoot th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table thead th:focus,.bootstrap-table .fixed-table-container .table tfoot th:focus{outline:0 solid rgba(0,0,0,0)}.bootstrap-table .fixed-table-container .table thead th.detail,.bootstrap-table .fixed-table-container .table tfoot th.detail{width:30px}.bootstrap-table .fixed-table-container .table thead th .th-inner,.bootstrap-table .fixed-table-container .table tfoot th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table thead th .sortable,.bootstrap-table .fixed-table-container .table tfoot th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px !important}.bootstrap-table .fixed-table-container .table thead th .sortable.sortable-center,.bootstrap-table .fixed-table-container .table tfoot th .sortable.sortable-center{padding-left:20px !important;padding-right:20px !important}.bootstrap-table .fixed-table-container .table thead th .both,.bootstrap-table .fixed-table-container .table tfoot th .both{background-image:url('data:image/svg+xml;utf8,');background-size:16px 16px;background-position:center right 2px}.bootstrap-table .fixed-table-container .table thead th .asc,.bootstrap-table .fixed-table-container .table tfoot th .asc{background-image:url('data:image/svg+xml;utf8,')}.bootstrap-table .fixed-table-container .table thead th .desc,.bootstrap-table .fixed-table-container .table tfoot th .desc{background-image:url('data:image/svg+xml;utf8,')}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:bold;display:inline-block;min-width:30%;width:auto !important;text-align:left !important}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value{width:100% !important;text-align:left !important}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox]{margin:0 auto !important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.25rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:flex;justify-content:center;position:absolute;bottom:0;width:100%;max-width:100%;z-index:1000;transition:visibility 0s,opacity .15s ease-in-out;opacity:0;visibility:hidden}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open{visibility:visible;opacity:1}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:loading;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination-detail,.bootstrap-table .fixed-table-pagination>.pagination{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:"⬅"}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:"➡"}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100% !important;background:#fff;height:100vh;overflow-y:scroll}.bootstrap-table.bootstrap4 .pagination-lg .page-link,.bootstrap-table.bootstrap5 .pagination-lg .page-link{padding:.5rem 1rem}.bootstrap-table.bootstrap5 .float-left{float:left}.bootstrap-table.bootstrap5 .float-right{float:right}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes loading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +@charset "UTF-8"; +/** + * @author zhixin wen + * version: 1.23.5 + * https://github.com/wenzhixin/bootstrap-table/ + */ +/* stylelint-disable annotation-no-unknown, max-line-length */ +/* stylelint-enable annotation-no-unknown, max-line-length */ +.bootstrap-table .fixed-table-toolbar::after { + content: ""; + display: block; + clear: both; +} +.bootstrap-table .fixed-table-toolbar .bs-bars, +.bootstrap-table .fixed-table-toolbar .search, +.bootstrap-table .fixed-table-toolbar .columns { + position: relative; + margin-top: 10px; + margin-bottom: 10px; +} +.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group { + display: inline-block; + margin-left: -1px !important; +} +.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group > .btn { + border-radius: 0; +} +.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:first-child > .btn { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:last-child > .btn { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu { + text-align: left; + max-height: 300px; + overflow: auto; + -ms-overflow-style: scrollbar; + z-index: 1001; +} +.bootstrap-table .fixed-table-toolbar .columns label { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.4286; +} +.bootstrap-table .fixed-table-toolbar .columns-left { + margin-right: 5px; +} +.bootstrap-table .fixed-table-toolbar .columns-right { + margin-left: 5px; +} +.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu { + right: 0; + left: auto; +} +.bootstrap-table .fixed-table-container { + position: relative; + clear: both; +} +.bootstrap-table .fixed-table-container .table { + width: 100%; + margin-bottom: 0 !important; +} +.bootstrap-table .fixed-table-container .table th, +.bootstrap-table .fixed-table-container .table td { + vertical-align: middle; + box-sizing: border-box; +} +.bootstrap-table .fixed-table-container .table thead th, +.bootstrap-table .fixed-table-container .table tfoot th { + vertical-align: bottom; + padding: 0; + margin: 0; +} +.bootstrap-table .fixed-table-container .table thead th:focus, +.bootstrap-table .fixed-table-container .table tfoot th:focus { + outline: 0 solid transparent; +} +.bootstrap-table .fixed-table-container .table thead th.detail, +.bootstrap-table .fixed-table-container .table tfoot th.detail { + width: 30px; +} +.bootstrap-table .fixed-table-container .table thead th .th-inner, +.bootstrap-table .fixed-table-container .table tfoot th .th-inner { + padding: 0.75rem; + vertical-align: bottom; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.bootstrap-table .fixed-table-container .table thead th .sortable, +.bootstrap-table .fixed-table-container .table tfoot th .sortable { + cursor: pointer; + background-position: right; + background-repeat: no-repeat; + padding-right: 30px !important; +} +.bootstrap-table .fixed-table-container .table thead th .sortable.sortable-center, +.bootstrap-table .fixed-table-container .table tfoot th .sortable.sortable-center { + padding-left: 20px !important; + padding-right: 20px !important; +} +.bootstrap-table .fixed-table-container .table thead th .both, +.bootstrap-table .fixed-table-container .table tfoot th .both { + background-image: url('data:image/svg+xml;utf8,'); + background-size: 16px 16px; + background-position: center right 2px; +} +.bootstrap-table .fixed-table-container .table thead th .asc, +.bootstrap-table .fixed-table-container .table tfoot th .asc { + background-image: url('data:image/svg+xml;utf8,'); +} +.bootstrap-table .fixed-table-container .table thead th .desc, +.bootstrap-table .fixed-table-container .table tfoot th .desc { + background-image: url('data:image/svg+xml;utf8,'); +} +.bootstrap-table .fixed-table-container .table tbody tr.selected td { + background-color: rgba(0, 0, 0, 0.075); +} +.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td { + text-align: center; +} +.bootstrap-table .fixed-table-container .table tbody tr .card-view { + display: flex; +} +.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title { + font-weight: bold; + display: inline-block; + min-width: 30%; + width: auto !important; + text-align: left !important; +} +.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value { + width: 100% !important; + text-align: left !important; +} +.bootstrap-table .fixed-table-container .table .bs-checkbox { + text-align: center; +} +.bootstrap-table .fixed-table-container .table .bs-checkbox label { + margin-bottom: 0; +} +.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio], +.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox] { + margin: 0 auto !important; +} +.bootstrap-table .fixed-table-container .table.table-sm .th-inner { + padding: 0.25rem; +} +.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer) { + border-bottom: 1px solid #dee2e6; +} +.bootstrap-table .fixed-table-container.fixed-height.has-card-view { + border-top: 1px solid #dee2e6; + border-bottom: 1px solid #dee2e6; +} +.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border { + border-left: 1px solid #dee2e6; + border-right: 1px solid #dee2e6; +} +.bootstrap-table .fixed-table-container.fixed-height .table thead th { + border-bottom: 1px solid #dee2e6; +} +.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th { + border-bottom: 1px solid #32383e; +} +.bootstrap-table .fixed-table-container .fixed-table-header { + overflow: hidden; +} +.bootstrap-table .fixed-table-container .fixed-table-body { + overflow: auto; + height: 100%; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading { + align-items: center; + background: #fff; + display: flex; + justify-content: center; + position: absolute; + bottom: 0; + width: 100%; + max-width: 100%; + z-index: 1000; + transition: visibility 0s, opacity 0.15s ease-in-out; + opacity: 0; + visibility: hidden; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open { + visibility: visible; + opacity: 1; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap { + align-items: baseline; + display: flex; + justify-content: center; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text { + margin-right: 6px; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap { + align-items: center; + display: flex; + justify-content: center; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot, +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after, +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before { + content: ""; + animation-duration: 1.5s; + animation-iteration-count: infinite; + animation-name: loading; + background: #212529; + border-radius: 50%; + display: block; + height: 5px; + margin: 0 4px; + opacity: 0; + width: 5px; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot { + animation-delay: 0.3s; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after { + animation-delay: 0.6s; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark { + background: #212529; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot, +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after, +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before { + background: #fff; +} +.bootstrap-table .fixed-table-container .fixed-table-footer { + overflow: hidden; +} +.bootstrap-table .fixed-table-pagination::after { + content: ""; + display: block; + clear: both; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail, +.bootstrap-table .fixed-table-pagination > .pagination { + margin-top: 10px; + margin-bottom: 10px; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail .pagination-info { + line-height: 34px; + margin-right: 5px; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list { + display: inline-block; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group { + position: relative; + display: inline-block; + vertical-align: middle; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group .dropdown-menu { + margin-bottom: 0; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination { + margin: 0; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a { + color: #c8c8c8; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::before { + content: "⬅"; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::after { + content: "➡"; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.disabled a { + pointer-events: none; + cursor: default; +} +.bootstrap-table.fullscreen { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + width: 100% !important; + background: #fff; + height: 100vh; + overflow-y: scroll; +} +.bootstrap-table.bootstrap4 .pagination-lg .page-link, .bootstrap-table.bootstrap5 .pagination-lg .page-link { + padding: 0.5rem 1rem; +} +.bootstrap-table.bootstrap5 .float-left { + float: left; +} +.bootstrap-table.bootstrap5 .float-right { + float: right; +} + +/* calculate scrollbar width */ +div.fixed-table-scroll-inner { + width: 100%; + height: 200px; +} + +div.fixed-table-scroll-outer { + top: 0; + left: 0; + visibility: hidden; + width: 200px; + height: 150px; + overflow: hidden; +} + +@keyframes loading { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } +} body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; @@ -21368,7 +21694,7 @@ img.navbar-brand-img, padding: 10px; background: #f4f4f4; margin-bottom: 3px; - border-left: 2px solid #e6e7e8; + border-inline: 2px solid #e6e7e8; color: #444; cursor: move; } @@ -22132,7 +22458,6 @@ th.css-component > .th-inner::before { } @media screen and (max-width: 992px) { .info-stack-container { - display: flex; flex-direction: column; } .col-md-3.col-xs-12.col-sm-push-9.info-stack { @@ -22148,6 +22473,11 @@ th.css-component > .th-inner::before { float: none; } } +@media screen and (max-width: 992px) { + .row-new-striped div { + width: 100%; + } +} @media screen and (max-width: 1318px) and (min-width: 1200px) { .admin.box { height: 170px; @@ -22849,7 +23179,7 @@ input[type="radio"]:checked::before { padding: 10px; background: #f4f4f4; margin-bottom: 3px; - border-left: 2px solid #e6e7e8; + border-inline: 2px solid #e6e7e8; color: #444; cursor: move; } @@ -23613,7 +23943,6 @@ th.css-component > .th-inner::before { } @media screen and (max-width: 992px) { .info-stack-container { - display: flex; flex-direction: column; } .col-md-3.col-xs-12.col-sm-push-9.info-stack { @@ -23629,6 +23958,11 @@ th.css-component > .th-inner::before { float: none; } } +@media screen and (max-width: 992px) { + .row-new-striped div { + width: 100%; + } +} @media screen and (max-width: 1318px) and (min-width: 1200px) { .admin.box { height: 170px; diff --git a/public/css/dist/bootstrap-table.css b/public/css/dist/bootstrap-table.css index 0b8274e974fd..8eb04d28a623 100644 --- a/public/css/dist/bootstrap-table.css +++ b/public/css/dist/bootstrap-table.css @@ -1,6 +1,352 @@ -.bootstrap-table .fixed-table-toolbar::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-toolbar .bs-bars,.bootstrap-table .fixed-table-toolbar .search,.bootstrap-table .fixed-table-toolbar .columns{position:relative;margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group{display:inline-block;margin-left:-1px !important}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group>.btn{border-radius:0}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:first-child>.btn{border-top-left-radius:4px;border-bottom-left-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .btn-group>.btn-group:last-child>.btn{border-top-right-radius:4px;border-bottom-right-radius:4px}.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu{text-align:left;max-height:300px;overflow:auto;-ms-overflow-style:scrollbar;z-index:1001}.bootstrap-table .fixed-table-toolbar .columns label{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.4286}.bootstrap-table .fixed-table-toolbar .columns-left{margin-right:5px}.bootstrap-table .fixed-table-toolbar .columns-right{margin-left:5px}.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu{right:0;left:auto}.bootstrap-table .fixed-table-container{position:relative;clear:both}.bootstrap-table .fixed-table-container .table{width:100%;margin-bottom:0 !important}.bootstrap-table .fixed-table-container .table th,.bootstrap-table .fixed-table-container .table td{vertical-align:middle;box-sizing:border-box}.bootstrap-table .fixed-table-container .table thead th,.bootstrap-table .fixed-table-container .table tfoot th{vertical-align:bottom;padding:0;margin:0}.bootstrap-table .fixed-table-container .table thead th:focus,.bootstrap-table .fixed-table-container .table tfoot th:focus{outline:0 solid rgba(0,0,0,0)}.bootstrap-table .fixed-table-container .table thead th.detail,.bootstrap-table .fixed-table-container .table tfoot th.detail{width:30px}.bootstrap-table .fixed-table-container .table thead th .th-inner,.bootstrap-table .fixed-table-container .table tfoot th .th-inner{padding:.75rem;vertical-align:bottom;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bootstrap-table .fixed-table-container .table thead th .sortable,.bootstrap-table .fixed-table-container .table tfoot th .sortable{cursor:pointer;background-position:right;background-repeat:no-repeat;padding-right:30px !important}.bootstrap-table .fixed-table-container .table thead th .sortable.sortable-center,.bootstrap-table .fixed-table-container .table tfoot th .sortable.sortable-center{padding-left:20px !important;padding-right:20px !important}.bootstrap-table .fixed-table-container .table thead th .both,.bootstrap-table .fixed-table-container .table tfoot th .both{background-image:url('data:image/svg+xml;utf8,');background-size:16px 16px;background-position:center right 2px}.bootstrap-table .fixed-table-container .table thead th .asc,.bootstrap-table .fixed-table-container .table tfoot th .asc{background-image:url('data:image/svg+xml;utf8,')}.bootstrap-table .fixed-table-container .table thead th .desc,.bootstrap-table .fixed-table-container .table tfoot th .desc{background-image:url('data:image/svg+xml;utf8,')}.bootstrap-table .fixed-table-container .table tbody tr.selected td{background-color:rgba(0,0,0,.075)}.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td{text-align:center}.bootstrap-table .fixed-table-container .table tbody tr .card-view{display:flex}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title{font-weight:bold;display:inline-block;min-width:30%;width:auto !important;text-align:left !important}.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value{width:100% !important;text-align:left !important}.bootstrap-table .fixed-table-container .table .bs-checkbox{text-align:center}.bootstrap-table .fixed-table-container .table .bs-checkbox label{margin-bottom:0}.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio],.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox]{margin:0 auto !important}.bootstrap-table .fixed-table-container .table.table-sm .th-inner{padding:.25rem}.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer){border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height.has-card-view{border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border{border-left:1px solid #dee2e6;border-right:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table thead th{border-bottom:1px solid #dee2e6}.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th{border-bottom:1px solid #32383e}.bootstrap-table .fixed-table-container .fixed-table-header{overflow:hidden}.bootstrap-table .fixed-table-container .fixed-table-body{overflow:auto;height:100%}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading{align-items:center;background:#fff;display:flex;justify-content:center;position:absolute;bottom:0;width:100%;max-width:100%;z-index:1000;transition:visibility 0s,opacity .15s ease-in-out;opacity:0;visibility:hidden}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open{visibility:visible;opacity:1}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap{align-items:baseline;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text{margin-right:6px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap{align-items:center;display:flex;justify-content:center}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before{content:"";animation-duration:1.5s;animation-iteration-count:infinite;animation-name:loading;background:#212529;border-radius:50%;display:block;height:5px;margin:0 4px;opacity:0;width:5px}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot{animation-delay:.3s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after{animation-delay:.6s}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark{background:#212529}.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after,.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before{background:#fff}.bootstrap-table .fixed-table-container .fixed-table-footer{overflow:hidden}.bootstrap-table .fixed-table-pagination::after{content:"";display:block;clear:both}.bootstrap-table .fixed-table-pagination>.pagination-detail,.bootstrap-table .fixed-table-pagination>.pagination{margin-top:10px;margin-bottom:10px}.bootstrap-table .fixed-table-pagination>.pagination-detail .pagination-info{line-height:34px;margin-right:5px}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list{display:inline-block}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group{position:relative;display:inline-block;vertical-align:middle}.bootstrap-table .fixed-table-pagination>.pagination-detail .page-list .btn-group .dropdown-menu{margin-bottom:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination{margin:0}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a{color:#c8c8c8}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::before{content:"⬅"}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.page-intermediate a::after{content:"➡"}.bootstrap-table .fixed-table-pagination>.pagination ul.pagination li.disabled a{pointer-events:none;cursor:default}.bootstrap-table.fullscreen{position:fixed;top:0;left:0;z-index:1050;width:100% !important;background:#fff;height:100vh;overflow-y:scroll}.bootstrap-table.bootstrap4 .pagination-lg .page-link,.bootstrap-table.bootstrap5 .pagination-lg .page-link{padding:.5rem 1rem}.bootstrap-table.bootstrap5 .float-left{float:left}.bootstrap-table.bootstrap5 .float-right{float:right}div.fixed-table-scroll-inner{width:100%;height:200px}div.fixed-table-scroll-outer{top:0;left:0;visibility:hidden;width:200px;height:150px;overflow:hidden}@keyframes loading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +@charset "UTF-8"; +/** + * @author zhixin wen + * version: 1.23.5 + * https://github.com/wenzhixin/bootstrap-table/ + */ +/* stylelint-disable annotation-no-unknown, max-line-length */ +/* stylelint-enable annotation-no-unknown, max-line-length */ +.bootstrap-table .fixed-table-toolbar::after { + content: ""; + display: block; + clear: both; +} +.bootstrap-table .fixed-table-toolbar .bs-bars, +.bootstrap-table .fixed-table-toolbar .search, +.bootstrap-table .fixed-table-toolbar .columns { + position: relative; + margin-top: 10px; + margin-bottom: 10px; +} +.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group { + display: inline-block; + margin-left: -1px !important; +} +.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group > .btn { + border-radius: 0; +} +.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:first-child > .btn { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.bootstrap-table .fixed-table-toolbar .columns .btn-group > .btn-group:last-child > .btn { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.bootstrap-table .fixed-table-toolbar .columns .dropdown-menu { + text-align: left; + max-height: 300px; + overflow: auto; + -ms-overflow-style: scrollbar; + z-index: 1001; +} +.bootstrap-table .fixed-table-toolbar .columns label { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.4286; +} +.bootstrap-table .fixed-table-toolbar .columns-left { + margin-right: 5px; +} +.bootstrap-table .fixed-table-toolbar .columns-right { + margin-left: 5px; +} +.bootstrap-table .fixed-table-toolbar .pull-right .dropdown-menu { + right: 0; + left: auto; +} +.bootstrap-table .fixed-table-container { + position: relative; + clear: both; +} +.bootstrap-table .fixed-table-container .table { + width: 100%; + margin-bottom: 0 !important; +} +.bootstrap-table .fixed-table-container .table th, +.bootstrap-table .fixed-table-container .table td { + vertical-align: middle; + box-sizing: border-box; +} +.bootstrap-table .fixed-table-container .table thead th, +.bootstrap-table .fixed-table-container .table tfoot th { + vertical-align: bottom; + padding: 0; + margin: 0; +} +.bootstrap-table .fixed-table-container .table thead th:focus, +.bootstrap-table .fixed-table-container .table tfoot th:focus { + outline: 0 solid transparent; +} +.bootstrap-table .fixed-table-container .table thead th.detail, +.bootstrap-table .fixed-table-container .table tfoot th.detail { + width: 30px; +} +.bootstrap-table .fixed-table-container .table thead th .th-inner, +.bootstrap-table .fixed-table-container .table tfoot th .th-inner { + padding: 0.75rem; + vertical-align: bottom; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.bootstrap-table .fixed-table-container .table thead th .sortable, +.bootstrap-table .fixed-table-container .table tfoot th .sortable { + cursor: pointer; + background-position: right; + background-repeat: no-repeat; + padding-right: 30px !important; +} +.bootstrap-table .fixed-table-container .table thead th .sortable.sortable-center, +.bootstrap-table .fixed-table-container .table tfoot th .sortable.sortable-center { + padding-left: 20px !important; + padding-right: 20px !important; +} +.bootstrap-table .fixed-table-container .table thead th .both, +.bootstrap-table .fixed-table-container .table tfoot th .both { + background-image: url('data:image/svg+xml;utf8,'); + background-size: 16px 16px; + background-position: center right 2px; +} +.bootstrap-table .fixed-table-container .table thead th .asc, +.bootstrap-table .fixed-table-container .table tfoot th .asc { + background-image: url('data:image/svg+xml;utf8,'); +} +.bootstrap-table .fixed-table-container .table thead th .desc, +.bootstrap-table .fixed-table-container .table tfoot th .desc { + background-image: url('data:image/svg+xml;utf8,'); +} +.bootstrap-table .fixed-table-container .table tbody tr.selected td { + background-color: rgba(0, 0, 0, 0.075); +} +.bootstrap-table .fixed-table-container .table tbody tr.no-records-found td { + text-align: center; +} +.bootstrap-table .fixed-table-container .table tbody tr .card-view { + display: flex; +} +.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-title { + font-weight: bold; + display: inline-block; + min-width: 30%; + width: auto !important; + text-align: left !important; +} +.bootstrap-table .fixed-table-container .table tbody tr .card-view .card-view-value { + width: 100% !important; + text-align: left !important; +} +.bootstrap-table .fixed-table-container .table .bs-checkbox { + text-align: center; +} +.bootstrap-table .fixed-table-container .table .bs-checkbox label { + margin-bottom: 0; +} +.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=radio], +.bootstrap-table .fixed-table-container .table .bs-checkbox label input[type=checkbox] { + margin: 0 auto !important; +} +.bootstrap-table .fixed-table-container .table.table-sm .th-inner { + padding: 0.25rem; +} +.bootstrap-table .fixed-table-container.fixed-height:not(.has-footer) { + border-bottom: 1px solid #dee2e6; +} +.bootstrap-table .fixed-table-container.fixed-height.has-card-view { + border-top: 1px solid #dee2e6; + border-bottom: 1px solid #dee2e6; +} +.bootstrap-table .fixed-table-container.fixed-height .fixed-table-border { + border-left: 1px solid #dee2e6; + border-right: 1px solid #dee2e6; +} +.bootstrap-table .fixed-table-container.fixed-height .table thead th { + border-bottom: 1px solid #dee2e6; +} +.bootstrap-table .fixed-table-container.fixed-height .table-dark thead th { + border-bottom: 1px solid #32383e; +} +.bootstrap-table .fixed-table-container .fixed-table-header { + overflow: hidden; +} +.bootstrap-table .fixed-table-container .fixed-table-body { + overflow: auto; + height: 100%; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading { + align-items: center; + background: #fff; + display: flex; + justify-content: center; + position: absolute; + bottom: 0; + width: 100%; + max-width: 100%; + z-index: 1000; + transition: visibility 0s, opacity 0.15s ease-in-out; + opacity: 0; + visibility: hidden; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.open { + visibility: visible; + opacity: 1; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap { + align-items: baseline; + display: flex; + justify-content: center; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .loading-text { + margin-right: 6px; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap { + align-items: center; + display: flex; + justify-content: center; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot, +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after, +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::before { + content: ""; + animation-duration: 1.5s; + animation-iteration-count: infinite; + animation-name: loading; + background: #212529; + border-radius: 50%; + display: block; + height: 5px; + margin: 0 4px; + opacity: 0; + width: 5px; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-dot { + animation-delay: 0.3s; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading .loading-wrap .animation-wrap::after { + animation-delay: 0.6s; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark { + background: #212529; +} +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-dot, +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::after, +.bootstrap-table .fixed-table-container .fixed-table-body .fixed-table-loading.table-dark .animation-wrap::before { + background: #fff; +} +.bootstrap-table .fixed-table-container .fixed-table-footer { + overflow: hidden; +} +.bootstrap-table .fixed-table-pagination::after { + content: ""; + display: block; + clear: both; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail, +.bootstrap-table .fixed-table-pagination > .pagination { + margin-top: 10px; + margin-bottom: 10px; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail .pagination-info { + line-height: 34px; + margin-right: 5px; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list { + display: inline-block; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group { + position: relative; + display: inline-block; + vertical-align: middle; +} +.bootstrap-table .fixed-table-pagination > .pagination-detail .page-list .btn-group .dropdown-menu { + margin-bottom: 0; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination { + margin: 0; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a { + color: #c8c8c8; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::before { + content: "⬅"; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.page-intermediate a::after { + content: "➡"; +} +.bootstrap-table .fixed-table-pagination > .pagination ul.pagination li.disabled a { + pointer-events: none; + cursor: default; +} +.bootstrap-table.fullscreen { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + width: 100% !important; + background: #fff; + height: 100vh; + overflow-y: scroll; +} +.bootstrap-table.bootstrap4 .pagination-lg .page-link, .bootstrap-table.bootstrap5 .pagination-lg .page-link { + padding: 0.5rem 1rem; +} +.bootstrap-table.bootstrap5 .float-left { + float: left; +} +.bootstrap-table.bootstrap5 .float-right { + float: right; +} + +/* calculate scrollbar width */ +div.fixed-table-scroll-inner { + width: 100%; + height: 200px; +} + +div.fixed-table-scroll-outer { + top: 0; + left: 0; + visibility: hidden; + width: 200px; + height: 150px; + overflow: hidden; +} -.fix-sticky{position:fixed !important;overflow:hidden;z-index:100}.fix-sticky table thead{background:#fff}.fix-sticky table thead.thead-light{background:#e9ecef}.fix-sticky table thead.thead-dark{background:#212529} +@keyframes loading { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } +} + +/** + * @author vincent loh + * @update zhixin wen + */ +.fix-sticky { + position: fixed !important; + overflow: hidden; + z-index: 100; +} + +.fix-sticky table thead { + background: #fff; +} + +.fix-sticky table thead.thead-light { + background: #e9ecef; +} + +.fix-sticky table thead.thead-dark { + background: #212529; +} /* * dragtable diff --git a/public/css/dist/skins/_all-skins.css b/public/css/dist/skins/_all-skins.css index c4d2130f99a8..b532ddb1f668 100644 --- a/public/css/dist/skins/_all-skins.css +++ b/public/css/dist/skins/_all-skins.css @@ -393,6 +393,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -1185,6 +1189,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -2151,6 +2159,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -2662,6 +2674,10 @@ li.dropdown-item-marker { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -3391,6 +3407,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -4128,6 +4148,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -5068,6 +5092,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/_all-skins.min.css b/public/css/dist/skins/_all-skins.min.css index c4d2130f99a8..b532ddb1f668 100644 --- a/public/css/dist/skins/_all-skins.min.css +++ b/public/css/dist/skins/_all-skins.min.css @@ -393,6 +393,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -1185,6 +1189,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -2151,6 +2159,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -2662,6 +2674,10 @@ li.dropdown-item-marker { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -3391,6 +3407,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -4128,6 +4148,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; @@ -5068,6 +5092,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-black-dark.css b/public/css/dist/skins/skin-black-dark.css index 89528e8ac46e..9b8aa60934a3 100644 --- a/public/css/dist/skins/skin-black-dark.css +++ b/public/css/dist/skins/skin-black-dark.css @@ -175,6 +175,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-black-dark.min.css b/public/css/dist/skins/skin-black-dark.min.css index 89528e8ac46e..9b8aa60934a3 100644 --- a/public/css/dist/skins/skin-black-dark.min.css +++ b/public/css/dist/skins/skin-black-dark.min.css @@ -175,6 +175,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-blue-dark.css b/public/css/dist/skins/skin-blue-dark.css index bfac907779fb..4290f090ba4f 100644 --- a/public/css/dist/skins/skin-blue-dark.css +++ b/public/css/dist/skins/skin-blue-dark.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-blue-dark.min.css b/public/css/dist/skins/skin-blue-dark.min.css index bfac907779fb..4290f090ba4f 100644 --- a/public/css/dist/skins/skin-blue-dark.min.css +++ b/public/css/dist/skins/skin-blue-dark.min.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-green-dark.css b/public/css/dist/skins/skin-green-dark.css index 275fa58ade03..af11e57391a2 100644 --- a/public/css/dist/skins/skin-green-dark.css +++ b/public/css/dist/skins/skin-green-dark.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-green-dark.min.css b/public/css/dist/skins/skin-green-dark.min.css index 275fa58ade03..af11e57391a2 100644 --- a/public/css/dist/skins/skin-green-dark.min.css +++ b/public/css/dist/skins/skin-green-dark.min.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-orange-dark.css b/public/css/dist/skins/skin-orange-dark.css index 816c529ef2ca..58a0afd1f27f 100644 --- a/public/css/dist/skins/skin-orange-dark.css +++ b/public/css/dist/skins/skin-orange-dark.css @@ -164,6 +164,10 @@ li.dropdown-item-marker { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-orange-dark.min.css b/public/css/dist/skins/skin-orange-dark.min.css index 816c529ef2ca..58a0afd1f27f 100644 --- a/public/css/dist/skins/skin-orange-dark.min.css +++ b/public/css/dist/skins/skin-orange-dark.min.css @@ -164,6 +164,10 @@ li.dropdown-item-marker { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-purple-dark.css b/public/css/dist/skins/skin-purple-dark.css index 88fe4ee35b8e..0517db814cd3 100644 --- a/public/css/dist/skins/skin-purple-dark.css +++ b/public/css/dist/skins/skin-purple-dark.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-purple-dark.min.css b/public/css/dist/skins/skin-purple-dark.min.css index 88fe4ee35b8e..0517db814cd3 100644 --- a/public/css/dist/skins/skin-purple-dark.min.css +++ b/public/css/dist/skins/skin-purple-dark.min.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-red-dark.css b/public/css/dist/skins/skin-red-dark.css index db532575b2c5..2b87b2b92159 100644 --- a/public/css/dist/skins/skin-red-dark.css +++ b/public/css/dist/skins/skin-red-dark.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-red-dark.min.css b/public/css/dist/skins/skin-red-dark.min.css index db532575b2c5..2b87b2b92159 100644 --- a/public/css/dist/skins/skin-red-dark.min.css +++ b/public/css/dist/skins/skin-red-dark.min.css @@ -170,6 +170,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-yellow-dark.css b/public/css/dist/skins/skin-yellow-dark.css index fb692ce058cb..119afcd4fd94 100644 --- a/public/css/dist/skins/skin-yellow-dark.css +++ b/public/css/dist/skins/skin-yellow-dark.css @@ -150,6 +150,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/css/dist/skins/skin-yellow-dark.min.css b/public/css/dist/skins/skin-yellow-dark.min.css index fb692ce058cb..119afcd4fd94 100644 --- a/public/css/dist/skins/skin-yellow-dark.min.css +++ b/public/css/dist/skins/skin-yellow-dark.min.css @@ -150,6 +150,10 @@ a:visited { .text-primary { color: #fff; } +#sort tr.cansort { + background-color: var(--back-main); + color: var(--text-main); +} :root { --background: #222; --back-main: #333; diff --git a/public/js/build/app.js b/public/js/build/app.js index 3c80510129f3..9bd78d354eeb 100644 --- a/public/js/build/app.js +++ b/public/js/build/app.js @@ -4047,7 +4047,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ $.ui = $.ui || {}; -return $.ui.version = "1.14.0"; +return $.ui.version = "1.14.1"; } ); @@ -4061,7 +4061,7 @@ return $.ui.version = "1.14.0"; /***/ ((module, exports, __webpack_require__) => { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! - * jQuery UI Widget 1.14.0 + * jQuery UI Widget 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -4117,6 +4117,9 @@ $.widget = function( name, base, prototype ) { var namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; + if ( name === "__proto__" || name === "constructor" ) { + return $.error( "Invalid widget name: " + name ); + } var fullName = namespace + "-" + name; if ( !prototype ) { diff --git a/public/js/build/vendor.js b/public/js/build/vendor.js index 6f934b73d07c..102490976bf7 100644 --- a/public/js/build/vendor.js +++ b/public/js/build/vendor.js @@ -12707,7 +12707,7 @@ return Tether; })); -/*! jQuery UI - v1.14.0 - 2024-08-05 +/*! jQuery UI - v1.14.1 - 2024-10-30 * https://jqueryui.com * Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-patch.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js * Copyright OpenJS Foundation and other contributors; Licensed MIT */ @@ -12729,11 +12729,11 @@ return Tether; $.ui = $.ui || {}; -var version = $.ui.version = "1.14.0"; +var version = $.ui.version = "1.14.1"; /*! - * jQuery UI Widget 1.14.0 + * jQuery UI Widget 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -12776,6 +12776,9 @@ $.widget = function( name, base, prototype ) { var namespace = name.split( "." )[ 0 ]; name = name.split( "." )[ 1 ]; + if ( name === "__proto__" || name === "constructor" ) { + return $.error( "Invalid widget name: " + name ); + } var fullName = namespace + "-" + name; if ( !prototype ) { @@ -13475,7 +13478,7 @@ var widget = $.widget; /*! - * jQuery UI Position 1.14.0 + * jQuery UI Position 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -13972,7 +13975,7 @@ var position = $.ui.position; /*! - * jQuery UI :data 1.14.0 + * jQuery UI :data 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -13995,7 +13998,7 @@ var data = $.extend( $.expr.pseudos, { } ); /*! - * jQuery UI Disable Selection 1.14.0 + * jQuery UI Disable Selection 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -14715,7 +14718,7 @@ colors = jQuery.Color.names = { /*! - * jQuery UI Effects 1.14.0 + * jQuery UI Effects 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15078,7 +15081,7 @@ if ( $.uiBackCompat === true ) { } $.extend( $.effects, { - version: "1.14.0", + version: "1.14.1", define: function( name, mode, effect ) { if ( !effect ) { @@ -15646,7 +15649,7 @@ var effect = $.effects; /*! - * jQuery UI Effects Blind 1.14.0 + * jQuery UI Effects Blind 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15701,7 +15704,7 @@ var effectsEffectBlind = $.effects.define( "blind", "hide", function( options, d /*! - * jQuery UI Effects Bounce 1.14.0 + * jQuery UI Effects Bounce 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15796,7 +15799,7 @@ var effectsEffectBounce = $.effects.define( "bounce", function( options, done ) /*! - * jQuery UI Effects Clip 1.14.0 + * jQuery UI Effects Clip 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15846,7 +15849,7 @@ var effectsEffectClip = $.effects.define( "clip", "hide", function( options, don /*! - * jQuery UI Effects Drop 1.14.0 + * jQuery UI Effects Drop 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15900,7 +15903,7 @@ var effectsEffectDrop = $.effects.define( "drop", "hide", function( options, don /*! - * jQuery UI Effects Explode 1.14.0 + * jQuery UI Effects Explode 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -15996,7 +15999,7 @@ var effectsEffectExplode = $.effects.define( "explode", "hide", function( option /*! - * jQuery UI Effects Fade 1.14.0 + * jQuery UI Effects Fade 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16028,7 +16031,7 @@ var effectsEffectFade = $.effects.define( "fade", "toggle", function( options, d /*! - * jQuery UI Effects Fold 1.14.0 + * jQuery UI Effects Fold 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16102,7 +16105,7 @@ var effectsEffectFold = $.effects.define( "fold", "hide", function( options, don /*! - * jQuery UI Effects Highlight 1.14.0 + * jQuery UI Effects Highlight 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16144,7 +16147,7 @@ var effectsEffectHighlight = $.effects.define( "highlight", "show", function( op /*! - * jQuery UI Effects Size 1.14.0 + * jQuery UI Effects Size 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16322,7 +16325,7 @@ var effectsEffectSize = $.effects.define( "size", function( options, done ) { /*! - * jQuery UI Effects Scale 1.14.0 + * jQuery UI Effects Scale 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16362,7 +16365,7 @@ var effectsEffectScale = $.effects.define( "scale", function( options, done ) { /*! - * jQuery UI Effects Puff 1.14.0 + * jQuery UI Effects Puff 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16388,7 +16391,7 @@ var effectsEffectPuff = $.effects.define( "puff", "hide", function( options, don /*! - * jQuery UI Effects Pulsate 1.14.0 + * jQuery UI Effects Pulsate 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16437,7 +16440,7 @@ var effectsEffectPulsate = $.effects.define( "pulsate", "show", function( option /*! - * jQuery UI Effects Shake 1.14.0 + * jQuery UI Effects Shake 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16496,7 +16499,7 @@ var effectsEffectShake = $.effects.define( "shake", function( options, done ) { /*! - * jQuery UI Effects Slide 1.14.0 + * jQuery UI Effects Slide 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16557,7 +16560,7 @@ var effectsEffectSlide = $.effects.define( "slide", "show", function( options, d /*! - * jQuery UI Effects Transfer 1.14.0 + * jQuery UI Effects Transfer 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16582,7 +16585,7 @@ var effectsEffectTransfer = effect; /*! - * jQuery UI Focusable 1.14.0 + * jQuery UI Focusable 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16645,7 +16648,7 @@ var focusable = $.ui.focusable; /*! - * jQuery UI Form Reset Mixin 1.14.0 + * jQuery UI Form Reset Mixin 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16707,7 +16710,7 @@ var formResetMixin = $.ui.formResetMixin = { /*! - * jQuery UI Legacy jQuery Core patches 1.14.0 + * jQuery UI Legacy jQuery Core patches 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16749,7 +16752,7 @@ if ( !$.fn.even || !$.fn.odd ) { ; /*! - * jQuery UI Keycode 1.14.0 + * jQuery UI Keycode 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16784,7 +16787,7 @@ var keycode = $.ui.keyCode = { /*! - * jQuery UI Labels 1.14.0 + * jQuery UI Labels 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16838,7 +16841,7 @@ var labels = $.fn.labels = function() { /*! - * jQuery UI Scroll Parent 1.14.0 + * jQuery UI Scroll Parent 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16872,7 +16875,7 @@ var scrollParent = $.fn.scrollParent = function( includeHidden ) { /*! - * jQuery UI Tabbable 1.14.0 + * jQuery UI Tabbable 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16896,7 +16899,7 @@ var tabbable = $.extend( $.expr.pseudos, { /*! - * jQuery UI Unique ID 1.14.0 + * jQuery UI Unique ID 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16934,7 +16937,7 @@ var uniqueId = $.fn.extend( { /*! - * jQuery UI Accordion 1.14.0 + * jQuery UI Accordion 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -16955,7 +16958,7 @@ var uniqueId = $.fn.extend( { var widgetsAccordion = $.widget( "ui.accordion", { - version: "1.14.0", + version: "1.14.1", options: { active: 0, animate: {}, @@ -17536,7 +17539,7 @@ var widgetsAccordion = $.widget( "ui.accordion", { /*! - * jQuery UI Menu 1.14.0 + * jQuery UI Menu 1.14.1 * https://jqueryui.com * * Copyright OpenJS Foundation and other contributors @@ -17555,7 +17558,7 @@ var widgetsAccordion = $.widget( "ui.accordion", { var widgetsMenu = $.widget( "ui.menu", { - version: "1.14.0", + version: "1.14.1", defaultElement: "