Compare commits

...

121 Commits

Author SHA1 Message Date
snipe
dffff07436 Removed codacy config 2026-03-13 18:29:21 +00:00
snipe
f69e2c671b Merge pull request #18709 from grokability/pint-resources
Applied pint to languages and resources
2026-03-13 18:22:55 +00:00
snipe
84fdb5d6c1 Applied pint to languages and resources 2026-03-13 18:17:51 +00:00
snipe
dc58ddce59 Merge pull request #18708 from grokability/pint-database
Apply pint to database directory
2026-03-13 18:14:02 +00:00
snipe
b5a46a370f Apply pint to database directory 2026-03-13 18:12:37 +00:00
snipe
199bdb219f Merge pull request #18707 from grokability/pint-config
Apply pint to config directory
2026-03-13 18:11:02 +00:00
snipe
c1a93e3ac8 Apply pint to config directory 2026-03-13 18:08:12 +00:00
snipe
f334b8caa3 Merge pull request #18706 from grokability/pint-providers
Apply pint to Providers directory
2026-03-13 18:03:57 +00:00
snipe
8b658a19b9 Apply pint to Providers directory 2026-03-13 17:56:51 +00:00
snipe
59dd9970d0 Merge pull request #18705 from grokability/pint-presenters
Apply pint to Presenters directory
2026-03-13 17:53:04 +00:00
snipe
55d46cbefe Apply pint to Presenters directory 2026-03-13 17:43:54 +00:00
snipe
e37ce27773 Merge pull request #18704 from grokability/pint-policies-and-observers
Apply pint to Observers and Policy directories
2026-03-13 17:42:57 +00:00
snipe
b2c0a21230 Apply pint to Observers and Policy directories 2026-03-13 17:41:08 +00:00
snipe
00704aea73 Merge pull request #18703 from grokability/pint-notifications
Apply pint to Notifications directory
2026-03-13 17:39:24 +00:00
snipe
31043d1f5c Apply pint to Notifications directory 2026-03-13 17:37:47 +00:00
snipe
33b68c11db Merge pull request #18702 from grokability/pint-mail
Apply pint to Mail directory
2026-03-13 17:32:33 +00:00
snipe
de607e7d83 Apply pint to Mail directory 2026-03-13 17:26:56 +00:00
snipe
ab7ff30a73 Merge pull request #18701 from grokability/pint-livewire
Applied pint to Livewire directory
2026-03-13 17:25:21 +00:00
snipe
53f2ef2ca1 Applied pint to Livewire directory 2026-03-13 17:19:19 +00:00
snipe
25f2e560f1 Merge pull request #18700 from grokability/pint-jobs-listeners
Apply pint to Jobs and Listeners directory
2026-03-13 17:18:21 +00:00
snipe
317b1a462e Apply pint to Jobs and Listeners directory 2026-03-13 17:13:08 +00:00
snipe
bf97734533 Merge pull request #18699 from grokability/pint-importer
Apply pint to Importer directory
2026-03-13 17:11:35 +00:00
snipe
3e831bf9b3 Apply pint to Importer directory 2026-03-13 17:10:06 +00:00
snipe
b4400b38a9 Merge pull request #18698 from grokability/pint-traits-and-transformers
Applied pint to Traits and Transformers
2026-03-13 17:07:54 +00:00
snipe
a613380811 Applied pint to Traits and Transformers 2026-03-13 17:02:28 +00:00
snipe
7f30fe1a95 Merge pull request #18697 from grokability/pint-requests
Apply pint to Requests directory
2026-03-13 17:00:47 +00:00
snipe
93168326da Apply pint to Requests directory 2026-03-13 16:56:26 +00:00
snipe
70c672eb52 Merge pull request #18696 from grokability/pint-middleware
Apply pint to Middleware directory
2026-03-13 16:54:17 +00:00
snipe
ec6caf9b59 Apply pint to Middleware directory 2026-03-13 16:53:11 +00:00
snipe
1f1d41ecfd Merge pull request #18695 from grokability/pint-controllers
Apply pint to non-API controllers
2026-03-13 16:50:28 +00:00
snipe
9bc92f57c8 Apply pint to non-API controllers 2026-03-13 16:45:30 +00:00
snipe
bbe6475eb2 Merge pull request #18694 from grokability/pint-api-controllers
Apply pint to API controllers
2026-03-13 16:43:33 +00:00
snipe
1e5d426e70 Apply pint to API controllers 2026-03-13 16:38:23 +00:00
snipe
ccf6856143 Apply pint to Helpers 2026-03-13 16:37:19 +00:00
snipe
175d8306b8 Merge pull request #18693 from grokability/pint-exceptions
Apply pint to Exceptions directory
2026-03-13 16:35:43 +00:00
snipe
2e7046a810 Apply pint to Exceptions directory 2026-03-13 16:32:11 +00:00
snipe
c03c913ce7 Merge pull request #18692 from grokability/pint-enums
Apply pint to enums and events
2026-03-13 16:30:52 +00:00
snipe
f7b82ad1ff Apply pint to enums and events 2026-03-13 16:25:59 +00:00
snipe
118ddfce94 Merge pull request #18691 from grokability/pint-console
Apply pint to Console directory
2026-03-13 16:24:39 +00:00
snipe
8bce38b918 Apply pint to Console directory 2026-03-13 16:20:24 +00:00
snipe
d31ba20dd5 Merge pull request #18690 from grokability/pint-actions
Use pint on actions directory
2026-03-13 16:19:11 +00:00
snipe
a3c7410c35 Apply pint to actions directory 2026-03-13 16:04:34 +00:00
snipe
624e6839c3 Merge pull request #18689 from grokability/pint-models
Use pint on models directory
2026-03-13 16:02:51 +00:00
snipe
9623fa4d87 Use pint on models directory 2026-03-13 15:55:28 +00:00
snipe
c196637922 Install pint dev dependency 2026-03-13 14:52:13 +00:00
snipe
67284e2e6d Updated badge in readme 2026-03-13 11:17:36 +00:00
snipe
f6ee500e6c Merge pull request #18684 from marcusmoore/copilot-updates
Updated copilot instructions
2026-03-13 11:16:41 +00:00
snipe
b76d909619 Merge pull request #18685 from marcusmoore/test-improvements
Improved asset tests
2026-03-13 11:16:29 +00:00
snipe
faf9cecc10 Merge pull request #18688 from grokability/normalize-requests-with-class-reference
Use Blah::class instead of new Blah in form requests
2026-03-13 11:09:42 +00:00
snipe
1246de2644 Use Blah::class instead of new Blah in form requests 2026-03-13 11:02:05 +00:00
snipe
0c72109ad8 Revert RMB for more consistent UX 2026-03-13 09:34:26 +00:00
Marcus Moore
5aad15256c Improve tests around assets 2026-03-12 16:15:27 -07:00
Marcus Moore
69f7778067 Revise copilot instructions 2026-03-12 15:56:37 -07:00
snipe
5d6b9890ca Import eloquent model 2026-03-12 18:44:45 +00:00
snipe
0d257d956f Undo json encode 2026-03-12 14:54:18 +00:00
snipe
4c05f26940 Merge pull request #18681 from grokability/redirect-fixes
Use intended() for redirect options
2026-03-12 14:44:38 +00:00
snipe
ce18ff669c Added admin check 2026-03-12 14:42:36 +00:00
snipe
a3b5346773 Ignore the sqlite database in git 2026-03-12 14:24:59 +00:00
snipe
3c96491295 Remove unused route 2026-03-12 14:24:46 +00:00
snipe
53abf8cdcc Load count with RMB controller 2026-03-12 14:10:43 +00:00
snipe
e376492128 Use intended() for redirect options 2026-03-12 13:57:50 +00:00
snipe
2658b9b064 Fixed variable in trans_choice for deployable check 2026-03-12 13:47:23 +00:00
snipe
f412b56caa Fixed #18678 - small UI tweaks 2026-03-12 13:40:47 +00:00
snipe
9c63b40a5a Merge pull request #18680 from grokability/copilot
Added copilot instructions file
2026-03-12 13:21:38 +00:00
snipe
40843f93dc Added copilot instructions file 2026-03-12 13:20:37 +00:00
snipe
9165f59dcc Normalized requiered colors 2026-03-12 12:23:20 +00:00
snipe
92ff333778 Force foreground color for label form-control 2026-03-12 12:13:59 +00:00
snipe
63e62cde1b Moved gates higher, switch to RMB for accessories 2026-03-12 12:13:38 +00:00
snipe
c5081ce3e5 Added colors to setting seeder 2026-03-12 11:47:04 +00:00
snipe
67d2a5d094 Use better maintenance name 2026-03-12 11:44:40 +00:00
snipe
a170da5c01 Merge pull request #18679 from grokability/normalize-breadcrumb-text
Normalize breadcrumb text
2026-03-12 11:42:27 +00:00
snipe
9652cb312a Normalize breadcrumb text 2026-03-12 11:34:38 +00:00
snipe
e4d7e08902 Merge pull request #18672 from marcusmoore/remove-laravel-collective-dep
Fixed #17199: Remove Laravel Collective HTML dependency
2026-03-11 19:53:25 +00:00
snipe
428095b71b Fixed #18670 - set nav link color override in ResetDemo console command 2026-03-11 17:56:58 +00:00
Marcus Moore
411ffb12ca Merge branch 'develop' into remove-laravel-collective-dep 2026-03-11 10:41:06 -07:00
snipe
3b93193da1 Merge pull request #18667 from marcusmoore/migrate-link-methods
Fixed #18666: Migrate Laravel Collective helper methods
2026-03-11 10:14:31 +00:00
snipe
224a813f25 Fixed #18668 - changed button type 2026-03-11 09:33:03 +00:00
Marcus Moore
7d079f74a1 Uninstall laravelcollective/html 2026-03-10 16:52:42 -07:00
Marcus Moore
4ca53e6f70 Remove Collective provider and aliases 2026-03-10 16:45:48 -07:00
Marcus Moore
70d1ffe294 Remove MacroServiceProvider and macros.php 2026-03-10 16:39:30 -07:00
Marcus Moore
335e1a7e18 Merge branch 'develop' into migrate-link-methods 2026-03-10 16:35:23 -07:00
Marcus Moore
287481a44e Fix model reference 2026-03-10 13:38:14 -07:00
Marcus Moore
2dd09f9702 Remove unused method 2026-03-10 13:35:25 -07:00
snipe
9317d6551d Fixed #18653 - “select company” to “company” in user edit 🙄 2026-03-10 20:25:53 +00:00
snipe
ed3d30e343 Merge pull request #18657 from marcusmoore/form-macros
Fixed #17200 and #17201: Remove alt_barcode_types and barcode_types macros
2026-03-10 20:11:01 +00:00
Marcus Moore
ca5a25f703 Replace remaining calls to link_to_route in Presenters 2026-03-10 13:06:46 -07:00
Marcus Moore
692a9ebebf Replace call to link_to_route 2026-03-10 11:58:27 -07:00
Marcus Moore
bf314a0f84 Replace call to link_to_route 2026-03-10 11:54:29 -07:00
Marcus Moore
c8c2bb6709 Remove unused serialUrl method from LicensePresenter 2026-03-10 11:31:07 -07:00
Marcus Moore
3fc8b976fc Merge branch 'develop' into form-macros
# Conflicts:
#	resources/macros/macros.php
2026-03-10 10:12:31 -07:00
snipe
6b9dc97fa1 Merge pull request #18665 from grokability/#18662-fix-seat-search
Fixed #18662 wire up search box in assigned license seats
2026-03-10 16:10:37 +00:00
snipe
4f3a30261e Fixed #18661 - return true/false in JSON 2026-03-10 15:42:35 +00:00
snipe
e7c478318c Fixed donked route 2026-03-10 15:39:29 +00:00
snipe
e75860c6ee Fixed #18662 wire up search box in assigned license seats 2026-03-10 15:31:54 +00:00
snipe
d0dbd1e561 Link parent company 2026-03-10 11:40:03 +00:00
snipe
e7eb4f0e80 More display_name 2026-03-10 10:25:01 +00:00
snipe
676a995889 Use update check for files controller api 2026-03-10 10:22:39 +00:00
snipe
a44fe14de1 Use display_name in more places 2026-03-10 10:14:27 +00:00
snipe
0015dbcd1d Merge pull request #18656 from marcusmoore/form-macro-username-select
Fixed #17208: Replace username_format macro
2026-03-10 09:42:15 +00:00
snipe
fc5e7cccbc Merge pull request #18652 from grokability/dependabot/github_actions/develop/docker/login-action-4
Bump docker/login-action from 3 to 4
2026-03-10 09:41:54 +00:00
dependabot[bot]
8987f3f951 Bump docker/login-action from 3 to 4
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 09:30:14 +00:00
snipe
ba671a8f1f Merge pull request #18651 from grokability/dependabot/github_actions/develop/docker/build-push-action-7
Bump docker/build-push-action from 6 to 7
2026-03-10 09:29:32 +00:00
snipe
fbe871f8d1 Merge pull request #18650 from grokability/dependabot/github_actions/develop/docker/metadata-action-6
Bump docker/metadata-action from 5 to 6
2026-03-10 09:29:10 +00:00
snipe
116b2d1229 Merge pull request #18649 from grokability/dependabot/github_actions/develop/docker/setup-buildx-action-4
Bump docker/setup-buildx-action from 3 to 4
2026-03-10 09:28:03 +00:00
snipe
20fd870b59 Merge pull request #18658 from marcusmoore/form-macro-countries
Fixed #17202: Replaced countries form macro
2026-03-10 09:27:05 +00:00
snipe
5c46990195 Check for user on enable_sounds 2026-03-10 09:20:35 +00:00
snipe
fab57020f2 Prevent browser errors since input field is display none 2026-03-10 09:16:24 +00:00
snipe
b37074f473 Null check on status (RB-4087) 2026-03-10 09:03:57 +00:00
snipe
24e2e81a28 Use component table in suppliers 2026-03-10 09:00:29 +00:00
Marcus Moore
ccabc1fbcc Remove countries macro 2026-03-09 17:37:58 -07:00
Marcus Moore
f847f83cb8 Replace macro in location modal 2026-03-09 17:36:26 -07:00
Marcus Moore
e0771827aa Replace country in address partial 2026-03-09 17:34:57 -07:00
Marcus Moore
fa6adaa155 Migrate countries macro on user page 2026-03-09 17:22:43 -07:00
Marcus Moore
f3504ce6fc Remove barcode_types macro 2026-03-09 16:36:54 -07:00
Marcus Moore
a075ca904b Remove alt_barcode_types macro 2026-03-09 16:36:26 -07:00
Marcus Moore
e3fb6fabf8 Remove username_format maco 2026-03-09 16:30:12 -07:00
Marcus Moore
6f89af790e Migrate username format to blade component 2026-03-09 16:29:55 -07:00
snipe
28f493d84d Escape pivot notes 2026-03-09 09:06:58 +00:00
dependabot[bot]
a87e862148 Bump docker/build-push-action from 6 to 7
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 08:42:34 +00:00
dependabot[bot]
6e264bfee0 Bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 08:42:27 +00:00
dependabot[bot]
1ecf862f2d Bump docker/setup-buildx-action from 3 to 4
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 08:42:22 +00:00
6642 changed files with 200967 additions and 201712 deletions

117
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,117 @@
# GitHub Copilot Custom Instructions for Snipe-IT
These instructions guide Copilot to generate code that aligns with modern Laravel 11 standards, PHP 8.2/8.4 features, software engineering principles, and industry best practices to improve software quality, maintainability, and security.
## ✅ General Coding Standards
- Prefer short, expressive, and readable code.
- Use **meaningful, descriptive variable, function, class, and file names**.
- Apply proper PHPDoc blocks for classes, methods, and complex logic.
- Organize code into small, reusable functions or classes with single responsibility.
- Avoid magic numbers or hard-coded strings; use constants or config files.
## ✅ PHP 8.2/8.4 Best Practices
- Use **readonly properties** to enforce immutability where applicable.
- Use **Enums** instead of string or integer constants.
- Utilize **First-class callable syntax** for callbacks.
- Leverage **Constructor Property Promotion**.
- Use **Union Types**, **Intersection Types**, and **true/false return types** for strict typing.
- Apply **Static Return Type** where needed.
- Use the **Nullsafe Operator (?->)** for optional chaining.
- Adopt **final classes** where extension is not intended.
- Use **Named Arguments** for improved clarity when calling functions with multiple parameters.
## ✅ Laravel 11 Project Structure & Conventions
- Follow the official Laravel project structure:
- `app/Http/Controllers` - Controllers
- `app/Models` - Eloquent models
- `app/Http/Requests` - Form request validation
- `app/Http/Resources` - API resource responses
- `app/Enums` - Enums
- `app/Actions` - Single-responsibility action classes
- `app/Policies` - Authorization logic
- Controllers must:
- Use dependency injection.
- Use Form Requests for validation. The request class should utilize the rules set on the model.
- Return typed responses (e.g., `JsonResponse`).
- Use Transformers for API responses.
## ✅ Eloquent ORM & Database
- Use **Eloquent Models** with proper `$fillable` or `$guarded` attributes for mass assignment protection.
- Utilize **casts** for date, boolean, JSON, and custom data types.
- Apply **accessors & mutators** for attribute transformation.
- Avoid direct raw SQL unless absolutely necessary; prefer Eloquent or Query Builder.
- Migrations:
- Always use migrations for schema changes.
- Include proper constraints (foreign keys, unique indexes, etc.).
- Prefer UUIDs or ULIDs as primary keys where applicable.
## ✅ API Development
- Use **Transformer classes** for consistent and structured JSON responses.
- Apply **route model binding** where possible.
- Use Form Requests for input validation.
## ✅ Blade & Frontend (if applicable)
- Keep Blade templates clean and logic-free; use View Composers or dedicated View Models for complex data.
- Use `@props`, `@aware`, `@once` Blade features appropriately.
- Utilize Alpine.js or Livewire for interactive frontend logic (optional).
## ✅ Security Best Practices
- Never trust user input; always validate and sanitize inputs.
- Use prepared statements via Eloquent or Query Builder to prevent SQL injection.
- Use Laravel's built-in CSRF, XSS, and validation mechanisms.
- Store sensitive information in `.env`, never hard-code secrets.
- Apply proper authorization checks using Policies or Gates.
- Follow principle of least privilege for users, roles, and permissions.
## ✅ Testing Standards
- Use **factories** for test data setup.
- Include feature tests for user-facing functionality.
- Include unit tests for business logic, services, and helper classes.
- Mock external services using Laravel's `Http::fake()` or equivalent.
- Maintain high code coverage but focus on meaningful tests over 100% coverage obsession.
## ✅ Software Quality & Maintainability
- Follow **SOLID Principles**:
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
- Follow **DRY** (Don't Repeat Yourself) and **KISS** (Keep It Simple, Stupid) principles.
- Apply **YAGNI** (You Aren't Gonna Need It) to avoid overengineering.
- Document complex logic with PHPDoc and inline comments.
## ✅ Performance & Optimization
- Eager load relationships to avoid N+1 queries.
- Use caching with Laravel's Cache system for frequently accessed data.
- Paginate large datasets using `paginate()` instead of `get()`.
- Queue long-running tasks using Laravel Queues.
- Optimize database indexes for common queries.
## ✅ Modern Laravel Features to Use
- Use **Event Broadcasting** if real-time updates are needed.
- Use **Full-text search** if search functionality is required.
- Use **Rate Limiting** for API routes.
## ✅ Additional Copilot Behavior Preferences
- Generate **strictly typed**, modern PHP code using latest language features.
- Prioritize **readable, clean, maintainable** code over cleverness.
- Avoid legacy or deprecated Laravel patterns (facade overuse, logic-heavy views, etc.).
- Suggest proper class placement based on Laravel directory structure.
- Suggest tests alongside new features where applicable.
- Default to **immutability**, **dependency injection**, and **encapsulation** best practices.
No newline at end of file

View File

@@ -1,57 +0,0 @@
# This workflow checks out code, performs a Codacy security scan
# and integrates the results with the
# GitHub Advanced Security code scanning feature. For more information on
# the Codacy security scan action usage and parameters, see
# https://github.com/codacy/codacy-analysis-cli-action.
# For more information on Codacy Analysis CLI in general, see
# https://github.com/codacy/codacy-analysis-cli.
name: Codacy Security Scan
on:
push:
branches: [ develop ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ develop ]
schedule:
- cron: '36 23 * * 3'
permissions:
contents: read
jobs:
codacy-security-scan:
# Ensure schedule job never runs on forked repos. It's only executed for 'grokability/snipe-it'
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
if: (github.repository == 'grokability/snipe-it') || ((github.repository != 'grokability/snipe-it') && (github.event_name != 'schedule'))
name: Codacy Security Scan
runs-on: ubuntu-latest
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout code
uses: actions/checkout@v6
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
uses: codacy/codacy-analysis-cli-action@v4.4.7
with:
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
# You can also omit the token and run the tools that support default configurations
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
verbose: true
output: results.sarif
format: sarif
# Adjust severity of non-security issues
gh-code-scanning-compat: true
# Force 0 exit code to allow SARIF file generation
# This will handover control about PR rejection to the GitHub side
max-allowed-issues: 2147483647
# Upload the SARIF file generated in the previous step
- name: Upload SARIF results file
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: results.sarif

View File

@@ -46,13 +46,13 @@ jobs:
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
@@ -73,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile.alpine

View File

@@ -46,13 +46,13 @@ jobs:
# https://github.com/docker/setup-buildx-action
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
# https://github.com/docker/login-action
- name: Login to DockerHub
# Only login if not a PR, as PRs only trigger a Docker build and not a push
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
@@ -64,7 +64,7 @@ jobs:
# Get Metadata for docker_build step below
- name: Sync metadata (tags, labels) from GitHub to Docker for 'snipe-it' image
id: meta_build
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: snipe/snipe-it
tags: ${{ env.IMAGE_TAGS }}
@@ -73,7 +73,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push 'snipe-it' image
id: docker_build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile

View File

@@ -1,6 +1,6 @@
![snipe-it-by-grok](https://github.com/grokability/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/) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/804dd1beb14a41f38810ab77d64fc4fc)](https://app.codacy.com/gh/grokability/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests](https://github.com/grokability/snipe-it/actions/workflows/tests.yml/badge.svg)](https://github.com/grokability/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/804dd1beb14a41f38810ab77d64fc4fc)](https://app.codacy.com/gh/grokability/snipe-it/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Tests in MySQL](https://github.com/grokability/snipe-it/actions/workflows/tests-mysql.yml/badge.svg)](https://github.com/grokability/snipe-it/actions/workflows/tests-mysql.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
@@ -124,7 +124,7 @@ We're currently working on our own mobile app, but in the meantime, check out th
### 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.** Please see our [AI Contribution Policy](https://snipe-it.readme.io/docs/contributing-overview#ai-usage-policy) for more information.
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 )

View File

@@ -21,7 +21,7 @@ class DestroyCategoryAction
* @throws ItemStillHasLicenses
* @throws ItemStillHasConsumables
*/
static function run(Category $category): bool
public static function run(Category $category): bool
{
$category->loadCount([
'assets as assets_count',
@@ -29,7 +29,7 @@ class DestroyCategoryAction
'consumables as consumables_count',
'components as components_count',
'licenses as licenses_count',
'models as models_count'
'models as models_count',
]);
if ($category->assets_count > 0) {
@@ -56,4 +56,4 @@ class DestroyCategoryAction
return true;
}
}
}

View File

@@ -14,8 +14,8 @@ class CancelCheckoutRequestAction
{
public static function run(Asset $asset, User $user)
{
if (!Company::isCurrentUserHasAccess($asset)) {
throw new AuthorizationException();
if (! Company::isCurrentUserHasAccess($asset)) {
throw new AuthorizationException;
}
$asset->cancelRequest();
@@ -27,7 +27,7 @@ class CancelCheckoutRequestAction
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction = new Actionlog;
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
@@ -44,5 +44,4 @@ class CancelCheckoutRequestAction
return true;
}
}
}

View File

@@ -23,8 +23,8 @@ class CreateCheckoutRequestAction
if (is_null(Asset::RequestableAssets()->find($asset->id))) {
throw new AssetNotRequestable($asset);
}
if (!Company::isCurrentUserHasAccess($asset)) {
throw new AuthorizationException();
if (! Company::isCurrentUserHasAccess($asset)) {
throw new AuthorizationException;
}
$data['item'] = $asset;
@@ -32,7 +32,7 @@ class CreateCheckoutRequestAction
$data['item_quantity'] = 1;
$settings = Setting::getSettings();
$logaction = new Actionlog();
$logaction = new Actionlog;
$logaction->item_id = $data['asset_id'] = $asset->id;
$logaction->item_type = $data['item_type'] = Asset::class;
$logaction->created_at = $data['requested_date'] = date('Y-m-d H:i:s');
@@ -51,4 +51,4 @@ class CreateCheckoutRequestAction
return true;
}
}
}

View File

@@ -20,7 +20,7 @@ class DeleteManufacturerAction
* @throws ItemStillHasLicenses
* @throws ItemStillHasConsumables
*/
static function run(Manufacturer $manufacturer): bool
public static function run(Manufacturer $manufacturer): bool
{
$manufacturer->loadCount([
'assets as assets_count',
@@ -55,9 +55,8 @@ class DeleteManufacturerAction
}
$manufacturer->delete();
//dd($manufacturer);
// dd($manufacturer);
return true;
}
}
}

View File

@@ -3,19 +3,18 @@
namespace App\Actions\Suppliers;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Models\Supplier;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasMaintenances;
use App\Exceptions\ItemStillHasLicenses;
use App\Exceptions\ItemStillHasMaintenances;
use App\Models\Supplier;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class DestroySupplierAction
{
/**
*
* @throws ItemStillHasLicenses
* @throws ItemStillHasAssets
* @throws ItemStillHasMaintenances
@@ -23,7 +22,7 @@ class DestroySupplierAction
* @throws ItemStillHasConsumables
* @throws ItemStillHasComponents
*/
static function run(Supplier $supplier): bool
public static function run(Supplier $supplier): bool
{
$supplier->loadCount([
'maintenances as maintenances_count',

View File

@@ -4,9 +4,7 @@ namespace App\Console\Commands;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
class CheckinLicensesFromAllUsers extends Command
{

View File

@@ -3,10 +3,8 @@
namespace App\Console\Commands;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
class CheckoutLicenseToAllUsers extends Command
{
@@ -75,6 +73,7 @@ class CheckoutLicenseToAllUsers extends Command
if ($user->licenses->where('id', '=', $license_id)->count()) {
$this->info($user->username.' already has this license checked out to them. Skipping... ');
continue;
}

View File

@@ -21,7 +21,7 @@ class CleanIncorrectCheckoutAcceptances extends Command
*
* @var string
*/
protected $description = "Delete checkout acceptances for checkouts to non-users";
protected $description = 'Delete checkout acceptances for checkouts to non-users';
/**
* Execute the console command.
@@ -35,30 +35,32 @@ class CleanIncorrectCheckoutAcceptances extends Command
$this->withProgressBar(CheckoutAcceptance::all(), function ($checkoutAcceptance) use (&$deletions, &$skips) {
$item = $checkoutAcceptance->checkoutable;
$checkout_to_id = $checkoutAcceptance->assigned_to_id;
if(is_null($item)) {
if (is_null($item)) {
$this->info("'Checkoutable' Item is null, going to next record");
return; //'false' allegedly breaks execution entirely, so 'true' maybe doesn't? hrm. just straight return maybe?
return; // 'false' allegedly breaks execution entirely, so 'true' maybe doesn't? hrm. just straight return maybe?
}
if(get_class($item) == LicenseSeat::class) {
if (get_class($item) == LicenseSeat::class) {
$item = $item->license;
}
foreach($item->assetlog()->where('action_type','checkout')->get() as $assetlog) {
foreach ($item->assetlog()->where('action_type', 'checkout')->get() as $assetlog) {
if ($assetlog->target_id == $checkout_to_id && $assetlog->target_type != User::class) {
//We have a checkout-to an ID for a non-User, which matches to an ID in the checkout_acceptances table
// We have a checkout-to an ID for a non-User, which matches to an ID in the checkout_acceptances table
//now, let's compare the _times_ - are they close?
//I'm picking `created_at` over `action_date` because I'm more interested in when the actionlogs
//were _created_, not when they were alleged to have happened - those created_at times need to be within 'X' seconds of
//each other (currently 5)
if ($assetlog->created_at->diffInSeconds($checkoutAcceptance->created_at, true) <= 5) { //we're allowing for five _ish_ seconds of slop
// now, let's compare the _times_ - are they close?
// I'm picking `created_at` over `action_date` because I'm more interested in when the actionlogs
// were _created_, not when they were alleged to have happened - those created_at times need to be within 'X' seconds of
// each other (currently 5)
if ($assetlog->created_at->diffInSeconds($checkoutAcceptance->created_at, true) <= 5) { // we're allowing for five _ish_ seconds of slop
$deletions++;
$checkoutAcceptance->forceDelete(); // HARD delete this record; it should have never been
return;
} else {
//$this->info("The two records are too far apart");
// $this->info("The two records are too far apart");
}
} else {
//$this->info("No match! checkout to id: " . $checkout_to_id." target_id: ".$assetlog->target_id." target_type: ".$assetlog->target_type);
// $this->info("No match! checkout to id: " . $checkout_to_id." target_id: ".$assetlog->target_id." target_type: ".$assetlog->target_type);
}
}
$skips++;

View File

@@ -8,6 +8,7 @@ use Illuminate\Console\Command;
class CleanOldCheckoutRequests extends Command
{
private int $deletions = 0;
private int $skips = 0;
/**
@@ -44,12 +45,14 @@ class CleanOldCheckoutRequests extends Command
if ($this->shouldForceDelete($request)) {
$request->forceDelete();
$this->deletions++;
return;
}
if ($this->shouldSoftDelete($request)) {
$request->delete();
$this->deletions++;
return;
}
@@ -64,7 +67,7 @@ class CleanOldCheckoutRequests extends Command
private function shouldForceDelete(CheckoutRequest $request)
{
// check if the requestable or user relationship is null
return !$request->requestable || !$request->user;
return ! $request->requestable || ! $request->user;
}
private function shouldSoftDelete(CheckoutRequest $request)

View File

@@ -2,31 +2,28 @@
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use \App\Models\User;
use Illuminate\Support\Carbon;
class CreateAdmin extends Command
{
/** @mixin User **/
/**
* App\Console\CreateAdmin
*
* @property mixed $first_name
* @property string $last_name
* @property string $username
* @property string $email
* @property string $permissions
* @property string $password
* @property boolean $activated
* @property boolean $show_in_list
* @property boolean $autoassign_licenses
* @property \Illuminate\Support\Carbon|null $created_at
* @property bool $activated
* @property bool $show_in_list
* @property bool $autoassign_licenses
* @property Carbon|null $created_at
* @property mixed $created_by
*/
protected $signature = 'snipeit:create-admin {--first_name=} {--last_name=} {--email=} {--username=} {--password=} {show_in_list?} {autoassign_licenses?}';
/**
@@ -46,7 +43,6 @@ class CreateAdmin extends Command
parent::__construct();
}
public function handle()
{
$first_name = $this->option('first_name');
@@ -57,8 +53,6 @@ class CreateAdmin extends Command
$show_in_list = $this->argument('show_in_list');
$autoassign_licenses = $this->argument('autoassign_licenses');
if (($first_name == '') || ($last_name == '') || ($username == '') || ($email == '') || ($password == '')) {
$this->info('ERROR: All fields are required.');
} else {

View File

@@ -24,6 +24,7 @@ class FixBulkAccessoryCheckinActionLogEntries extends Command
protected $description = 'This script attempts to fix timestamps and missing created_by values for bulk checkin entries in the log table';
private bool $dryrun = false;
private bool $skipBackup = false;
/**
@@ -50,10 +51,11 @@ class FixBulkAccessoryCheckinActionLogEntries extends Command
if ($logs->isEmpty()) {
$this->info('No logs found with incorrect timestamps.');
return 0;
}
$this->info('Found ' . $logs->count() . ' logs with incorrect timestamps:');
$this->info('Found '.$logs->count().' logs with incorrect timestamps:');
$this->table(
['ID', 'Created By', 'Created At', 'Updated At'],
@@ -67,11 +69,11 @@ class FixBulkAccessoryCheckinActionLogEntries extends Command
})
);
if (!$this->dryrun && !$this->confirm('Update these logs?')) {
if (! $this->dryrun && ! $this->confirm('Update these logs?')) {
return 0;
}
if (!$this->dryrun && !$this->skipBackup) {
if (! $this->dryrun && ! $this->skipBackup) {
$this->info('Backing up the database before making changes...');
$this->call('snipeit:backup');
}
@@ -83,7 +85,7 @@ class FixBulkAccessoryCheckinActionLogEntries extends Command
foreach ($logs as $log) {
$this->newLine();
$this->info('Processing log id:' . $log->id);
$this->info('Processing log id:'.$log->id);
// created_by was not being set for accessory bulk checkins
// so let's see if there was another bulk checkin log
@@ -106,7 +108,7 @@ class FixBulkAccessoryCheckinActionLogEntries extends Command
$this->line(vsprintf('Updating log id:%s from %s to %s', [$log->id, $log->created_at, $log->updated_at]));
$log->created_at = $log->updated_at;
if (!$this->dryrun) {
if (! $this->dryrun) {
Model::withoutTimestamps(function () use ($log) {
$log->saveQuietly();
});
@@ -129,7 +131,7 @@ class FixBulkAccessoryCheckinActionLogEntries extends Command
* This method attempts to find a bulk check in log that was
* created at the same time as the log passed in.
*/
private function getCreatedByAttributeFromSimilarLog(Actionlog $log): null|int
private function getCreatedByAttributeFromSimilarLog(Actionlog $log): ?int
{
$similarLog = Actionlog::query()
->whereNotNull('created_by')

View File

@@ -2,6 +2,21 @@
namespace App\Console\Commands;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\Department;
use App\Models\Depreciation;
use App\Models\Group;
use App\Models\License;
use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Statuslabel;
use App\Models\Supplier;
use App\Models\User;
use Illuminate\Console\Command;
class FixDoubleEscape extends Command
@@ -38,21 +53,21 @@ class FixDoubleEscape extends Command
public function handle()
{
$tables = [
\App\Models\Asset::class => ['name'],
\App\Models\License::class => ['name'],
\App\Models\Consumable::class => ['name'],
\App\Models\Accessory::class => ['name'],
\App\Models\Component::class => ['name'],
\App\Models\Company::class => ['name'],
\App\Models\Manufacturer::class => ['name'],
\App\Models\Supplier::class => ['name'],
\App\Models\Statuslabel::class => ['name'],
\App\Models\Depreciation::class => ['name'],
\App\Models\AssetModel::class => ['name'],
\App\Models\Group::class => ['name'],
\App\Models\Department::class => ['name'],
\App\Models\Location::class => ['name'],
\App\Models\User::class => ['first_name', 'last_name'],
Asset::class => ['name'],
License::class => ['name'],
Consumable::class => ['name'],
Accessory::class => ['name'],
Component::class => ['name'],
Company::class => ['name'],
Manufacturer::class => ['name'],
Supplier::class => ['name'],
Statuslabel::class => ['name'],
Depreciation::class => ['name'],
AssetModel::class => ['name'],
Group::class => ['name'],
Department::class => ['name'],
Location::class => ['name'],
User::class => ['first_name', 'last_name'],
];
$count = [];

View File

@@ -4,6 +4,7 @@ namespace App\Console\Commands;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\User;
use Illuminate\Console\Command;
class FixMismatchedAssetsAndLogs extends Command
@@ -56,26 +57,26 @@ class FixMismatchedAssetsAndLogs extends Command
$mismatch_count = 0;
$assets = Asset::whereNotNull('assigned_to')
->where('assigned_type', '=', \App\Models\User::class)
->where('assigned_type', '=', User::class)
->orderBy('id', 'ASC')->get();
foreach ($assets as $asset) {
// get the last checkout of the asset
if ($checkout_log = Actionlog::where('target_type', '=', \App\Models\User::class)
if ($checkout_log = Actionlog::where('target_type', '=', User::class)
->where('action_type', '=', 'checkout')
->where('item_id', '=', $asset->id)
->orderBy('created_at', 'DESC')
->first()) {
// Now check for a subsequent checkin log - we want to ignore those
if (! $checkin_log = Actionlog::where('target_type', '=', \App\Models\User::class)
->where('action_type', '=', 'checkin from')
->where('item_id', '=', $asset->id)
->whereDate('created_at', '>', $checkout_log->created_at)
->orderBy('created_at', 'DESC')
->first()) {
// Now check for a subsequent checkin log - we want to ignore those
if (! $checkin_log = Actionlog::where('target_type', '=', User::class)
->where('action_type', '=', 'checkin from')
->where('item_id', '=', $asset->id)
->whereDate('created_at', '>', $checkout_log->created_at)
->orderBy('created_at', 'DESC')
->first()) {
//print_r($asset);
// print_r($asset);
if ($checkout_log->target_id != $asset->assigned_to) {
$this->error('Log ID: '.$checkout_log->id.' -- Asset ID '.$checkout_log->item_id.' SHOULD BE checked out to User '.$checkout_log->target_id.' but its assigned_to is '.$asset->assigned_to);
@@ -90,7 +91,7 @@ class FixMismatchedAssetsAndLogs extends Command
$mismatch_count++;
}
} else {
//$this->info('Asset ID '.$asset->id.': There is a checkin '.$checkin_log->created_at.' after this checkout '.$checkout_log->created_at);
// $this->info('Asset ID '.$asset->id.': There is a checkin '.$checkin_log->created_at.' after this checkout '.$checkout_log->created_at);
}
}
}

View File

@@ -27,6 +27,6 @@ class FixUpAssignedTypeWithoutAssignedTo extends Command
public function handle()
{
DB::table('assets')->whereNotNull('assigned_type')->whereNull('assigned_to')->update(['assigned_type' => null]);
$this->info("Assets with an assigned_type but no assigned_to are fixed");
$this->info('Assets with an assigned_type but no assigned_to are fixed');
}
}

View File

@@ -27,40 +27,42 @@ class FixupAssignedToWithoutAssignedType extends Command
*/
public function handle()
{
$assets = Asset::whereNull("assigned_type")->whereNotNull("assigned_to")->withTrashed();
$assets = Asset::whereNull('assigned_type')->whereNotNull('assigned_to')->withTrashed();
$this->withProgressBar($assets->get(), function (Asset $asset) {
//now check each action log, from the most recent backwards, to find the last checkin or checkout
foreach($asset->log()->orderBy("id","desc")->get() as $action_log) {
if($this->option("debug")) {
$this->info("Asset id: " . $asset->id . " action log, action type is: " . $action_log->action_type);
// now check each action log, from the most recent backwards, to find the last checkin or checkout
foreach ($asset->log()->orderBy('id', 'desc')->get() as $action_log) {
if ($this->option('debug')) {
$this->info('Asset id: '.$asset->id.' action log, action type is: '.$action_log->action_type);
}
switch($action_log->action_type) {
switch ($action_log->action_type) {
case 'checkin from':
if($this->option("debug")) {
$this->info("Doing a checkin for ".$asset->id);
if ($this->option('debug')) {
$this->info('Doing a checkin for '.$asset->id);
}
$asset->assigned_to = null;
// if you have a required custom field, we still want to save, and we *don't* want an action_log
$asset->saveQuietly();
return;
case 'checkout':
if($this->option("debug")) {
$this->info("Doing a checkout for " . $asset->id . " picking target type: " . $action_log->target_type);
if ($this->option('debug')) {
$this->info('Doing a checkout for '.$asset->id.' picking target type: '.$action_log->target_type);
}
if($asset->assigned_to != $action_log->target_id) {
$this->error("Asset's assigned_to does *NOT* match Action Log's target_id. \$asset->assigned_to=".$asset->assigned_to." vs. \$action_log->target_id=".$action_log->target_id);
//FIXME - do we abort here? Do we try to keep looking? I don't know, this means your data is *really* messed up...
if ($asset->assigned_to != $action_log->target_id) {
$this->error("Asset's assigned_to does *NOT* match Action Log's target_id. \$asset->assigned_to=".$asset->assigned_to.' vs. $action_log->target_id='.$action_log->target_id);
// FIXME - do we abort here? Do we try to keep looking? I don't know, this means your data is *really* messed up...
}
$asset->assigned_type = $action_log->target_type;
$asset->saveQuietly(); // see above
return;
}
}
$asset->assigned_to = null; //asset was never checked in or out in its lifetime - it stays 'checked in'
$asset->saveQuietly(); //see above
$asset->assigned_to = null; // asset was never checked in or out in its lifetime - it stays 'checked in'
$asset->saveQuietly(); // see above
});
$this->newLine();
$this->info("Assets assigned_type are fixed");
$this->info('Assets assigned_type are fixed');
}
}

View File

@@ -2,14 +2,13 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use Laravel\Passport\TokenRepository;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Laravel\Passport\TokenRepository;
class GeneratePersonalAccessToken extends Command
{
/**
* The name and signature of the console command.
*
@@ -27,15 +26,13 @@ class GeneratePersonalAccessToken extends Command
*/
protected $description = 'This console command allows you to generate Personal API tokens to be used with the Snipe-IT JSON REST API on behalf of a user.';
/**
* The token repository implementation.
*
* @var \Laravel\Passport\TokenRepository
* @var TokenRepository
*/
protected $tokenRepository;
/**
* Create a new command instance.
*
@@ -56,11 +53,11 @@ class GeneratePersonalAccessToken extends Command
{
$accessTokenName = $this->option('name');
if ($accessTokenName=='') {
if ($accessTokenName == '') {
$accessTokenName = 'CLI Auth Token';
}
if ($this->option('user_id')=='') {
if ($this->option('user_id') == '') {
return $this->error('ERROR: user_id cannot be blank.');
}
@@ -75,7 +72,7 @@ class GeneratePersonalAccessToken extends Command
$this->warn('Your API Token has been created. Be sure to copy this token now, as it WILL NOT be accessible again.');
if ($token = DB::table('oauth_access_tokens')->where('user_id', '=', $user->id)->where('name','=',$accessTokenName)->orderBy('created_at', 'desc')->first()) {
if ($token = DB::table('oauth_access_tokens')->where('user_id', '=', $user->id)->where('name', '=', $accessTokenName)->orderBy('created_at', 'desc')->first()) {
$this->info('API Token ID: '.$token->id);
}
@@ -84,11 +81,8 @@ class GeneratePersonalAccessToken extends Command
$this->info('API Token: '.$createAccessToken);
}
} else {
return $this->error('ERROR: Invalid user. API key was not created.');
return $this->error('ERROR: Invalid user. API key was not created.');
}
}
}

View File

@@ -46,7 +46,7 @@ class ImportLocations extends Command
$filename = $this->argument('filename');
$csv = Reader::createFromPath(storage_path('private_uploads/imports/').$filename, 'r');
$this->info('Attempting to process: '.storage_path('private_uploads/imports/').$filename);
$csv->setHeaderOffset(0); //because we don't want to insert the header
$csv->setHeaderOffset(0); // because we don't want to insert the header
$results = $csv->getRecords();
// Import parent location names first if they don't exist

View File

@@ -38,22 +38,23 @@ class KillAllSessions extends Command
public function handle()
{
if (!$this->option('force') && !$this->confirm("****************************************************\nTHIS WILL FORCE A LOGIN FOR ALL LOGGED IN USERS.\n\nAre you SURE you wish to continue? ")) {
return $this->error("Session loss not confirmed");
if (! $this->option('force') && ! $this->confirm("****************************************************\nTHIS WILL FORCE A LOGIN FOR ALL LOGGED IN USERS.\n\nAre you SURE you wish to continue? ")) {
return $this->error('Session loss not confirmed');
}
$session_files = glob(storage_path("framework/sessions/*"));
$session_files = glob(storage_path('framework/sessions/*'));
$count = 0;
foreach ($session_files as $file) {
if (is_file($file))
if (is_file($file)) {
unlink($file);
$count++;
}
$count++;
}
\DB::table('users')->update(['remember_token' => null]);
$this->info($count. ' sessions cleared!');
$this->info($count.' sessions cleared!');
}
}

View File

@@ -5,11 +5,11 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\Department;
use App\Models\Group;
use Illuminate\Console\Command;
use App\Models\Setting;
use App\Models\Ldap;
use App\Models\User;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class LdapSync extends Command
@@ -47,35 +47,34 @@ class LdapSync extends Command
{
// If LDAP enabled isn't set to 1 (ldap_enabled!=1) then we should cut this short immediately without going any further
if (Setting::getSettings()->ldap_enabled!='1') {
if (Setting::getSettings()->ldap_enabled != '1') {
$this->error('LDAP is not enabled. Aborting. See Settings > LDAP to enable it.');
exit();
}
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); //600 seconds = 10 minutes
ini_set('max_execution_time', env('LDAP_TIME_LIM', 600)); // 600 seconds = 10 minutes
ini_set('memory_limit', env('LDAP_MEM_LIM', '500M'));
// Map the LDAP attributes to the Snipe-IT user fields.
$ldap_map = [
"username" => Setting::getSettings()->ldap_username_field,
"last_name" => Setting::getSettings()->ldap_lname_field,
"first_name" => Setting::getSettings()->ldap_fname_field,
"active_flag" => Setting::getSettings()->ldap_active_flag,
"emp_num" => Setting::getSettings()->ldap_emp_num,
"email" => Setting::getSettings()->ldap_email,
"phone" => Setting::getSettings()->ldap_phone_field,
"mobile" => Setting::getSettings()->ldap_mobile,
"jobtitle" => Setting::getSettings()->ldap_jobtitle,
"address" => Setting::getSettings()->ldap_address,
"city" => Setting::getSettings()->ldap_city,
"state" => Setting::getSettings()->ldap_state,
"zip" => Setting::getSettings()->ldap_zip,
"country" => Setting::getSettings()->ldap_country,
"location" => Setting::getSettings()->ldap_location,
"dept" => Setting::getSettings()->ldap_dept,
"manager" => Setting::getSettings()->ldap_manager,
"display_name" => Setting::getSettings()->ldap_display_name,
'username' => Setting::getSettings()->ldap_username_field,
'last_name' => Setting::getSettings()->ldap_lname_field,
'first_name' => Setting::getSettings()->ldap_fname_field,
'active_flag' => Setting::getSettings()->ldap_active_flag,
'emp_num' => Setting::getSettings()->ldap_emp_num,
'email' => Setting::getSettings()->ldap_email,
'phone' => Setting::getSettings()->ldap_phone_field,
'mobile' => Setting::getSettings()->ldap_mobile,
'jobtitle' => Setting::getSettings()->ldap_jobtitle,
'address' => Setting::getSettings()->ldap_address,
'city' => Setting::getSettings()->ldap_city,
'state' => Setting::getSettings()->ldap_state,
'zip' => Setting::getSettings()->ldap_zip,
'country' => Setting::getSettings()->ldap_country,
'location' => Setting::getSettings()->ldap_location,
'dept' => Setting::getSettings()->ldap_dept,
'manager' => Setting::getSettings()->ldap_manager,
'display_name' => Setting::getSettings()->ldap_display_name,
];
$ldap_default_group = Setting::getSettings()->ldap_default_group;
@@ -101,13 +100,13 @@ class LdapSync extends Command
/**
* if a location ID has been specified, use that OU
*/
if ( $this->option('location_id') ) {
if ($this->option('location_id')) {
foreach($this->option('location_id') as $location_id){
foreach ($this->option('location_id') as $location_id) {
$location_ou = Location::where('id', '=', $location_id)->value('ldap_ou');
$search_base = $location_ou;
Log::debug('Importing users from specified location OU: \"'.$search_base.'\".');
}
}
}
/**
@@ -153,21 +152,21 @@ class LdapSync extends Command
$default_location = null;
if ($this->option('location') != '') {
if ($default_location = Location::where('name', '=', $this->option('location'))->first()) {
Log::debug('Location name ' . $this->option('location') . ' passed');
Log::debug('Location name '.$this->option('location').' passed');
Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')');
}
} elseif ($this->option('location_id')) {
//TODO - figure out how or why this is an array?
foreach($this->option('location_id') as $location_id) {
// TODO - figure out how or why this is an array?
foreach ($this->option('location_id') as $location_id) {
if ($default_location = Location::where('id', '=', $location_id)->first()) {
Log::debug('Location ID ' . $location_id . ' passed');
Log::debug('Location ID '.$location_id.' passed');
Log::debug('Importing to '.$default_location->name.' ('.$default_location->id.')');
}
}
}
if (!isset($default_location)) {
if (! isset($default_location)) {
Log::debug('That location is invalid or a location was not provided, so no location will be assigned by default.');
}
@@ -208,17 +207,17 @@ class LdapSync extends Command
}
$usernames = [];
for ($i = 0; $i < $location_users['count']; $i++) {
if (array_key_exists($ldap_map["username"], $location_users[$i])) {
if (array_key_exists($ldap_map['username'], $location_users[$i])) {
$location_users[$i]['ldap_location_override'] = true;
$location_users[$i]['location_id'] = $ldap_loc['id'];
$usernames[] = $location_users[$i][$ldap_map["username"]][0];
$usernames[] = $location_users[$i][$ldap_map['username']][0];
}
}
// Delete located users from the general group.
foreach ($results as $key => $generic_entry) {
if ((is_array($generic_entry)) && (array_key_exists($ldap_map["username"], $generic_entry))) {
if (in_array($generic_entry[$ldap_map["username"]][0], $usernames)) {
if ((is_array($generic_entry)) && (array_key_exists($ldap_map['username'], $generic_entry))) {
if (in_array($generic_entry[$ldap_map['username']][0], $usernames)) {
unset($results[$key]);
}
}
@@ -232,42 +231,41 @@ class LdapSync extends Command
$manager_cache = [];
if($ldap_default_group != null) {
if ($ldap_default_group != null) {
$default = Group::find($ldap_default_group);
if (!$default) {
if (! $default) {
$ldap_default_group = null; // un-set the default group if that group doesn't exist
}
}
// Assign the mapped LDAP attributes for each user to the Snipe-IT user fields
for ($i = 0; $i < $results['count']; $i++) {
$item = [];
$item['username'] = $results[$i][$ldap_map["username"]][0] ?? null;
$item['display_name'] = $results[$i][$ldap_map["display_name"]][0] ?? null;
$item['employee_number'] = $results[$i][$ldap_map["emp_num"]][0] ?? null;
$item['lastname'] = $results[$i][$ldap_map["last_name"]][0] ?? null;
$item['firstname'] = $results[$i][$ldap_map["first_name"]][0] ?? null;
$item['email'] = $results[$i][$ldap_map["email"]][0] ?? null;
$item['username'] = $results[$i][$ldap_map['username']][0] ?? null;
$item['display_name'] = $results[$i][$ldap_map['display_name']][0] ?? null;
$item['employee_number'] = $results[$i][$ldap_map['emp_num']][0] ?? null;
$item['lastname'] = $results[$i][$ldap_map['last_name']][0] ?? null;
$item['firstname'] = $results[$i][$ldap_map['first_name']][0] ?? null;
$item['email'] = $results[$i][$ldap_map['email']][0] ?? null;
$item['ldap_location_override'] = $results[$i]['ldap_location_override'] ?? null;
$item['location_id'] = $results[$i]['location_id'] ?? null;
$item['telephone'] = $results[$i][$ldap_map["phone"]][0] ?? null;
$item['mobile'] = $results[$i][$ldap_map["mobile"]][0] ?? null;
$item['jobtitle'] = $results[$i][$ldap_map["jobtitle"]][0] ?? null;
$item['address'] = $results[$i][$ldap_map["address"]][0] ?? null;
$item['city'] = $results[$i][$ldap_map["city"]][0] ?? null;
$item['state'] = $results[$i][$ldap_map["state"]][0] ?? null;
$item['country'] = $results[$i][$ldap_map["country"]][0] ?? null;
$item['zip'] = $results[$i][$ldap_map["zip"]][0] ?? null;
$item['department'] = $results[$i][$ldap_map["dept"]][0] ?? null;
$item['manager'] = $results[$i][$ldap_map["manager"]][0] ?? null;
$item['location'] = $results[$i][$ldap_map["location"]][0] ?? null;
$location = $default_location; //initially, set '$location' to the default_location (which may just be null)
$item['telephone'] = $results[$i][$ldap_map['phone']][0] ?? null;
$item['mobile'] = $results[$i][$ldap_map['mobile']][0] ?? null;
$item['jobtitle'] = $results[$i][$ldap_map['jobtitle']][0] ?? null;
$item['address'] = $results[$i][$ldap_map['address']][0] ?? null;
$item['city'] = $results[$i][$ldap_map['city']][0] ?? null;
$item['state'] = $results[$i][$ldap_map['state']][0] ?? null;
$item['country'] = $results[$i][$ldap_map['country']][0] ?? null;
$item['zip'] = $results[$i][$ldap_map['zip']][0] ?? null;
$item['department'] = $results[$i][$ldap_map['dept']][0] ?? null;
$item['manager'] = $results[$i][$ldap_map['manager']][0] ?? null;
$item['location'] = $results[$i][$ldap_map['location']][0] ?? null;
$location = $default_location; // initially, set '$location' to the default_location (which may just be null)
// ONLY if you are using the "ldap_location" option *AND* you have an actual result
if ($ldap_map["location"] && $item['location']) {
if ($ldap_map['location'] && $item['location']) {
$location = Location::firstOrCreate([
'name' => $item['location'],
]);
@@ -289,58 +287,58 @@ class LdapSync extends Command
$item['createorupdate'] = 'created';
}
//If a sync option is not filled in on the LDAP settings don't populate the user field
if($ldap_map["username"] != null){
// If a sync option is not filled in on the LDAP settings don't populate the user field
if ($ldap_map['username'] != null) {
$user->username = $item['username'];
}
if($ldap_map["display_name"] != null){
if ($ldap_map['display_name'] != null) {
$user->display_name = $item['display_name'];
}
if($ldap_map["last_name"] != null){
if ($ldap_map['last_name'] != null) {
$user->last_name = $item['lastname'];
}
if($ldap_map["first_name"] != null){
if ($ldap_map['first_name'] != null) {
$user->first_name = $item['firstname'];
}
if($ldap_map["emp_num"] != null){
if ($ldap_map['emp_num'] != null) {
$user->employee_num = e($item['employee_number']);
}
if($ldap_map["email"] != null){
if ($ldap_map['email'] != null) {
$user->email = $item['email'];
}
if($ldap_map["phone"] != null){
if ($ldap_map['phone'] != null) {
$user->phone = $item['telephone'];
}
if($ldap_map["mobile"] != null){
if ($ldap_map['mobile'] != null) {
$user->mobile = $item['mobile'];
}
if($ldap_map["jobtitle"] != null){
if ($ldap_map['jobtitle'] != null) {
$user->jobtitle = $item['jobtitle'];
}
if($ldap_map["address"] != null){
if ($ldap_map['address'] != null) {
$user->address = $item['address'];
}
if($ldap_map["city"] != null){
if ($ldap_map['city'] != null) {
$user->city = $item['city'];
}
if($ldap_map["state"] != null){
if ($ldap_map['state'] != null) {
$user->state = $item['state'];
}
if($ldap_map["country"] != null){
if ($ldap_map['country'] != null) {
$user->country = $item['country'];
}
if($ldap_map["zip"] != null){
if ($ldap_map['zip'] != null) {
$user->zip = $item['zip'];
}
if($ldap_map["dept"] != null){
if ($ldap_map['dept'] != null) {
$user->department_id = $department->id;
}
if($ldap_map["location"] != null){
if ($ldap_map['location'] != null) {
$user->location_id = $location?->id;
}
if($ldap_map["manager"] != null){
if($item['manager'] != null) {
if ($ldap_map['manager'] != null) {
if ($item['manager'] != null) {
// Check Cache first
if (isset($manager_cache[$item['manager']])) {
// found in cache; use that and avoid extra lookups
@@ -350,23 +348,23 @@ class LdapSync extends Command
try {
$ldap_manager = Ldap::findLdapUsers($item['manager'], -1, $this->option('filter'));
} catch (\Exception $e) {
Log::warning("Manager lookup caused an exception: " . $e->getMessage() . ". Falling back to direct username lookup");
Log::warning('Manager lookup caused an exception: '.$e->getMessage().'. Falling back to direct username lookup');
// Hail-mary for Okta manager 'shortnames' - will only work if
// Okta configuration is using full email-address-style usernames
$ldap_manager = [
"count" => 1,
'count' => 1,
0 => [
$ldap_map["username"] => [$item['manager']]
]
$ldap_map['username'] => [$item['manager']],
],
];
}
$add_manager_to_cache = true;
if ($ldap_manager["count"] > 0) {
if ($ldap_manager['count'] > 0) {
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];
$ldapManagerUsername = $ldap_manager[0][$ldap_map['username']][0];
// Get User from Manager username.
$ldap_manager = User::where('username', $ldapManagerUsername)->first();
@@ -377,11 +375,11 @@ class LdapSync extends Command
}
} catch (\Exception $e) {
$add_manager_to_cache = false;
\Log::warning('Handling ldap manager ' . $item['manager'] . ' caused an exception: ' . $e->getMessage() . '. Continuing synchronization.');
\Log::warning('Handling ldap manager '.$item['manager'].' caused an exception: '.$e->getMessage().'. Continuing synchronization.');
}
}
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'
$manager_cache[$item['manager']] = $ldap_manager && isset($ldap_manager->id) ? $ldap_manager->id : null; // Store results in cache, even if 'failed'
}
}
@@ -389,18 +387,18 @@ class LdapSync extends Command
}
// Sync activated state for Active Directory.
if (!empty($ldap_map["active_flag"])) { // IF we have an 'active' flag set....
if (! empty($ldap_map['active_flag'])) { // IF we have an 'active' flag set....
// ....then *most* things that are truthy will activate the user. Anything falsey will deactivate them.
// (Specifically, we don't handle a value of '0.0' correctly)
$raw_value = @$results[$i][$ldap_map["active_flag"]][0];
$raw_value = @$results[$i][$ldap_map['active_flag']][0];
$filter_var = filter_var($raw_value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
$boolean_cast = (bool) $raw_value;
if (Setting::getSettings()->ldap_invert_active_flag === 1) {
// Because ldap_active_flag is set, if filter_var is true or boolean_cast is true, then user is suspended
$user->activated = !($filter_var ?? $boolean_cast);
}else{
$user->activated = ! ($filter_var ?? $boolean_cast);
} else {
$user->activated = $filter_var ?? $boolean_cast; // if filter_var() was true or false, use that. If it's null, use the $boolean_cast
}
@@ -408,7 +406,6 @@ class LdapSync extends Command
// ....otherwise, (ie if no 'active' LDAP flag is defined), IF the UAC setting exists,
// ....then use the UAC setting on the account to determine can-log-in vs. cannot-log-in
/* The following is _probably_ the correct logic, but we can't use it because
some users may have been dependent upon the previous behavior, and this
could cause additional access to be available to users they don't want
@@ -434,7 +431,7 @@ class LdapSync extends Command
'262688', // 0x40220 NORMAL_ACCOUNT, PASSWD_NOTREQD, SMARTCARD_REQUIRED
'328192', // 0x50200 NORMAL_ACCOUNT, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'328224', // 0x50220 NORMAL_ACCOUNT, PASSWD_NOT_REQD, SMARTCARD_REQUIRED, DONT_EXPIRE_PASSWORD
'4194816',// 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
'4194816', // 0x400200 NORMAL_ACCOUNT, DONT_REQ_PREAUTH
'4260352', // 0x410200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, DONT_REQ_PREAUTH
'1049088', // 0x100200 NORMAL_ACCOUNT, NOT_DELEGATED
'1114624', // 0x110200 NORMAL_ACCOUNT, DONT_EXPIRE_PASSWORD, NOT_DELEGATED,
@@ -445,14 +442,13 @@ class LdapSync extends Command
} /* implied 'else' here - leave the $user->activated flag alone. Newly-created accounts will be active.
already-existing accounts will be however the administrator has set them */
if ($item['ldap_location_override'] == true) {
$user->location_id = $item['location_id'];
} elseif ((isset($location)) && (!empty($location))) {
} elseif ((isset($location)) && (! empty($location))) {
if ((is_array($location)) && (array_key_exists('id', $location))) {
$user->location_id = $location['id'];
} elseif (is_object($location)) {
$user->location_id = $location->id; //THIS is the magic line, this should do it.
$user->location_id = $location->id; // THIS is the magic line, this should do it.
}
}
// TODO - should we be NULLING locations if $location is really `null`, and that's what we came up with?
@@ -468,13 +464,13 @@ class LdapSync extends Command
$item['note'] = $item['createorupdate'];
$item['status'] = 'success';
if ($item['createorupdate'] === 'created' && $ldap_default_group) {
// Check if the relationship already exists
if (!$user->groups()->where('group_id', $ldap_default_group)->exists()) {
$user->groups()->attach($ldap_default_group);
// Check if the relationship already exists
if (! $user->groups()->where('group_id', $ldap_default_group)->exists()) {
$user->groups()->attach($ldap_default_group);
}
}
//updates assets location based on user's location
// updates assets location based on user's location
if ($user->wasChanged('location_id')) {
foreach ($user->assets as $asset) {
$asset->location_id = $user->location_id;

View File

@@ -2,29 +2,32 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Ldap;
use App\Models\Setting;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Crypt;
use App\Models\Ldap;
/**
* Check if a given ip is in a network
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
* @return boolean true if the ip is in this range / false if not.
*
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
* @return bool true if the ip is in this range / false if not.
*/
function ip_in_range( $ip, $range ) {
if ( strpos( $range, '/' ) == false ) {
$range .= '/32';
}
// $range is in IP/CIDR format eg 127.0.0.1/24
list( $range, $netmask ) = explode( '/', $range, 2 );
$range_decimal = ip2long( $range );
$ip_decimal = ip2long( $ip );
$wildcard_decimal = pow( 2, ( 32 - $netmask ) ) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
function ip_in_range($ip, $range)
{
if (strpos($range, '/') == false) {
$range .= '/32';
}
// $range is in IP/CIDR format eg 127.0.0.1/24
[$range, $netmask] = explode('/', $range, 2);
$range_decimal = ip2long($range);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, (32 - $netmask)) - 1;
$netmask_decimal = ~$wildcard_decimal;
return ($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal);
}
// NOTE - this function was shamelessly stolen from this gist: https://gist.github.com/tott/7684443
@@ -33,10 +36,10 @@ function ip_in_range( $ip, $range ) {
*/
function parenthesized_filter($filter)
{
if(substr($filter,0,1) == "(" ) {
if (substr($filter, 0, 1) == '(') {
return $filter;
} else {
return "(".$filter.")";
return '('.$filter.')';
}
}
@@ -74,41 +77,44 @@ class LdapTroubleshooter extends Command
/**
* Output something *only* if debug is enabled
*
*
* @return void
*/
public function debugout($string)
{
if($this->option('debug')) {
if ($this->option('debug')) {
$this->line($string);
}
}
/**
* Clean the results from ldap_get_entries into something useful
* @param array $array
*
* @param array $array
* @return array
*/
public function ldap_results_cleaner ($array) {
public function ldap_results_cleaner($array)
{
$cleaned = [];
for($i = 0; $i < $array['count']; $i++) {
for ($i = 0; $i < $array['count']; $i++) {
$row = $array[$i];
$clean_row = [];
foreach($row AS $key => $val ) {
$this->debugout("Key is: ".$key);
if($key == "count" || is_int($key) || $key == "dn") {
foreach ($row as $key => $val) {
$this->debugout('Key is: '.$key);
if ($key == 'count' || is_int($key) || $key == 'dn') {
$this->debugout(" and we're gonna skip it\n");
continue;
}
$this->debugout(" And that seems fine.\n");
if(array_key_exists('count',$val)) {
if($val['count'] == 1) {
if (array_key_exists('count', $val)) {
if ($val['count'] == 1) {
$clean_row[$key] = $val[0];
} else {
unset($val['count']); //these counts are annoying
unset($val['count']); // these counts are annoying
$elements = [];
foreach($val as $entry) {
if(isset($ldap_constants[$entry])) {
foreach ($val as $entry) {
if (isset($ldap_constants[$entry])) {
$elements[] = $ldap_constants[$entry];
} else {
$elements[] = $entry;
@@ -122,6 +128,7 @@ class LdapTroubleshooter extends Command
}
$cleaned[$i] = $clean_row;
}
return $cleaned;
}
@@ -132,58 +139,58 @@ class LdapTroubleshooter extends Command
*/
public function handle()
{
if($this->option('trace')) {
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
if ($this->option('trace')) {
ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7);
}
$settings = Setting::getSettings();
$this->settings = $settings;
if($this->option('ldap-search')) {
if(!$this->option('force')) {
if ($this->option('ldap-search')) {
if (! $this->option('force')) {
$confirmation = $this->confirm('WARNING: This command will display your LDAP password on your terminal. Are you sure this is ok?');
if(!$confirmation) {
if (! $confirmation) {
$this->error('ABORTING');
exit(-1);
}
}
$output = [];
if($settings->ldap_server_cert_ignore) {
$this->line("# Ignoring server certificate validity");
$output[] = "LDAPTLS_REQCERT=never";
if ($settings->ldap_server_cert_ignore) {
$this->line('# Ignoring server certificate validity');
$output[] = 'LDAPTLS_REQCERT=never';
}
if($settings->ldap_client_tls_cert && $settings->ldap_client_tls_key) {
$this->line("# Adding LDAP Client Certificate and Key");
$output[] = "LDAPTLS_CERT=storage/ldap_client_tls.cert";
$output[] = "LDAPTLS_KEY=storage/ldap_client_tls.key";
if ($settings->ldap_client_tls_cert && $settings->ldap_client_tls_key) {
$this->line('# Adding LDAP Client Certificate and Key');
$output[] = 'LDAPTLS_CERT=storage/ldap_client_tls.cert';
$output[] = 'LDAPTLS_KEY=storage/ldap_client_tls.key';
}
$output[] = "ldapsearch";
$output[] = "-H ".$settings->ldap_server;
$output[] = "-x";
$output[] = "-b ".escapeshellarg($settings->ldap_basedn);
$output[] = "-D ".escapeshellarg($settings->ldap_uname);
$output[] = 'ldapsearch';
$output[] = '-H '.$settings->ldap_server;
$output[] = '-x';
$output[] = '-b '.escapeshellarg($settings->ldap_basedn);
$output[] = '-D '.escapeshellarg($settings->ldap_uname);
try {
$w = Crypt::Decrypt($settings->ldap_pword);
} catch (\Exception $e) {
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
} catch (Exception $e) {
$this->warn('Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.');
exit(0);
}
$output[] = "-w ". escapeshellarg($w);
$output[] = '-w '.escapeshellarg($w);
$output[] = escapeshellarg(parenthesized_filter($settings->ldap_filter));
if($settings->ldap_tls) {
$this->line("# adding STARTTLS option");
$output[] = "-Z";
if ($settings->ldap_tls) {
$this->line('# adding STARTTLS option');
$output[] = '-Z';
}
$output[] = "-v";
$output[] = '-v';
$this->line("\n");
$this->line(implode(" \\\n",$output));
$this->line(implode(" \\\n", $output));
exit(0);
}
//PHP Version check for warning
// PHP Version check for warning
$php_version = phpversion();
list($major, $minor, $patch) = explode('.', $php_version);
[$major, $minor, $patch] = explode('.', $php_version);
if (
$major < 8 ||
($major == 8 && $minor < 3) ||
@@ -191,22 +198,22 @@ class LdapTroubleshooter extends Command
($major == 8 && $minor == 4 && $patch < 7)
) {
$this->warn("PHP Version: $php_version WARNING - Versions before 8.3.21 or 8.4.7 will return INCONSISTENT results!");
if (!$this->confirm("Are you sure you wish to continue?")) {
$this->warn("ABORTING");
if (! $this->confirm('Are you sure you wish to continue?')) {
$this->warn('ABORTING');
exit(-1);
}
}
if(!$this->option('force')) {
if (! $this->option('force')) {
$confirmation = $this->confirm('WARNING: This command will make several attempts to connect to your LDAP server. Are you sure this is ok?');
if(!$confirmation) {
if (! $confirmation) {
$this->error('ABORTING');
exit(-1);
}
}
//$this->line(print_r($settings,true));
$this->line("STAGE 1: Checking settings");
if(!$settings->ldap_enabled) {
// $this->line(print_r($settings,true));
$this->line('STAGE 1: Checking settings');
if (! $settings->ldap_enabled) {
$this->error("WARNING: Snipe-IT's LDAP setting is not turned on. (That may be OK if you're still trying to figure out settings)");
}
@@ -214,123 +221,126 @@ class LdapTroubleshooter extends Command
try {
$ldap_conn = ldap_connect($settings->ldap_server);
} catch (Exception $e) {
$this->error("WARNING: Exception caught when executing 'ldap_connect()' - ".$e->getMessage().". We will try to guess.");
$this->error("WARNING: Exception caught when executing 'ldap_connect()' - ".$e->getMessage().'. We will try to guess.');
}
if(!$ldap_conn) {
$this->error("WARNING: LDAP Server setting of: ".$settings->ldap_server." cannot be parsed. We will try to guess.");
//exit(-1);
if (! $ldap_conn) {
$this->error('WARNING: LDAP Server setting of: '.$settings->ldap_server.' cannot be parsed. We will try to guess.');
// exit(-1);
}
//since we never use $ldap_conn again, we don't have to ldap_unbind() it (it's not even connected, tbh - that only happens at bind-time)
// since we never use $ldap_conn again, we don't have to ldap_unbind() it (it's not even connected, tbh - that only happens at bind-time)
$parsed = parse_url($settings->ldap_server);
if(@$parsed['scheme'] != 'ldap' && @$parsed['scheme'] != 'ldaps') {
if (@$parsed['scheme'] != 'ldap' && @$parsed['scheme'] != 'ldaps') {
$this->error("WARNING: LDAP URL Scheme of '".@$parsed['scheme']."' is probably incorrect; should usually be ldap or ldaps");
}
if(!@$parsed['host']) {
$this->error("ERROR: Cannot determine hostname or IP from ldap URL: ".$settings->ldap_server.". ABORTING.");
if (! @$parsed['host']) {
$this->error('ERROR: Cannot determine hostname or IP from ldap URL: '.$settings->ldap_server.'. ABORTING.');
exit(-1);
} else {
$this->info("Determined LDAP hostname to be: ".$parsed['host']);
$this->info('Determined LDAP hostname to be: '.$parsed['host']);
}
$raw_ips = [];
if (inet_pton($parsed['host']) !== false) {
$this->line($parsed['host'] . " already looks like an address; skipping DNS lookup");
$this->line($parsed['host'].' already looks like an address; skipping DNS lookup');
$raw_ips[] = $parsed['host'];
} else {
$this->line("Performing DNS lookup of: " . $parsed['host']);
$this->line('Performing DNS lookup of: '.$parsed['host']);
$ips = dns_get_record($parsed['host']);
//$this->info("Host IP is: ".print_r($ips,true));
// $this->info("Host IP is: ".print_r($ips,true));
if (!$ips || count($ips) == 0) {
$this->error("ERROR: DNS lookup of host: " . $parsed['host'] . " has failed. ABORTING.");
if (! $ips || count($ips) == 0) {
$this->error('ERROR: DNS lookup of host: '.$parsed['host'].' has failed. ABORTING.');
exit(-1);
}
$this->debugout("IP's? " . print_r($ips, true));
$this->debugout("IP's? ".print_r($ips, true));
foreach ($ips as $ip) {
if (!isset($ip['ip'])) {
if (! isset($ip['ip'])) {
continue;
}
$raw_ips[] = $ip['ip'];
}
}
foreach ($raw_ips as $ip) {
if ($ip == "127.0.0.1") {
$this->error("WARNING: Using the localhost IP as the LDAP server. This is usually wrong");
if ($ip == '127.0.0.1') {
$this->error('WARNING: Using the localhost IP as the LDAP server. This is usually wrong');
}
if (ip_in_range($ip, '10.0.0.0/8') || ip_in_range($ip, '192.168.0.0/16') || ip_in_range($ip, '172.16.0.0/12')) {
$this->error("WARNING: Using an RFC1918 Private address for LDAP server. This may be correct, but it can be a problem if your Snipe-IT instance is not hosted on your private network");
$this->error('WARNING: Using an RFC1918 Private address for LDAP server. This may be correct, but it can be a problem if your Snipe-IT instance is not hosted on your private network');
}
}
$this->line("STAGE 2: Checking basic network connectivity");
$this->line('STAGE 2: Checking basic network connectivity');
$ports = [636, 389];
if(@$parsed['port'] && !in_array($parsed['port'],$ports)) {
if (@$parsed['port'] && ! in_array($parsed['port'], $ports)) {
$ports[] = $parsed['port'];
}
$open_ports=[];
foreach($ports as $port ) {
$open_ports = [];
foreach ($ports as $port) {
$errno = 0;
$errstr = '';
$timeout = 30.0;
$result = '';
$this->line("Attempting to connect to port: " . $port . " - may take up to $timeout seconds");
$this->line('Attempting to connect to port: '.$port." - may take up to $timeout seconds");
try {
$result = fsockopen($parsed['host'], $port, $errno, $errstr, 30.0);
} catch(Exception $e) {
$this->error("Exception: ".$e->getMessage());
} catch (Exception $e) {
$this->error('Exception: '.$e->getMessage());
}
if($result) {
$this->info("Success!");
if ($result) {
$this->info('Success!');
$open_ports[] = $port;
} else {
$this->error("WARNING: Cannot connect to port: $port - $errstr ($errno)");
}
}
if(count($open_ports) == 0) {
$this->error("ERROR - no open ports. ABORTING.");
if (count($open_ports) == 0) {
$this->error('ERROR - no open ports. ABORTING.');
exit(-1);
}
$this->line("STAGE 3: Determine encryption algorithm, if any");
$this->line('STAGE 3: Determine encryption algorithm, if any');
$ldap_urls = []; // [url, cert-check?, start_tls?]
$pretty_ldap_urls = [];
foreach($open_ports as $port) {
foreach ($open_ports as $port) {
$this->line("Trying TLS first for port $port");
$ldap_url = "ldaps://".$parsed['host'].":$port";
if($this->test_anonymous_bind($ldap_url)) {
$ldap_url = 'ldaps://'.$parsed['host'].":$port";
if ($this->test_anonymous_bind($ldap_url)) {
$this->info("Anonymous bind succesful to $ldap_url!");
$ldap_urls[] = [ $ldap_url, true, false ];
$pretty_ldap_urls[] = [$ldap_url, "enabled", "n/a (no)"];
$ldap_urls[] = [$ldap_url, true, false];
$pretty_ldap_urls[] = [$ldap_url, 'enabled', 'n/a (no)'];
continue; // TODO - lots of copypasta in these if(test_anonymous_bind()) routines...
} else {
$this->error("WARNING: Failed to bind to $ldap_url - trying without certificate checks.");
}
if($this->test_anonymous_bind($ldap_url, false)) {
if ($this->test_anonymous_bind($ldap_url, false)) {
$this->info("Anonymous bind successful to $ldap_url with certificate-checks disabled");
$ldap_urls[] = [$ldap_url, false, false];
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "n/a (no)"];
$pretty_ldap_urls[] = [$ldap_url, 'DISABLED', 'n/a (no)'];
continue;
} else {
$this->error("WARNING: Failed to bind to $ldap_url with certificate checks disabled. Trying unencrypted with STARTTLS");
}
// now switching to ldap:// URL's from ldaps://
$ldap_url = "ldap://".$parsed['host'].":$port";
$ldap_url = 'ldap://'.$parsed['host'].":$port";
if($this->test_anonymous_bind($ldap_url, true, true)) {
if ($this->test_anonymous_bind($ldap_url, true, true)) {
$this->info("Plain connection to $ldap_url with STARTTLS succesful!");
$ldap_urls[] = [ $ldap_url, true, true ];
$pretty_ldap_urls[] = [$ldap_url, "enabled", "STARTTLS ENABLED"];
$ldap_urls[] = [$ldap_url, true, true];
$pretty_ldap_urls[] = [$ldap_url, 'enabled', 'STARTTLS ENABLED'];
continue;
} else {
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled. Trying without certificate checks.");
@@ -339,224 +349,235 @@ class LdapTroubleshooter extends Command
if ($this->test_anonymous_bind($ldap_url, false, true)) {
$this->info("Plain connection to $ldap_url with STARTTLS and cert checks *disabled* successful!");
$ldap_urls[] = [$ldap_url, false, true];
$pretty_ldap_urls[] = [$ldap_url, "DISABLED", "STARTTLS ENABLED"];
$pretty_ldap_urls[] = [$ldap_url, 'DISABLED', 'STARTTLS ENABLED'];
continue;
} else {
$this->error("WARNING: Failed to bind to $ldap_url with STARTTLS enabled, and cert checks disabled. Trying without STARTTLS");
}
if($this->test_anonymous_bind($ldap_url)) {
if ($this->test_anonymous_bind($ldap_url)) {
$this->info("Plain connection to $ldap_url succesful!");
$ldap_urls[] = [ $ldap_url, true, false ];
$pretty_ldap_urls[] = [$ldap_url, "n/a", "starttls disabled"];
$ldap_urls[] = [$ldap_url, true, false];
$pretty_ldap_urls[] = [$ldap_url, 'n/a', 'starttls disabled'];
continue;
} else {
$this->error("WARNING: Failed to bind to $ldap_url. Giving up on port $port");
}
}
$this->debugout(print_r($ldap_urls,true));
$this->debugout(print_r($ldap_urls, true));
if(count($ldap_urls) > 0 ) {
if (count($ldap_urls) > 0) {
$this->debugout("Found working LDAP URL's: ");
foreach($ldap_urls as $ldap_url) { // TODO maybe do this as a $this->table() instead?
$this->debugout("LDAP URL: " . $ldap_url[0]);
$this->debugout($ldap_url[0] . ($ldap_url[1] ? " certificate checks enabled" : " certificate checks disabled") . ($ldap_url[2] ? " STARTTLS Enabled " : " STARTTLS Disabled"));
foreach ($ldap_urls as $ldap_url) { // TODO maybe do this as a $this->table() instead?
$this->debugout('LDAP URL: '.$ldap_url[0]);
$this->debugout($ldap_url[0].($ldap_url[1] ? ' certificate checks enabled' : ' certificate checks disabled').($ldap_url[2] ? ' STARTTLS Enabled ' : ' STARTTLS Disabled'));
}
$this->table(["URL", "Cert Checks?", "STARTTLS?"], $pretty_ldap_urls);
$this->table(['URL', 'Cert Checks?', 'STARTTLS?'], $pretty_ldap_urls);
} else {
$this->error("ERROR - no valid LDAP URL's available - ABORTING");
exit(1);
}
$this->line("STAGE 4: Test Administrative Bind for LDAP Sync");
foreach($ldap_urls AS $ldap_url) {
$this->line('STAGE 4: Test Administrative Bind for LDAP Sync');
foreach ($ldap_urls as $ldap_url) {
try {
$w = Crypt::Decrypt($settings->ldap_pword);
} catch (\Exception $e) {
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
} catch (Exception $e) {
$this->warn('Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.');
exit(0);
}
$this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, $w);
}
$this->line("STAGE 5: Test BaseDN");
//grab all LDAP_ constants and fill up a reversed array mapping from weird LDAP dotted-strings to (Constant Name)
$this->line('STAGE 5: Test BaseDN');
// grab all LDAP_ constants and fill up a reversed array mapping from weird LDAP dotted-strings to (Constant Name)
$all_defined_constants = get_defined_constants();
$ldap_constants = [];
foreach($all_defined_constants AS $key => $val) {
if(starts_with($key,"LDAP_") && is_string($val)) {
foreach ($all_defined_constants as $key => $val) {
if (starts_with($key, 'LDAP_') && is_string($val)) {
$ldap_constants[$val] = $key; // INVERT the meaning here!
}
}
$this->debugout("LDAP constants are: ".print_r($ldap_constants,true));
$this->debugout('LDAP constants are: '.print_r($ldap_constants, true));
foreach($ldap_urls AS $ldap_url) {
foreach ($ldap_urls as $ldap_url) {
try {
$w = Crypt::Decrypt($settings->ldap_pword);
} catch (\Exception $e) {
$this->warn("Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.");
} catch (Exception $e) {
$this->warn('Could not decrypt password. This usually means an LDAP password was not set or the APP_KEY was changed since the LDAP pasword was last saved. Aborting.');
exit(0);
}
if($this->test_informational_bind($ldap_url[0],$ldap_url[1],$ldap_url[2],$settings->ldap_uname,$w,$settings)) {
$this->info("Success getting informational bind!");
if ($this->test_informational_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $settings->ldap_uname, $w, $settings)) {
$this->info('Success getting informational bind!');
} else {
$this->error("Unable to get information from bind.");
$this->error('Unable to get information from bind.');
}
}
$this->line("STAGE 6: Test LDAP Login to Snipe-IT");
foreach($ldap_urls AS $ldap_url) {
$this->line("Starting auth to " . $ldap_url[0]);
while(true) {
$with_tls = $ldap_url[1] ? "with": "without";
$with_startssl = $ldap_url[2] ? "using": "not using";
if(!$this->confirm('Do you wish to try to authenticate to this directory: '.$ldap_url[0]." $with_tls TLS and $with_startssl STARTSSL?")) {
$this->line('STAGE 6: Test LDAP Login to Snipe-IT');
foreach ($ldap_urls as $ldap_url) {
$this->line('Starting auth to '.$ldap_url[0]);
while (true) {
$with_tls = $ldap_url[1] ? 'with' : 'without';
$with_startssl = $ldap_url[2] ? 'using' : 'not using';
if (! $this->confirm('Do you wish to try to authenticate to this directory: '.$ldap_url[0]." $with_tls TLS and $with_startssl STARTSSL?")) {
break;
}
$username = $this->ask("Username");
$password = $this->secret("Password");
$username = $this->ask('Username');
$password = $this->secret('Password');
$results = $this->test_authed_bind($ldap_url[0], $ldap_url[1], $ldap_url[2], $username, $password); // FIXME - should do some other stuff here, maybe with the concatenating or something? maybe? and/or should put up some results?
if ($results) {
$this->info("Success authenticating with " . $username);
$this->info('Success authenticating with '.$username);
} else {
$this->error("Unable to authenticate with " . $username);
$this->error('Unable to authenticate with '.$username);
}
}
}
$this->info("LDAP TROUBLESHOOTING COMPLETE!");
$this->info('LDAP TROUBLESHOOTING COMPLETE!');
}
public function connect_to_ldap($ldap_url, $check_cert, $start_tls)
public function connect_to_ldap($ldap_url, $check_cert, $start_tls)
{
if ($check_cert) {
$this->line("we *ARE* checking certs");
$this->line('we *ARE* checking certs');
Ldap::ignoreCertificates(false);
} else {
$this->line("we are IGNORING certs");
$this->line('we are IGNORING certs');
Ldap::ignoreCertificates(true);
}
$lconn = ldap_connect($ldap_url);
ldap_set_option($lconn, LDAP_OPT_PROTOCOL_VERSION, 3); // should we 'test' different protocol versions here? Does anyone even use anything other than LDAPv3?
// no - it's formally deprecated: https://tools.ietf.org/html/rfc3494
if($this->settings->ldap_client_tls_cert && $this->settings->ldap_client_tls_key) {
// no - it's formally deprecated: https://tools.ietf.org/html/rfc3494
if ($this->settings->ldap_client_tls_cert && $this->settings->ldap_client_tls_key) {
// client-side TLS certificate support for LDAP (Google Secure LDAP)
putenv('LDAPTLS_CERT=storage/ldap_client_tls.cert');
putenv('LDAPTLS_KEY=storage/ldap_client_tls.key');
}
if($start_tls) {
if(!ldap_start_tls($lconn)) {
$this->error("WARNING: Unable to start TLS");
if ($start_tls) {
if (! ldap_start_tls($lconn)) {
$this->error('WARNING: Unable to start TLS');
return false;
}
}
if(!$lconn) {
$this->error("WARNING: Failed to generate connection string - using: ".$ldap_url);
if (! $lconn) {
$this->error('WARNING: Failed to generate connection string - using: '.$ldap_url);
return false;
}
$net = ldap_set_option($lconn, LDAP_OPT_NETWORK_TIMEOUT, $this->option('timeout'));
$time = ldap_set_option($lconn, LDAP_OPT_TIMELIMIT, $this->option('timeout'));
if(!$net || !$time) {
$this->error("Unable to set timeouts!");
if (! $net || ! $time) {
$this->error('Unable to set timeouts!');
}
return $lconn;
}
public function test_anonymous_bind($ldap_url, $check_cert = true, $start_tls = false)
{
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert , $start_tls) {
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert, $start_tls) {
try {
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
$this->line("Attempting to bind now, this can take a while if we mess it up");
$this->line('Attempting to bind now, this can take a while if we mess it up');
$bind_results = ldap_bind($lconn);
$this->line("Bind results are: " . $bind_results . " which translate into boolean: " . (bool)$bind_results);
$this->line('Bind results are: '.$bind_results.' which translate into boolean: '.(bool) $bind_results);
ldap_close($lconn);
return (bool)$bind_results;
return (bool) $bind_results;
} catch (Exception $e) {
$this->error("WARNING: Exception caught during bind - ".$e->getMessage());
$this->error('WARNING: Exception caught during bind - '.$e->getMessage());
return false;
}
});
}
public function test_authed_bind($ldap_url, $check_cert, $start_tls, $username, $password)
public function test_authed_bind($ldap_url, $check_cert, $start_tls, $username, $password)
{
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert, $start_tls, $username, $password) {
try {
$lconn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
$bind_results = ldap_bind($lconn, $username, $password);
ldap_close($lconn);
if(!$bind_results) {
if (! $bind_results) {
$this->error("WARNING: Failed to bind to $ldap_url as $username");
return false;
} else {
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
return (bool)$lconn;
return (bool) $lconn;
}
} catch (Exception $e) {
$this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage());
return false;
}
});
}
public function test_informational_bind($ldap_url, $check_cert, $start_tls, $username, $password,$settings)
public function test_informational_bind($ldap_url, $check_cert, $start_tls, $username, $password, $settings)
{
return $this->timed_boolean_execute(function () use ($ldap_url, $check_cert, $start_tls, $username, $password, $settings) {
try { // TODO - copypasta'ed from test_authed_bind
$conn = $this->connect_to_ldap($ldap_url, $check_cert, $start_tls);
$bind_results = ldap_bind($conn, $username, $password);
if(!$bind_results) {
if (! $bind_results) {
$this->error("WARNING: Failed to bind to $ldap_url as $username");
return false;
}
$this->info("SUCCESS - Able to bind to $ldap_url as $username");
$cleaned_results = [];
try {
// This _may_ only work for Active Directory?
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl']*/);
$result = ldap_read($conn, '', '(objectClass=*)'/* , ['supportedControl'] */);
$results = ldap_get_entries($conn, $result);
$cleaned_results = $this->ldap_results_cleaner($results);
//$this->line(print_r($cleaned_results,true));
// $this->line(print_r($cleaned_results,true));
$default_naming_contexts = $cleaned_results[0]['namingcontexts'];
$this->info("Default Naming Contexts:");
$this->info(implode(", ", $default_naming_contexts));
//okay, great - now how do we display those results? I have no idea.
} catch (\Exception $e) {
$this->info('Default Naming Contexts:');
$this->info(implode(', ', $default_naming_contexts));
// okay, great - now how do we display those results? I have no idea.
} catch (Exception $e) {
$this->error("Unable to get base naming contexts - here's what we *did* get:");
$this->line(print_r($cleaned_results, true));
}
// I don't see why this throws an Exception for Google LDAP, but I guess we ought to try and catch it?
$this->debugout("I guess we're trying to do the ldap search here, but sometimes it takes too long?");
$this->debugout("Base DN is: ".$settings->ldap_basedn." and filter is: ".parenthesized_filter($settings->ldap_filter));
$this->debugout('Base DN is: '.$settings->ldap_basedn.' and filter is: '.parenthesized_filter($settings->ldap_filter));
$search_results = ldap_search($conn, $settings->ldap_basedn, parenthesized_filter($settings->ldap_filter));
$entries = ldap_get_entries($conn, $search_results);
$this->info("Printing first 10 results: ");
$this->info('Printing first 10 results: ');
$pretty_data = array_slice($this->ldap_results_cleaner($entries), 0, 10);
//print_r($data);
// print_r($data);
$headers = [];
foreach ($pretty_data as $row) {
//populate headers
// populate headers
foreach ($row as $key => $value) {
//skip objectsid and objectguid because it junks up output
if ($key == "objectsid" || $key == "objectguid") {
// skip objectsid and objectguid because it junks up output
if ($key == 'objectsid' || $key == 'objectguid') {
continue;
}
if (!in_array($key, $headers)) {
if (! in_array($key, $headers)) {
$headers[] = $key;
}
}
}
$table = [];
//repeat again to populate table
// repeat again to populate table
foreach ($pretty_data as $row) {
$newrow = [];
foreach ($headers as $header) {
if (is_array(@$row[$header])) {
$newrow[] = "[" . implode(", ", $row[$header]) . "]";
$newrow[] = '['.implode(', ', $row[$header]).']';
} else {
$newrow[] = @$row[$header];
}
@@ -565,8 +586,9 @@ class LdapTroubleshooter extends Command
}
$this->table($headers, $table);
} catch (\Exception $e) {
} catch (Exception $e) {
$this->error("WARNING: Exception caught during Authed bind to $username - ".$e->getMessage());
return false;
} finally {
ldap_close($conn);
@@ -575,53 +597,54 @@ class LdapTroubleshooter extends Command
}
/***********************************************
*
* This function executes $function - which is expected to be some kind of executable function -
*
* This function executes $function - which is expected to be some kind of executable function -
* with a timeout set. It respects the timeout by forking execution and setting a strict timer
* for which to get back a SIGUSR1 or SIGUSR2 signal from the forked process.
*
*
***********************************************/
private function timed_boolean_execute($function)
{
if(!(function_exists('pcntl_sigtimedwait') && function_exists('posix_getpid') && function_exists('pcntl_fork') && function_exists('posix_kill') && function_exists('pcntl_wifsignaled'))) {
if (! (function_exists('pcntl_sigtimedwait') && function_exists('posix_getpid') && function_exists('pcntl_fork') && function_exists('posix_kill') && function_exists('pcntl_wifsignaled'))) {
// POSIX functions needed for forking aren't present, just run the function inline (ignoring timeout)
$this->line('WARNING: Unable to execute POSIX fork() commands, timeout may not be respected');
return $function();
} else {
$parent_pid = posix_getpid();
$pid = pcntl_fork();
switch($pid) {
switch ($pid) {
case 0:
//we're the 'child'
if($function()) {
//SUCCESS = SIGUSR1
// we're the 'child'
if ($function()) {
// SUCCESS = SIGUSR1
posix_kill($parent_pid, SIGUSR1);
} else {
//FAILURE = SIGUSR2
// FAILURE = SIGUSR2
posix_kill($parent_pid, SIGUSR2);
}
exit();
break; //yes I know we don't need it.
break; // yes I know we don't need it.
case -1:
//couldn't fork
$this->error("COULD NOT FORK - assuming failure");
// couldn't fork
$this->error('COULD NOT FORK - assuming failure');
return false;
break; //I still know that we don't need it
break; // I still know that we don't need it
default:
//we remain the 'parent', $pid is the PID of the forked process.
// we remain the 'parent', $pid is the PID of the forked process.
$siginfo = [];
$exit_status = pcntl_sigtimedwait ([SIGUSR1, SIGUSR2], $siginfo, $this->option('timeout'));
$exit_status = pcntl_sigtimedwait([SIGUSR1, SIGUSR2], $siginfo, $this->option('timeout'));
if ($exit_status == SIGUSR1) {
return true;
} else {
posix_kill($pid, SIGKILL); //make sure we don't have processes hanging around that might try and send signals during later executions, confusing us
posix_kill($pid, SIGKILL); // make sure we don't have processes hanging around that might try and send signals during later executions, confusing us
return false;
}
break; //Yeah I get it already, shush.
break; // Yeah I get it already, shush.
}
}
}
}

View File

@@ -44,19 +44,15 @@ class MergeUsersByUsername extends Command
$users = User::where('username', 'LIKE', '%@%')->whereNull('deleted_at')->get();
$this->info($users->count().' total non-deleted users whose usernames contain a @ symbol.');
foreach ($users as $user) {
$parts = explode('@', trim($user->username));
$this->info('Checking against username '.trim($parts[0]).'.');
$bad_users = User::where('username', '=', trim($parts[0]))
->whereNull('deleted_at')
->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations','uploads', 'acceptances')
->with('assets', 'manager', 'userlog', 'licenses', 'consumables', 'accessories', 'managedLocations', 'uploads', 'acceptances')
->get();
foreach ($bad_users as $bad_user) {
$this->info($bad_user->username.' ('.$bad_user->id.') will be merged into '.$user->username.' ('.$user->id.') ');
@@ -125,7 +121,6 @@ class MergeUsersByUsername extends Command
event(new UserMerged($bad_user, $user, null));
}
}
}

View File

@@ -5,7 +5,6 @@ namespace App\Console\Commands;
use App\Enums\ActionType;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class MigrateLicenseSeatQuantitiesInActionLogs extends Command
@@ -51,9 +50,9 @@ class MigrateLicenseSeatQuantitiesInActionLogs extends Command
if ($this->option('no-interaction') || $this->confirm('Update quantities in the action log?')) {
$query->chunk(50, function ($logs) {
$logs->each(function ($log) {
$quantityFromNote = Str::between($log->note, "ed ", " seats");
$quantityFromNote = Str::between($log->note, 'ed ', ' seats');
if (!is_numeric($quantityFromNote)) {
if (! is_numeric($quantityFromNote)) {
$this->error('Could not parse quantity from ID: {id}', ['id' => $log->id]);
}

View File

@@ -3,8 +3,8 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class MoveUploadsToNewDisk extends Command
{
@@ -47,30 +47,29 @@ class MoveUploadsToNewDisk extends Command
}
$delete_local = $this->argument('delete_local');
$public_uploads['accessories'] = glob('public/uploads/accessories'."/*.*");
$public_uploads['assets'] = glob('public/uploads/assets'."/*.*");
$public_uploads['avatars'] = glob('public/uploads/avatars'."/*.*");
$public_uploads['categories'] = glob('public/uploads/categories'."/*.*");
$public_uploads['companies'] = glob('public/uploads/companies'."/*.*");
$public_uploads['components'] = glob('public/uploads/components'."/*.*");
$public_uploads['consumables'] = glob('public/uploads/consumables'."/*.*");
$public_uploads['departments'] = glob('public/uploads/departments'."/*.*");
$public_uploads['locations'] = glob('public/uploads/locations'."/*.*");
$public_uploads['manufacturers'] = glob('public/uploads/manufacturers'."/*.*");
$public_uploads['suppliers'] = glob('public/uploads/suppliers'."/*.*");
$public_uploads['assetmodels'] = glob('public/uploads/models'."/*.*");
$public_uploads['accessories'] = glob('public/uploads/accessories'.'/*.*');
$public_uploads['assets'] = glob('public/uploads/assets'.'/*.*');
$public_uploads['avatars'] = glob('public/uploads/avatars'.'/*.*');
$public_uploads['categories'] = glob('public/uploads/categories'.'/*.*');
$public_uploads['companies'] = glob('public/uploads/companies'.'/*.*');
$public_uploads['components'] = glob('public/uploads/components'.'/*.*');
$public_uploads['consumables'] = glob('public/uploads/consumables'.'/*.*');
$public_uploads['departments'] = glob('public/uploads/departments'.'/*.*');
$public_uploads['locations'] = glob('public/uploads/locations'.'/*.*');
$public_uploads['manufacturers'] = glob('public/uploads/manufacturers'.'/*.*');
$public_uploads['suppliers'] = glob('public/uploads/suppliers'.'/*.*');
$public_uploads['assetmodels'] = glob('public/uploads/models'.'/*.*');
// iterate files
foreach ($public_uploads as $public_type => $public_upload) {
$type_count = 0;
$this->info('- There are ' . count($public_upload) . ' PUBLIC ' . $public_type . ' files.');
$this->info('- There are '.count($public_upload).' PUBLIC '.$public_type.' files.');
for ($i = 0; $i < count($public_upload); $i++) {
$type_count++;
$filename = basename($public_upload[$i]);
try {
try {
Storage::disk('public')->put('uploads/'.$public_type.'/'.$filename, file_get_contents($public_upload[$i]));
$new_url = Storage::disk('public')->url('uploads/'.$public_type.'/'.$filename, $filename);
$this->info($type_count.'. PUBLIC: '.$filename.' was copied to '.$new_url);
@@ -81,49 +80,46 @@ class MoveUploadsToNewDisk extends Command
}
}
$logos = glob("public/uploads/setting*.*");
$this->info("- There are ".count($logos).' files that might be logos.');
$logos = glob('public/uploads/setting*.*');
$this->info('- There are '.count($logos).' files that might be logos.');
$type_count = 0;
foreach ($logos as $logo) {
$this->info($logo);
$type_count++;
$filename = basename($logo);
Storage::disk('public')->put('uploads/' . $filename, file_get_contents($logo));
$this->info($type_count . '. LOGO: ' . $filename . ' was copied to ' . env('PUBLIC_AWS_URL') . '/uploads/' . $filename);
Storage::disk('public')->put('uploads/'.$filename, file_get_contents($logo));
$this->info($type_count.'. LOGO: '.$filename.' was copied to '.env('PUBLIC_AWS_URL').'/uploads/'.$filename);
}
$private_uploads['assets'] = glob('storage/private_uploads/assets'."/*.*");
$private_uploads['signatures'] = glob('storage/private_uploads/signatures'."/*.*");
$private_uploads['audits'] = glob('storage/private_uploads/audits'."/*.*");
$private_uploads['assetmodels'] = glob('storage/private_uploads/models'."/*.*");
$private_uploads['imports'] = glob('storage/private_uploads/imports'."/*.*");
$private_uploads['licenses'] = glob('storage/private_uploads/licenses'."/*.*");
$private_uploads['users'] = glob('storage/private_uploads/users'."/*.*");
$private_uploads['backups'] = glob('storage/private_uploads/backups'."/*.*");
$private_uploads['assets'] = glob('storage/private_uploads/assets'.'/*.*');
$private_uploads['signatures'] = glob('storage/private_uploads/signatures'.'/*.*');
$private_uploads['audits'] = glob('storage/private_uploads/audits'.'/*.*');
$private_uploads['assetmodels'] = glob('storage/private_uploads/models'.'/*.*');
$private_uploads['imports'] = glob('storage/private_uploads/imports'.'/*.*');
$private_uploads['licenses'] = glob('storage/private_uploads/licenses'.'/*.*');
$private_uploads['users'] = glob('storage/private_uploads/users'.'/*.*');
$private_uploads['backups'] = glob('storage/private_uploads/backups'.'/*.*');
foreach ($private_uploads as $private_type => $private_upload) {
{
$this->info('- There are ' . count($private_upload) . ' PRIVATE ' . $private_type . ' files.');
$type_count = 0;
for ($x = 0; $x < count($private_upload); $x++) {
$type_count++;
$filename = basename($private_upload[$x]);
$this->info('- There are '.count($private_upload).' PRIVATE '.$private_type.' files.');
try {
Storage::put($private_type . '/' . $filename, file_get_contents($private_upload[$x]));
$new_url = Storage::url($private_type . '/' . $filename, $filename);
$this->info($type_count . '. PRIVATE: ' . $filename . ' was copied to ' . $new_url);
} catch (\Exception $e) {
Log::debug($e);
$this->error($e);
}
$type_count = 0;
for ($x = 0; $x < count($private_upload); $x++) {
$type_count++;
$filename = basename($private_upload[$x]);
try {
Storage::put($private_type.'/'.$filename, file_get_contents($private_upload[$x]));
$new_url = Storage::url($private_type.'/'.$filename, $filename);
$this->info($type_count.'. PRIVATE: '.$filename.' was copied to '.$new_url);
} catch (\Exception $e) {
Log::debug($e);
$this->error($e);
}
}
if ($delete_local == 'true') {
$public_delete_count = 0;
$private_delete_count = 0;
@@ -160,7 +156,7 @@ class MoveUploadsToNewDisk extends Command
}
}
$this->info($public_delete_count . ' PUBLIC local files and ' . $private_delete_count . ' PRIVATE local files were deleted from your filesystem.');
$this->info($public_delete_count.' PUBLIC local files and '.$private_delete_count.' PRIVATE local files were deleted from your filesystem.');
}
}
}

View File

@@ -2,8 +2,8 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use Illuminate\Console\Command;
class NormalizeUserNames extends Command
{
@@ -40,13 +40,13 @@ class NormalizeUserNames extends Command
{
$users = User::get();
$this->info($users->count() . ' users');
$this->info($users->count().' users');
foreach ($users as $user) {
$user->first_name = ucwords(strtolower($user->first_name));
$user->last_name = ucwords(strtolower($user->last_name));
$user->email = strtolower($user->email);
$user->save();
foreach ($users as $user) {
$user->first_name = ucwords(strtolower($user->first_name));
$user->last_name = ucwords(strtolower($user->last_name));
$user->email = strtolower($user->email);
$user->save();
}
}
}

View File

@@ -3,11 +3,10 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Helper\ProgressIndicator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
/**
* Class ObjectImportCommand
@@ -55,7 +54,7 @@ class ObjectImportCommand extends Command
*/
public function handle()
{
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('max_execution_time', env('IMPORT_TIME_LIMIT', 600)); // 600 seconds = 10 minutes
ini_set('memory_limit', env('IMPORT_MEMORY_LIMIT', '500M'));
$this->progressIndicator = new ProgressIndicator($this->output);
@@ -65,15 +64,15 @@ class ObjectImportCommand extends Command
$classString = "App\\Importer\\{$class}Importer";
$importer = new $classString($filename);
$importer->setCallbacks([$this, 'log'], [$this, 'progress'], [$this, 'errorCallback'])
->setCreatedBy($this->option('user_id'))
->setUpdating($this->option('update'))
->setShouldNotify($this->option('send-welcome'))
->setUsernameFormat($this->option('username_format'));
->setCreatedBy($this->option('user_id'))
->setUpdating($this->option('update'))
->setShouldNotify($this->option('send-welcome'))
->setUsernameFormat($this->option('username_format'));
$this->logger = Log::build([
'driver' => 'single',
'path' => $this->option('logfile'),
]);
]);
$this->progressIndicator->start('======= Importing Items from '.$filename.' =========');
@@ -99,9 +98,11 @@ class ObjectImportCommand extends Command
* If a warning message is passed, we'll spit it to the console as well.
*
* @author Daniel Melzter
*
* @since 3.0
* @param string $string
* @param string $level
*
* @param string $string
* @param string $level
*/
public function log($string, $level = 'info')
{
@@ -120,7 +121,9 @@ class ObjectImportCommand extends Command
* Get the console command arguments.
*
* @author Daniel Melzter
*
* @since 3.0
*
* @return array
*/
protected function getArguments()
@@ -134,20 +137,22 @@ class ObjectImportCommand extends Command
* Get the console command options.
*
* @author Daniel Melzter
*
* @since 3.0
*
* @return array
*/
protected function getOptions()
{
return [
['email_format', null, InputOption::VALUE_REQUIRED, 'The format of the email addresses that should be generated. Options are firstname.lastname, firstname, filastname', null],
['username_format', null, InputOption::VALUE_REQUIRED, 'The format of the username that should be generated. Options are firstname.lastname, firstname, filastname, email', null],
['logfile', null, InputOption::VALUE_REQUIRED, 'The path to log output to. storage/logs/importer.log by default', storage_path('logs/importer.log')],
['item-type', null, InputOption::VALUE_REQUIRED, 'Item Type To import. Valid Options are Asset, Consumable, Accessory, License, or User', 'Asset'],
['web-importer', null, InputOption::VALUE_NONE, 'Internal: packages output for use with the web importer'],
['user_id', null, InputOption::VALUE_REQUIRED, 'ID of user creating items', 1],
['update', null, InputOption::VALUE_NONE, 'If a matching item is found, update item information'],
['send-welcome', null, InputOption::VALUE_NONE, 'Whether to send a welcome email to any new users that are created.'],
['email_format', null, InputOption::VALUE_REQUIRED, 'The format of the email addresses that should be generated. Options are firstname.lastname, firstname, filastname', null],
['username_format', null, InputOption::VALUE_REQUIRED, 'The format of the username that should be generated. Options are firstname.lastname, firstname, filastname, email', null],
['logfile', null, InputOption::VALUE_REQUIRED, 'The path to log output to. storage/logs/importer.log by default', storage_path('logs/importer.log')],
['item-type', null, InputOption::VALUE_REQUIRED, 'Item Type To import. Valid Options are Asset, Consumable, Accessory, License, or User', 'Asset'],
['web-importer', null, InputOption::VALUE_NONE, 'Internal: packages output for use with the web importer'],
['user_id', null, InputOption::VALUE_REQUIRED, 'ID of user creating items', 1],
['update', null, InputOption::VALUE_NONE, 'If a matching item is found, update item information'],
['send-welcome', null, InputOption::VALUE_NONE, 'Whether to send a welcome email to any new users that are created.'],
];
}
}

View File

@@ -2,11 +2,10 @@
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\CustomField;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class PaveIt extends Command
{
@@ -42,9 +41,9 @@ class PaveIt extends Command
public function handle()
{
if (!$this->option('force')) {
if (! $this->option('force')) {
$confirmation = $this->confirm("\n****************************************************\nTHIS WILL DELETE ALL OF THE DATA IN YOUR DATABASE. \nThere is NO undo. This WILL destroy ALL of your data, \nINCLUDING ANY non-Snipe-IT tables you have in this database. \n****************************************************\n\nDo you wish to continue? No backsies! ");
if (!$confirmation) {
if (! $confirmation) {
$this->error('ABORTING');
exit(-1);
}
@@ -79,16 +78,16 @@ class PaveIt extends Command
foreach ($tables as $table_obj) {
$table = $table_obj['name'];
if (in_array($table, $except_tables)) {
$this->info($table. ' is SKIPPED.');
$this->info($table.' is SKIPPED.');
} else {
\DB::statement('truncate '.$table);
$this->info($table. ' is TRUNCATED.');
$this->info($table.' is TRUNCATED.');
}
}
// Leave in the demo oauth keys so we don't have to reset them every day in the demos
DB::statement('delete from oauth_clients WHERE id > 2');
DB::statement('delete from oauth_access_tokens WHERE user_id > 2');
}
}
}

View File

@@ -149,13 +149,13 @@ class Purge extends Command
$filenames = Actionlog::where('action_type', 'uploaded')
->where('item_id', $user->id)
->pluck('filename');
foreach($filenames as $filename) {
foreach ($filenames as $filename) {
try {
if (Storage::exists($rel_path . '/' . $filename)) {
Storage::delete($rel_path . '/' . $filename);
if (Storage::exists($rel_path.'/'.$filename)) {
Storage::delete($rel_path.'/'.$filename);
}
} catch (\Exception $e) {
Log::info('An error occurred while deleting files: ' . $e->getMessage());
Log::info('An error occurred while deleting files: '.$e->getMessage());
}
}
$this->info('- User "'.$user->username.'" deleted.');

View File

@@ -35,7 +35,7 @@ class PurgeEulaPDFs extends Command
$before = $this->option('older-than-days');
if (($before=='') || (!is_numeric($before))) {
if (($before == '') || (! is_numeric($before))) {
return $this->error('ERROR: You must pass a valid number for --older-than-days (example: snipeit:purge-eula-pdfs --older-than-days=365.)');
}
@@ -43,35 +43,32 @@ class PurgeEulaPDFs extends Command
$signature_path = 'private_uploads/signatures/';
$eula_path = 'private_uploads/eula-pdfs/';
if (!Storage::exists($eula_path)) {
if (! Storage::exists($eula_path)) {
$this->fail('The storage directory "'.$eula_path.'" does not exist. No EULA files will be deleted.');
}
if (!Storage::exists($signature_path)) {
if (! Storage::exists($signature_path)) {
$this->fail('The storage directory "'.$signature_path.'" does not exist. No signature files will be deleted.');
}
if ($this->option('dryrun')) {
$this->info('This script is being run with the --dryrun option. No files or records will be deleted.');
}
$acceptances = CheckoutAcceptance::HasFiles()->where('updated_at','<', $interval_date)->with('assignedTo')->get();
$acceptances = CheckoutAcceptance::HasFiles()->where('updated_at', '<', $interval_date)->with('assignedTo')->get();
if (!$this->option('force')) {
if (! $this->option('force')) {
if ($this->confirm("\n****************************************************\nTHIS WILL DELETE ALL OF THE SIGNATURES AND EULA PDF FILES SINCE $interval_date. \nThere is NO undo! \n****************************************************\n\nDo you wish to continue? No backsies! [y|N]")) {
}
}
if ($acceptances->count() == 0) {
return $this->warn('There are no item acceptances with signatures or EULA PDFs from before '.$interval_date);
}
$this->info(number_format($acceptances->count()) . ' EULA PDFs from before '.$interval_date.' will be purged');
$this->info(number_format($acceptances->count()).' EULA PDFs from before '.$interval_date.' will be purged');
if (!$this->option('with-output')) {
if (! $this->option('with-output')) {
$this->info('Run this command with the --with-output option to see the full list in the console.');
} else {
$this->table(
@@ -86,7 +83,7 @@ class PurgeEulaPDFs extends Command
trans('general.filename'),
],
$acceptances->map(fn($acceptance) => [
$acceptances->map(fn ($acceptance) => [
trans('general.user') => $acceptance->assignedTo->display_name,
trans('general.type') => $acceptance->display_checkoutable_type,
trans('general.item') => $acceptance->checkoutable_type::find($acceptance->checkoutable_id)->display_name,
@@ -99,31 +96,28 @@ class PurgeEulaPDFs extends Command
);
}
foreach ($acceptances as $acceptance) {
$signature_file = $signature_path.$acceptance->signature_filename;
$eula_file = $eula_path.$acceptance->stored_eula_file;
if (Storage::exists($signature_file)) {
if (!$this->option('dryrun')) {
if (! $this->option('dryrun')) {
Storage::delete($signature_file);
}
} else {
$this->error('The file "'. $signature_file.'" does not exist.');
$this->error('The file "'.$signature_file.'" does not exist.');
}
if (Storage::exists($eula_file)) {
if (!$this->option('dryrun')) {
if (! $this->option('dryrun')) {
Storage::delete($eula_file);
}
} else {
$this->error('The file "'.$eula_file.'" does not exist.');
}
if (!$this->option('dryrun')) {
if (! $this->option('dryrun')) {
$acceptance->delete();
}
}

View File

@@ -82,10 +82,10 @@ class ReEncodeCustomFieldNames extends Command
if ($field->db_column == $field->convertUnicodeDbSlug() && \Schema::hasColumn('assets', $field->convertUnicodeDbSlug())) {
$this->info('-- ✓ This field exists on the assets table and the value for db_column matches in the custom_fields table.');
/**
* There is a mismatch between the fieldname on the assets table and
* what $field->convertUnicodeDbSlug() is *now* expecting.
*/
/**
* There is a mismatch between the fieldname on the assets table and
* what $field->convertUnicodeDbSlug() is *now* expecting.
*/
} else {
if ($field->db_column != $field->convertUnicodeDbSlug()) {
@@ -96,7 +96,6 @@ class ReEncodeCustomFieldNames extends Command
}
/** Make sure the custom_field_columns array has the ID */
if (array_key_exists($field->id, $custom_field_columns)) {
@@ -114,7 +113,6 @@ class ReEncodeCustomFieldNames extends Command
$field->db_column = $field->convertUnicodeDbSlug();
$field->save();
} else {
$this->warn('-- ✘ WARNING: There is no field on the assets table ending in '.$field->id.'. This may require more in-depth investigation and may mean the schema was altered manually.');
}

View File

@@ -4,8 +4,8 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\Setting;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class RegenerateAssetTags extends Command
{

View File

@@ -44,7 +44,7 @@ class RemoveExplicitEols extends Command
}
$endTime = microtime(true);
$executionTime = ($endTime - $startTime);
$this->info('Command executed in ' . round($executionTime, 2) . ' seconds.');
$this->info('Command executed in '.round($executionTime, 2).' seconds.');
}
private function updateAssets($assets)
@@ -55,6 +55,6 @@ class RemoveExplicitEols extends Command
$asset->save();
}
$this->info($assets->count() . ' Assets updated successfully');
$this->info($assets->count().' Assets updated successfully');
}
}

View File

@@ -38,7 +38,7 @@ class RemoveInvalidUploadDeleteActionLogItems extends Command
return 0;
}
$this->table(['ID', 'Action Type', 'Item Type', 'Item ID', 'Created At', 'Deleted At'], $invalidLogs->map(fn($log) => [
$this->table(['ID', 'Action Type', 'Item Type', 'Item ID', 'Created At', 'Deleted At'], $invalidLogs->map(fn ($log) => [
$log->id,
$log->action_type,
$log->item_type,
@@ -48,7 +48,7 @@ class RemoveInvalidUploadDeleteActionLogItems extends Command
])->toArray());
if ($this->confirm("Do you wish to remove {$invalidLogs->count()} log items?")) {
$invalidLogs->each(fn($log) => $log->forceDelete());
$invalidLogs->each(fn ($log) => $log->forceDelete());
}
return 0;

View File

@@ -2,7 +2,6 @@
namespace App\Console\Commands;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Console\Command;
@@ -52,6 +51,7 @@ class ResetDemoSettings extends Command
$settings->header_color = '#3c8dbc';
$settings->link_dark_color = '#5fa4cc';
$settings->link_light_color = '#296282;';
$settings->nav_link_color = '#FFFFFF';
$settings->label2_2d_type = 'QRCODE';
$settings->default_currency = 'USD';
$settings->brand = 2;
@@ -76,7 +76,6 @@ class ResetDemoSettings extends Command
$settings->saml_custom_settings = null;
$settings->default_avatar = 'default.png';
$settings->save();
if ($user = User::where('username', '=', 'admin')->first()) {
@@ -90,5 +89,4 @@ class ResetDemoSettings extends Command
\Storage::disk('public')->put('snipe-logo-lg.png', file_get_contents(public_path('img/demo/snipe-logo-lg.png')));
}
}

View File

@@ -6,9 +6,9 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\License;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Console\Command;
class RestoreDeletedUsers extends Command
{
@@ -75,7 +75,7 @@ class RestoreDeletedUsers extends Command
DB::table('assets')
->where('id', $user_log->item_id)
->update(['assigned_to' => $user->id, 'assigned_type'=> User::class]);
->update(['assigned_to' => $user->id, 'assigned_type' => User::class]);
$this->info(' ** Asset '.$user_log->item->id.' ('.$user_log->item->asset_tag.') restored to user '.$user->id.'');
} elseif ($user_log->item_type == License::class) {

View File

@@ -2,14 +2,17 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use ZipArchive;
use Illuminate\Support\Facades\Log;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use ZipArchive;
class SQLStreamer {
class SQLStreamer
{
private $input;
private $output;
// embed the prefix here?
public ?string $prefix;
@@ -18,106 +21,112 @@ class SQLStreamer {
public static $buffer_size = 1024 * 1024; // use a 1MB buffer, ought to work fine for most cases?
public array $tablenames = [];
private bool $should_guess = false;
private bool $statement_is_permitted = false;
public function __construct($input, $output, string $prefix = null)
public function __construct($input, $output, ?string $prefix = null)
{
$this->input = $input;
$this->output = $output;
$this->prefix = $prefix;
}
public function parse_sql(string $line): string {
public function parse_sql(string $line): string
{
// take into account the 'start of line or not' setting as an instance variable?
// 'continuation' lines for a permitted statement are PERMITTED.
// remove *only* line-feeds & carriage-returns; helpful for regexes against lines from
// Windows dumps
$line = trim($line, "\r\n");
if($this->statement_is_permitted && $line[0] === ' ') {
return $line . "\n"; //re-add the newline
if ($this->statement_is_permitted && $line[0] === ' ') {
return $line."\n"; // re-add the newline
}
$table_regex = '`?([a-zA-Z0-9_]+)`?';
$allowed_statements = [
"/^(DROP TABLE (?:IF EXISTS )?)`$table_regex(.*)$/" => false,
"/^(CREATE TABLE )$table_regex(.*)$/" => true, //sets up 'continuation'
"/^(CREATE TABLE )$table_regex(.*)$/" => true, // sets up 'continuation'
"/^(LOCK TABLES )$table_regex(.*)$/" => false,
"/^(INSERT INTO )$table_regex(.*)$/" => false,
"/^UNLOCK TABLES/" => false,
'/^UNLOCK TABLES/' => false,
// "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
"/^\\)[a-zA-Z0-9_= ]*;$/" => false,
'/^\\)[a-zA-Z0-9_= ]*;$/' => false,
// ^^^^^^ that bit should *exit* the 'permitted' block
"/^\\(.*\\)[,;]$/" => false, //older MySQL dump style with one set of values per line
'/^\\(.*\\)[,;]$/' => false, // older MySQL dump style with one set of values per line
/* we *could* have made the ^INSERT INTO blah VALUES$ turn on the capturing state, and closed it with
a ^(blahblah);$ but it's cleaner to not have to manage the state machine. We're just going to
assume that (blahblah), or (blahblah); are values for INSERT and are always acceptable. */
"<^/\*![0-9]{5} SET NAMES '?[a-zA-Z0-9_-]+'? \*/;$>" => false, //using weird delimiters (<,>) for readability. allow quoted or unquoted charsets
"<^/\*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' \*/;$>" => false, //same, now handle zero-values
"<^/\*![0-9]{5} SET NAMES '?[a-zA-Z0-9_-]+'? \*/;$>" => false, // using weird delimiters (<,>) for readability. allow quoted or unquoted charsets
"<^/\*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' \*/;$>" => false, // same, now handle zero-values
];
foreach($allowed_statements as $statement => $statechange) {
// $this->info("Checking regex: $statement...\n");
foreach ($allowed_statements as $statement => $statechange) {
// $this->info("Checking regex: $statement...\n");
$matches = [];
if (preg_match($statement,$line,$matches)) {
if (preg_match($statement, $line, $matches)) {
$this->statement_is_permitted = $statechange;
// matches are: 1 => first part of the statement, 2 => tablename, 3 => rest of statement
// (with of course 0 being "the whole match")
if (@$matches[2]) {
// print "Found a tablename! It's: ".$matches[2]."\n";
// print "Found a tablename! It's: ".$matches[2]."\n";
if ($this->should_guess) {
@$this->tablenames[$matches[2]] += 1;
continue; //oh? FIXME
continue; // oh? FIXME
} else {
$cleaned_tablename = \DB::getTablePrefix().preg_replace('/^'.$this->prefix.'/','',$matches[2]);
$line = preg_replace($statement,'$1`'.$cleaned_tablename.'`$3' , $line);
$cleaned_tablename = \DB::getTablePrefix().preg_replace('/^'.$this->prefix.'/', '', $matches[2]);
$line = preg_replace($statement, '$1`'.$cleaned_tablename.'`$3', $line);
}
} else {
// no explicit tablename in this one, leave the line alone
}
//how do we *replace* the tablename?
// print "RETURNING LINE: $line";
return $line . "\n"; //re-add newline
// how do we *replace* the tablename?
// print "RETURNING LINE: $line";
return $line."\n"; // re-add newline
}
}
// all that is not allowed is denied.
return "";
return '';
}
//this is used in exactly *TWO* places, and in both cases should return a prefix I think?
// this is used in exactly *TWO* places, and in both cases should return a prefix I think?
// first - if you do the --sanitize-only one (which is mostly for testing/development)
// next - when you run *without* a guessed prefix, this is run first to figure out the prefix
// I think we have to *duplicate* the call to be able to run it again?
public static function guess_prefix($input):string
public static function guess_prefix($input): string
{
$parser = new self($input, null);
$parser->should_guess = true;
$parser->line_aware_piping(); // <----- THIS is doing the heavy lifting!
$check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; //TODO - move to statics?
//can't use 'users' because the 'accessories_checkout' table?
$check_tables = ['settings' => null, 'migrations' => null /* 'assets' => null */]; // TODO - move to statics?
// can't use 'users' because the 'accessories_checkout' table?
// can't use 'assets' because 'ver1_components_assets'
foreach($check_tables as $check_table => $_ignore) {
foreach ($check_tables as $check_table => $_ignore) {
foreach ($parser->tablenames as $tablename => $_count) {
// print "Comparing $tablename to $check_table\n";
if (str_ends_with($tablename,$check_table)) {
// print "Found one!\n";
$check_tables[$check_table] = substr($tablename,0,-strlen($check_table));
// print "Comparing $tablename to $check_table\n";
if (str_ends_with($tablename, $check_table)) {
// print "Found one!\n";
$check_tables[$check_table] = substr($tablename, 0, -strlen($check_table));
}
}
}
$guessed_prefix = null;
foreach ($check_tables as $clean_table => $prefix_guess) {
if(is_null($prefix_guess)) {
print("Couldn't find table $clean_table\n");
die();
if (is_null($prefix_guess)) {
echo "Couldn't find table $clean_table\n";
exit();
}
if(is_null($guessed_prefix)) {
if (is_null($guessed_prefix)) {
$guessed_prefix = $prefix_guess;
} else {
if ($guessed_prefix != $prefix_guess) {
print("Prefix mismatch! Had guessed $guessed_prefix but got $prefix_guess\n");
die();
echo "Prefix mismatch! Had guessed $guessed_prefix but got $prefix_guess\n";
exit();
}
}
}
@@ -130,7 +139,7 @@ class SQLStreamer {
{
$bytes_read = 0;
if (! $this->input) {
throw new \Exception("No Input available for line_aware_piping");
throw new \Exception('No Input available for line_aware_piping');
}
while (($buffer = fgets($this->input, SQLStreamer::$buffer_size)) !== false) {
@@ -142,25 +151,24 @@ class SQLStreamer {
$bytes_written = fwrite($this->output, $cleaned_buffer);
if ($bytes_written === false) {
throw new \Exception("Unable to write to pipe");
throw new \Exception('Unable to write to pipe');
}
}
}
// if we got a newline at the end of this, then the _next_ read is the beginning of a line
if($buffer[strlen($buffer)-1] === "\n") {
if ($buffer[strlen($buffer) - 1] === "\n") {
$this->reading_beginning_of_line = true;
} else {
$this->reading_beginning_of_line = false;
}
}
return $bytes_read;
}
}
class RestoreFromBackup extends Command
{
/**
@@ -202,7 +210,7 @@ class RestoreFromBackup extends Command
public function handle()
{
$dir = getcwd();
if( $dir != base_path() ) { // usually only the case when running via webserver, not via command-line
if ($dir != base_path()) { // usually only the case when running via webserver, not via command-line
Log::debug("Current working directory is: $dir, changing directory to: ".base_path());
chdir(base_path()); // TODO - is this *safe* to change on a running script?!
}
@@ -221,7 +229,7 @@ class RestoreFromBackup extends Command
return $this->error('DB_CONNECTION must be MySQL in order to perform a restore. Detected: '.config('database.default'));
}
$za = new ZipArchive();
$za = new ZipArchive;
$errcode = $za->open($filename/* , ZipArchive::RDONLY */); // that constant only exists in PHP 7.4 and higher
if ($errcode !== true) {
@@ -240,13 +248,12 @@ class RestoreFromBackup extends Command
return $this->error('Could not access file: '.$filename.' - '.array_key_exists($errcode, $errors) ? $errors[$errcode] : " Unknown reason: $errcode");
}
$private_dirs = [
'storage/private_uploads/accessories',
'storage/private_uploads/assetmodels' => 'storage/private_uploads/models', //this was changed from assetmodels => models Aug 10 2025
'storage/private_uploads/asset_maintenances' => 'storage/private_uploads/maintenances', //this was changed from asset_maintenances => maintenances Aug 10 2025
'storage/private_uploads/maintenances', //but let 'maintenances' take precedence
'storage/private_uploads/models', //and let 'models' take precedence
'storage/private_uploads/assetmodels' => 'storage/private_uploads/models', // this was changed from assetmodels => models Aug 10 2025
'storage/private_uploads/asset_maintenances' => 'storage/private_uploads/maintenances', // this was changed from asset_maintenances => maintenances Aug 10 2025
'storage/private_uploads/maintenances', // but let 'maintenances' take precedence
'storage/private_uploads/models', // and let 'models' take precedence
'storage/private_uploads/assets', // these are asset _files_, not the pictures.
'storage/private_uploads/audits',
'storage/private_uploads/components',
@@ -297,10 +304,10 @@ class RestoreFromBackup extends Command
$good_extensions = config('filesystems.allowed_upload_extensions_array');
$private_extensions = array_merge($good_extensions, ["csv", "key"]); //add csv, and 'key'
$public_extensions = array_diff($good_extensions, ["xml"]); //remove xml
$private_extensions = array_merge($good_extensions, ['csv', 'key']); // add csv, and 'key'
$public_extensions = array_diff($good_extensions, ['xml']); // remove xml
$sanitizer = new Sanitizer();
$sanitizer = new Sanitizer;
/**
* TODO: I _hate_ the "continue 3" thing we keep doing here
@@ -315,29 +322,30 @@ class RestoreFromBackup extends Command
// print_r($stat_results);
$raw_path = $stat_results['name'];
if (strpos($raw_path, '\\') !== false) { //found a backslash, swap it to forward-slash
if (strpos($raw_path, '\\') !== false) { // found a backslash, swap it to forward-slash
$raw_path = strtr($raw_path, '\\', '/');
//print "Translating file: ".$stat_results['name']." to: ".$raw_path."\n";
// print "Translating file: ".$stat_results['name']." to: ".$raw_path."\n";
}
// skip macOS resource fork files (?!?!?!)
if (strpos($raw_path, '__MACOSX') !== false && strpos($raw_path, '._') !== false) {
//print "SKIPPING macOS Resource fork file: $raw_path\n";
// print "SKIPPING macOS Resource fork file: $raw_path\n";
// $boring_files[] = $raw_path; //stop adding this to the boring files list; it's just confusing
continue;
}
if (@pathinfo($raw_path, PATHINFO_EXTENSION) == 'sql') {
Log::debug("Found a sql file!");
Log::debug('Found a sql file!');
$sqlfiles[] = $raw_path;
$sqlfile_indices[] = $i;
continue;
}
if ($raw_path[-1] == '/') {
//last character is '/' - this is a directory, and we don't need it, and we don't need to warn about it
// last character is '/' - this is a directory, and we don't need it, and we don't need to warn about it
continue;
}
if (in_array(basename($raw_path), [".gitkeep", ".gitignore", ".DS_Store"])) {
//skip these boring files silently without reporting on them; they're stupid
if (in_array(basename($raw_path), ['.gitkeep', '.gitignore', '.DS_Store'])) {
// skip these boring files silently without reporting on them; they're stupid
continue;
}
$extension = strtolower(pathinfo($raw_path, PATHINFO_EXTENSION));
@@ -351,20 +359,21 @@ class RestoreFromBackup extends Command
if (is_int($dir)) {
$dir = $destdir;
}
$last_pos = strrpos($raw_path, $dir . '/');
$last_pos = strrpos($raw_path, $dir.'/');
if ($last_pos !== false) {
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
//the CSV bit, below, is because we store CSV files as "blahcsv" - without an extension
if (!in_array($extension, $allowed_extensions) && !($dir == "storage/private_uploads/imports" && substr($raw_path, -3) == "csv" && $extension == "")) {
// print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
// print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
// the CSV bit, below, is because we store CSV files as "blahcsv" - without an extension
if (! in_array($extension, $allowed_extensions) && ! ($dir == 'storage/private_uploads/imports' && substr($raw_path, -3) == 'csv' && $extension == '')) {
$unsafe_files[] = $raw_path;
Log::debug($raw_path . ' from directory ' . $dir . ' is being skipped');
Log::debug($raw_path.' from directory '.$dir.' is being skipped');
} else {
if ($dir != $destdir) {
Log::debug("Getting ready to save file $raw_path to new directory $destdir");
}
$interesting_files[$raw_path] = ['dest' => $destdir, 'index' => $i];
}
continue 3;
}
}
@@ -378,28 +387,30 @@ class RestoreFromBackup extends Command
foreach ($files as $file) {
$has_wildcard = (strpos($file, '*') !== false);
if ($has_wildcard) {
$file = substr($file, 0, -1); //trim last character (which should be the wildcard)
$file = substr($file, 0, -1); // trim last character (which should be the wildcard)
}
$last_pos = strrpos($raw_path, $file); // no trailing slash!
if ($last_pos !== false) {
if (!in_array($extension, $allowed_extensions)) {
if (! in_array($extension, $allowed_extensions)) {
// gathering potentially unsafe files here to return at exit
$unsafe_files[] = $raw_path;
Log::debug('Potentially unsafe file ' . $raw_path . ' is being skipped');
Log::debug('Potentially unsafe file '.$raw_path.' is being skipped');
$boring_files[] = $raw_path;
continue 3;
}
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
//no wildcards found in $file, process 'normally'
if ($last_pos + strlen($file) == strlen($raw_path) || $has_wildcard) { //again, no trailing slash. or this is a wildcard and we just take it.
// print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
// no wildcards found in $file, process 'normally'
if ($last_pos + strlen($file) == strlen($raw_path) || $has_wildcard) { // again, no trailing slash. or this is a wildcard and we just take it.
// print("FOUND THE EXACT FILE: $file AT: $raw_path!!!\n"); //we *do* care about this, though.
$interesting_files[$raw_path] = ['dest' => dirname($file), 'index' => $i];
continue 3;
}
}
}
}
$boring_files[] = $raw_path; //if we've gotten to here and haven't continue'ed our way into the next iteration, we don't want this file
$boring_files[] = $raw_path; // if we've gotten to here and haven't continue'ed our way into the next iteration, we don't want this file
} // end of pre-processing the ZIP file for-loop
// print_r($interesting_files);exit(-1);
@@ -408,12 +419,12 @@ class RestoreFromBackup extends Command
}
if (strpos($sqlfiles[0], 'db-dumps') === false) {
//return $this->error("SQL backup file is missing 'db-dumps' component of full pathname: ".$sqlfiles[0]);
//older Snipe-IT installs don't have the db-dumps subdirectory component
// return $this->error("SQL backup file is missing 'db-dumps' component of full pathname: ".$sqlfiles[0]);
// older Snipe-IT installs don't have the db-dumps subdirectory component
}
$sql_stat = $za->statIndex($sqlfile_indices[0]);
//$this->info("SQL Stat is: ".print_r($sql_stat,true));
// $this->info("SQL Stat is: ".print_r($sql_stat,true));
$sql_contents = $za->getStream($sql_stat['name']); // maybe copy *THIS* thing?
// OKAY, now that we *found* the sql file if we're doing just the guess-prefix thing, we can do that *HERE* I think?
@@ -428,27 +439,28 @@ class RestoreFromBackup extends Command
if ($this->option('sql-stdout-only')) {
$sql_importer = new SQLStreamer($sql_contents, STDOUT, $this->option('sanitize-with-prefix'));
$bytes_read = $sql_importer->line_aware_piping();
return $this->warn("$bytes_read total bytes read");
//TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL,
// TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL,
// which would be good for redirecting to a file, and not having to trim the last line off of it
}
//how to invoke the restore?
// how to invoke the restore?
$pipes = [];
$env_vars = getenv();
$env_vars['MYSQL_PWD'] = config('database.connections.mysql.password');
// TODO notes: we are stealing the dump_binary_path (which *probably* also has your copy of the mysql binary in it. But it might not, so we might need to extend this)
// we unilaterally prepend a slash to the `mysql` command. This might mean your path could look like /blah/blah/blah//mysql - which should be fine. But maybe in some environments it isn't?
$mysql_binary = config('database.connections.mysql.dump.dump_binary_path').\DIRECTORY_SEPARATOR.'mysql'.(\DIRECTORY_SEPARATOR == '\\' ? ".exe" : "");
if( ! file_exists($mysql_binary) ) {
$mysql_binary = config('database.connections.mysql.dump.dump_binary_path').\DIRECTORY_SEPARATOR.'mysql'.(\DIRECTORY_SEPARATOR == '\\' ? '.exe' : '');
if (! file_exists($mysql_binary)) {
return $this->error("mysql tool at: '$mysql_binary' does not exist, cannot restore. Please edit DB_DUMP_PATH in your .env to point to a directory that contains the mysqldump and mysql binary");
}
$proc_results = proc_open("$mysql_binary -h ".escapeshellarg(config('database.connections.mysql.host')).' -u '.escapeshellarg(config('database.connections.mysql.username')).' '.escapeshellarg(config('database.connections.mysql.database')), // yanked -p since we pass via ENV
[0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
$pipes,
null,
$env_vars); // this is not super-duper awesome-secure, but definitely more secure than showing it on the CLI, or dropping temporary files with passwords in them.
[0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
$pipes,
null,
$env_vars); // this is not super-duper awesome-secure, but definitely more secure than showing it on the CLI, or dropping temporary files with passwords in them.
if ($proc_results === false) {
return $this->error('Unable to invoke mysql via CLI');
}
@@ -462,7 +474,7 @@ class RestoreFromBackup extends Command
// should we read stdout?
// fwrite($pipes[0],config("database.connections.mysql.password")."\n"); //this doesn't work :(
//$sql_contents = fopen($sqlfiles[0], "r"); //NOPE! This isn't a real file yet, silly-billy!
// $sql_contents = fopen($sqlfiles[0], "r"); //NOPE! This isn't a real file yet, silly-billy!
// FIXME - this feels like it wants to go somewhere else?
// and it doesn't seem 'right' - if you can't get a stream to the .sql file,
@@ -477,7 +489,7 @@ class RestoreFromBackup extends Command
}
try {
if ( $this->option('sanitize-with-prefix') === null) {
if ($this->option('sanitize-with-prefix') === null) {
// "Legacy" direct-piping
$bytes_read = 0;
while (($buffer = fgets($sql_contents, SQLStreamer::$buffer_size)) !== false) {
@@ -486,7 +498,7 @@ class RestoreFromBackup extends Command
$bytes_written = fwrite($pipes[0], $buffer);
if ($bytes_written === false) {
throw new Exception("Unable to write to pipe");
throw new Exception('Unable to write to pipe');
}
}
} else {
@@ -494,37 +506,37 @@ class RestoreFromBackup extends Command
$bytes_read = $sql_importer->line_aware_piping();
}
} catch (\Exception $e) {
Log::error("Error during restore!!!! ".$e->getMessage());
Log::error('Error during restore!!!! '.$e->getMessage());
// FIXME - put these back and/or put them in the right places?!
$err_out = fgets($pipes[1]);
$err_err = fgets($pipes[2]);
Log::error("Error OUTPUT: ".$err_out);
Log::error('Error OUTPUT: '.$err_out);
$this->info($err_out);
Log::error("Error ERROR : ".$err_err);
Log::error('Error ERROR : '.$err_err);
$this->error($err_err);
throw $e;
}
if (!feof($sql_contents) || $bytes_read == 0) {
return $this->error("Not at end of file for sql file, or zero bytes read. aborting!");
if (! feof($sql_contents) || $bytes_read == 0) {
return $this->error('Not at end of file for sql file, or zero bytes read. aborting!');
}
fclose($pipes[0]);
fclose($sql_contents);
$this->line(stream_get_contents($pipes[1]));
fclose($pipes[1]);
$this->error(stream_get_contents($pipes[2]));
fclose($pipes[2]);
//wait, have to do fclose() on all pipes first?
// wait, have to do fclose() on all pipes first?
$close_results = proc_close($proc_results);
if ($close_results != 0) {
return $this->error('There may have been a problem with the database import: Error number '.$close_results);
}
//and now copy the files over too (right?)
//FIXME - we don't prune the filesystem space yet!!!!
// and now copy the files over too (right?)
// FIXME - we don't prune the filesystem space yet!!!!
if ($this->option('no-progress')) {
$bar = null;
} else {
@@ -532,16 +544,16 @@ class RestoreFromBackup extends Command
}
foreach ($interesting_files as $pretty_file_name => $file_details) {
$ugly_file_name = $za->statIndex($file_details['index'])['name'];
$migrated_file_name = $file_details['dest'] . '/' . basename($pretty_file_name);
if (strcasecmp(substr($pretty_file_name, -4), ".svg") === 0) {
$migrated_file_name = $file_details['dest'].'/'.basename($pretty_file_name);
if (strcasecmp(substr($pretty_file_name, -4), '.svg') === 0) {
$svg_contents = $za->getFromIndex($file_details['index']);
$cleaned_svg = $sanitizer->sanitize($svg_contents);
file_put_contents($migrated_file_name, $cleaned_svg);
} else {
$fp = $za->getStream($ugly_file_name);
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
if (!is_dir($file_details['dest'])) {
mkdir($file_details['dest'], 0755, true); //0755 is what Laravel uses, so we do that
// $this->info("Weird problem, here are file details? ".print_r($file_details,true));
if (! is_dir($file_details['dest'])) {
mkdir($file_details['dest'], 0755, true); // 0755 is what Laravel uses, so we do that
}
$migrated_file = fopen($migrated_file_name, 'w');
while (($buffer = fgets($fp, SQLStreamer::$buffer_size)) !== false) {
@@ -549,7 +561,7 @@ class RestoreFromBackup extends Command
}
fclose($migrated_file);
fclose($fp);
//$this->info("Wrote $ugly_file_name to $pretty_file_name");
// $this->info("Wrote $ugly_file_name to $pretty_file_name");
}
if ($bar) {
$bar->advance();

View File

@@ -5,10 +5,10 @@ namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\CustomField;
use App\Models\Setting;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Encryption\Encrypter;
use Illuminate\Support\Facades\Artisan;
class RotateAppKey extends Command
{
@@ -46,12 +46,13 @@ class RotateAppKey extends Command
*/
public function handle()
{
//make sure they specify only exactly one of --emergency, or a filename. Not neither, and not both.
if ( (!$this->option('emergency') && !$this->argument('previous_key')) || ( $this->option('emergency') && $this->argument('previous_key'))) {
$this->error("Specify only one of --emergency, or an app key value, in order to rotate keys");
// make sure they specify only exactly one of --emergency, or a filename. Not neither, and not both.
if ((! $this->option('emergency') && ! $this->argument('previous_key')) || ($this->option('emergency') && $this->argument('previous_key'))) {
$this->error('Specify only one of --emergency, or an app key value, in order to rotate keys');
return 1;
}
if ( $this->option('emergency') ) {
if ($this->option('emergency')) {
$msg = "\n****************************************************\nTHIS WILL MODIFY YOUR APP_KEY AND DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND \nRE-ENCRYPT THEM WITH A NEWLY GENERATED KEY. \n\nThere is NO undo. \n\nMake SURE you have a database backup and a backup of your .env generated BEFORE running this command. \n\nIf you do not save the newly generated APP_KEY to your .env in this process, \nyour encrypted data will no longer be decryptable. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup and an .env backup? ";
} else {
$msg = "\n****************************************************\nTHIS WILL DE-CRYPT YOUR ENCRYPTED CUSTOM FIELDS AND RE-ENCRYPT THEM WITH YOUR\nAPP_KEY.\n\nThere is NO undo. \n\nMake SURE you have a database backup BEFORE running this command. \n\nAre you SURE you wish to continue, and have confirmed you have a database backup? ";
@@ -79,9 +80,9 @@ class RotateAppKey extends Command
$new_app_key = config('app.key');
}
$this->warn('Your app cipher is: ' . $cipher);
$this->warn('Your old APP_KEY is: ' . $old_app_key);
$this->warn('Your new APP_KEY is: ' . $new_app_key);
$this->warn('Your app cipher is: '.$cipher);
$this->warn('Your old APP_KEY is: '.$old_app_key);
$this->warn('Your new APP_KEY is: '.$new_app_key);
// Manually create an old encrypter instance using the old app key
// and also create a new encrypter instance so we can re-crypt the field
@@ -97,12 +98,13 @@ class RotateAppKey extends Command
foreach ($assets as $asset) {
try {
$asset->{$field->db_column} = $oldEncrypter->decrypt($asset->{$field->db_column});
$this->line('DECRYPTED: ' . $field->db_column);
$this->line('DECRYPTED: '.$field->db_column);
} catch (DecryptException $e) {
$this->line('Could not decrypt '. $field->db_column.' using "old key" - skipping...');
$this->line('Could not decrypt '.$field->db_column.' using "old key" - skipping...');
continue;
} catch (\Exception $e) {
$this->error("Error decrypting ".$field->db_column.", reason: ".$e->getMessage().". Aborting key rotation");
$this->error('Error decrypting '.$field->db_column.', reason: '.$e->getMessage().'. Aborting key rotation');
throw $e;
}
$asset->{$field->db_column} = $newEncrypter->encrypt($asset->{$field->db_column});
@@ -119,8 +121,8 @@ class RotateAppKey extends Command
$setting->ldap_pword = $newEncrypter->encrypt($setting->ldap_pword);
$setting->save();
$this->warn('LDAP password has been re-encrypted.');
} catch(DecryptException $e) {
$this->warn("Unable to decrypt old LDAP password; skipping");
} catch (DecryptException $e) {
$this->warn('Unable to decrypt old LDAP password; skipping');
}
}
} else {

View File

@@ -2,8 +2,8 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\SamlNonce;
use Illuminate\Console\Command;
class SamlClearExpiredNonces extends Command
{
@@ -38,7 +38,8 @@ class SamlClearExpiredNonces extends Command
*/
public function handle()
{
SamlNonce::where('not_valid_after','<=',now())->delete();
SamlNonce::where('not_valid_after', '<=', now())->delete();
return 0;
}
}

View File

@@ -9,10 +9,7 @@ use App\Models\CheckoutAcceptance;
use App\Models\Component;
use App\Models\Consumable;
use App\Models\LicenseSeat;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\CheckoutAssetNotification;
use App\Notifications\CurrentInventory;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Facades\Mail;
@@ -54,11 +51,11 @@ class SendAcceptanceReminder extends Command
->with([
'checkoutable' => function (MorphTo $morph) {
$morph->morphWith([
Asset::class => ['model.category', 'assignedTo', 'adminuser', 'company', 'checkouts'],
Accessory::class => ['category', 'company', 'checkouts'],
Asset::class => ['model.category', 'assignedTo', 'adminuser', 'company', 'checkouts'],
Accessory::class => ['category', 'company', 'checkouts'],
LicenseSeat::class => ['user', 'license', 'checkouts'],
Component::class => ['assignedTo', 'company', 'checkouts'],
Consumable::class => ['company', 'checkouts'],
Component::class => ['assignedTo', 'company', 'checkouts'],
Consumable::class => ['company', 'checkouts'],
]);
},
'assignedTo',
@@ -74,15 +71,15 @@ class SendAcceptanceReminder extends Command
$count = 0;
$unacceptedAssetGroups = $pending
->map(function($acceptance) {
->map(function ($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
})
->groupBy(function($item) {
->groupBy(function ($item) {
return $item['acceptance']->assignedTo ? $item['acceptance']->assignedTo->id : '';
});
$no_email_list= [];
$no_email_list = [];
foreach($unacceptedAssetGroups as $unacceptedAssetGroup) {
foreach ($unacceptedAssetGroups as $unacceptedAssetGroup) {
// The [0] is weird, but it allows for the item_count to work and grabs the appropriate info for each user.
// Collapsing and flattening the collection doesn't work above.
$acceptance = $unacceptedAssetGroup[0]['acceptance'];
@@ -90,7 +87,7 @@ class SendAcceptanceReminder extends Command
$locale = $acceptance->assignedTo?->locale;
$email = $acceptance->assignedTo?->email;
if(!$email){
if (! $email) {
$no_email_list[] = [
'id' => $acceptance->assignedTo?->id,
'name' => $acceptance->assignedTo?->display_name,
@@ -116,12 +113,11 @@ class SendAcceptanceReminder extends Command
$rows[] = [$user['id'], $user['name']];
}
if (!empty($rows)) {
$this->info("The following users do not have an email address:");
if (! empty($rows)) {
$this->info('The following users do not have an email address:');
$this->table($headers, $rows);
}
return 0;
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Recipients\AlertRecipient;
use App\Models\Setting;
@@ -10,7 +11,6 @@ use App\Notifications\ExpectedCheckinNotification;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Notification;
use App\Helpers\Helper;
class SendExpectedCheckinAlerts extends Command
{
@@ -49,7 +49,7 @@ class SendExpectedCheckinAlerts extends Command
$interval_date = $today->copy()->addDays($interval);
$count = 0;
if (!$this->option('with-output')) {
if (! $this->option('with-output')) {
$this->info('Run this command with the --with-output option to see the full list in the console.');
}
@@ -57,9 +57,8 @@ class SendExpectedCheckinAlerts extends Command
$this->info($assets->count().' assets must be checked on or before '.Helper::getFormattedDateObject($interval_date, 'date', false));
foreach ($assets as $asset) {
if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email!='') && $asset->checkedOutToUser()) {
if ($asset->assignedTo && (isset($asset->assignedTo->email)) && ($asset->assignedTo->email != '') && $asset->checkedOutToUser()) {
$asset->assignedTo->notify((new ExpectedCheckinNotification($asset)));
$count++;
}
@@ -76,13 +75,13 @@ class SendExpectedCheckinAlerts extends Command
trans('general.purchase_date'),
trans('admin/hardware/form.expected_checkin'),
],
$assets->map(fn($assets) => [
$assets->map(fn ($assets) => [
trans('general.id') => $assets->id,
trans('admin/hardware/form.tag') => $assets->asset_tag,
trans('admin/hardware/form.model') => $assets->model->name,
trans('general.model_no') => $assets->model->model_number,
trans('general.purchase_date') => $assets->purchase_date_formatted,
trans('admin/hardware/form.eol_date') => $assets->expected_checkin_formattedDate ? $assets->expected_checkin_formattedDate . ' (' . $assets->expected_checkin_diff_for_humans . ')' : '',
trans('admin/hardware/form.eol_date') => $assets->expected_checkin_formattedDate ? $assets->expected_checkin_formattedDate.' ('.$assets->expected_checkin_diff_for_humans.')' : '',
])
);
}
@@ -96,7 +95,7 @@ class SendExpectedCheckinAlerts extends Command
Notification::send($recipients, new ExpectedCheckinAdminNotification($assets));
}
$this->info('Sent checkin reminders to to '.$count.' users.');
}

View File

@@ -2,7 +2,6 @@
namespace App\Console\Commands;
use App\Helpers\Helper;
use App\Mail\ExpiringAssetsMail;
use App\Mail\ExpiringLicenseMail;
use App\Models\Asset;
@@ -15,7 +14,7 @@ class SendExpirationAlerts extends Command
{
/**
* The name and signature of the console command.
*
*
* @var string
*/
protected $signature = 'snipeit:expiring-alerts {--expired-licenses}';
@@ -49,8 +48,8 @@ class SendExpirationAlerts extends Command
// Send a rollup to the admin, if settings dictate
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item)) // Trim each email
->filter(fn($item) => !empty($item))
->map(fn ($item) => trim($item)) // Trim each email
->filter(fn ($item) => ! empty($item))
->all();
// Expiring Assets
$assets = Asset::getExpiringWarrantyOrEol($alert_interval);
@@ -72,23 +71,22 @@ class SendExpirationAlerts extends Command
trans('admin/hardware/form.eol_date'),
trans('admin/hardware/form.warranty_expires'),
],
$assets->map(fn($item) =>
[
trans('general.id') => $item->id,
$assets->map(fn ($item) => [
trans('general.id') => $item->id,
trans('admin/hardware/form.tag') => $item->asset_tag,
trans('admin/hardware/form.model') => $item->model->name,
trans('general.model_no') => $item->model->model_number,
trans('general.purchase_date') => $item->purchase_date_formatted,
trans('admin/hardware/form.eol_rate') => $item->model->eol,
trans('admin/hardware/form.eol_date') => $item->eol_date ? $item->eol_formatted_date .' ('.$item->eol_diff_for_humans.')' : '',
trans('admin/hardware/form.warranty_expires') => $item->warranty_expires ? $item->warranty_expires_formatted_date .' ('.$item->warranty_expires_diff_for_humans.')' : '',
])
);
trans('admin/hardware/form.eol_rate') => $item->model->eol,
trans('admin/hardware/form.eol_date') => $item->eol_date ? $item->eol_formatted_date.' ('.$item->eol_diff_for_humans.')' : '',
trans('admin/hardware/form.warranty_expires') => $item->warranty_expires ? $item->warranty_expires_formatted_date.' ('.$item->warranty_expires_diff_for_humans.')' : '',
])
);
}
// Expiring licenses
$licenses = License::query()->ExpiringLicenses($alert_interval, $this->option('expired-licenses'))
->with('manufacturer','category')
->with('manufacturer', 'category')
->orderBy('expiration_date', 'ASC')
->orderBy('termination_date', 'ASC')
->get();
@@ -104,14 +102,14 @@ class SendExpirationAlerts extends Command
trans('mail.expires'),
trans('admin/licenses/form.termination_date'),
trans('mail.terminates')],
$licenses->map(fn($item) => [
$licenses->map(fn ($item) => [
trans('general.id') => $item->id,
trans('general.name') => $item->name,
trans('general.purchase_date') => $item->purchase_date_formatted,
trans('admin/licenses/form.expiration') => $item->expires_formatted_date,
trans('mail.expires') => $item->expires_formatted_date ? $item->expires_diff_for_humans : '',
trans('admin/licenses/form.termination_date') => $item->terminates_formatted_date,
trans('mail.terminates') => $item->terminates_diff_for_humans
trans('mail.terminates') => $item->terminates_diff_for_humans,
])
);
}
@@ -120,12 +118,10 @@ class SendExpirationAlerts extends Command
$this->info(trans_choice('mail.assets_warrantee_alert', $assets->count(), ['count' => $assets->count(), 'threshold' => $alert_interval]));
$this->info(trans_choice('mail.license_expiring_alert', $licenses->count(), ['count' => $licenses->count(), 'threshold' => $alert_interval]));
} else {
if ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
} elseif (1 != $settings->alerts_enabled) {
} elseif ($settings->alerts_enabled != 1) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
}
}

View File

@@ -59,7 +59,7 @@ class SendInventoryAlerts extends Command
} else {
if ($settings->alert_email == '') {
$this->error('Could not send email. No alert email configured in settings');
} elseif (1 != $settings->alerts_enabled) {
} elseif ($settings->alerts_enabled != 1) {
$this->info('Alerts are disabled in the settings. No mail will be sent');
}
}

View File

@@ -49,12 +49,11 @@ class SendUpcomingAuditReport extends Command
$assets_query = Asset::whereNull('deleted_at')->dueOrOverdueForAudit($settings)->orderBy('assets.next_audit_date', 'asc')->with('supplier');
$asset_count = $assets_query->count();
$this->info(number_format($asset_count) . ' assets must be audited on or before ' . $interval_date);
if (!$this->option('with-output')) {
$this->info(number_format($asset_count).' assets must be audited on or before '.$interval_date);
if (! $this->option('with-output')) {
$this->info('Run this command with the --with-output option to see the full list in the console.');
}
if ($asset_count > 0) {
$assets_for_email = $assets_query->limit(30)->get();
@@ -63,22 +62,19 @@ class SendUpcomingAuditReport extends Command
if ($settings->alert_email != '') {
$recipients = collect(explode(',', $settings->alert_email))
->map(fn($item) => trim($item))
->filter(fn($item) => !empty($item))
->map(fn ($item) => trim($item))
->filter(fn ($item) => ! empty($item))
->all();
Mail::to($recipients)->send(new SendUpcomingAuditMail($assets_for_email, $settings->audit_warning_days, $asset_count));
$this->info('Audit notification sent to: ' . $settings->alert_email);
$this->info('Audit notification sent to: '.$settings->alert_email);
} else {
$this->info('There is no admin alert email set so no email will be sent.');
}
if ($this->option('with-output')) {
// Get the full list if the user wants output in the console
$assets_for_output = $assets_query->limit(null)->get();
@@ -93,7 +89,7 @@ class SendUpcomingAuditReport extends Command
trans('mail.assigned_to'),
],
$assets_for_output->map(fn($item) => [
$assets_for_output->map(fn ($item) => [
trans('general.id') => $item->id,
trans('general.name') => $item->display_name,
trans('general.last_audit') => $item->last_audit_formatted_date,
@@ -106,10 +102,8 @@ class SendUpcomingAuditReport extends Command
}
} else {
$this->info('There are no assets due for audit in the next ' . $interval . ' days.');
$this->info('There are no assets due for audit in the next '.$interval.' days.');
}
}
}

View File

@@ -63,16 +63,14 @@ class SyncAssetCounters extends Command
}
} else {
$this->info('No assets to sync');
}
});
} else {
$this->info('No assets to sync');
}
});
$bar->finish();
$time_elapsed_secs = microtime(true) - $start;
$this->info("\nSync of ".$assets_count.' assets executed in '.$time_elapsed_secs.' seconds');
}
}

View File

@@ -3,6 +3,8 @@
namespace App\Console\Commands;
use App\Models\Asset;
use App\Models\Location;
use App\Models\User;
use Illuminate\Console\Command;
class SyncAssetLocations extends Command
@@ -57,7 +59,7 @@ class SyncAssetLocations extends Command
$bar->advance();
}
$assigned_user_assets = Asset::where('assigned_type', \App\Models\User::class)->whereNotNull('assigned_to')->whereNull('deleted_at')->get();
$assigned_user_assets = Asset::where('assigned_type', User::class)->whereNotNull('assigned_to')->whereNull('deleted_at')->get();
$output['info'][] = 'There are '.$assigned_user_assets->count().' assets checked out to users.';
foreach ($assigned_user_assets as $assigned_user_asset) {
if (($assigned_user_asset->assignedTo) && ($assigned_user_asset->assignedTo->userLoc)) {
@@ -73,7 +75,7 @@ class SyncAssetLocations extends Command
$bar->advance();
}
$assigned_location_assets = Asset::where('assigned_type', \App\Models\Location::class)
$assigned_location_assets = Asset::where('assigned_type', Location::class)
->whereNotNull('assigned_to')->whereNull('deleted_at')->get();
$output['info'][] = 'There are '.$assigned_location_assets->count().' assets checked out to locations.';
@@ -90,13 +92,13 @@ class SyncAssetLocations extends Command
}
// Assigned to assets
$assigned_asset_assets = Asset::where('assigned_type', \App\Models\Asset::class)
$assigned_asset_assets = Asset::where('assigned_type', Asset::class)
->whereNotNull('assigned_to')->whereNull('deleted_at')->get();
$output['info'][] = 'Asset-assigned assets: '.$assigned_asset_assets->count();
foreach ($assigned_asset_assets as $assigned_asset_asset) {
// Check to make sure there aren't any invalid relationships
// Check to make sure there aren't any invalid relationships
if ($assigned_asset_asset->assetLoc()) {
$assigned_asset_asset->location_id = $assigned_asset_asset->assetLoc()->id;
$output['info'][] = 'Setting Asset Assigned asset '.$assigned_asset_asset->assetLoc()->id.' ('.$assigned_asset_asset->asset_tag.') location to: '.$assigned_asset_asset->assetLoc()->id;

View File

@@ -37,13 +37,13 @@ class SystemBackup extends Command
*/
public function handle()
{
ini_set('max_execution_time', env('BACKUP_TIME_LIMIT', 600)); //600 seconds = 10 minutes
ini_set('max_execution_time', env('BACKUP_TIME_LIMIT', 600)); // 600 seconds = 10 minutes
if ($this->option('filename')) {
$filename = $this->option('filename');
// Make sure the filename ends in .zip
if (!ends_with($filename, '.zip')) {
if (! ends_with($filename, '.zip')) {
$filename = $filename.'.zip';
}

View File

@@ -47,5 +47,4 @@ class TestLocationsFMCS extends Command
$this->table($header, $mismatched);
}
}

View File

@@ -17,7 +17,6 @@ class ToggleCustomfieldEncryption extends Command
protected $signature = 'snipeit:customfield-encryption
{fieldname : the db_column_name of the field}';
/**
* The console command description.
*
@@ -61,15 +60,15 @@ class ToggleCustomfieldEncryption extends Command
$field->field_encrypted = 1;
$field->save();
// This field is already encrypted. Do nothing.
// This field is already encrypted. Do nothing.
} else {
$this->error('The custom field ' . $field->db_column.' is already encrypted. No action was taken.');
$this->error('The custom field '.$field->db_column.' is already encrypted. No action was taken.');
}
});
// No matching column name found
// No matching column name found
} else {
$this->error('No matching results for unencrypted custom fields with db_column name: ' . $fieldname.'. Please check the fieldname.');
$this->error('No matching results for unencrypted custom fields with db_column name: '.$fieldname.'. Please check the fieldname.');
}
}

View File

@@ -87,7 +87,7 @@ class Version extends Command
$app_version = 'v'."$maj.".($min + 1).".$patch";
} elseif ($use_type == 'patch') {
$app_version = 'v'."$maj.$min.".($patch + 1);
// If nothing is passed, leave the version as it is, just increment the build
// If nothing is passed, leave the version as it is, just increment the build
} else {
$app_version = 'v'."$maj.$min.".$patch;
}

View File

@@ -2,9 +2,6 @@
namespace App\Console;
use App\Console\Commands\ImportLocations;
use App\Console\Commands\ReEncodeCustomFieldNames;
use App\Console\Commands\RestoreDeletedUsers;
use App\Models\Setting;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -14,12 +11,11 @@ class Kernel extends ConsoleKernel
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
if(Setting::getSettings()?->alerts_enabled === 1) {
if (Setting::getSettings()?->alerts_enabled === 1) {
$schedule->command('snipeit:inventory-alerts')->daily();
$schedule->command('snipeit:expiring-alerts')->daily();
$schedule->command('snipeit:expected-checkin')->daily();

View File

@@ -1,6 +1,7 @@
<?php
namespace App\Enums;
enum ActionType: string
{
// General
@@ -30,4 +31,4 @@ enum ActionType: string
// File Uploads
case Uploaded = 'uploaded';
case UploadDeleted = 'upload deleted';
}
}

View File

@@ -3,7 +3,6 @@
namespace App\Events;
use App\Models\CheckoutAcceptance;
use App\Models\Contracts\Acceptable;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

View File

@@ -3,7 +3,6 @@
namespace App\Events;
use App\Models\CheckoutAcceptance;
use App\Models\Contracts\Acceptable;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

View File

@@ -11,10 +11,15 @@ class CheckoutableCheckedIn
use Dispatchable, SerializesModels;
public $checkoutable;
public $checkedOutTo;
public $checkedInBy;
public $note;
public $action_date; // Date setted in the hardware.checkin view at the checkin_at input, for the action log
public $originalValues;
/**

View File

@@ -11,10 +11,15 @@ class CheckoutableCheckedOut
use Dispatchable, SerializesModels;
public $checkoutable;
public $checkedOutTo;
public $checkedOutBy;
public $note;
public $originalValues;
public int $quantity;
/**

View File

@@ -19,6 +19,5 @@ class CheckoutablesCheckedOutInBulk
public string $checkout_at,
public string $expected_checkin,
public string $note,
) {
}
) {}
}

View File

@@ -2,9 +2,9 @@
namespace App\Events;
use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\Models\User;
class UserMerged
{
@@ -17,8 +17,8 @@ class UserMerged
*/
public function __construct(User $from_user, User $to_user, ?User $admin)
{
$this->merged_from = $from_user;
$this->merged_to = $to_user;
$this->admin = $admin;
$this->merged_from = $from_user;
$this->merged_to = $to_user;
$this->admin = $admin;
}
}

View File

@@ -4,6 +4,4 @@ namespace App\Exceptions;
use Exception;
class AssetNotRequestable extends Exception
{
}
class AssetNotRequestable extends Exception {}

View File

@@ -2,16 +2,25 @@
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use App\Helpers\Helper;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\AuthenticationException;
use ArieTimmerman\Laravel\SCIMServer\Exceptions\SCIMException;
use Illuminate\Support\Facades\Log;
use Throwable;
use JsonException;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Intervention\Image\Exception\NotSupportedException;
use JsonException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
class Handler extends ExceptionHandler
{
@@ -21,16 +30,16 @@ class Handler extends ExceptionHandler
* @var array
*/
protected $dontReport = [
\Illuminate\Auth\AuthenticationException::class,
\Illuminate\Auth\Access\AuthorizationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
\Intervention\Image\Exception\NotSupportedException::class,
\League\OAuth2\Server\Exception\OAuthServerException::class,
AuthenticationException::class,
AuthorizationException::class,
HttpException::class,
ModelNotFoundException::class,
TokenMismatchException::class,
ValidationException::class,
NotSupportedException::class,
OAuthServerException::class,
JsonException::class,
SCIMException::class, //these generally don't need to be reported
SCIMException::class, // these generally don't need to be reported
InvalidFormatException::class,
];
@@ -39,7 +48,6 @@ class Handler extends ExceptionHandler
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Throwable $exception
* @return void
*/
public function report(Throwable $exception)
@@ -48,23 +56,23 @@ class Handler extends ExceptionHandler
if (class_exists(Log::class)) {
Log::error($exception);
}
return parent::report($exception);
}
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
*
* @param Request $request
* @param \Exception $e
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
* @return JsonResponse|RedirectResponse|Response
*/
public function render($request, Throwable $e)
{
// CSRF token mismatch error
if ($e instanceof \Illuminate\Session\TokenMismatchException) {
if ($e instanceof TokenMismatchException) {
return redirect()->back()->with('error', trans('general.token_expired'));
}
@@ -78,9 +86,10 @@ class Handler extends ExceptionHandler
if ($e instanceof SCIMException) {
try {
$e->report(); // logs as 'debug', so shouldn't get too noisy
} catch(\Exception $reportException) {
//do nothing
} catch (\Exception $reportException) {
// do nothing
}
return $e->render($request); // ALL SCIMExceptions have the 'render()' method
}
@@ -98,9 +107,10 @@ class Handler extends ExceptionHandler
}
// Handle API requests that fail because the model doesn't exist
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
if ($e instanceof ModelNotFoundException) {
$className = last(explode('\\', $e->getModel()));
return response()->json(Helper::formatStandardApiResponse('error', null, $className . ' not found'), 200);
return response()->json(Helper::formatStandardApiResponse('error', null, $className.' not found'), 200);
}
// Handle API requests that fail because of an HTTP status code and return a useful error message
@@ -111,8 +121,8 @@ class Handler extends ExceptionHandler
// API throttle requests are handled in the RouteServiceProvider configureRateLimiting() method, so we don't need to handle them here
switch ($e->getStatusCode()) {
case '404':
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode . ' endpoint not found'), 404);
case '405':
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode.' endpoint not found'), 404);
case '405':
return response()->json(Helper::formatStandardApiResponse('error', null, 'Method not allowed'), 405);
default:
return response()->json(Helper::formatStandardApiResponse('error', null, $statusCode), $statusCode);
@@ -124,19 +134,20 @@ class Handler extends ExceptionHandler
// never even get to the controller where we normally nicely format JSON responses
if ($e instanceof ValidationException) {
$response = $this->invalidJson($request, $e);
return response()->json(Helper::formatStandardApiResponse('error', null, $e->errors()), 200);
return response()->json(Helper::formatStandardApiResponse('error', null, $e->errors()), 200);
}
}
// This is traaaaash but it handles models that are not found while using route model binding :(
// The only alternative is to set that at *each* route, which is crazypants
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
if ($e instanceof ModelNotFoundException) {
$ids = method_exists($e, 'getIds') ? $e->getIds() : [];
if (in_array('bulkedit', $ids, true)) {
$error_array = session()->get('bulk_asset_errors');
$error_array = session()->get('bulk_asset_errors');
return redirect()
->route('hardware.index')
->withErrors($error_array, 'bulk_asset_errors')
@@ -144,7 +155,7 @@ class Handler extends ExceptionHandler
}
// This gets the MVC model name from the exception and formats in a way that's less fugly
$model_name = trim(strtolower(implode(" ", preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel()))))));
$model_name = trim(strtolower(implode(' ', preg_split('/(?=[A-Z])/', last(explode('\\', $e->getModel()))))));
$route = str_plural(strtolower(last(explode('\\', $e->getModel())))).'.index';
// Sigh.
@@ -171,24 +182,22 @@ class Handler extends ExceptionHandler
->withError(trans('general.generic_model_not_found', ['model' => $model_name]));
}
if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404' )) {
if ($this->isHttpException($e) && (isset($statusCode)) && ($statusCode == '404')) {
return response()->view('layouts/basic', [
'content' => view('errors/404')
],$statusCode);
'content' => view('errors/404'),
], $statusCode);
}
return parent::render($request, $e);
}
/**
/**
* Convert an authentication exception into an unauthenticated response.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Auth\AuthenticationException $exception
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
*/
* @param Request $request
* @return JsonResponse|RedirectResponse
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
@@ -203,8 +212,7 @@ class Handler extends ExceptionHandler
return response()->json(Helper::formatStandardApiResponse('error', null, $exception->errors()), 200);
}
/**
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array

View File

@@ -2,8 +2,6 @@
namespace App\Exceptions;
use Exception;
class ItemStillHasAccessories extends ItemStillHasChildren
{
//

View File

@@ -2,8 +2,6 @@
namespace App\Exceptions;
use Exception;
class ItemStillHasAssetModels extends ItemStillHasChildren
{
//

View File

@@ -2,8 +2,4 @@
namespace App\Exceptions;
use Exception;
class ItemStillHasAssets extends ItemStillHasChildren
{
}
class ItemStillHasAssets extends ItemStillHasChildren {}

View File

@@ -6,9 +6,9 @@ use Exception;
class ItemStillHasChildren extends Exception
{
//public function __construct($message, $code = 0, Exception $previous = null, $parent, $children)
//{
// public function __construct($message, $code = 0, Exception $previous = null, $parent, $children)
// {
// trans()
//
//}
// }
}

View File

@@ -2,8 +2,6 @@
namespace App\Exceptions;
use Exception;
class ItemStillHasComponents extends ItemStillHasChildren
{
//

View File

@@ -2,8 +2,6 @@
namespace App\Exceptions;
use Exception;
class ItemStillHasConsumables extends ItemStillHasChildren
{
//

View File

@@ -2,8 +2,6 @@
namespace App\Exceptions;
use Exception;
class ItemStillHasLicenses extends ItemStillHasChildren
{
//

View File

@@ -2,8 +2,6 @@
namespace App\Exceptions;
use Exception;
class ItemStillHasMaintenances extends ItemStillHasChildren
{
//

View File

@@ -4,7 +4,4 @@ namespace App\Exceptions;
use Exception;
class UserDoestExistException extends Exception
{
}
class UserDoestExistException extends Exception {}

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,8 @@ namespace App\Helpers;
class IconHelper
{
public static function icon($type) {
public static function icon($type)
{
switch ($type) {
case 'checkout':
return 'fa-solid fa-rotate-left';
@@ -120,7 +120,7 @@ class IconHelper
case 'dashboard':
return 'fas fa-tachometer-alt';
case 'info-circle':
return 'fas fa-info-circle';
return 'fas fa-info-circle';
case 'caret-right':
return 'fa fa-caret-right';
case 'caret-up':
@@ -199,11 +199,11 @@ class IconHelper
return 'fas fa-crosshairs';
case 'oauth':
return 'fas fa-user-secret';
case 'employee_num' :
case 'employee_num':
return 'fa-regular fa-id-card';
case 'department' :
case 'department':
return 'fa-solid fa-building-user';
case 'home' :
case 'home':
return 'fa-solid fa-house';
case 'note':
case 'notes':
@@ -228,7 +228,7 @@ class IconHelper
return 'fa-regular fa-calendar-xmark';
case 'manufacturer':
return 'fa-solid fa-industry';
case 'fieldset' :
case 'fieldset':
return 'fa-regular fa-rectangle-list';
case 'deleted-date':
return 'fa-solid fa-calendar-xmark';
@@ -260,8 +260,6 @@ class IconHelper
case 'min-qty':
return 'fa-solid fa-chart-pie';
}
}
}

View File

@@ -2,32 +2,33 @@
namespace App\Helpers;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Response;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
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
public static function downloader($filename, $disk = 'default'): BinaryFileResponse|RedirectResponse|StreamedResponse
{
if ($disk == 'default') {
$disk = config('filesystems.default');
}
switch (config("filesystems.disks.$disk.driver")) {
case 'local':
return response()->download(Storage::disk($disk)->path($filename)); //works for PRIVATE or public?!
case 'local':
return response()->download(Storage::disk($disk)->path($filename)); // works for PRIVATE or public?!
case 's3':
return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); //works for private or public, I guess?
case 's3':
return redirect()->away(Storage::disk($disk)->temporaryUrl($filename, now()->addMinutes(5))); // works for private or public, I guess?
default:
return Storage::disk($disk)->download($filename);
default:
return Storage::disk($disk)->download($filename);
}
}
public static function getMediaType($file_with_path) {
public static function getMediaType($file_with_path)
{
// Get the file extension and determine the media type
if (Storage::exists($file_with_path)) {
@@ -64,6 +65,7 @@ class StorageHelper
return $extension; // Default for unknown types
}
}
return null;
}
@@ -72,8 +74,9 @@ class StorageHelper
* to determine that they are safe to display inline.
*
* @author <A. Gianotto> [<snipe@snipe.net]>
*
* @since v7.0.14
* @param $file_with_path
*
* @return bool
*/
public static function allowSafeInline($file_with_path)
@@ -96,11 +99,11 @@ class StorageHelper
'webp',
];
// 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;
}
@@ -117,7 +120,6 @@ class StorageHelper
}
/**
* Decide whether to show the file inline or download it.
*/
@@ -140,7 +142,7 @@ class StorageHelper
// Everything else seems okay, but the file doesn't exist on the server.
if (Storage::missing($file)) {
throw new FileNotFoundException();
throw new FileNotFoundException;
}
return Storage::download($file, $filename, $headers);

View File

@@ -7,11 +7,11 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Models\Accessory;
use App\Models\Company;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
/** This controller handles all actions related to Accessories for
* the Snipe-IT Asset Management application.
@@ -25,12 +25,14 @@ class AccessoriesController extends Controller
* the content for the accessories listing, which is generated in getDatatable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @see AccessoriesController::getDatatable() method that generates the JSON response
* @since [v1.0]
*/
public function index() : View
public function index(): View
{
$this->authorize('index', Accessory::class);
return view('accessories.index');
}
@@ -39,43 +41,42 @@ class AccessoriesController extends Controller
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function create() : View
public function create(): View
{
$this->authorize('create', Accessory::class);
$category_type = 'accessory';
return view('accessories/edit')->with('category_type', $category_type)
->with('item', new Accessory);
->with('item', new Accessory);
}
/**
* Validate and save new Accessory from form post
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param ImageUploadRequest $request
*/
public function store(ImageUploadRequest $request) : RedirectResponse
public function store(ImageUploadRequest $request): RedirectResponse
{
$this->authorize(Accessory::class);
// create a new model instance
$accessory = new Accessory();
$accessory = new Accessory;
// Update the accessory data
$accessory->name = request('name');
$accessory->category_id = request('category_id');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->order_number = request('order_number');
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->created_by = auth()->id();
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
$accessory->name = request('name');
$accessory->category_id = request('category_id');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->order_number = request('order_number');
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->created_by = auth()->id();
$accessory->supplier_id = request('supplier_id');
$accessory->notes = request('notes');
if ($request->has('use_cloned_image')) {
$cloned_model_img = Accessory::select('image')->find($request->input('clone_image_from_id'));
@@ -90,7 +91,7 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
}
if($request->input('redirect_option') === 'back'){
if ($request->input('redirect_option') === 'back') {
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->input('redirect_option')]);
@@ -110,11 +111,14 @@ class AccessoriesController extends Controller
* Return view for the Accessory update form, prepopulated with existing data
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
*
* @param int $accessoryId
*/
public function edit(Accessory $accessory) : View | RedirectResponse
public function edit(Accessory $accessory): View|RedirectResponse
{
$this->authorize('update', Accessory::class);
$this->authorize('update', $accessory);
session()->put('url.intended', url()->previous());
return view('accessories.edit')->with('item', $accessory)->with('category_type', 'accessory');
}
@@ -122,13 +126,15 @@ class AccessoriesController extends Controller
* Returns a view that presents a form to clone an accessory.
*
* @author [J. Vinsmoke]
* @param int $accessoryId
*
* @param int $accessoryId
*
* @since [v6.0]
*/
public function getClone(Accessory $accessory) : View | RedirectResponse
public function getClone(Accessory $accessory): View|RedirectResponse
{
$this->authorize('create', Accessory::class);
$this->authorize('create', $accessory);
$cloned = clone $accessory;
$accessory_to_clone = $accessory;
$cloned->id = null;
@@ -137,24 +143,24 @@ class AccessoriesController extends Controller
return view('accessories/edit')
->with('cloned_model', $accessory_to_clone)
->with('item', $cloned);
}
/**
* Save edited Accessory from form post
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param ImageUploadRequest $request
* @param int $accessoryId
*
* @param int $accessoryId
*/
public function update(ImageUploadRequest $request, Accessory $accessory) : RedirectResponse
public function update(ImageUploadRequest $request, Accessory $accessory): RedirectResponse
{
$this->authorize('update', $accessory);
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessory->id)) {
$this->authorize($accessory);
$validator = Validator::make($request->all(), [
"qty" => "required|numeric|min:$accessory->checkouts_count"
'qty' => "required|numeric|min:$accessory->checkouts_count",
]);
if ($validator->fails()) {
@@ -163,8 +169,6 @@ class AccessoriesController extends Controller
->withInput();
}
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
@@ -182,7 +186,7 @@ class AccessoriesController extends Controller
$accessory = $request->handleImages($accessory);
if($request->input('redirect_option') === 'back'){
if ($request->input('redirect_option') === 'back') {
session()->put(['redirect_option' => 'index']);
} else {
session()->put(['redirect_option' => $request->input('redirect_option')]);
@@ -203,51 +207,48 @@ class AccessoriesController extends Controller
* Delete the given accessory.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryId
*
* @param int $accessoryId
*/
public function destroy($accessoryId) : RedirectResponse
public function destroy(Accessory $accessory): RedirectResponse
{
if (is_null($accessory = Accessory::withCount('checkouts as checkouts_count')->find($accessoryId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$this->authorize('delete', $accessory);
$accessory->loadCount('checkouts as checkouts_count');
$this->authorize($accessory);
if ($accessory->checkouts_count > 0) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled'));
}
if ($accessory->image) {
try {
Storage::disk('public')->delete('accessories'.'/'.$accessory->image);
} catch (\Exception $e) {
Log::debug($e);
if ($accessory->isDeletable()) {
if ($accessory->image) {
try {
Storage::disk('public')->delete('accessories'.'/'.$accessory->image);
} catch (\Exception $e) {
Log::debug($e);
}
}
$accessory->delete();
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
}
$accessory->delete();
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/general.delete_disabled'));
}
/**
* Returns a view that invokes the ajax table which contains
* the content for the accessory detail view, which is generated in getDataView.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $accessoryID
*
* @param int $accessoryID
*
* @see AccessoriesController::getDataView() method that generates the JSON response
* @since [v1.0]
*/
public function show(Accessory $accessory) : View | RedirectResponse
public function show(Accessory $accessory): View|RedirectResponse
{
$accessory->loadCount('checkouts as checkouts_count');
$accessory->load(['adminuser' => fn($query) => $query->withTrashed()]);
$this->authorize('view', $accessory);
$accessory->loadCount('checkouts as checkouts_count');
$accessory->load(['adminuser' => fn ($query) => $query->withTrashed()]);
return view('accessories.view', compact('accessory'));
}
}

View File

@@ -7,10 +7,10 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
class AccessoryCheckinController extends Controller
{
@@ -18,25 +18,26 @@ class AccessoryCheckinController extends Controller
* Check the accessory back into inventory
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param int $accessoryUserId
* @param string $backto
*
* @param Request $request
* @param int $accessoryUserId
* @param string $backto
*/
public function create($accessoryUserId = null, $backto = null) : View | RedirectResponse
public function create($accessoryUserId = null, $backto = null): View|RedirectResponse
{
if (is_null($accessory_user = DB::table('accessories_checkout')->find($accessoryUserId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
}
$accessory = Accessory::find($accessory_user->accessory_id);
$this->authorize('checkin', $accessory);
//based on what the accessory is checked out to the target redirect option will be displayed accordingly.
// based on what the accessory is checked out to the target redirect option will be displayed accordingly.
$target_option = match ($accessory_user->assigned_type) {
'App\Models\Asset' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.asset')]),
'App\Models\Location' => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.location')]),
default => trans('admin/hardware/form.redirect_to_type', ['type' => trans('general.user')]),
};
$this->authorize('checkin', $accessory);
return view('accessories/checkin', compact('accessory', 'target_option'))->with('backto', $backto);
@@ -46,17 +47,20 @@ class AccessoryCheckinController extends Controller
* Check in the item so that it can be checked out again to someone else
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param null $accessoryCheckoutId
* @param string $backto
*
* @param null $accessoryCheckoutId
* @param string $backto
*/
public function store(Request $request, $accessoryCheckoutId = null, $backto = null) : RedirectResponse
public function store(Request $request, $accessoryCheckoutId = null, $backto = null): RedirectResponse
{
if (is_null($accessory_checkout = AccessoryCheckout::find($accessoryCheckoutId))) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
}
$accessory = Accessory::find($accessory_checkout->accessory_id);
$this->authorize('checkin', $accessory);
session()->put('checkedInFrom', $accessory_checkout->assigned_to);
session()->put('checkout_to_type', match ($accessory_checkout->assigned_type) {
@@ -65,7 +69,6 @@ class AccessoryCheckinController extends Controller
'App\Models\Asset' => 'asset',
});
$this->authorize('checkin', $accessory);
$checkin_hours = date('H:i:s');
$checkin_at = date('Y-m-d H:i:s');
if ($request->filled('checkin_at')) {
@@ -81,6 +84,7 @@ class AccessoryCheckinController extends Controller
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
->with('success', trans('admin/accessories/message.checkin.success'));
}
// Redirect to the accessory management page with error
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkin.error'));
}

View File

@@ -11,47 +11,39 @@ use App\Models\Accessory;
use App\Models\AccessoryCheckout;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
class AccessoryCheckoutController extends Controller
{
use CheckInOutRequest;
/**
* Return the form to checkout an Accessory to a user.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
*
* @param int $id
*/
public function create($id) : View | RedirectResponse
public function create(Accessory $accessory): View|RedirectResponse
{
if ($accessory = Accessory::withCount('checkouts as checkouts_count')->find($id)) {
$this->authorize('checkout', $accessory);
$this->authorize('checkout', $accessory);
if ($accessory->category) {
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0){
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
// Return the checkout view
return view('accessories/checkout', compact('accessory'));
if ($accessory->category) {
// Make sure there is at least one available to checkout
if ($accessory->numRemaining() <= 0) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.checkout.unavailable'));
}
// Invalid category
return redirect()->route('accessories.edit', ['accessory' => $accessory->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.accessory')]));
// Return the checkout view
return view('accessories/checkout', compact('accessory'));
}
// Not found
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.not_found'));
// Invalid category
return redirect()->route('accessories.edit', ['accessory' => $accessory->id])
->with('error', trans('general.invalid_item_category_single', ['type' => trans('general.accessory')]));
}
@@ -62,19 +54,19 @@ class AccessoryCheckoutController extends Controller
* trigger a Slack message and send an email.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param Request $request
* @param Accessory $accessory
*
* @param Request $request
*/
public function store(AccessoryCheckoutRequest $request, Accessory $accessory) : RedirectResponse
public function store(AccessoryCheckoutRequest $request, Accessory $accessory): RedirectResponse
{
$this->authorize('checkout', $accessory);
$target = $this->determineCheckoutTarget();
session()->put(['checkout_to_type' => $target]);
$accessory->checkout_qty = $request->input('checkout_qty', 1);
for ($i = 0; $i < $accessory->checkout_qty; $i++) {
$accessory_checkout = new AccessoryCheckout([
@@ -103,7 +95,6 @@ class AccessoryCheckoutController extends Controller
session()->put(['redirect_option' => $request->input('redirect_option'), 'checkout_to_type' => $request->input('checkout_to_type')]);
// Redirect to the new accessory page
return Helper::getRedirectOption($request, $accessory->id, 'Accessories')
->with('success', trans('admin/accessories/message.checkout.success'));

View File

@@ -4,36 +4,34 @@ namespace App\Http\Controllers\Account;
use App\Events\CheckoutAccepted;
use App\Events\CheckoutDeclined;
use App\Events\ItemAccepted;
use App\Events\ItemDeclined;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Mail\CheckoutAcceptanceResponseMail;
use App\Models\CheckoutAcceptance;
use App\Models\Company;
use App\Models\Contracts\Acceptable;
use App\Models\Setting;
use App\Models\User;
use App\Notifications\AcceptanceItemAcceptedNotification;
use App\Notifications\AcceptanceItemAcceptedToUserNotification;
use App\Notifications\AcceptanceItemDeclinedNotification;
use Exception;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use \Illuminate\Contracts\View\View;
use \Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
use App\Helpers\Helper;
class AcceptanceController extends Controller
{
/**
* Show a listing of pending checkout acceptances for the current user
*/
public function index() : View
public function index(): View
{
$acceptances = CheckoutAcceptance::forUser(auth()->user())->pending()->get();
return view('account/accept.index', compact('acceptances'));
}
@@ -42,11 +40,10 @@ class AcceptanceController extends Controller
*
* @param int $id
*/
public function create($id) : View | RedirectResponse
public function create($id): View|RedirectResponse
{
$acceptance = CheckoutAcceptance::find($id);
if (is_null($acceptance)) {
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
}
@@ -69,20 +66,18 @@ class AcceptanceController extends Controller
/**
* Stores the accept/decline of the checkout acceptance
*
* @param Request $request
* @param int $id
*/
public function store(Request $request, $id) : RedirectResponse
public function store(Request $request, $id): RedirectResponse
{
if (!$acceptance = CheckoutAcceptance::find($id)) {
if (! $acceptance = CheckoutAcceptance::find($id)) {
return redirect()->route('account.accept')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$assigned_user = User::find($acceptance->assigned_to_id);
$settings = Setting::getSettings();
$sig_filename='';
$sig_filename = '';
if (! $acceptance->isPending()) {
return redirect()->route('account.accept')->with('error', trans('admin/users/message.error.asset_already_accepted'));
@@ -121,11 +116,11 @@ class AcceptanceController extends Controller
// The item was accepted, check for a signature
if ($request->filled('signature_output')) {
$sig_filename = 'siglog-' . Str::uuid() . '-' . date('Y-m-d-his') . '.png';
$sig_filename = 'siglog-'.Str::uuid().'-'.date('Y-m-d-his').'.png';
$data_uri = $request->input('signature_output');
$encoded_image = explode(',', $data_uri);
$decoded_image = base64_decode($encoded_image[1]);
Storage::put('private_uploads/signatures/' . $sig_filename, (string)$decoded_image);
Storage::put('private_uploads/signatures/'.$sig_filename, (string) $decoded_image);
// No image data is present, kick them back.
// This mostly only applies to users on super-duper crapola browsers *cough* IE *cough*
@@ -134,12 +129,11 @@ class AcceptanceController extends Controller
}
}
// Convert PDF logo to base64 for TCPDF
// This is needed for TCPDF to properly embed the image if it's a png and the cache isn't writable
$encoded_logo = null;
if (($settings->acceptance_pdf_logo) && (Storage::disk('public')->exists($settings->acceptance_pdf_logo))) {
$encoded_logo = base64_encode(file_get_contents(public_path() . '/uploads/' . $settings->acceptance_pdf_logo));
$encoded_logo = base64_encode(file_get_contents(public_path().'/uploads/'.$settings->acceptance_pdf_logo));
}
// Get the data array ready for the notifications and PDF generation
@@ -158,7 +152,7 @@ class AcceptanceController extends Controller
'email' => $assigned_user->email,
'employee_num' => $assigned_user->employee_num,
'site_name' => $settings->site_name,
'company_name' => $item->company?->name?? $settings->site_name,
'company_name' => $item->company?->name ?? $settings->site_name,
'signature' => (($sig_filename && array_key_exists('1', $encoded_image))) ? $encoded_image[1] : null,
'logo' => ($encoded_logo) ?? null,
'date_settings' => $settings->date_display_format,
@@ -167,37 +161,36 @@ class AcceptanceController extends Controller
if ($request->input('asset_acceptance') == 'accepted') {
$pdf_filename = 'accepted-'.$acceptance->checkoutable_id.'-'.$acceptance->display_checkoutable_type.'-eula-'.date('Y-m-d-h-i-s').'.pdf';
// Generate the PDF content
$pdf_content = $acceptance->generateAcceptancePdf($data, $acceptance);
Storage::put('private_uploads/eula-pdfs/' .$pdf_filename, $pdf_content);
Storage::put('private_uploads/eula-pdfs/'.$pdf_filename, $pdf_content);
// Log the acceptance
$acceptance->accept($sig_filename, $item->getEula(), $pdf_filename, $request->input('note'));
// Send the PDF to the signing user
if (($request->input('send_copy') == '1') && ($assigned_user->email !='')) {
if (($request->input('send_copy') == '1') && ($assigned_user->email != '')) {
// Add the attachment for the signing user into the $data array
$data['file'] = $pdf_filename;
try {
$assigned_user->notify((new AcceptanceItemAcceptedToUserNotification($data))->locale($assigned_user->locale));
} catch (\Exception $e) {
} catch (Exception $e) {
Log::warning($e);
}
}
try {
$acceptance->notify((new AcceptanceItemAcceptedNotification($data))->locale(Setting::getSettings()->locale));
} catch (\Exception $e) {
} catch (Exception $e) {
Log::warning($e);
}
event(new CheckoutAccepted($acceptance));
$return_msg = trans('admin/users/message.accepted');
// Item was declined
// Item was declined
} else {
for ($i = 0; $i < ($acceptance->qty ?? 1); $i++) {
@@ -210,7 +203,6 @@ class AcceptanceController extends Controller
$return_msg = trans('admin/users/message.declined');
}
// Send an email notification if one is requested
if ($acceptance->alert_on_response_id) {
try {
@@ -230,10 +222,8 @@ class AcceptanceController extends Controller
Log::warning($e);
}
}
return redirect()->to('account/accept')->with('success', $return_msg);
}
}

View File

@@ -4,17 +4,18 @@ namespace App\Http\Controllers;
use App\Helpers\Helper;
use App\Models\Actionlog;
use App\Models\Asset;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use \Illuminate\Http\Response;
class ActionlogController extends Controller
{
public function displaySig($filename) : RedirectResponse | Response | bool
public function displaySig($filename): RedirectResponse|Response|bool
{
// PHP doesn't let you handle file not found errors well with
// PHP doesn't let you handle file not found errors well with
// file_get_contents, so we set the error reporting for just this class
error_reporting(0);
@@ -23,15 +24,17 @@ class ActionlogController extends Controller
case 's3':
$file = 'private_uploads/signatures/'.$filename;
return redirect()->away(Storage::disk($disk)->temporaryUrl($file, now()->addMinutes(5)));
default:
$this->authorize('view', \App\Models\Asset::class);
$this->authorize('view', Asset::class);
$file = config('app.private_uploads').'/signatures/'.$filename;
$filetype = Helper::checkUploadIsImage($file);
$contents = file_get_contents($file, false, stream_context_create(['http' => ['ignore_errors' => true]]));
if ($contents === false) {
Log::warning('File '.$file.' not found');
return false;
} else {
return response()->make($contents)->header('Content-Type', $filetype);
@@ -39,7 +42,7 @@ class ActionlogController extends Controller
}
}
public function getStoredEula($filename) : Response | BinaryFileResponse | RedirectResponse
public function getStoredEula($filename): Response|BinaryFileResponse|RedirectResponse
{
if ($actionlog = Actionlog::where('filename', $filename)->with('user')->with('target')->firstOrFail()) {
@@ -47,18 +50,17 @@ class ActionlogController extends Controller
$this->authorize('view', $actionlog->target);
$this->authorize('view', $actionlog->user);
if (config('filesystems.default') == 's3_private') {
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/' . $filename, now()->addMinutes(5)));
return redirect()->away(Storage::disk('s3_private')->temporaryUrl('private_uploads/eula-pdfs/'.$filename, now()->addMinutes(5)));
}
if (Storage::exists('private_uploads/eula-pdfs/' . $filename)) {
if (Storage::exists('private_uploads/eula-pdfs/'.$filename)) {
if (request()->input('inline') == 'true') {
return response()->file(config('app.private_uploads') . '/eula-pdfs/' . $filename);
return response()->file(config('app.private_uploads').'/eula-pdfs/'.$filename);
}
return response()->download(config('app.private_uploads') . '/eula-pdfs/' . $filename);
return response()->download(config('app.private_uploads').'/eula-pdfs/'.$filename);
}
return redirect()->back()->with('error', trans('general.file_does_not_exist'));

View File

@@ -7,19 +7,17 @@ use App\Helpers\Helper;
use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Http\Requests\AccessoryCheckoutRequest;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\StoreAccessoryRequest;
use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Accessory;
use App\Models\Company;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use App\Models\AccessoryCheckout;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class AccessoriesController extends Controller
{
@@ -29,8 +27,10 @@ class AccessoriesController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @return \Illuminate\Http\Response
*
* @return Response
*/
public function index(Request $request)
{
@@ -38,10 +38,9 @@ class AccessoriesController extends Controller
$this->authorize('view', Accessory::class);
}
// This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
// Relations will be handled in query scopes a little further down.
$allowed_columns =
$allowed_columns =
[
'id',
'name',
@@ -66,7 +65,6 @@ class AccessoriesController extends Controller
'manufacturer',
];
$accessories = Accessory::select('accessories.*')
->with('category', 'company', 'manufacturer', 'checkouts', 'location', 'supplier', 'adminuser')
->withCount('checkouts as checkouts_count');
@@ -87,7 +85,6 @@ class AccessoriesController extends Controller
$accessories->TextSearch($request->input('search'));
}
if ($request->filled('company_id')) {
$accessories->where('accessories.company_id', '=', $request->input('company_id'));
}
@@ -109,11 +106,11 @@ class AccessoriesController extends Controller
}
if ($request->filled('location_id')) {
$accessories->where('location_id','=',$request->input('location_id'));
$accessories->where('location_id', '=', $request->input('location_id'));
}
if ($request->filled('notes')) {
$accessories->where('notes','=',$request->input('notes'));
$accessories->where('notes', '=', $request->input('notes'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
@@ -121,7 +118,7 @@ class AccessoriesController extends Controller
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) {
@@ -136,7 +133,7 @@ class AccessoriesController extends Controller
break;
case 'manufacturer':
$accessories = $accessories->OrderManufacturer($order);
break;
break;
case 'supplier':
$accessories = $accessories->OrderSupplier($order);
break;
@@ -147,20 +144,21 @@ class AccessoriesController extends Controller
$accessories = $accessories->orderBy($column_sort, $order);
break;
}
$total = $accessories->count();
$accessories = $accessories->skip($offset)->take($limit)->get();
return (new AccessoriesTransformer)->transformAccessories($accessories, $total);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\JsonResponse
* @param ImageUploadRequest $request
* @return JsonResponse
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function store(StoreAccessoryRequest $request)
@@ -182,7 +180,9 @@ class AccessoriesController extends Controller
*
* @param int $id
* @return array
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function show($id)
@@ -193,13 +193,14 @@ class AccessoriesController extends Controller
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
/**
* Display the specified resource.
*
* @param int $id
* @return array
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function accessory_detail($id)
@@ -210,14 +211,15 @@ class AccessoriesController extends Controller
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
/**
* Get the list of checkouts for a specific accessory
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
* @return | array
* @return | array
*/
public function checkedout(Request $request, $id)
{
@@ -241,15 +243,15 @@ class AccessoriesController extends Controller
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory_checkouts, $total);
}
/**
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param int $id
* @return \Illuminate\Http\JsonResponse
* @return JsonResponse
*/
public function update(ImageUploadRequest $request, $id)
{
@@ -269,9 +271,11 @@ class AccessoriesController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
* @return \Illuminate\Http\JsonResponse
* @return JsonResponse
*/
public function destroy($id)
{
@@ -288,7 +292,6 @@ class AccessoriesController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.delete.success')));
}
/**
* Save the Accessory checkout information.
*
@@ -296,7 +299,8 @@ class AccessoriesController extends Controller
* trigger a Slack message and send an email.
*
* @param int $accessoryId
* @return \Illuminate\Http\JsonResponse
* @return JsonResponse
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
public function checkout(AccessoryCheckoutRequest $request, Accessory $accessory)
@@ -315,7 +319,6 @@ class AccessoriesController extends Controller
'note' => $request->input('note'),
]);
$accessory_checkout->created_by = auth()->id();
$accessory_checkout->save();
@@ -346,12 +349,14 @@ class AccessoriesController extends Controller
/**
* Check in the item so that it can be checked out again to someone else
*
* @param Request $request
* @param int $accessoryUserId
* @param string $backto
* @param int $accessoryUserId
* @param string $backto
* @return JsonResponse
*
* @uses Accessory::checkin_email() to determine if an email can and should be sent
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @internal param int $accessoryId
*/
public function checkin(Request $request, $accessoryUserId = null)
@@ -378,20 +383,18 @@ class AccessoriesController extends Controller
'pivot' => $accessory_checkout->id,
];
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkin.success')));
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/accessories/message.checkin.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
}
/**
* Gets a paginated collection for the select2 menus
*
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
* Gets a paginated collection for the select2 menus
*
* @see SelectlistTransformer
*/
public function selectlist(Request $request)
{
@@ -408,5 +411,4 @@ class AccessoriesController extends Controller
return (new SelectlistTransformer)->transformSelectlist($accessories);
}
}

View File

@@ -4,22 +4,26 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\StoreAssetModelRequest;
use App\Http\Transformers\AssetModelsTransformer;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Asset;
use App\Models\AssetModel;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
/**
* This class controls all actions related to asset models for
* the Snipe-IT Asset Management application.
*
* @version v4.0
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
class AssetModelsController extends Controller
@@ -28,9 +32,10 @@ class AssetModelsController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', AssetModel::class);
$allowed_columns =
@@ -79,8 +84,8 @@ class AssetModelsController extends Controller
'models.fieldset_id',
'models.deleted_at',
'models.updated_at',
'models.require_serial'
])
'models.require_serial',
])
->with('category', 'depreciation', 'manufacturer', 'fieldset.fields.defaultValues', 'adminuser')
->withCount('assets as assets_count')
->withCount('availableAssets as remaining')
@@ -104,8 +109,7 @@ class AssetModelsController extends Controller
$assetmodels->TextSearch($request->input('search'));
}
if ($request->input('status')=='deleted') {
if ($request->input('status') == 'deleted') {
$assetmodels->onlyTrashed();
}
@@ -121,7 +125,7 @@ class AssetModelsController extends Controller
$assetmodels = $assetmodels->where('models.requestable', '=', '1');
} elseif ($request->input('requestable') == 'false') {
$assetmodels = $assetmodels->where('models.requestable', '=', '0');
}
}
if ($request->filled('notes')) {
$assetmodels = $assetmodels->where('models.notes', '=', $request->input('notes'));
@@ -170,15 +174,14 @@ class AssetModelsController extends Controller
return (new AssetModelsTransformer)->transformAssetModels($assetmodels, $total);
}
/**
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\StoreAssetModelRequest $request
*/
public function store(StoreAssetModelRequest $request) : JsonResponse
public function store(StoreAssetModelRequest $request): JsonResponse
{
$this->authorize('create', AssetModel::class);
$assetmodel = new AssetModel;
@@ -188,8 +191,8 @@ class AssetModelsController extends Controller
if ($assetmodel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
return response()->json(Helper::formatStandardApiResponse('error', null, $assetmodel->getErrors()));
}
@@ -197,10 +200,12 @@ class AssetModelsController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function show($id) : array
public function show($id): array
{
$this->authorize('view', AssetModel::class);
$assetmodel = AssetModel::withCount('assets as assets_count')->findOrFail($id);
@@ -212,10 +217,12 @@ class AssetModelsController extends Controller
* Display the specified resource's assets
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function assets($id) : array
public function assets($id): array
{
$this->authorize('view', AssetModel::class);
$assets = Asset::where('model_id', '=', $id)->get();
@@ -223,17 +230,18 @@ class AssetModelsController extends Controller
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
}
/**
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param ImageUploadRequest $request
* @param int $id
* @return \Illuminate\Http\Response
* @return Response
*/
public function update(StoreAssetModelRequest $request, $id) : JsonResponse
public function update(StoreAssetModelRequest $request, $id): JsonResponse
{
$this->authorize('update', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id);
@@ -252,7 +260,6 @@ class AssetModelsController extends Controller
$assetmodel->fieldset_id = $request->input('custom_fieldset_id');
}
if ($assetmodel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new AssetModelsTransformer)->transformAssetModel($assetmodel), trans('admin/models/message.update.success')));
}
@@ -264,10 +271,12 @@ class AssetModelsController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$this->authorize('delete', AssetModel::class);
$assetmodel = AssetModel::findOrFail($id);
@@ -294,10 +303,11 @@ class AssetModelsController extends Controller
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
* @see SelectlistTransformer
*/
public function selectlist(Request $request) : array
public function selectlist(Request $request): array
{
$this->authorize('view.selectlists');
@@ -310,7 +320,7 @@ class AssetModelsController extends Controller
'models.category_id',
])->with('manufacturer', 'category');
$settings = \App\Models\Setting::getSettings();
$settings = Setting::getSettings();
if ($request->filled('search')) {
$assetmodels = $assetmodels->SearchByManufacturerOrCat($request->input('search'));

View File

@@ -7,6 +7,7 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Requests\FilterRequest;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\StoreAssetRequest;
use App\Http\Requests\UpdateAssetRequest;
use App\Http\Traits\MigratesLegacyAssetLocations;
@@ -26,6 +27,7 @@ use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\Setting;
use App\Models\User;
use App\Observers\AssetObserver;
use App\View\Label;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
@@ -37,14 +39,13 @@ use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
/**
* This class controls all actions related to assets for
* the Snipe-IT Asset Management application.
*
* @version v1.0
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
class AssetsController extends Controller
@@ -55,13 +56,14 @@ class AssetsController extends Controller
* Returns JSON listing of all assets
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
*
* @param int $assetId
*
* @since [v4.0]
*/
public function index(FilterRequest $request, $action = null, $upcoming_status = null) : JsonResponse | array
public function index(FilterRequest $request, $action = null, $upcoming_status = null): JsonResponse|array
{
// This handles the legacy audit endpoints :(
if ($action == 'audit') {
$action = 'audits';
@@ -69,17 +71,17 @@ class AssetsController extends Controller
$filter_non_deprecable_assets = false;
/**
* This looks MAD janky (and it is), but the AssetsController@index does a LOT of heavy lifting throughout the
* app. This bit here just makes sure that someone without permission to view assets doesn't
* end up with priv escalations because they asked for a different endpoint.
*
* Since we never gave the specification for which transformer to use before, it should default
* gracefully to just use the AssetTransformer by default, which shouldn't break anything.
*
* It was either this mess, or repeating ALL of the searching and sorting and filtering code,
* This looks MAD janky (and it is), but the AssetsController@index does a LOT of heavy lifting throughout the
* app. This bit here just makes sure that someone without permission to view assets doesn't
* end up with priv escalations because they asked for a different endpoint.
*
* Since we never gave the specification for which transformer to use before, it should default
* gracefully to just use the AssetTransformer by default, which shouldn't break anything.
*
* It was either this mess, or repeating ALL of the searching and sorting and filtering code,
* which would have been far worse of a mess. *sad face* - snipe (Sept 1, 2021)
*/
if (Route::currentRouteName()=='api.depreciation-report.index') {
if (Route::currentRouteName() == 'api.depreciation-report.index') {
$filter_non_deprecable_assets = true;
$transformer = 'App\Http\Transformers\DepreciationReportTransformer';
$this->authorize('reports.view');
@@ -88,7 +90,6 @@ class AssetsController extends Controller
$this->authorize('index', Asset::class);
}
$settings = Setting::getSettings();
$allowed_columns = [
@@ -136,7 +137,7 @@ class AssetsController extends Controller
];
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
$all_custom_fields = CustomField::all(); // used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[] = $field->db_column_name();
@@ -179,14 +180,11 @@ class AssetsController extends Controller
'supplier'
); // it might be tempting to add 'assetlog' here, but don't. It blows up update-heavy users.
if ($filter_non_deprecable_assets) {
$non_deprecable_models = AssetModel::select('id')->whereNotNull('depreciation_id')->get();
$assets->InModelList($non_deprecable_models->toArray());
}
// These are used by the API to query against specific ID numbers.
// They are also used by the individual searches on detail pages like
// locations, etc.
@@ -204,12 +202,11 @@ class AssetsController extends Controller
$assets->TextSearch($request->input('search'));
}
/**
* Handle due and overdue audits and checkin dates
*/
switch ($action) {
// Audit (singular) is left over from earlier legacy APIs
// Audit (singular) is left over from earlier legacy APIs
case 'audits':
switch ($upcoming_status) {
case 'due':
@@ -243,7 +240,6 @@ class AssetsController extends Controller
* End handling due and overdue audits and checkin dates
*/
// This is used by the sidenav, mostly
// We switched from using query scopes here because of a Laravel bug
@@ -317,7 +313,6 @@ class AssetsController extends Controller
}
}
// Leave these under the TextSearch scope, else the fuzziness will override the specific ID (status ID, etc) requested
if ($request->filled('status_id')) {
$assets->where('assets.status_id', '=', $request->input('status_id'));
@@ -334,7 +329,7 @@ class AssetsController extends Controller
if ($request->input('requestable') == 'true') {
$assets->where('assets.requestable', '=', '1');
}
if ($request->filled('model_id')) {
// If model_id is already an array, just use it as-is
if (is_array($request->input('model_id'))) {
@@ -453,7 +448,7 @@ class AssetsController extends Controller
// This may not work for all databases, but it works for MySQL
if ($numeric_sort) {
$assets->orderByRaw(DB::getTablePrefix() . 'assets.' . $sort_override . ' * 1 ' . $order);
$assets->orderByRaw(DB::getTablePrefix().'assets.'.$sort_override.' * 1 '.$order);
} else {
$assets->orderBy($sort_override, $order);
}
@@ -463,7 +458,6 @@ class AssetsController extends Controller
break;
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $assets->count()) ? $assets->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -471,7 +465,6 @@ class AssetsController extends Controller
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
/**
* Include additional associated relationships
*/
@@ -484,15 +477,16 @@ class AssetsController extends Controller
return (new $transformer)->transformAssets($assets, $total, $request);
}
/**
* Returns JSON with information about an asset (by tag) for detail view.
*
* @param string $tag
* @param string $tag
*
* @since [v4.2.1]
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*/
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');
@@ -524,11 +518,14 @@ class AssetsController extends Controller
* Returns JSON with information about an asset (by serial) for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param string $serial
*
* @param string $serial
*
* @since [v4.2.1]
* @return \Illuminate\Http\JsonResponse
*
* @return 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([
@@ -567,11 +564,14 @@ class AssetsController extends Controller
* Returns JSON with information about an asset for detail view.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
*
* @param int $assetId
*
* @since [v4.0]
* @return \Illuminate\Http\JsonResponse
*
* @return 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()
@@ -581,6 +581,7 @@ class AssetsController extends Controller
return (new AssetsTransformer)->transformAsset($asset, $request->input('components'));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
@@ -591,16 +592,16 @@ class AssetsController extends Controller
$asset = Asset::where('id', $id)->withTrashed()->firstorfail();
$licenses = $asset->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
return (new LicensesTransformer)->transformLicenses($licenses, $licenses->count());
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
* @see SelectlistTransformer
*/
public function selectlist(Request $request): array
{
@@ -616,7 +617,7 @@ class AssetsController extends Controller
])->with('model', 'assetstatus', 'assignedTo')
->NotArchived();
if ((Setting::getSettings()->full_multiple_companies_support=='1') && ($request->filled('companyId'))) {
if ((Setting::getSettings()->full_multiple_companies_support == '1') && ($request->filled('companyId'))) {
$assets->where('assets.company_id', $request->input('companyId'));
}
@@ -635,16 +636,14 @@ class AssetsController extends Controller
// they may not have a ->name value but we want to display something anyway
foreach ($assets as $asset) {
$asset->use_text = $asset->present()->fullName;
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
$asset->use_text .= ' → ' . $asset->assigned->display_name;
$asset->use_text .= ' → '.$asset->assigned->display_name;
}
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;
@@ -653,21 +652,22 @@ class AssetsController extends Controller
return (new SelectlistTransformer)->transformSelectlist($assets);
}
/**
* Accepts a POST request to create a new asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param ImageUploadRequest $request
*
* @since [v4.0]
*/
public function store(StoreAssetRequest $request): JsonResponse
{
$asset = new Asset();
$asset = new Asset;
$asset->model()->associate(AssetModel::find((int) $request->input('model_id')));
$asset->fill($request->validated());
$asset->created_by = auth()->id();
$asset->created_by = auth()->id();
/**
* this is here just legacy reasons. Api\AssetController
@@ -692,9 +692,9 @@ class AssetsController extends Controller
// 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->input('model_id'));
Log::debug('Use the default fieldset value of ' . $field->defaultValue($request->input('model_id')));
Log::debug('Use the default fieldset value of '.$field->defaultValue($request->input('model_id')));
}
// if the field is set to encrypted, make sure we encrypt the value
@@ -717,7 +717,6 @@ class AssetsController extends Controller
}
}
$asset->{$field->db_column} = $field_val;
}
}
@@ -748,11 +747,11 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
/**
* Accepts a POST request to update an asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function update(UpdateAssetRequest $request, Asset $asset): JsonResponse
@@ -765,7 +764,7 @@ class AssetsController extends Controller
if ($request->has('company_id')) {
$asset->company_id = Company::getIdForCurrentUser($request->validated()['company_id']);
}
if ($request->has('rtd_location_id') && !$request->has('location_id')) {
if ($request->has('rtd_location_id') && ! $request->has('location_id')) {
$asset->location_id = $request->validated()['rtd_location_id'];
}
if ($request->input('last_audit_date')) {
@@ -800,6 +799,7 @@ class AssetsController extends Controller
$field_val = Crypt::encrypt($field_val);
} else {
$problems_updating_encrypted_custom_fields = true;
continue;
}
}
@@ -813,7 +813,7 @@ class AssetsController extends Controller
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->input('assigned_asset')))) {
$location = $target->location_id;
Asset::where('assigned_type', \App\Models\Asset::class)->where('assigned_to', $asset->id)
Asset::where('assigned_type', Asset::class)->where('assigned_to', $asset->id)
->update(['location_id' => $target->location_id]);
} elseif (($request->filled('assigned_location')) && ($target = Location::find($request->input('assigned_location')))) {
$location = $target->id;
@@ -837,18 +837,20 @@ class AssetsController extends Controller
} else {
return response()->json(Helper::formatStandardApiResponse('success', $asset, trans('admin/hardware/message.update.success')));
// Below is the *correct* return since it uses the transformer, but we have to use the old, flat return for now until we can update Jamf2Snipe and Kanji2Snipe
/// return response()->json(Helper::formatStandardApiResponse('success', (new AssetsTransformer)->transformAsset($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);
}
/**
* Delete a given asset (mark as deleted).
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
*
* @param int $assetId
*
* @since [v4.0]
*/
public function destroy($id): JsonResponse
@@ -877,13 +879,13 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
/**
* Restore a soft-deleted asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
*
* @param int $assetId
*
* @since [v5.1.18]
*/
public function restore(Request $request, $assetId = null): JsonResponse
@@ -911,7 +913,9 @@ class AssetsController extends Controller
* Checkout an asset by its tag.
*
* @author [N. Butler]
* @param string $tag
*
* @param string $tag
*
* @since [v6.0.5]
*/
public function checkoutByTag(AssetCheckoutRequest $request, $tag): JsonResponse
@@ -919,6 +923,7 @@ class AssetsController extends Controller
if ($asset = Asset::where('asset_tag', $tag)->first()) {
return $this->checkout($request, $asset->id);
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
}
@@ -926,7 +931,9 @@ class AssetsController extends Controller
* Checkout an asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
*
* @param int $assetId
*
* @since [v4.0]
*/
public function checkout(AssetCheckoutRequest $request, $asset_id): JsonResponse
@@ -971,7 +978,7 @@ class AssetsController extends Controller
}
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'));
@@ -982,8 +989,7 @@ class AssetsController extends Controller
// Set the location ID to the RTD location id if there is one
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.
// TODO: Follow up here. WTF. Commented out for now.
// 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;
@@ -996,12 +1002,13 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', ['asset' => e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
}
/**
* Checkin an asset
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
*
* @param int $assetId
*
* @since [v4.0]
*/
public function checkin(Request $request, $asset_id): JsonResponse
@@ -1014,12 +1021,12 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', [
'asset_tag' => e($asset->asset_tag),
'model' => e($asset->model->name),
'model_number' => e($asset->model->model_number)
'model_number' => e($asset->model->model_number),
], trans('admin/hardware/message.checkin.already_checked_in')));
}
$asset->expected_checkin = null;
//$asset->last_checkout = null;
// $asset->last_checkout = null;
$asset->last_checkin = now();
$asset->assignedTo()->disassociate($asset);
$asset->accepted = null;
@@ -1044,7 +1051,7 @@ class AssetsController extends Controller
$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->input('checkin_at') != date('Y-m-d'))) {
@@ -1075,7 +1082,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', [
'asset_tag' => e($asset->asset_tag),
'model' => e($asset->model->name),
'model_number' => e($asset->model->model_number)
'model_number' => e($asset->model->model_number),
], trans('admin/hardware/message.checkin.success')));
}
@@ -1086,12 +1093,13 @@ class AssetsController extends Controller
* Checkin an asset by asset tag
*
* @author [A. Janes] [<ajanes@adagiohealth.org>]
*
* @since [v6.0]
*/
public function checkinByTag(Request $request, $tag = null): JsonResponse
{
$this->authorize('checkin', Asset::class);
if (null == $tag && null !== ($request->input('asset_tag'))) {
if ($tag == null && null !== ($request->input('asset_tag'))) {
$tag = $request->input('asset_tag');
}
$asset = Asset::where('asset_tag', $tag)->first();
@@ -1101,20 +1109,20 @@ class AssetsController extends Controller
}
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'));
}
/**
* Mark an asset as audited
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
*
* @param int $id
*
* @since [v4.0]
*/
public function audit(Request $request, Asset $asset): JsonResponse
{
$this->authorize('audit', Asset::class);
@@ -1149,21 +1157,20 @@ class AssetsController extends Controller
'id' => $asset->id,
'asset_tag' => $asset->asset_tag,
'note' => e($request->input('note')),
'status_label' => e($asset->assetstatus->display_name),
'status_label' => e($asset->assetstatus?->display_name),
'status_type' => $asset->assetstatus->getStatuslabelType(),
'next_audit_date' => Helper::getFormattedDateObject($asset->next_audit_date),
];
/**
* Update custom fields in the database.
* Validation for these fields is handled through the AssetRequest form request
* $model = AssetModel::find($request->input('model_id'));
*/
*/
if (($asset->model) && ($asset->model->fieldset)) {
$payload['custom_fields'] = [];
foreach ($asset->model->fieldset->fields as $field) {
if (($field->display_audit=='1') && ($request->has($field->db_column))) {
if (($field->display_audit == '1') && ($request->has($field->db_column))) {
if ($field->field_encrypted == '1') {
if (Gate::allows('assets.view.encrypted_custom_fields')) {
if (is_array($request->input($field->db_column))) {
@@ -1179,7 +1186,7 @@ class AssetsController extends Controller
$asset->{$field->db_column} = $request->input($field->db_column);
}
}
$payload['custom_fields'][$field->db_column] = $request->input($field->db_column);
$payload['custom_fields'][$field->db_column] = $request->input($field->db_column);
}
}
@@ -1190,10 +1197,9 @@ class AssetsController extends Controller
// Validate the rest of the data before we turn off the event dispatcher
if ($asset->isInvalid()) {
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag' => $asset->asset_tag], $asset->getErrors()));
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag' => $asset->asset_tag], $asset->getErrors()));
}
/**
* Even though we do a save() further down, we don't want to log this as a "normal" asset update,
* which would trigger the Asset Observer and would log an asset *update* log entry (because the
@@ -1207,12 +1213,10 @@ class AssetsController extends Controller
* We handle validation on the save() by checking if the asset is valid via the ->isValid() method,
* which manually invokes Watson Validating to make sure the asset's model is valid.
*
* @see \App\Observers\AssetObserver::updating()
* @see \App\Models\Asset::save()
* @see AssetObserver::updating()
* @see Asset::save()
*/
$asset->unsetEventDispatcher();
$asset->unsetEventDispatcher();
/**
* Invoke Watson Validating to check the asset itself and check to make sure it saved correctly.
@@ -1220,26 +1224,25 @@ class AssetsController extends Controller
*/
if ($asset->isValid() && $asset->save()) {
$asset->logAudit(request('note'), request('location_id'), null, $originalValues);
return response()->json(Helper::formatStandardApiResponse('success', $payload, trans('admin/hardware/message.audit.success')));
}
}
// No matching asset for the asset tag that was passed.
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
/**
* Returns JSON listing of all requestable assets
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function requestable(Request $request): JsonResponse | array
public function requestable(Request $request): JsonResponse|array
{
$this->authorize('viewRequestable', Asset::class);
@@ -1253,7 +1256,7 @@ class AssetsController extends Controller
'expected_checkin',
];
$all_custom_fields = CustomField::all(); //used as a 'cache' of custom fields throughout this page load
$all_custom_fields = CustomField::all(); // used as a 'cache' of custom fields throughout this page load
foreach ($all_custom_fields as $field) {
$allowed_columns[] = $field->db_column_name();
@@ -1273,9 +1276,6 @@ class AssetsController extends Controller
'requests'
);
if ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
@@ -1321,8 +1321,7 @@ class AssetsController extends Controller
return (new AssetsTransformer)->transformRequestedAssets($assets, $total);
}
public function assignedAssets(Request $request, Asset $asset) : JsonResponse | array
public function assignedAssets(Request $request, Asset $asset): JsonResponse|array
{
$this->authorize('view', Asset::class);
$this->authorize('view', $asset);
@@ -1339,7 +1338,7 @@ class AssetsController extends Controller
return (new AssetsTransformer)->transformAssets($assets, $total);
}
public function assignedAccessories(Request $request, Asset $asset) : JsonResponse | array
public function assignedAccessories(Request $request, Asset $asset): JsonResponse|array
{
$this->authorize('view', Asset::class);
$this->authorize('view', $asset);
@@ -1353,6 +1352,7 @@ class AssetsController extends Controller
$total = $accessory_checkouts->count();
$accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get();
return (new AssetsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
}
@@ -1364,17 +1364,17 @@ class AssetsController extends Controller
$asset->loadCount('components');
$total = $asset->components_count;
$components = $asset->load(['components' => fn($query) => $query->applyOffsetAndLimit($total)])->components;
$components = $asset->load(['components' => fn ($query) => $query->applyOffsetAndLimit($total)])->components;
return (new ComponentsTransformer)->transformComponents($components, $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
*
* @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
@@ -1382,17 +1382,17 @@ class AssetsController extends Controller
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,
// 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
// 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
// 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);
@@ -1403,27 +1403,27 @@ class AssetsController extends Controller
// 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;
$settings->label_logo = ($original_logo = $settings->label_logo) && ! Storage::disk('public')->exists('/'.$original_logo) ? null : $settings->label_logo;
$label = new Label;
$label = new Label();
if (!$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);
->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) {
$label->render(function ($pdf) use (&$pdf_content) {
$pdf_content = $pdf->Output('', 'S');
return $pdf;
});
@@ -1435,21 +1435,21 @@ class AssetsController extends Controller
$encoded_content = base64_encode($pdf_content);
return response()->json(Helper::formatStandardApiResponse('success', [
'pdf' => $encoded_content
'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()
'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()
'error_file' => $e->getFile(),
], $e->getMessage()), 500);
}
}

View File

@@ -6,12 +6,13 @@ use App\Actions\Categories\DestroyCategoryAction;
use App\Exceptions\ItemStillHasChildren;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\CategoriesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Category;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
class CategoriesController extends Controller
@@ -20,10 +21,12 @@ class CategoriesController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @return \Illuminate\Http\Response
*
* @return Response
*/
public function index(Request $request) : array
public function index(Request $request): array
{
$this->authorize('view', Category::class);
$allowed_columns = [
@@ -60,11 +63,10 @@ class CategoriesController extends Controller
'image',
'tag_color',
'notes',
])
])
->with('adminuser')
->withCount('accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count', 'models as models_count');
$filter = [];
if ($request->filled('filter')) {
@@ -89,7 +91,7 @@ class CategoriesController extends Controller
*
* @see \App\Models\Category::showableAssets()
*/
if ($request->input('archived')=='true') {
if ($request->input('archived') == 'true') {
$categories = $categories->withCount('assets as assets_count');
} else {
$categories = $categories->withCount('showableAssets as assets_count');
@@ -131,7 +133,7 @@ class CategoriesController extends Controller
$offset = ($request->input('offset') > $categories->count()) ? $categories->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets_count';
switch ($sort_override) {
@@ -150,16 +152,16 @@ class CategoriesController extends Controller
}
/**
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @return \Illuminate\Http\Response
*
* @return Response
*/
public function store(ImageUploadRequest $request) : JsonResponse
public function store(ImageUploadRequest $request): JsonResponse
{
$this->authorize('create', Category::class);
$category = new Category;
@@ -170,6 +172,7 @@ class CategoriesController extends Controller
if ($category->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $category, trans('admin/categories/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $category->getErrors()));
}
@@ -178,28 +181,31 @@ class CategoriesController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function show($id) : array
public function show($id): array
{
$this->authorize('view', Category::class);
$category = Category::withCount('assets as assets_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'licenses as licenses_count')->findOrFail($id);
return (new CategoriesTransformer)->transformCategory($category);
}
/**
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param int $id
* @return \Illuminate\Http\Response
* @return Response
*/
public function update(ImageUploadRequest $request, $id) : JsonResponse
public function update(ImageUploadRequest $request, $id): JsonResponse
{
$this->authorize('update', Category::class);
$category = Category::findOrFail($id);
@@ -207,7 +213,7 @@ class CategoriesController extends Controller
// Don't allow the user to change the category_type once it's been created
if (($request->filled('category_type')) && ($category->category_type != $request->input('category_type'))) {
return response()->json(
Helper::formatStandardApiResponse('error', null, ['category_type' => trans('admin/categories/message.update.cannot_change_category_type')], 422)
Helper::formatStandardApiResponse('error', null, ['category_type' => trans('admin/categories/message.update.cannot_change_category_type')], 422)
);
}
$category->fill($request->all());
@@ -224,9 +230,11 @@ class CategoriesController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
* @return \Illuminate\Http\Response
* @return Response
*/
public function destroy(Category $category): JsonResponse
{
@@ -239,6 +247,7 @@ class CategoriesController extends Controller
);
} catch (\Exception $e) {
report($e);
return response()->json(
Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong'))
);
@@ -247,15 +256,15 @@ class CategoriesController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/categories/message.delete.success')));
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
* @see SelectlistTransformer
*/
public function selectlist(Request $request, $category_type = 'asset') : array
public function selectlist(Request $request, $category_type = 'asset'): array
{
$this->authorize('view.selectlists');
$categories = Category::select([

View File

@@ -8,9 +8,9 @@ use App\Exceptions\AssetNotRequestable;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Exception;
class CheckoutRequest extends Controller
{
@@ -18,6 +18,7 @@ class CheckoutRequest extends Controller
{
try {
CreateCheckoutRequestAction::run($asset, auth()->user());
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.success')));
} catch (AssetNotRequestable $e) {
return response()->json(Helper::formatStandardApiResponse('error', 'Asset is not requestable'));
@@ -25,6 +26,7 @@ class CheckoutRequest extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
} catch (Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
}
@@ -33,11 +35,13 @@ class CheckoutRequest extends Controller
{
try {
CancelCheckoutRequestAction::run($asset, auth()->user());
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.requests.canceled')));
} catch (AuthorizationException $e) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.insufficient_permissions')));
} catch (Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
}

View File

@@ -4,13 +4,13 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\CompaniesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class CompaniesController extends Controller
{
@@ -18,9 +18,10 @@ class CompaniesController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', Company::class);
@@ -42,13 +43,12 @@ class CompaniesController extends Controller
'notes',
];
$companies = Company::withCount(['assets as assets_count' => function ($query) {
$companies = Company::withCount(['assets as assets_count' => function ($query) {
$query->AssetsForShow();
}])
->with('adminuser')
->withCount('licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'components as components_count', 'users as users_count');
if ($request->filled('search')) {
$companies->TextSearch($request->input('search'));
}
@@ -57,7 +57,7 @@ class CompaniesController extends Controller
$companies->where('name', '=', $request->input('name'));
}
if ($request->filled('email')) {
if ($request->filled('email')) {
$companies->where('email', '=', $request->input('email'));
}
@@ -69,13 +69,11 @@ class CompaniesController extends Controller
$companies->where('tag_color', '=', $request->input('tag_color'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $companies->count()) ? $companies->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) {
@@ -90,25 +88,25 @@ class CompaniesController extends Controller
$total = $companies->count();
$companies = $companies->skip($offset)->take($limit)->get();
return (new CompaniesTransformer)->transformCompanies($companies, $total);
}
/**
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*/
public function store(ImageUploadRequest $request) : JsonResponse
public function store(ImageUploadRequest $request): JsonResponse
{
$this->authorize('create', Company::class);
$company = new Company;
$company->fill($request->all());
$company = $request->handleImages($company);
if ($company->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new CompaniesTransformer)->transformCompany($company), trans('admin/companies/message.create.success')));
}
@@ -121,28 +119,31 @@ class CompaniesController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function show($id) : array
public function show($id): array
{
$this->authorize('view', Company::class);
$company = Company::findOrFail($id);
$this->authorize('view', $company);
return (new CompaniesTransformer)->transformCompany($company);
}
/**
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param int $id
*/
public function update(ImageUploadRequest $request, $id) : JsonResponse
public function update(ImageUploadRequest $request, $id): JsonResponse
{
$this->authorize('update', Company::class);
$company = Company::findOrFail($id);
@@ -163,10 +164,12 @@ class CompaniesController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$this->authorize('delete', Company::class);
$company = Company::findOrFail($id);
@@ -174,7 +177,7 @@ class CompaniesController extends Controller
if (! $company->isDeletable()) {
return response()
->json(Helper::formatStandardApiResponse('error', null, trans('admin/companies/message.assoc_users')));
->json(Helper::formatStandardApiResponse('error', null, trans('admin/companies/message.assoc_users')));
}
$company->delete();
@@ -186,10 +189,11 @@ class CompaniesController extends Controller
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
* @see SelectlistTransformer
*/
public function selectlist(Request $request) : array
public function selectlist(Request $request): array
{
$this->authorize('view.selectlists');
$companies = Company::select([
@@ -200,7 +204,6 @@ class CompaniesController extends Controller
'companies.tag_color',
]);
if ($request->filled('search')) {
$companies = $companies->where('companies.name', 'LIKE', '%'.$request->input('search').'%');
}

View File

@@ -2,20 +2,20 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\ComponentsTransformer;
use App\Models\Component;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use App\Events\CheckoutableCheckedIn;
use App\Http\Transformers\ComponentsTransformer;
use App\Models\Asset;
use Illuminate\Support\Facades\Validator;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use App\Models\Component;
use Carbon\Carbon;
use Illuminate\Database\Query\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class ComponentsController extends Controller
{
@@ -23,16 +23,16 @@ class ComponentsController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
*
* @since [v4.0]
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', Component::class);
// This array is what determines which fields should be allowed to be sorted on ON the table itself, no relations
// Relations will be handled in query scopes a little further down.
$allowed_columns =
$allowed_columns =
[
'id',
'name',
@@ -78,7 +78,6 @@ class ComponentsController extends Controller
$components->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$components->where('name', '=', $request->input('name'));
}
@@ -112,7 +111,7 @@ class ComponentsController extends Controller
}
if ($request->filled('notes')) {
$components->where('notes','=',$request->input('notes'));
$components->where('notes', '=', $request->input('notes'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
@@ -121,7 +120,7 @@ class ComponentsController extends Controller
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) {
@@ -154,15 +153,14 @@ class ComponentsController extends Controller
return (new ComponentsTransformer)->transformComponents($components, $total);
}
/**
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*/
public function store(ImageUploadRequest $request) : JsonResponse
public function store(ImageUploadRequest $request): JsonResponse
{
$this->authorize('create', Component::class);
$component = new Component;
@@ -180,9 +178,10 @@ class ComponentsController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @param int $id
*/
public function show($id) : array
public function show($id): array
{
$this->authorize('view', Component::class);
$component = Component::findOrFail($id);
@@ -196,17 +195,17 @@ class ComponentsController extends Controller
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param int $id
*/
public function update(ImageUploadRequest $request, $id) : JsonResponse
public function update(ImageUploadRequest $request, $id): JsonResponse
{
$this->authorize('update', Component::class);
$component = Component::findOrFail($id);
$component->fill($request->all());
$component = $request->handleImages($component);
if ($component->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $component, trans('admin/components/message.update.success')));
@@ -219,17 +218,19 @@ class ComponentsController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$this->authorize('delete', Component::class);
$component = Component::findOrFail($id);
$this->authorize('delete', $component);
if ($component->numCheckedOut() > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.delete.error_qty')));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.delete.error_qty')));
}
$component->delete();
@@ -241,35 +242,36 @@ class ComponentsController extends Controller
* Display all assets attached to a component
*
* @author [A. Bergamasco] [@vjandrea]
*
* @since [v4.0]
* @param Request $request
* @param int $id
*/
public function getAssets(Request $request, $id) : array
*
* @param int $id
*/
public function getAssets(Request $request, $id): array
{
$this->authorize('view', \App\Models\Asset::class);
$this->authorize('view', Asset::class);
$component = Component::findOrFail($id);
$offset = request('offset', 0);
$limit = $request->input('limit', 50);
if ($request->filled('search')) {
$assets = $component->assets()
->where(function ($query) use ($request) {
$search_str = '%' . $request->input('search') . '%';
$query->where('name', 'like', $search_str)
->orWhereIn('model_id', function (Builder $query) use ($request) {
$search_str = '%' . $request->input('search') . '%';
$query->selectRaw('id')->from('models')->where('name', 'like', $search_str);
})
->orWhere('asset_tag', 'like', $search_str);
})
->get();
->where(function ($query) use ($request) {
$search_str = '%'.$request->input('search').'%';
$query->where('name', 'like', $search_str)
->orWhereIn('model_id', function (Builder $query) use ($request) {
$search_str = '%'.$request->input('search').'%';
$query->selectRaw('id')->from('models')->where('name', 'like', $search_str);
})
->orWhere('asset_tag', 'like', $search_str);
})
->get();
$total = $assets->count();
} else {
$assets = $component->assets();
$total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get();
}
@@ -277,28 +279,28 @@ class ComponentsController extends Controller
return (new ComponentsTransformer)->transformCheckedoutComponents($assets, $total);
}
/**
* Validate and checkout the component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* t
*
* @since [v5.1.8]
* @param Request $request
* @param int $componentId
*
* @param int $componentId
*/
public function checkout(Request $request, $componentId) : JsonResponse
public function checkout(Request $request, $componentId): JsonResponse
{
// Check if the component exists
if (!$component = Component::find($componentId)) {
if (! $component = Component::find($componentId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.does_not_exist')));
}
$this->authorize('checkout', $component);
$validator = Validator::make($request->all(), [
'assigned_to' => 'required|exists:assets,id',
'assigned_qty' => "required|numeric|min:1|digits_between:1,".$component->numRemaining(),
'assigned_to' => 'required|exists:assets,id',
'assigned_qty' => 'required|numeric|min:1|digits_between:1,'.$component->numRemaining(),
]);
if ($validator->fails()) {
@@ -327,7 +329,7 @@ class ComponentsController extends Controller
$component->logCheckout($request->input('note'), $asset, null, [], $request->get('assigned_qty', 1));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkout.success')));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.checkout.unavailable', ['remaining' => $component->numRemaining(), 'requested' => $request->input('assigned_qty')])));
@@ -337,11 +339,10 @@ class ComponentsController extends Controller
* Validate and store checkin data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v5.1.8]
* @param Request $request
* @param $component_asset_id
*/
public function checkin(Request $request, $component_asset_id) : 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))) {
@@ -353,15 +354,15 @@ class ComponentsController extends Controller
$max_to_checkin = $component_assets->assigned_qty;
$validator = Validator::make($request->all(), [
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
'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));
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));
$qty_remaining_in_checkout = ($component_assets->assigned_qty - (int) $request->input('checkin_qty', 1));
// We have to modify the record to reflect the new qty that's
// actually checked out.
@@ -381,10 +382,9 @@ class ComponentsController extends Controller
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('success', null, trans('admin/components/message.checkin.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No matching checkouts for that component join record'));
}
}

View File

@@ -5,16 +5,15 @@ namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedOut;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\StoreConsumableRequest;
use App\Http\Transformers\ConsumablesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Company;
use App\Models\Consumable;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ConsumablesController extends Controller
{
@@ -22,9 +21,10 @@ class ConsumablesController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function index(Request $request) : array
public function index(Request $request): array
{
$this->authorize('index', Consumable::class);
@@ -59,7 +59,6 @@ class ConsumablesController extends Controller
'manufacturer',
];
$filter = [];
if ($request->filled('filter')) {
@@ -77,7 +76,6 @@ class ConsumablesController extends Controller
$consumables->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$consumables->where('name', '=', $request->input('name'));
}
@@ -95,7 +93,7 @@ class ConsumablesController extends Controller
}
if ($request->filled('model_number')) {
$consumables->where('model_number','=',$request->input('model_number'));
$consumables->where('model_number', '=', $request->input('model_number'));
}
if ($request->filled('manufacturer_id')) {
@@ -107,14 +105,13 @@ class ConsumablesController extends Controller
}
if ($request->filled('location_id')) {
$consumables->where('location_id','=',$request->input('location_id'));
$consumables->where('location_id', '=', $request->input('location_id'));
}
if ($request->filled('notes')) {
$consumables->where('notes','=',$request->input('notes'));
$consumables->where('notes', '=', $request->input('notes'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $consumables->count()) ? $consumables->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -158,10 +155,12 @@ class ConsumablesController extends Controller
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param ImageUploadRequest $request
*/
public function store(StoreConsumableRequest $request) : JsonResponse
public function store(StoreConsumableRequest $request): JsonResponse
{
$this->authorize('create', Consumable::class);
$consumable = new Consumable;
@@ -179,9 +178,10 @@ class ConsumablesController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $id
*
* @param int $id
*/
public function show($id) : array
public function show($id): array
{
$this->authorize('view', Consumable::class);
$consumable = Consumable::with('users')->findOrFail($id);
@@ -193,17 +193,19 @@ class ConsumablesController extends Controller
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
* @param int $id
*
* @param ImageUploadRequest $request
* @param int $id
*/
public function update(StoreConsumableRequest $request, $id) : JsonResponse
public function update(StoreConsumableRequest $request, $id): JsonResponse
{
$this->authorize('update', Consumable::class);
$consumable = Consumable::findOrFail($id);
$consumable->fill($request->all());
$consumable = $request->handleImages($consumable);
if ($consumable->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $consumable, trans('admin/consumables/message.update.success')));
}
@@ -215,10 +217,12 @@ class ConsumablesController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param int $id
*
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$this->authorize('delete', Consumable::class);
$consumable = Consumable::findOrFail($id);
@@ -229,22 +233,22 @@ class ConsumablesController extends Controller
}
/**
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0]
*
* @param int $consumableId
*/
public function getDataView($consumableId) : array
public function getDataView($consumableId): array
{
$consumable = Consumable::with(['consumableAssignments'=> function ($query) {
$consumable = Consumable::with(['consumableAssignments' => function ($query) {
$query->orderBy($query->getModel()->getTable().'.created_at', 'DESC');
},
'consumableAssignments.adminuser'=> function ($query) {
},
'consumableAssignments.user'=> function ($query) {
},
'consumableAssignments.adminuser' => function ($query) {},
'consumableAssignments.user' => function ($query) {},
])->find($consumableId);
if (! Company::isCurrentUserHasAccess($consumable)) {
@@ -258,13 +262,13 @@ class ConsumablesController extends Controller
'avatar' => ($consumable_assignment->user) ? e($consumable_assignment->user->present()->gravatar) : '',
'user' => ($consumable_assignment->user) ? [
'id' => (int) $consumable_assignment->user->id,
'name'=> e($consumable_assignment->user->display_name),
'name' => e($consumable_assignment->user->display_name),
] : null,
'created_at' => Helper::getFormattedDateObject($consumable_assignment->created_at, 'datetime'),
'note' => ($consumable_assignment->note) ? e($consumable_assignment->note) : null,
'created_by' => ($consumable_assignment->adminuser) ? [
'id' => (int) $consumable_assignment->adminuser->id,
'name'=> e($consumable_assignment->adminuser->display_name),
'name' => e($consumable_assignment->adminuser->display_name),
] : null,
];
}
@@ -279,13 +283,15 @@ class ConsumablesController extends Controller
* Checkout a consumable
*
* @author [A. Gutierrez] [<andres@baller.tv>]
* @param int $id
*
* @param int $id
*
* @since [v4.9.5]
*/
public function checkout(Request $request, $id) : JsonResponse
public function checkout(Request $request, $id): JsonResponse
{
// Check if the consumable exists
if (!$consumable = Consumable::with('users')->find($id)) {
if (! $consumable = Consumable::with('users')->find($id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/consumables/message.does_not_exist')));
}
@@ -299,19 +305,17 @@ class ConsumablesController extends Controller
}
// Make sure there is a valid category
if (!$consumable->category){
if (! $consumable->category) {
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() ])));
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'))) {
if (! $user = User::find($request->input('assigned_to'))) {
// Return error message
return response()->json(Helper::formatStandardApiResponse('error', null, 'No user found'));
}
@@ -344,11 +348,11 @@ class ConsumablesController extends Controller
}
/**
* Gets a paginated collection for the select2 menus
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
public function selectlist(Request $request) : array
* Gets a paginated collection for the select2 menus
*
* @see SelectlistTransformer
*/
public function selectlist(Request $request): array
{
$consumables = Consumable::select([
'consumables.id',

View File

@@ -7,9 +7,9 @@ use App\Http\Controllers\Controller;
use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomField;
use App\Models\CustomFieldset;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\JsonResponse;
class CustomFieldsController extends Controller
{
@@ -17,11 +17,12 @@ class CustomFieldsController extends Controller
* Reorder the custom fields within a fieldset
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
*
* @param int $id
*
* @since [v3.0]
* @return array
*/
public function index() : array
public function index(): array
{
$this->authorize('index', CustomField::class);
$fields = CustomField::get();
@@ -31,11 +32,14 @@ class CustomFieldsController extends Controller
/**
* Shows the given field
*
* @author [V. Cordes] [<volker@fdatek.de>]
* @param int $id
*
* @param int $id
*
* @since [v4.1.10]
*/
public function show($id) : JsonResponse | array
public function show($id): JsonResponse|array
{
$this->authorize('view', CustomField::class);
if ($field = CustomField::find($id)) {
@@ -49,11 +53,12 @@ class CustomFieldsController extends Controller
* Update the specified field
*
* @author [V. Cordes] [<volker@fdatek.de>]
*
* @since [v4.1.10]
* @param \Illuminate\Http\Request $request
*
* @param int $id
*/
public function update(Request $request, $id) : JsonResponse
public function update(Request $request, $id): JsonResponse
{
$this->authorize('update', CustomField::class);
$field = CustomField::findOrFail($id);
@@ -61,6 +66,7 @@ class CustomFieldsController extends Controller
/**
* Updated values for the field,
* without the "field_encrypted" flag, preventing the change of encryption status
*
* @var array
*/
$data = $request->except(['field_encrypted']);
@@ -83,10 +89,10 @@ class CustomFieldsController extends Controller
* Store a newly created field.
*
* @author [V. Cordes] [<volker@fdatek.de>]
*
* @since [v4.1.10]
* @param \Illuminate\Http\Request $request
*/
public function store(Request $request) : JsonResponse
public function store(Request $request): JsonResponse
{
$this->authorize('create', CustomField::class);
$field = new CustomField;
@@ -134,7 +140,7 @@ class CustomFieldsController extends Controller
return $fieldset->fields()->sync($fields);
}
public function associate(Request $request, $field_id) : JsonResponse
public function associate(Request $request, $field_id): JsonResponse
{
$this->authorize('update', CustomFieldset::class);
@@ -153,7 +159,7 @@ class CustomFieldsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', $fieldset, trans('admin/custom_fields/message.fieldset.update.success')));
}
public function disassociate(Request $request, $field_id) : JsonResponse
public function disassociate(Request $request, $field_id): JsonResponse
{
$this->authorize('update', CustomFieldset::class);
$field = CustomField::findOrFail($field_id);
@@ -175,9 +181,10 @@ class CustomFieldsController extends Controller
* Delete a custom field.
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
*
* @since [v1.8]
*/
public function destroy($field_id) : JsonResponse
public function destroy($field_id): JsonResponse
{
$field = CustomField::findOrFail($field_id);

View File

@@ -6,10 +6,10 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\CustomFieldsetsTransformer;
use App\Http\Transformers\CustomFieldsTransformer;
use App\Models\CustomFieldset;
use App\Models\CustomField;
use Illuminate\Http\Request;
use App\Models\CustomFieldset;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* This controller handles all actions related to Custom Asset Fieldsets for
@@ -17,7 +17,9 @@ use Illuminate\Http\JsonResponse;
*
* @todo Improve documentation here.
* @todo Check for raw DB queries and try to convert them to query builder statements
*
* @version v2.0
*
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
* @author [Josh Gibson]
*/
@@ -25,12 +27,15 @@ class CustomFieldsetsController extends Controller
{
/**
* Shows the given fieldset and its fields
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @author [Josh Gibson]
* @param int $id
*
* @param int $id
*
* @since [v1.8]
*/
public function index() : array
public function index(): array
{
$this->authorize('index', CustomField::class);
$fieldsets = CustomFieldset::withCount('fields as fields_count', 'models as models_count')->get();
@@ -40,12 +45,15 @@ class CustomFieldsetsController extends Controller
/**
* Shows the given fieldset and its fields
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @author [Josh Gibson]
* @param int $id
*
* @param int $id
*
* @since [v1.8]
*/
public function show($id) : JsonResponse | array
public function show($id): JsonResponse|array
{
$this->authorize('view', CustomField::class);
if ($fieldset = CustomFieldset::find($id)) {
@@ -59,11 +67,12 @@ class CustomFieldsetsController extends Controller
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \Illuminate\Http\Request $request
*
* @param int $id
*/
public function update(Request $request, $id) : JsonResponse
public function update(Request $request, $id): JsonResponse
{
$this->authorize('update', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
@@ -80,10 +89,10 @@ class CustomFieldsetsController extends Controller
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \Illuminate\Http\Request $request
*/
public function store(Request $request) : JsonResponse
public function store(Request $request): JsonResponse
{
$this->authorize('create', CustomField::class);
$fieldset = new CustomFieldset;
@@ -112,9 +121,10 @@ class CustomFieldsetsController extends Controller
* Delete a custom fieldset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$this->authorize('delete', CustomField::class);
$fieldset = CustomFieldset::findOrFail($id);
@@ -137,11 +147,13 @@ class CustomFieldsetsController extends Controller
* Return JSON containing a list of fields belonging to a fieldset.
*
* @author [V. Cordes] [<volker@fdatek.de>]
*
* @since [v4.1.10]
* @param $fieldsetId
*
* @param $fieldsetId
* @return string JSON
*/
public function fields($id) : array
public function fields($id): array
{
$this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($id);
@@ -154,15 +166,14 @@ class CustomFieldsetsController extends Controller
* Return JSON containing a list of fields belonging to a fieldset with the
* default values for a given model
*
* @param $modelId
* @param $fieldsetId
* @return string JSON
*/
public function fieldsWithDefaultValues($fieldsetId, $modelId) : array
public function fieldsWithDefaultValues($fieldsetId, $modelId): array
{
$this->authorize('view', CustomField::class);
$set = CustomFieldset::findOrFail($fieldsetId);
$fields = $set->fields;
return (new CustomFieldsTransformer)->transformCustomFieldsWithDefaultValues($fields, $modelId, $fields->count());
}
}

View File

@@ -4,14 +4,14 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Requests\StoreDepartmentRequest;
use App\Http\Transformers\DepartmentsTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Department;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class DepartmentsController extends Controller
{
@@ -19,9 +19,10 @@ class DepartmentsController extends Controller
* Display a listing of the resource.
*
* @author [Godfrey Martinez] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', Department::class);
$allowed_columns = ['id', 'name', 'image', 'users_count', 'notes', 'tag_color'];
@@ -39,7 +40,7 @@ class DepartmentsController extends Controller
'departments.updated_at',
'departments.image',
'departments.tag_color',
'departments.notes'
'departments.notes',
])->with('location')->with('manager')->with('company')->withCount('users as users_count');
if ($request->filled('search')) {
@@ -90,6 +91,7 @@ class DepartmentsController extends Controller
$total = $departments->count();
$departments = $departments->skip($offset)->take($limit)->get();
return (new DepartmentsTransformer)->transformDepartments($departments, $total);
}
@@ -98,8 +100,10 @@ class DepartmentsController extends Controller
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param ImageUploadRequest $request
*/
public function store(StoreDepartmentRequest $request): JsonResponse
{
@@ -113,6 +117,7 @@ class DepartmentsController extends Controller
if ($department->save()) {
return response()->json(Helper::formatStandardApiResponse('success', (new DepartmentsTransformer)->transformDepartment($department), trans('admin/departments/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
}
@@ -121,13 +126,16 @@ class DepartmentsController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function show($id) : array
public function show($id): array
{
$this->authorize('view', Department::class);
$department = Department::withCount('users as users_count')->findOrFail($id);
return (new DepartmentsTransformer)->transformDepartment($department);
}
@@ -135,11 +143,12 @@ class DepartmentsController extends Controller
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v5.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param int $id
*/
public function update(ImageUploadRequest $request, $id) : JsonResponse
public function update(ImageUploadRequest $request, $id): JsonResponse
{
$this->authorize('update', Department::class);
$department = Department::findOrFail($id);
@@ -153,15 +162,16 @@ class DepartmentsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $department->getErrors()));
}
/**
* Validates and deletes selected department.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $locationId
*
* @param int $locationId
*
* @since [v4.0]
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$department = Department::findOrFail($id);
@@ -172,6 +182,7 @@ class DepartmentsController extends Controller
}
$department->delete();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/departments/message.delete.success')));
}
@@ -180,10 +191,11 @@ class DepartmentsController extends Controller
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
* @see SelectlistTransformer
*/
public function selectlist(Request $request) : array
public function selectlist(Request $request): array
{
$this->authorize('view.selectlists');

View File

@@ -6,8 +6,8 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\DepreciationsTransformer;
use App\Models\Depreciation;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class DepreciationsController extends Controller
{
@@ -15,9 +15,10 @@ class DepreciationsController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', Depreciation::class);
$allowed_columns = [
@@ -32,7 +33,7 @@ class DepreciationsController extends Controller
'licenses_count',
];
$depreciations = Depreciation::select('id','name','months','depreciation_min','depreciation_type','created_at','updated_at', 'created_by')
$depreciations = Depreciation::select('id', 'name', 'months', 'depreciation_min', 'depreciation_type', 'created_at', 'updated_at', 'created_by')
->with('adminuser')
->withCount('assets as assets_count')
->withCount('models as models_count')
@@ -46,7 +47,7 @@ class DepreciationsController extends Controller
$offset = ($request->input('offset') > $depreciations->count()) ? $depreciations->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) {
@@ -68,10 +69,10 @@ class DepreciationsController extends Controller
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \Illuminate\Http\Request $request
*/
public function store(Request $request) : JsonResponse
public function store(Request $request): JsonResponse
{
$this->authorize('create', Depreciation::class);
$depreciation = new Depreciation;
@@ -88,10 +89,12 @@ class DepreciationsController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function show($id) : JsonResponse | array
public function show($id): JsonResponse|array
{
$this->authorize('view', Depreciation::class);
$depreciation = Depreciation::findOrFail($id);
@@ -103,11 +106,12 @@ class DepreciationsController extends Controller
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \Illuminate\Http\Request $request
*
* @param int $id
*/
public function update(Request $request, $id) : JsonResponse
public function update(Request $request, $id): JsonResponse
{
$this->authorize('update', Depreciation::class);
$depreciation = Depreciation::findOrFail($id);
@@ -124,10 +128,12 @@ class DepreciationsController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$this->authorize('delete', Depreciation::class);
$depreciation = Depreciation::withCount('models as models_count')->findOrFail($id);

View File

@@ -6,9 +6,8 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\GroupsTransformer;
use App\Models\Group;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class GroupsController extends Controller
{
@@ -16,9 +15,10 @@ class GroupsController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('superadmin');
@@ -34,7 +34,6 @@ class GroupsController extends Controller
$groups->where('name', '=', $request->input('name'));
}
$offset = ($request->input('offset') > $groups->count()) ? $groups->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
@@ -69,10 +68,10 @@ class GroupsController extends Controller
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \Illuminate\Http\Request $request
*/
public function store(Request $request) : JsonResponse
public function store(Request $request): JsonResponse
{
$this->authorize('superadmin');
$group = new Group;
@@ -96,13 +95,16 @@ class GroupsController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function show($id) : array
public function show($id): array
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);
return (new GroupsTransformer)->transformGroup($group);
}
@@ -110,11 +112,12 @@ class GroupsController extends Controller
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \Illuminate\Http\Request $request
*
* @param int $id
*/
public function update(Request $request, $id) : JsonResponse
public function update(Request $request, $id): JsonResponse
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);
@@ -134,10 +137,12 @@ class GroupsController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$this->authorize('superadmin');
$group = Group::findOrFail($id);

View File

@@ -6,12 +6,12 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ItemImportRequest;
use App\Http\Transformers\ImportsTransformer;
use App\Models\Asset;
use App\Models\Company;
use App\Models\Import;
use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Database\Eloquent\JsonEncodingException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
@@ -19,19 +19,17 @@ use Illuminate\Support\Str;
use League\Csv\Reader;
use Onnov\DetectEncoding\EncodingDetector;
use Symfony\Component\HttpFoundation\File\Exception\FileException;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\JsonResponse;
class ImportController extends Controller
{
/**
* Display a listing of the resource.
*
*/
public function index() : JsonResponse | array
public function index(): JsonResponse|array
{
$this->authorize('import');
$imports = Import::with('adminuser')->latest()->get();
return (new ImportsTransformer)->transformImports($imports);
}
@@ -40,7 +38,7 @@ class ImportController extends Controller
*
* @param \Illuminate\Http\Request $request
*/
public function store() : JsonResponse
public function store(): JsonResponse
{
$this->authorize('import');
if (! config('app.lock_passwords')) {
@@ -48,7 +46,7 @@ class ImportController extends Controller
$path = config('app.private_uploads').'/imports';
$results = [];
$import = new Import;
$detector = new EncodingDetector();
$detector = new EncodingDetector;
foreach ($files as $file) {
if (! in_array($file->getMimeType(), [
@@ -60,15 +58,16 @@ class ImportController extends Controller
'text/comma-separated-values',
'text/tsv', ])) {
$results['error'] = 'File type must be CSV. Uploaded file is '.$file->getMimeType();
return response()->json(Helper::formatStandardApiResponse('error', null, $results['error']), 422);
}
//TODO: is there a lighter way to do this?
// TODO: is there a lighter way to do this?
if (! ini_get('auto_detect_line_endings')) {
ini_set('auto_detect_line_endings', '1');
}
if (function_exists('iconv')) {
$file_contents = $file->getContent(); //TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$file_contents = $file->getContent(); // TODO - this *does* load the whole file in RAM, but we need that to be able to 'iconv' it?
$encoding = $detector->getEncoding($file_contents);
\Log::debug("Discovered encoding: $encoding in uploaded CSV");
$reader = null;
@@ -77,12 +76,13 @@ class ImportController extends Controller
try {
$transliterated = iconv(strtoupper($encoding), 'UTF-8', $file_contents);
} catch (\Exception $e) {
$transliterated = false; //blank out the partially-decoded string
$transliterated = false; // blank out the partially-decoded string
return response()->json(
Helper::formatStandardApiResponse(
'error',
null,
trans('admin/hardware/message.import.transliterate_failure', ["encoding" => $encoding])
trans('admin/hardware/message.import.transliterate_failure', ['encoding' => $encoding])
),
422
);
@@ -90,18 +90,18 @@ class ImportController extends Controller
if ($transliterated !== false) {
$tmpname = tempnam(sys_get_temp_dir(), '');
$tmpresults = file_put_contents($tmpname, $transliterated);
$transliterated = null; //save on memory?
$transliterated = null; // save on memory?
if ($tmpresults !== false) {
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); //WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
$newfile = new UploadedFile($tmpname, $file->getClientOriginalName(), null, null, true); // WARNING: this is enabling 'test mode' - which is gross, but otherwise the file won't be treated as 'uploaded'
if ($newfile->isValid()) {
$file = $newfile;
}
}
}
}
$file_contents = null; //try to save on memory, I guess?
$file_contents = null; // try to save on memory, I guess?
}
$reader = Reader::createFromFileObject($file->openFile('r')); //file pointer leak?
$reader = Reader::createFromFileObject($file->openFile('r')); // file pointer leak?
try {
$import->header_row = $reader->nth(0);
@@ -116,7 +116,7 @@ class ImportController extends Controller
);
}
//duplicate headers check
// duplicate headers check
$duplicate_headers = [];
for ($i = 0; $i < count($import->header_row); $i++) {
@@ -124,15 +124,15 @@ class ImportController extends Controller
if (in_array($header, $import->header_row)) {
$found_at = array_search($header, $import->header_row);
if ($i > $found_at) {
//avoid reporting duplicates twice, e.g. "1 is same as 17! 17 is same as 1!!!"
//as well as "1 is same as 1!!!" (which is always true)
//has to be > because otherwise the first result of array_search will always be $i itself(!)
// avoid reporting duplicates twice, e.g. "1 is same as 17! 17 is same as 1!!!"
// as well as "1 is same as 1!!!" (which is always true)
// has to be > because otherwise the first result of array_search will always be $i itself(!)
array_push($duplicate_headers, "Duplicate header '$header' detected, first at column: ".($found_at + 1).', repeats at column: '.($i + 1));
}
}
}
if (count($duplicate_headers) > 0) {
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)),422);
return response()->json(Helper::formatStandardApiResponse('error', null, implode('; ', $duplicate_headers)), 422);
}
try {
@@ -167,7 +167,7 @@ class ImportController extends Controller
$import->file_path = $file_name;
$import->filesize = null;
if (!file_exists($path.'/'.$file_name)) {
if (! file_exists($path.'/'.$file_name)) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.file_not_found')), 500);
}
@@ -191,7 +191,7 @@ class ImportController extends Controller
*
* @param int $import_id
*/
public function process(ItemImportRequest $request, $import_id) : JsonResponse
public function process(ItemImportRequest $request, $import_id): JsonResponse
{
$this->authorize('import');
@@ -205,8 +205,9 @@ class ImportController extends Controller
$import = Import::find($import_id);
if(is_null($import)){
$error[0][0] = trans("validation.exists", ["attribute" => "file"]);
if (is_null($import)) {
$error[0][0] = trans('validation.exists', ['attribute' => 'file']);
return response()->json(Helper::formatStandardApiResponse('import-errors', null, $error), 500);
}
@@ -259,10 +260,10 @@ class ImportController extends Controller
break;
}
if ($errors) { //Failure
if ($errors) { // Failure
return response()->json(Helper::formatStandardApiResponse('import-errors', null, $errors), 500);
}
//Flash message before the redirect
// Flash message before the redirect
Session::flash('success', trans('admin/hardware/message.import.success'));
if (auth()->user()->can('view', $model_perms)) {
@@ -277,18 +278,16 @@ class ImportController extends Controller
*
* @param int $import_id
*/
public function destroy($import_id) : JsonResponse
public function destroy($import_id): JsonResponse
{
$this->authorize('import');
if ($import = Import::find($import_id)) {
if ((auth()->user()->id != $import->created_by) && (!auth()->user()->isSuperUser())) {
if ((auth()->user()->id != $import->created_by) && (! auth()->user()->isSuperUser())) {
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
try {
// Try to delete the file
Storage::delete('imports/'.$import->file_path);
@@ -303,8 +302,7 @@ class ImportController extends Controller
}
}
return response()->json(Helper::formatStandardApiResponse('warning', null, trans('admin/hardware/message.import.file_not_deleted_warning')));
}
}

View File

@@ -6,9 +6,9 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Transformers\LabelsTransformer;
use App\Models\Labels\Label;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\ItemNotFoundException;
use Illuminate\Http\JsonResponse;
class LabelsController extends Controller
{
@@ -17,7 +17,7 @@ class LabelsController extends Controller
*
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', Label::class);
@@ -38,7 +38,7 @@ class LabelsController extends Controller
$maxLimit = config('app.max_results');
$limit = $request->input('limit', $maxLimit);
$limit = ($limit > $maxLimit) ? $maxLimit : $limit;
$labels = $labels->skip($offset)->take($limit);
return (new LabelsTransformer)->transformLabels($labels, $total, $request);
@@ -48,22 +48,21 @@ class LabelsController extends Controller
* Returns JSON with information about a label for detail view.
*
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
* @param string $labelName
*/
public function show(string $labelName) : JsonResponse | array
public function show(string $labelName): JsonResponse|array
{
$labelName = str_replace('/', '\\', $labelName);
try {
$label = Label::find($labelName);
} catch(ItemNotFoundException $e) {
} catch (ItemNotFoundException $e) {
return response()
->json(
Helper::formatStandardApiResponse('error', null, trans('admin/labels/message.does_not_exist')),
Helper::formatStandardApiResponse('error', null, trans('admin/labels/message.does_not_exist')),
404
);
}
$this->authorize('view', $label);
return (new LabelsTransformer)->transformLabel($label);
}
}

View File

@@ -17,16 +17,15 @@ class LicenseSeatsController extends Controller
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @param int $licenseId
*/
public function index(Request $request, $licenseId) : JsonResponse | array
public function index(Request $request, $licenseId): JsonResponse|array
{
if ($license = License::find($licenseId)) {
$this->authorize('view', $license);
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department', 'user.company', 'asset.company')
$seats = LicenseSeat::with('license', 'user', 'asset', 'user.department', 'user.company', 'asset.company')
->where('license_seats.license_id', $licenseId);
if ($request->input('status') == 'available') {
@@ -37,13 +36,16 @@ class LicenseSeatsController extends Controller
$seats->ByAssigned();
}
if ($request->filled('search')) {
$seats->TextSearch($request->input('search'));
}
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
if ($request->input('sort') == 'assigned_user.department') {
$seats->OrderDepartments($order);
} elseif ($request->input('sort') == 'assigned_user.company') {
$seats->OrderCompany($order);
$seats->OrderCompany($order);
} else {
$seats->orderBy('updated_at', $order);
}
@@ -53,7 +55,7 @@ class LicenseSeatsController extends Controller
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $seats->count()) ? $seats->count() : app('api_offset_value');
if ($offset >= $total ){
if ($offset >= $total) {
$offset = 0;
}
@@ -75,7 +77,7 @@ class LicenseSeatsController extends Controller
* @param int $licenseId
* @param int $seatId
*/
public function show($licenseId, $seatId) : JsonResponse | array
public function show($licenseId, $seatId): JsonResponse|array
{
$this->authorize('view', License::class);
@@ -86,17 +88,15 @@ class LicenseSeatsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat ID or license not found or the seat does not belong to this license'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $licenseId
* @param int $seatId
*/
public function update(Request $request, $licenseId, $seatId) : JsonResponse | array
public function update(Request $request, $licenseId, $seatId): JsonResponse|array
{
$validated = $this->validate($request, [
'assigned_to' => [
@@ -106,7 +106,7 @@ class LicenseSeatsController extends Controller
'prohibits:asset_id',
// must be a valid user or null to unassign
function ($attribute, $value, $fail) {
if (!is_null($value) && !User::where('id', $value)->whereNull('deleted_at')->exists()) {
if (! is_null($value) && ! User::where('id', $value)->whereNull('deleted_at')->exists()) {
$fail('The selected assigned_to is invalid.');
}
},
@@ -118,7 +118,7 @@ class LicenseSeatsController extends Controller
'prohibits:assigned_to',
// must be a valid asset or null to unassign
function ($attribute, $value, $fail) {
if (!is_null($value) && !Asset::where('id', $value)->whereNull('deleted_at')->exists()) {
if (! is_null($value) && ! Asset::where('id', $value)->whereNull('deleted_at')->exists()) {
$fail('The selected asset_id is invalid.');
}
},
@@ -130,12 +130,12 @@ class LicenseSeatsController extends Controller
$licenseSeat = LicenseSeat::with(['license', 'asset', 'user'])->find($seatId);
if (!$licenseSeat) {
if (! $licenseSeat) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat not found'));
}
$license = $licenseSeat->license;
if (!$license || $license->id != intval($licenseId)) {
if (! $license || $license->id != intval($licenseId)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Seat does not belong to the specified license'));
}
@@ -155,7 +155,7 @@ class LicenseSeatsController extends Controller
Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success'))
);
}
if( $assignmentTouched && $licenseSeat->unreassignable_seat) {
if ($assignmentTouched && $licenseSeat->unreassignable_seat) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/licenses/message.checkout.unavailable')));
}
@@ -173,18 +173,18 @@ class LicenseSeatsController extends Controller
$target = $is_checkin ? $oldAsset : Asset::find($licenseSeat->asset_id);
}
if ($assignmentTouched && is_null($target)){
if ($assignmentTouched && is_null($target)) {
// if both asset_id and assigned_to are null then we are "checking-in"
// a related model that does not exist (possible purged or bad data).
if (!is_null($request->input('asset_id')) || !is_null($request->input('assigned_to'))) {
if (! is_null($request->input('asset_id')) || ! is_null($request->input('assigned_to'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Target not found'));
}
}
if ($licenseSeat->save()) {
if($assignmentTouched) {
if ($assignmentTouched) {
if ($is_checkin) {
if (!$licenseSeat->license->reassignable) {
if (! $licenseSeat->license->reassignable) {
$licenseSeat->unreassignable_seat = true;
$licenseSeat->save();
}
@@ -195,6 +195,7 @@ class LicenseSeatsController extends Controller
$licenseSeat->logCheckout($request->input('notes'), $target);
}
}
return response()->json(Helper::formatStandardApiResponse('success', $licenseSeat, trans('admin/licenses/message.update.success')));
}

View File

@@ -8,9 +8,9 @@ use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\License;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\JsonResponse;
class LicensesController extends Controller
{
@@ -18,21 +18,21 @@ class LicensesController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0]
*
* @since [v4.0]
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', License::class);
$licenses = License::with('company', 'manufacturer', 'supplier','category', 'adminuser')->withCount('freeSeats as free_seats_count');
$licenses = License::with('company', 'manufacturer', 'supplier', 'category', 'adminuser')->withCount('freeSeats as free_seats_count');
$settings = Setting::getSettings();
if ($request->input('status')=='inactive') {
if ($request->input('status') == 'inactive') {
$licenses->ExpiredLicenses();
} elseif ($request->input('status')=='expiring') {
} elseif ($request->input('status') == 'expiring') {
$licenses->ExpiringLicenses($settings->alert_interval);
} elseif ($request->input('status')=='active') {
} elseif ($request->input('status') == 'active') {
$licenses->ActiveLicenses();
}
@@ -84,15 +84,15 @@ class LicensesController extends Controller
$licenses->where('created_by', '=', $request->input('created_by'));
}
if (($request->filled('maintained')) && ($request->input('maintained')=='true')) {
$licenses->where('maintained','=',1);
} elseif (($request->filled('maintained')) && ($request->input('maintained')=='false')) {
$licenses->where('maintained','=',0);
if (($request->filled('maintained')) && ($request->input('maintained') == 'true')) {
$licenses->where('maintained', '=', 1);
} elseif (($request->filled('maintained')) && ($request->input('maintained') == 'false')) {
$licenses->where('maintained', '=', 0);
}
if (($request->filled('expires')) && ($request->input('expires')=='true')) {
if (($request->filled('expires')) && ($request->input('expires') == 'true')) {
$licenses->whereNotNull('expiration_date');
} elseif (($request->filled('expires')) && ($request->input('expires')=='false')) {
} elseif (($request->filled('expires')) && ($request->input('expires') == 'false')) {
$licenses->whereNull('expiration_date');
}
@@ -100,12 +100,10 @@ class LicensesController extends Controller
$licenses = $licenses->TextSearch($request->input('search'));
}
if ($request->input('deleted')=='true') {
if ($request->input('deleted') == 'true') {
$licenses->onlyTrashed();
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $licenses->count()) ? $licenses->count() : app('api_offset_value');
$limit = app('api_limit_value');
@@ -113,8 +111,8 @@ class LicensesController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
switch ($request->input('sort')) {
case 'manufacturer':
$licenses = $licenses->leftJoin('manufacturers', 'licenses.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order);
case 'manufacturer':
$licenses = $licenses->leftJoin('manufacturers', 'licenses.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order);
break;
case 'supplier':
$licenses = $licenses->leftJoin('suppliers', 'licenses.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
@@ -161,6 +159,7 @@ class LicensesController extends Controller
$total = $licenses->count();
$licenses = $licenses->skip($offset)->take($limit)->get();
return (new LicensesTransformer)->transformLicenses($licenses, $total);
}
@@ -169,10 +168,10 @@ class LicensesController extends Controller
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \Illuminate\Http\Request $request
*/
public function store(Request $request) : JsonResponse
public function store(Request $request): JsonResponse
{
$this->authorize('create', License::class);
$license = new License;
@@ -189,9 +188,10 @@ class LicensesController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @param int $id
*/
public function show($id) : JsonResponse | array
public function show($id): JsonResponse|array
{
$this->authorize('view', License::class);
$license = License::withCount('freeSeats as free_seats_count')->findOrFail($id);
@@ -204,11 +204,12 @@ class LicensesController extends Controller
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \Illuminate\Http\Request $request
*
* @param int $id
*/
public function update(Request $request, $id) : JsonResponse | array
public function update(Request $request, $id): JsonResponse|array
{
//
$this->authorize('update', License::class);
@@ -227,10 +228,12 @@ class LicensesController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$license = License::findOrFail($id);
$this->authorize('delete', $license);
@@ -255,9 +258,9 @@ class LicensesController extends Controller
/**
* Gets a paginated collection for the select2 menus
*
* @see \App\Http\Transformers\SelectlistTransformer
* @see SelectlistTransformer
*/
public function selectlist(Request $request) : array
public function selectlist(Request $request): array
{
$licenses = License::select([
'licenses.id',

View File

@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\AccessoriesTransformer;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer;
@@ -17,6 +16,7 @@ use App\Models\Location;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
@@ -26,10 +26,12 @@ class LocationsController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @return \Illuminate\Http\Response
*
* @return Response
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', Location::class);
$allowed_columns = [
@@ -61,7 +63,7 @@ class LocationsController extends Controller
'zip',
'tag_color',
'notes',
];
];
$locations = Location::with('parent', 'manager', 'children')->select([
'locations.id',
@@ -159,8 +161,6 @@ class LocationsController extends Controller
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
switch ($request->input('sort')) {
case 'parent':
$locations->OrderParent($order);
@@ -176,22 +176,20 @@ class LocationsController extends Controller
break;
}
$total = $locations->count();
$locations = $locations->skip($offset)->take($limit)->get();
return (new LocationsTransformer)->transformLocations($locations, $total);
}
/**
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*/
public function store(ImageUploadRequest $request) : JsonResponse
public function store(ImageUploadRequest $request): JsonResponse
{
$this->authorize('create', Location::class);
$location = new Location;
@@ -204,7 +202,7 @@ class LocationsController extends Controller
// check if parent is set and has a different company
if ($location->parent_id && Location::find($location->parent_id)->company_id != $location->company_id) {
response()->json(Helper::formatStandardApiResponse('error', null, 'different company than parent'));
}
}
}
if ($location->save()) {
@@ -218,10 +216,12 @@ class LocationsController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function show($id) : JsonResponse | array
public function show($id): JsonResponse|array
{
$this->authorize('view', Location::class);
$location = Location::with('parent', 'manager', 'children', 'company')
@@ -258,16 +258,16 @@ class LocationsController extends Controller
return (new LocationsTransformer)->transformLocation($location);
}
/**
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param int $id
*/
public function update(ImageUploadRequest $request, $id) : JsonResponse
public function update(ImageUploadRequest $request, $id): JsonResponse
{
$this->authorize('update', Location::class);
$location = Location::findOrFail($id);
@@ -282,7 +282,7 @@ class LocationsController extends Controller
// check if there are related objects with different company
if (Helper::test_locations_fmcs(false, $id, $location->company_id)) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'error scoped locations'));
}
}
} else {
$location->company_id = $request->input('company_id');
}
@@ -291,6 +291,7 @@ class LocationsController extends Controller
if ($location->isValid()) {
$location->save();
return response()->json(
Helper::formatStandardApiResponse(
'success',
@@ -303,26 +304,27 @@ class LocationsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $location->getErrors()));
}
public function assets(Request $request, Location $location) : JsonResponse | array
public function assets(Request $request, Location $location): JsonResponse|array
{
$this->authorize('view', Asset::class);
$this->authorize('view', $location);
$assets = Asset::where('location_id', '=', $location->id)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc');
$assets = $assets->get();
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
}
public function assignedAssets(Request $request, Location $location) : JsonResponse | array
public function assignedAssets(Request $request, Location $location): JsonResponse|array
{
$this->authorize('view', Asset::class);
$this->authorize('view', $location);
$assets = Asset::where('assigned_to', '=', $location->id)->where('assigned_type', '=', Location::class)->with('model', 'model.category', 'assetstatus', 'location', 'company', 'defaultLoc');
$assets = $assets->get();
return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
}
public function assignedAccessories(Request $request, Location $location) : JsonResponse | array
public function assignedAccessories(Request $request, Location $location): JsonResponse|array
{
$this->authorize('view', Accessory::class);
$this->authorize('view', $location);
@@ -333,6 +335,7 @@ class LocationsController extends Controller
$total = $accessory_checkouts->count();
$accessory_checkouts = $accessory_checkouts->skip($offset)->take($limit)->get();
return (new LocationsTransformer)->transformCheckedoutAccessories($accessory_checkouts, $total);
}
@@ -340,10 +343,12 @@ class LocationsController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function destroy($id) : JsonResponse
public function destroy($id): JsonResponse
{
$this->authorize('delete', Location::class);
$location = Location::withCount('assignedAssets as assigned_assets_count')
@@ -360,7 +365,7 @@ class LocationsController extends Controller
if (! $location->isDeletable()) {
return response()
->json(Helper::formatStandardApiResponse('error', null, trans('admin/locations/message.assoc_users')));
->json(Helper::formatStandardApiResponse('error', null, trans('admin/locations/message.assoc_users')));
}
$this->authorize('delete', $location);
$location->delete();
@@ -393,10 +398,11 @@ class LocationsController extends Controller
* sea... this time.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
* @see SelectlistTransformer
*/
public function selectlist(Request $request) : array
public function selectlist(Request $request): array
{
// If a user is in the process of editing their profile, as determined by the referrer,
// then we check that they have permission to edit their own location.

View File

@@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\MaintenancesTransformer;
use App\Models\Asset;
use App\Models\Maintenance;
use App\Models\Company;
use Illuminate\Http\Request;
use App\Models\Maintenance;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* This controller handles all actions related to Asset Maintenance for
@@ -20,21 +20,23 @@ use Illuminate\Http\JsonResponse;
*/
class MaintenancesController extends Controller
{
/**
* Generates the JSON response for asset maintenances listing view.
*
* @see MaintenancesController::getIndex() method that generates view
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
*
* @version v1.0
*
* @since [v1.8]
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', Asset::class);
$maintenances = Maintenance::select('maintenances.*')
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser');
->with('asset', 'asset.model', 'asset.location', 'asset.defaultLoc', 'supplier', 'asset.company', 'asset.assetstatus', 'adminuser');
if ($request->filled('search')) {
$maintenances = $maintenances->TextSearch($request->input('search'));
@@ -60,31 +62,30 @@ class MaintenancesController extends Controller
$maintenances->where('asset_maintenance_type', '=', $request->input('asset_maintenance_type'));
}
// Make sure the offset and limit are actually integers and do not exceed system limits
$offset = ($request->input('offset') > $maintenances->count()) ? $maintenances->count() : abs($request->input('offset'));
$limit = app('api_limit_value');
$allowed_columns = [
'id',
'name',
'asset_maintenance_time',
'asset_maintenance_type',
'cost',
'start_date',
'completion_date',
'notes',
'asset_tag',
'asset_name',
'serial',
'created_by',
'supplier',
'location',
'is_warranty',
'status_label',
'model',
'model_number',
];
'id',
'name',
'asset_maintenance_time',
'asset_maintenance_type',
'cost',
'start_date',
'completion_date',
'notes',
'asset_tag',
'asset_name',
'serial',
'created_by',
'supplier',
'location',
'is_warranty',
'status_label',
'model',
'model_number',
];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? e($request->input('sort')) : 'created_at';
@@ -124,26 +125,28 @@ class MaintenancesController extends Controller
$total = $maintenances->count();
$maintenances = $maintenances->skip($offset)->take($limit)->get();
return (new MaintenancesTransformer())->transformMaintenances($maintenances, $total);
return (new MaintenancesTransformer)->transformMaintenances($maintenances, $total);
}
/**
* Validates and stores the new asset maintenance
*
* @see MaintenancesController::getCreate() method for the form
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
*
* @version v1.0
*
* @since [v1.8]
*/
public function store(ImageUploadRequest $request) : JsonResponse | array
public function store(ImageUploadRequest $request): JsonResponse|array
{
$this->authorize('update', Asset::class);
// create a new model instance
$maintenance = new Maintenance();
$maintenance = new Maintenance;
$maintenance->fill($request->all());
$maintenance->created_by = auth()->id();
$maintenance = $request->handleImages($maintenance);
@@ -161,12 +164,15 @@ class MaintenancesController extends Controller
* Validates and stores an update to an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
* @param int $id
* @param int $request
*
* @param int $id
* @param int $request
*
* @version v1.0
*
* @since [v4.0]
*/
public function update(Request $request, $id) : JsonResponse | array
public function update(Request $request, $id): JsonResponse|array
{
$this->authorize('update', Asset::class);
@@ -178,7 +184,7 @@ class MaintenancesController extends Controller
}
// The asset this miantenance is attached to is not valid or has been deleted
if (!$maintenance->asset) {
if (! $maintenance->asset) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.item_not_found', ['item_type' => trans('general.asset'), 'id' => $id])));
}
@@ -199,11 +205,14 @@ class MaintenancesController extends Controller
* Delete an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
* @param int $maintenanceId
*
* @param int $maintenanceId
*
* @version v1.0
*
* @since [v4.0]
*/
public function destroy($maintenanceId) : JsonResponse | array
public function destroy($maintenanceId): JsonResponse|array
{
$this->authorize('update', Asset::class);
// Check if the asset maintenance exists
@@ -214,18 +223,20 @@ class MaintenancesController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', $maintenance, trans('admin/maintenances/message.delete.success')));
}
/**
* View an asset maintenance
*
* @author A. Gianotto <snipe@snipe.net>
* @param int $maintenanceId
*
* @param int $maintenanceId
*
* @version v1.0
*
* @since [v4.0]
*/
public function show($maintenanceId) : JsonResponse | array
public function show($maintenanceId): JsonResponse|array
{
$this->authorize('view', Asset::class);
$maintenance = Maintenance::findOrFail($maintenanceId);
@@ -233,7 +244,7 @@ class MaintenancesController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, 'You cannot view a maintenance for that asset'));
}
return (new MaintenancesTransformer())->transformMaintenance($maintenance);
return (new MaintenancesTransformer)->transformMaintenance($maintenance);
}
}

View File

@@ -3,22 +3,19 @@
namespace App\Http\Controllers\Api;
use App\Actions\Manufacturers\DeleteManufacturerAction;
use App\Exceptions\ItemStillHasAccessories;
use App\Exceptions\ItemStillHasAssets;
use App\Exceptions\ItemStillHasChildren;
use App\Exceptions\ItemStillHasComponents;
use App\Exceptions\ItemStillHasConsumables;
use App\Exceptions\ItemStillHasLicenses;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Transformers\ManufacturersTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Actionlog;
use App\Models\Manufacturer;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
class ManufacturersController extends Controller
{
@@ -26,13 +23,15 @@ class ManufacturersController extends Controller
* Display a listing of the resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @return \Illuminate\Http\Response
*
* @return Response
*/
public function index(Request $request) : JsonResponse | array
public function index(Request $request): JsonResponse|array
{
$this->authorize('view', Manufacturer::class);
$allowed_columns = [
$allowed_columns = [
'id',
'name',
'url',
@@ -52,21 +51,21 @@ class ManufacturersController extends Controller
];
$manufacturers = Manufacturer::select([
'id',
'name',
'url',
'support_url',
'warranty_lookup_url',
'support_email',
'support_phone',
'created_by',
'created_at',
'updated_at',
'image',
'deleted_at',
'tag_color',
'notes',
])
'id',
'name',
'url',
'support_url',
'warranty_lookup_url',
'support_email',
'support_phone',
'created_by',
'created_at',
'updated_at',
'image',
'deleted_at',
'tag_color',
'notes',
])
->with('adminuser')
->withCount('assets as assets_count')
->withCount('licenses as licenses_count')
@@ -86,8 +85,6 @@ class ManufacturersController extends Controller
$manufacturers = $manufacturers->TextSearch($request->input('search'));
}
if ($request->filled('name')) {
$manufacturers->where('name', '=', $request->input('name'));
}
@@ -120,7 +117,7 @@ class ManufacturersController extends Controller
$offset = ($request->input('offset') > $manufacturers->count()) ? $manufacturers->count() : app('api_offset_value');
$limit = app('api_limit_value');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort_override = $request->input('sort');
$sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) {
@@ -142,10 +139,10 @@ class ManufacturersController extends Controller
* Store a newly created resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*/
public function store(ImageUploadRequest $request) : JsonResponse
public function store(ImageUploadRequest $request): JsonResponse
{
$this->authorize('create', Manufacturer::class);
$manufacturer = new Manufacturer;
@@ -155,6 +152,7 @@ class ManufacturersController extends Controller
if ($manufacturer->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $manufacturer, trans('admin/manufacturers/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $manufacturer->getErrors()));
}
@@ -163,10 +161,12 @@ class ManufacturersController extends Controller
* Display the specified resource.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function show($id) : JsonResponse | array
public function show($id): JsonResponse|array
{
$this->authorize('view', Manufacturer::class);
$manufacturer = Manufacturer::withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('consumables as consumables_count')->withCount('accessories as accessories_count')->findOrFail($id);
@@ -178,11 +178,12 @@ class ManufacturersController extends Controller
* Update the specified resource in storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
* @param \App\Http\Requests\ImageUploadRequest $request
*
* @param int $id
*/
public function update(ImageUploadRequest $request, $id) : JsonResponse
public function update(ImageUploadRequest $request, $id): JsonResponse
{
$this->authorize('update', Manufacturer::class);
$manufacturer = Manufacturer::findOrFail($id);
@@ -200,7 +201,9 @@ class ManufacturersController extends Controller
* Remove the specified resource from storage.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0]
*
* @param int $id
*/
public function destroy(Manufacturer $manufacturer): JsonResponse
@@ -212,6 +215,7 @@ class ManufacturersController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.bulk_delete_associations.general_assoc_warning', ['item' => trans('general.manufacturer')])));
} catch (\Exception $e) {
report($e);
return response()->json(Helper::formatStandardApiResponse('error', null, trans('general.something_went_wrong')));
}
@@ -222,11 +226,14 @@ class ManufacturersController extends Controller
* Restore a given Manufacturer (mark as un-deleted)
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v6.3.4]
* @param int $id
* @throws \Illuminate\Auth\Access\AuthorizationException
*
* @param int $id
*
* @throws AuthorizationException
*/
public function restore($id) : JsonResponse
public function restore($id): JsonResponse
{
$this->authorize('delete', Manufacturer::class);
@@ -238,7 +245,7 @@ class ManufacturersController extends Controller
if ($manufacturer->restore()) {
$logaction = new Actionlog();
$logaction = new Actionlog;
$logaction->item_type = Manufacturer::class;
$logaction->item_id = $manufacturer->id;
$logaction->created_at = date('Y-m-d H:i:s');
@@ -252,17 +259,18 @@ class ManufacturersController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', trans('general.could_not_restore', ['item_type' => trans('general.manufacturer'), 'error' => $manufacturer->getErrors()->first()])), 200);
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.does_not_exist')));
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/manufacturers/message.does_not_exist')));
}
/**
* Gets a paginated collection for the select2 menus
*
* @author [A. Gianotto] [<snipe@snipe.net>]
*
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
* @see SelectlistTransformer
*/
public function selectlist(Request $request) : array
public function selectlist(Request $request): array
{
$this->authorize('view.selectlists');

Some files were not shown because too many files have changed in this diff Show More