Merge branch 'develop' into bug/sc-17129/v6-integration-pie-chart-disappears-if-you

This commit is contained in:
snipe 2021-10-28 17:13:37 -07:00 committed by GitHub
commit 3951de1669
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1269 changed files with 51380 additions and 31177 deletions

View file

@ -2360,6 +2360,123 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "Delta5",
"name": "Evan Taylor",
"avatar_url": "https://avatars.githubusercontent.com/u/1975640?v=4",
"profile": "https://github.com/Delta5",
"contributions": [
"code"
]
},
{
"login": "PetriAsi",
"name": "Petri Asikainen",
"avatar_url": "https://avatars.githubusercontent.com/u/8735148?v=4",
"profile": "https://github.com/PetriAsi",
"contributions": [
"code"
]
},
{
"login": "derdeagle",
"name": "derdeagle",
"avatar_url": "https://avatars.githubusercontent.com/u/11424540?v=4",
"profile": "https://github.com/derdeagle",
"contributions": [
"code"
]
},
{
"login": "vapier",
"name": "Mike Frysinger",
"avatar_url": "https://avatars.githubusercontent.com/u/176950?v=4",
"profile": "https://wh0rd.org/",
"contributions": [
"code"
]
},
{
"login": "AL4AL",
"name": "ALPHA",
"avatar_url": "https://avatars.githubusercontent.com/u/22044358?v=4",
"profile": "https://github.com/AL4AL",
"contributions": [
"code"
]
},
{
"login": "FliegenKLATSCH",
"name": "FliegenKLATSCH",
"avatar_url": "https://avatars.githubusercontent.com/u/1042587?v=4",
"profile": "https://www.ifern.de",
"contributions": [
"code"
]
},
{
"login": "jerm",
"name": "Jeremy Price",
"avatar_url": "https://avatars.githubusercontent.com/u/442138?v=4",
"profile": "https://github.com/jerm",
"contributions": [
"code"
]
},
{
"login": "Toreg87",
"name": "Toreg87",
"avatar_url": "https://avatars.githubusercontent.com/u/84392209?v=4",
"profile": "https://github.com/Toreg87",
"contributions": [
"code"
]
},
{
"login": "Computroniks",
"name": "Matthew Nickson",
"avatar_url": "https://avatars.githubusercontent.com/u/67638596?v=4",
"profile": "https://github.com/Computroniks",
"contributions": [
"code"
]
},
{
"login": "jethron",
"name": "Jethro Nederhof",
"avatar_url": "https://avatars.githubusercontent.com/u/1646397?v=4",
"profile": "https://jethron.id.au",
"contributions": [
"code"
]
},
{
"login": "01ste02",
"name": "Oskar Stenberg",
"avatar_url": "https://avatars.githubusercontent.com/u/23289826?v=4",
"profile": "https://github.com/01ste02",
"contributions": [
"code"
]
},
{
"login": "Robert-Azelis",
"name": "Robert-Azelis",
"avatar_url": "https://avatars.githubusercontent.com/u/82208283?v=4",
"profile": "https://github.com/Robert-Azelis",
"contributions": [
"code"
]
},
{
"login": "alwism",
"name": "Alexander William Smith",
"avatar_url": "https://avatars.githubusercontent.com/u/60648387?v=4",
"profile": "https://github.com/alwism",
"contributions": [
"code"
]
} }
] ]
} }

View file

@ -1,62 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
#### Please confirm you have done the following before posting your bug report:
- [ ] I have enabled debug mode
- [ ] I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues)
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Server (please complete the following information):**
- Snipe-IT Version
- OS: [e.g. Ubuntu, CentOS]
- Web Server: [e.g. Apache, IIS]
- PHP Version
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Error Messages**
- WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error
- If a stacktrace is provided in the error, include that too.
- Any errors that appear in your browser's error console.
- Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
- Include any additional information you can find in `storage/logs` and your webserver's logs.
- Include the output from `php -m` (this should display what modules you have enabled.)
**Additional context**
- Is this a fresh install or an upgrade?
- What OS and web server you're running Snipe-IT on
- What method you used to install Snipe-IT (install.sh, manual installation, docker, etc)
- Include what you've done so far in the installation, and if you got any error messages along the way.
- Indicate whether or not you've manually edited any data directly in the database
Add any other context about the problem here.
Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you.

View file

@ -1,23 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
**Server (please complete the following information):**
- Snipe-IT Version
- OS: [e.g. Ubuntu, CentOS]
- Web Server: [e.g. Apache, IIS]
- PHP Version
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

129
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,129 @@
name: Bug Report
description: Create a report to help us improve
body:
- type: checkboxes
attributes:
label: Debug mode
description: Please confirm you have done the following before posting your bug report
options:
- label: I have enabled debug mode
required: true
- label: I have read [checked the Common Issues page](https://snipe-it.readme.io/docs/common-issues)
required: true
- type: textarea
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Reproduction steps
description: Steps to reproduce the behavior.
value: |
1.
2.
3.
...
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Screenshots
description: 'If applicable, add screenshots to help explain your problem.'
- type: markdown
attributes:
value: "### Server"
- type: input
attributes:
label: Snipe-IT Version
validations:
required: true
- type: input
id: server_operatingSystem
attributes:
label: Operating System
description: 'e.g. Ubuntu, Windows'
validations:
required: true
- type: input
attributes:
label: Web Server
description: 'e.g. Apache, IIS'
validations:
required: true
- type: input
attributes:
label: PHP Version
validations:
required: true
- type: markdown
attributes:
value: "### Desktop"
- type: input
id: desktop_operatingSystem
attributes:
label: Operating System
description: 'e.g. Ubuntu, Windows'
- type: input
id: desktop_browser
attributes:
label: Browser
description: 'e.g. Google Chrome, Safari'
- type: input
id: desktop_version
attributes:
label: Version
description: 'e.g. 93'
- type: markdown
attributes:
value: "### Mobile"
- type: input
attributes:
label: Device
description: 'e.g. iPhone 6, Pixel 4a'
- type: input
id: mobile_operatingSystem
attributes:
label: Operating System
description: 'e.g. iOS 8.1, Android 9'
- type: input
id: mobile_browser
attributes:
label: Browser
description: 'e.g. Google Chrome, Safari'
- type: input
id: mobile_version
attributes:
label: Version
description: 'e.g. 93'
- type: textarea
attributes:
label: Error messages
description: |
WITH DEBUG TURNED ON, if you're getting an error in your browser, include that error
If a stacktrace is provided in the error, include that too.
Any errors that appear in your browser's error console.
Confirm whether the error is reproducible on the demo: https://snipeitapp.com/demo.
Include any additional information you can find in `storage/logs` and your webserver's logs.
Include the output from `php -m` (this should display what modules you have enabled.)
render: shell
- type: textarea
attributes:
label: Additional context
description: |
Is this a fresh install or an upgrade?
What OS and web server you're running Snipe-IT on
What method you used to install Snipe-IT (install.sh, manual installation, docker, etc)
Include what you've done so far in the installation, and if you got any error messages along the way.
Indicate whether or not you've manually edited any data directly in the database
Add any other context about the problem here.
- type: markdown
attributes:
value: Please do not post an issue without answering the related questions above. If you have opened a different issue and already answered these questions, answer them again, once for every ticket. It will be next to impossible for us to help you.

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1 @@
blank_issues_enabled: false

View file

@ -0,0 +1,42 @@
name: Feature Request
description: Suggest an idea for this project
body:
- type: input
attributes:
label: Snipe-IT Version
validations:
required: true
- type: input
id: server_operatingSystem
attributes:
label: Operating System
description: 'e.g. Ubuntu, Windows'
validations:
required: true
- type: input
attributes:
label: Web Server
description: 'e.g. Apache, IIS'
validations:
required: true
- type: input
attributes:
label: PHP Version
validations:
required: true
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
attributes:
label: Additional context Add any other context or screenshots about the feature request here.

2
.gitignore vendored
View file

@ -63,3 +63,5 @@ _ide_helper.php
.phpstorm.meta.php .phpstorm.meta.php
_ide_helper_models.php _ide_helper_models.php
/.phplint-cache /.phplint-cache
storage/ldap_client_tls.cert
storage/ldap_client_tls.key

View file

@ -1,5 +1,5 @@
FROM ubuntu:bionic FROM ubuntu:20.04
LABEL maintainer Brady Wetherington <uberbrady@gmail.com> LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>"
# No need to add `apt-get clean` here, reference: # No need to add `apt-get clean` here, reference:
# - https://github.com/snipe/snipe-it/pull/9201 # - https://github.com/snipe/snipe-it/pull/9201
@ -14,15 +14,15 @@ RUN export DEBIAN_FRONTEND=noninteractive; \
apt-utils \ apt-utils \
apache2 \ apache2 \
apache2-bin \ apache2-bin \
libapache2-mod-php7.2 \ libapache2-mod-php7.4 \
php7.2-curl \ php7.4-curl \
php7.2-ldap \ php7.4-ldap \
php7.2-mysql \ php7.4-mysql \
php7.2-gd \ php7.4-gd \
php7.2-xml \ php7.4-xml \
php7.2-mbstring \ php7.4-mbstring \
php7.2-zip \ php7.4-zip \
php7.2-bcmath \ php7.4-bcmath \
patch \ patch \
curl \ curl \
wget \ wget \
@ -38,7 +38,7 @@ autoconf \
libc-dev \ libc-dev \
pkg-config \ pkg-config \
libmcrypt-dev \ libmcrypt-dev \
php7.2-dev \ php7.4-dev \
ca-certificates \ ca-certificates \
unzip \ unzip \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
@ -47,16 +47,16 @@ unzip \
RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar RUN curl -L -O https://github.com/pear/pearweb_phars/raw/master/go-pear.phar
RUN php go-pear.phar RUN php go-pear.phar
RUN pecl install mcrypt-1.0.2 RUN pecl install mcrypt-1.0.3
RUN bash -c "echo extension=/usr/lib/php/20170718/mcrypt.so > /etc/php/7.2/mods-available/mcrypt.ini" RUN bash -c "echo extension=/usr/lib/php/20190902/mcrypt.so > /etc/php/7.4/mods-available/mcrypt.ini"
RUN phpenmod mcrypt RUN phpenmod mcrypt
RUN phpenmod gd RUN phpenmod gd
RUN phpenmod bcmath RUN phpenmod bcmath
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.2/apache2/php.ini RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.4/apache2/php.ini
RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.2/cli/php.ini RUN sed -i 's/variables_order = .*/variables_order = "EGPCS"/' /etc/php/7.4/cli/php.ini
RUN useradd -m --uid 1000 --gid 50 docker RUN useradd -m --uid 1000 --gid 50 docker
@ -133,4 +133,4 @@ RUN chmod +x /startup.sh /usr/bin/supervisor-exit-event-listener
CMD ["/startup.sh"] CMD ["/startup.sh"]
EXPOSE 80 EXPOSE 80
EXPOSE 443 EXPOSE 443

View file

@ -1,4 +1,4 @@
FROM alpine:3.13 FROM alpine:3.14.2
# Apache + PHP # Apache + PHP
RUN apk add --no-cache \ RUN apk add --no-cache \
apache2 \ apache2 \
@ -82,4 +82,4 @@ ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/entrypoint.sh"] CMD ["/entrypoint.sh"]
EXPOSE 80 EXPOSE 80

1
Procfile Normal file
View file

@ -0,0 +1 @@
web: php heroku/startup.php && heroku-php-apache2 public/

View file

@ -1,5 +1,5 @@
![Build Status](https://app.chipperci.com/projects/0e5f8979-31eb-4ee6-9abf-050b76ab0383/status/master) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/snipe/snipe-it?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=snipe/snipe-it&amp;utm_campaign=Badge_Grade) ![Build Status](https://app.chipperci.com/projects/0e5f8979-31eb-4ee6-9abf-050b76ab0383/status/master) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=snipe/snipe-it&amp;utm_campaign=Badge_Grade)
[![All Contributors](https://img.shields.io/badge/all_contributors-259-orange.svg?style=flat-square)](#contributors) [![All Contributors](https://img.shields.io/badge/all_contributors-271-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev)
## Snipe-IT - Open Source Asset Management System ## Snipe-IT - Open Source Asset Management System
@ -19,6 +19,8 @@ For instructions on installing and configuring Snipe-IT on your server, check ou
If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help. If you're having trouble with the installation, please check the [Common Issues](https://snipe-it.readme.io/docs/common-issues) and [Getting Help](https://snipe-it.readme.io/docs/getting-help) documentation, and search this repository's open *and* closed issues for help.
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
----- -----
### User's Manual ### User's Manual
For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.io/docs/overview). For help using Snipe-IT, check out the [user's manual](https://snipe-it.readme.io/docs/overview).
@ -55,11 +57,11 @@ Since the release of the JSON REST API, several third-party developers have been
- [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer) - [Python Module](https://github.com/jbloomer/SnipeIT-PythonAPI) by [@jbloomer](https://github.com/jbloomer)
- [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey) - [SnipeSharp - .NET module in C#](https://github.com/barrycarey/SnipeSharp) by [@barrycarey](https://github.com/barrycarey)
- [InQRy](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft) - [InQRy -unmaintained-](https://github.com/Microsoft/InQRy) by [@Microsoft](https://github.com/Microsoft)
- [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it - [SnipeitPS](https://github.com/snazy2000/SnipeitPS) by [@snazy2000](https://github.com/snazy2000) - Powershell API Wrapper for Snipe-it
- [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance - [jamf2snipe](https://github.com/ParadoxGuitarist/jamf2snipe) by [@ParadoxGuitarist](https://github.com/ParadoxGuitarist) - Python script to sync assets between a JAMFPro instance and a Snipe-IT instance
- [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT - [Marksman](https://github.com/Scope-IT/marksman) - A Windows agent for Snipe-IT
- [Snipe-IT plugin for Jira Service Desk (beta)](https://marketplace.atlassian.com/apps/1220379/snipe-it-for-jira-service-desk-beta?hosting=cloud&tab=overview) - for the upcoming Snipe-IT v5 only - [Snipe-IT plugin for Jira Service Desk](https://marketplace.atlassian.com/apps/1220964/snipe-it-for-jira)
- [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag. - [Python 3 CSV importer](https://github.com/gastamper/snipeit-csvimporter) - allows importing assets into Snipe-IT based on Item Name rather than Asset Tag.
- [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit). - [Snipe-IT Kubernetes Helm Chart](https://github.com/t3n/helm-charts/tree/master/snipeit) - For more information, [click here](https://hub.helm.sh/charts/t3n/snipeit).
- [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it. - [Snipe-IT Bulk Edit](https://github.com/bricelabelle/snipe-it-bulkedit) - Google Script files to use Google Sheets as a bulk checkout/checkin/edit tool for Snipe-it.
@ -126,6 +128,8 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [<img src="https://avatars.githubusercontent.com/u/9255772?v=4" width="110px;"/><br /><sub>Mark Stenglein</sub>](https://markstenglein.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [<img src="https://avatars.githubusercontent.com/u/35658596?v=4" width="110px;"/><br /><sub>ajsy</sub>](https://github.com/ajsy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [<img src="https://avatars.githubusercontent.com/u/3628035?v=4" width="110px;"/><br /><sub>Jan Kiesewetter</sub>](https://github.com/t3easy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [<img src="https://avatars.githubusercontent.com/u/79449630?v=4" width="110px;"/><br /><sub>Tetrachloromethane250</sub>](https://github.com/Tetrachloromethane250)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [<img src="https://avatars.githubusercontent.com/u/22004482?v=4" width="110px;"/><br /><sub>Lars Kajes</sub>](https://www.kajes.se/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [<img src="https://avatars.githubusercontent.com/u/13993216?v=4" width="110px;"/><br /><sub>Joly0</sub>](https://github.com/Joly0)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>theburger</sub>](https://github.com/limeless)<br />[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") | | [<img src="https://avatars.githubusercontent.com/u/9255772?v=4" width="110px;"/><br /><sub>Mark Stenglein</sub>](https://markstenglein.com)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ocelotsloth "Code") | [<img src="https://avatars.githubusercontent.com/u/35658596?v=4" width="110px;"/><br /><sub>ajsy</sub>](https://github.com/ajsy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=ajsy "Code") | [<img src="https://avatars.githubusercontent.com/u/3628035?v=4" width="110px;"/><br /><sub>Jan Kiesewetter</sub>](https://github.com/t3easy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=t3easy "Code") | [<img src="https://avatars.githubusercontent.com/u/79449630?v=4" width="110px;"/><br /><sub>Tetrachloromethane250</sub>](https://github.com/Tetrachloromethane250)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Tetrachloromethane250 "Code") | [<img src="https://avatars.githubusercontent.com/u/22004482?v=4" width="110px;"/><br /><sub>Lars Kajes</sub>](https://www.kajes.se/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kajes "Code") | [<img src="https://avatars.githubusercontent.com/u/13993216?v=4" width="110px;"/><br /><sub>Joly0</sub>](https://github.com/Joly0)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Joly0 "Code") | [<img src="https://avatars.githubusercontent.com/u/1501022?v=4" width="110px;"/><br /><sub>theburger</sub>](https://github.com/limeless)<br />[💻](https://github.com/snipe/snipe-it/commits?author=limeless "Code") |
| [<img src="https://avatars.githubusercontent.com/u/36065681?v=4" width="110px;"/><br /><sub>David Valin Alonso</sub>](https://github.com/deivishome)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [<img src="https://avatars.githubusercontent.com/u/8290389?v=4" width="110px;"/><br /><sub>andreaci</sub>](https://github.com/andreaci)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [<img src="https://avatars.githubusercontent.com/u/1828542?v=4" width="110px;"/><br /><sub>Jelle Sebreghts</sub>](http://www.jellesebreghts.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [<img src="https://avatars.githubusercontent.com/u/11180862?v=4" width="110px;"/><br /><sub>Michael Pietsch</sub>](https://github.com/Skywalker-11)<br /> | [<img src="https://avatars.githubusercontent.com/u/22068886?v=4" width="110px;"/><br /><sub>Masudul Haque Shihab</sub>](https://github.com/sh1hab)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [<img src="https://avatars.githubusercontent.com/u/16099942?v=4" width="110px;"/><br /><sub>Supapong Areeprasertkul</sub>](http://www.freedomdive.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [<img src="https://avatars.githubusercontent.com/u/207358?v=4" width="110px;"/><br /><sub>Peter Sarossy</sub>](https://github.com/psarossy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") | | [<img src="https://avatars.githubusercontent.com/u/36065681?v=4" width="110px;"/><br /><sub>David Valin Alonso</sub>](https://github.com/deivishome)<br />[💻](https://github.com/snipe/snipe-it/commits?author=deivishome "Code") | [<img src="https://avatars.githubusercontent.com/u/8290389?v=4" width="110px;"/><br /><sub>andreaci</sub>](https://github.com/andreaci)<br />[💻](https://github.com/snipe/snipe-it/commits?author=andreaci "Code") | [<img src="https://avatars.githubusercontent.com/u/1828542?v=4" width="110px;"/><br /><sub>Jelle Sebreghts</sub>](http://www.jellesebreghts.be)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Jelle-S "Code") | [<img src="https://avatars.githubusercontent.com/u/11180862?v=4" width="110px;"/><br /><sub>Michael Pietsch</sub>](https://github.com/Skywalker-11)<br /> | [<img src="https://avatars.githubusercontent.com/u/22068886?v=4" width="110px;"/><br /><sub>Masudul Haque Shihab</sub>](https://github.com/sh1hab)<br />[💻](https://github.com/snipe/snipe-it/commits?author=sh1hab "Code") | [<img src="https://avatars.githubusercontent.com/u/16099942?v=4" width="110px;"/><br /><sub>Supapong Areeprasertkul</sub>](http://www.freedomdive.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=zybersup "Code") | [<img src="https://avatars.githubusercontent.com/u/207358?v=4" width="110px;"/><br /><sub>Peter Sarossy</sub>](https://github.com/psarossy)<br />[💻](https://github.com/snipe/snipe-it/commits?author=psarossy "Code") |
| [<img src="https://avatars.githubusercontent.com/u/11823649?v=4" width="110px;"/><br /><sub>Renee Margaret McConahy</sub>](https://github.com/nepella)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [<img src="https://avatars.githubusercontent.com/u/5553884?v=4" width="110px;"/><br /><sub>JohnnyPicnic</sub>](https://github.com/JohnnyPicnic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [<img src="https://avatars.githubusercontent.com/u/8799594?v=4" width="110px;"/><br /><sub>markbrule</sub>](https://github.com/markbrule)<br />[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [<img src="https://avatars.githubusercontent.com/u/1962801?v=4" width="110px;"/><br /><sub>Mike Campbell</sub>](https://github.com/mikecmpbll)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [<img src="https://avatars.githubusercontent.com/u/11973217?v=4" width="110px;"/><br /><sub>tbrconnect</sub>](https://github.com/tbrconnect)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [<img src="https://avatars.githubusercontent.com/u/12447225?v=4" width="110px;"/><br /><sub>kcoyo</sub>](https://github.com/kcoyo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [<img src="https://avatars.githubusercontent.com/u/494017?v=4" width="110px;"/><br /><sub>Travis Miller</sub>](https://travismiller.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") | | [<img src="https://avatars.githubusercontent.com/u/11823649?v=4" width="110px;"/><br /><sub>Renee Margaret McConahy</sub>](https://github.com/nepella)<br />[💻](https://github.com/snipe/snipe-it/commits?author=nepella "Code") | [<img src="https://avatars.githubusercontent.com/u/5553884?v=4" width="110px;"/><br /><sub>JohnnyPicnic</sub>](https://github.com/JohnnyPicnic)<br />[💻](https://github.com/snipe/snipe-it/commits?author=JohnnyPicnic "Code") | [<img src="https://avatars.githubusercontent.com/u/8799594?v=4" width="110px;"/><br /><sub>markbrule</sub>](https://github.com/markbrule)<br />[💻](https://github.com/snipe/snipe-it/commits?author=markbrule "Code") | [<img src="https://avatars.githubusercontent.com/u/1962801?v=4" width="110px;"/><br /><sub>Mike Campbell</sub>](https://github.com/mikecmpbll)<br />[💻](https://github.com/snipe/snipe-it/commits?author=mikecmpbll "Code") | [<img src="https://avatars.githubusercontent.com/u/11973217?v=4" width="110px;"/><br /><sub>tbrconnect</sub>](https://github.com/tbrconnect)<br />[💻](https://github.com/snipe/snipe-it/commits?author=tbrconnect "Code") | [<img src="https://avatars.githubusercontent.com/u/12447225?v=4" width="110px;"/><br /><sub>kcoyo</sub>](https://github.com/kcoyo)<br />[💻](https://github.com/snipe/snipe-it/commits?author=kcoyo "Code") | [<img src="https://avatars.githubusercontent.com/u/494017?v=4" width="110px;"/><br /><sub>Travis Miller</sub>](https://travismiller.com/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=travismiller "Code") |
| [<img src="https://avatars.githubusercontent.com/u/8735148?v=4" width="110px;"/><br /><sub>Petri Asikainen</sub>](https://github.com/PetriAsi)<br />[💻](https://github.com/snipe/snipe-it/commits?author=PetriAsi "Code") | [<img src="https://avatars.githubusercontent.com/u/11424540?v=4" width="110px;"/><br /><sub>derdeagle</sub>](https://github.com/derdeagle)<br />[💻](https://github.com/snipe/snipe-it/commits?author=derdeagle "Code") | [<img src="https://avatars.githubusercontent.com/u/176950?v=4" width="110px;"/><br /><sub>Mike Frysinger</sub>](https://wh0rd.org/)<br />[💻](https://github.com/snipe/snipe-it/commits?author=vapier "Code") | [<img src="https://avatars.githubusercontent.com/u/22044358?v=4" width="110px;"/><br /><sub>ALPHA</sub>](https://github.com/AL4AL)<br />[💻](https://github.com/snipe/snipe-it/commits?author=AL4AL "Code") | [<img src="https://avatars.githubusercontent.com/u/1042587?v=4" width="110px;"/><br /><sub>FliegenKLATSCH</sub>](https://www.ifern.de)<br />[💻](https://github.com/snipe/snipe-it/commits?author=FliegenKLATSCH "Code") | [<img src="https://avatars.githubusercontent.com/u/442138?v=4" width="110px;"/><br /><sub>Jeremy Price</sub>](https://github.com/jerm)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jerm "Code") | [<img src="https://avatars.githubusercontent.com/u/84392209?v=4" width="110px;"/><br /><sub>Toreg87</sub>](https://github.com/Toreg87)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Toreg87 "Code") |
| [<img src="https://avatars.githubusercontent.com/u/67638596?v=4" width="110px;"/><br /><sub>Matthew Nickson</sub>](https://github.com/Computroniks)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Computroniks "Code") | [<img src="https://avatars.githubusercontent.com/u/1646397?v=4" width="110px;"/><br /><sub>Jethro Nederhof</sub>](https://jethron.id.au)<br />[💻](https://github.com/snipe/snipe-it/commits?author=jethron "Code") | [<img src="https://avatars.githubusercontent.com/u/23289826?v=4" width="110px;"/><br /><sub>Oskar Stenberg</sub>](https://github.com/01ste02)<br />[💻](https://github.com/snipe/snipe-it/commits?author=01ste02 "Code") | [<img src="https://avatars.githubusercontent.com/u/82208283?v=4" width="110px;"/><br /><sub>Robert-Azelis</sub>](https://github.com/Robert-Azelis)<br />[💻](https://github.com/snipe/snipe-it/commits?author=Robert-Azelis "Code") | [<img src="https://avatars.githubusercontent.com/u/60648387?v=4" width="110px;"/><br /><sub>Alexander William Smith</sub>](https://github.com/alwism)<br />[💻](https://github.com/snipe/snipe-it/commits?author=alwism "Code") |
<!-- ALL-CONTRIBUTORS-LIST:END --> <!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!

154
app.json Normal file
View file

@ -0,0 +1,154 @@
{
"name": "Snipe-IT",
"description": "Open source asset management.",
"keywords": [
"asset management",
"it asset"
],
"website": "https://snipeitapp.com/",
"repository": "https://github.com/snipe/snipe-it",
"logo": "https://pbs.twimg.com/profile_images/976748875733020672/K-HnZCCK_400x400.jpg",
"success_url": "/setup",
"env": {
"APP_ENV": {
"description": "Laravel environment mode. Unless developing the application, this should be production.",
"value": "production"
},
"APP_DEBUG": {
"description": "Laravel debug mode. Unless developing the application or actively debugging a problem, this should be set to false.",
"value": "false"
},
"APP_KEY": {
"description": "A secret key for verifying the integrity of signed cookies. (See either https://snipe-it.readme.io/docs/generate-your-app-key or generate at https://coderstoolbox.online/toolbox/generate-symfony-secret)",
"value": ""
},
"APP_URL": {
"description": "URL where your Snipe-IT install will be available at.",
"value": "https://your-app-name.herokuapp.com"
},
"APP_TIMEZONE": {
"description": "Which timezone do you want to use for your install? (http://php.net/manual/en/timezones.php)",
"value": "UTC"
},
"APP_LOCALE": {
"description": "Which language do you want to use for your install? (https://snipe-it.readme.io/docs/configuration#setting-a-language)",
"value": "en"
},
"MAX_RESULTS": {
"description": "The maximum number of search results that can be returned at one time.",
"value": "500"
},
"MAIL_DRIVER": {
"description": "Mail driver - Generally SMTP on Heroku - https://snipe-it.readme.io/docs/configuration#required-outgoing-mail-settings",
"value": "smtp"
},
"MAIL_HOST": {
"description": "SMTP Server Hostname",
"value": "smtp.your.domain.name"
},
"MAIL_PORT": {
"description": "SMTP Server Port",
"value": "25"
},
"MAIL_USERNAME": {
"description": "SMTP Server Username",
"value": "YOURUSERNAME"
},
"MAIL_PASSWORD": {
"description": "SMTP Server Password",
"value": "YOURPASSWORD"
},
"MAIL_ENCRYPTION": {
"description": "Encryption protocol for email sending.",
"value": "null"
},
"MAIL_FROM_ADDR": {
"description": "Email from address",
"value": "no-reply@domain.name"
},
"MAIL_FROM_NAME": {
"description": "Email from Name",
"value": "Snipe-IT"
},
"MAIL_REPLYTO_ADDR": {
"description": "Email Reply-To address",
"value": "your@domain.name"
},
"MAIL_REPLYTO_NAME": {
"description": "Email Reply-To Name",
"value": "Snipe-IT"
},
"MAIL_AUTO_EMBED": {
"description": "Whether or not to embed images in emails (via CID or base64) versus linking to them.",
"value": "true"
},
"MAIL_AUTO_EMBED_METHOD": {
"description": "Method that should be used for attaching inline images.",
"value": "base64"
},
"SESSION_LIFETIME": {
"description": "Specify the time in minutes that the session should remain valid.",
"value": "12000"
},
"EXPIRE_ON_CLOSE": {
"description": "Specify whether or not the logged in session should be expired when the user closes their browser window.",
"value": "false"
},
"ENCRYPT": {
"description": "Specify whether you wish to use encrypted cookies for your Snipe-IT sessions.",
"value": "true"
},
"COOKIE_NAME": {
"description": "The name of the cookie set by Snipe-IT for session management.",
"value": "snipeit_session"
},
"COOKIE_DOMAIN": {
"description": "The domain name that the session cookie should be sent for.",
"value": "your-app-name.herokuapp.com"
},
"SECURE_COOKIES": {
"description": "Should cookies only be sent for HTTPS connections? Generally true on Heroku.",
"value": "true"
},
"LOGIN_MAX_ATTEMPTS": {
"description": "The maximum number of failed attempts allowed before the user is throttled.",
"value": "5"
},
"LOGIN_LOCKOUT_DURATION": {
"description": "The duration (in seconds) that the user should be blocked from attempting to authenticate again.",
"value": "60"
},
"APP_LOG": {
"description": "Driver to send logs to. (errorlog for stderr)",
"value": "errorlog"
},
"ALLOW_IFRAMING": {
"description": "Allow Snipe-IT to be loaded using an iFrame?",
"value": "false"
},
"GOOGLE_MAPS_API": {
"description": "Include your Google Maps API key here if you'd like Snipe-IT to load maps from Google on your locations and suppliers pages.",
"required": false
},
"BACKUP_ENV": {
"description": "Set this to true if you wish to backup your .env file in your Admin > Backups process.",
"value": "true"
},
"ENABLE_HSTS": {
"description": "Whether or not to send the HSTS security policy header.",
"value": "false"
}
},
"formation": {
"web": {
"quantity": 1,
"size": "free"
}
},
"image": "heroku/php",
"addons": [
"cleardb:ignite",
"heroku-redis:hobby-dev",
"papertrail:choklad"
]
}

View file

@ -2,23 +2,9 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Accessory;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetModel; use App\Models\CustomField;
use App\Models\Category; use Schema;
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\Import;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\Location;
use App\Models\Manufacturer;
use App\Models\Statuslabel;
use App\Models\Supplier;
use DB; use DB;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -29,15 +15,14 @@ class PaveIt extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'snipeit:pave protected $signature = 'snipeit:pave {--force : Skip the interactive yes/no prompt for confirmation}';
{--soft : Perform a "Soft" Delete, leaving all migrations, table structure, and the first user in place.}';
/** /**
* The console command description. * The console command description.
* *
* @var string * @var string
*/ */
protected $description = 'Pave the database to start over. This should ALMOST NEVER BE USED. (It is primarily a quick tool for developers.)'; protected $description = 'Clear the database tables, leaving all migrations, table structure, and the first user in place. (It is primarily a quick tool for developers.) If you want to destroy all tables as well, use php artisan db:wipe.';
/** /**
* Create a new command instance. * Create a new command instance.
@ -56,106 +41,51 @@ class PaveIt extends Command
*/ */
public function handle() public function handle()
{ {
if ($this->confirm("\n****************************************************\nTHIS WILL DELETE ALL OF THE DATA IN YOUR DATABASE. \nThere is NO undo. This WILL destroy ALL of your data. \n****************************************************\n\nDo you wish to continue? No backsies! [y|N]")) {
if ($this->option('soft')) {
Accessory::getQuery()->delete();
Asset::getQuery()->delete();
Category::getQuery()->delete();
Company::getQuery()->delete();
Component::getQuery()->delete();
Consumable::getQuery()->delete();
Department::getQuery()->delete();
Depreciation::getQuery()->delete();
License::getQuery()->delete();
LicenseSeat::getQuery()->delete();
Location::getQuery()->delete();
Manufacturer::getQuery()->delete();
AssetModel::getQuery()->delete();
Statuslabel::getQuery()->delete();
Supplier::getQuery()->delete();
Group::getQuery()->delete();
Import::getQuery()->delete();
DB::statement('delete from accessories_users'); if (!$this->option('force')) {
DB::statement('delete from asset_logs'); $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! ");
DB::statement('delete from asset_maintenances'); if (!$confirmation) {
DB::statement('delete from login_attempts'); $this->error('ABORTING');
DB::statement('delete from asset_uploads'); exit(-1);
DB::statement('delete from action_logs');
DB::statement('delete from checkout_requests');
DB::statement('delete from checkout_acceptances');
DB::statement('delete from consumables_users');
DB::statement('delete from custom_field_custom_fieldset');
DB::statement('delete from custom_fields');
DB::statement('delete from custom_fieldsets');
DB::statement('delete from components_assets');
DB::statement('delete from kits');
DB::statement('delete from kits_accessories');
DB::statement('delete from kits_consumables');
DB::statement('delete from kits_licenses');
DB::statement('delete from kits_models');
DB::statement('delete from login_attempts');
DB::statement('delete from models_custom_fields');
DB::statement('delete from permission_groups');
DB::statement('delete from password_resets');
DB::statement('delete from requested_assets');
DB::statement('delete from requests');
DB::statement('delete from throttle');
DB::statement('delete from users_groups');
DB::statement('delete from users WHERE id!=1');
} else {
\DB::statement('drop table IF EXISTS accessories_users');
\DB::statement('drop table IF EXISTS accessories');
\DB::statement('drop table IF EXISTS asset_logs');
\DB::statement('drop table IF EXISTS action_logs');
\DB::statement('drop table IF EXISTS asset_maintenances');
\DB::statement('drop table IF EXISTS asset_uploads');
\DB::statement('drop table IF EXISTS assets');
\DB::statement('drop table IF EXISTS categories');
\DB::statement('drop table IF EXISTS checkout_requests');
\DB::statement('drop table IF EXISTS checkout_acceptances');
\DB::statement('drop table IF EXISTS companies');
\DB::statement('drop table IF EXISTS components');
\DB::statement('drop table IF EXISTS components_assets');
\DB::statement('drop table IF EXISTS consumables_users');
\DB::statement('drop table IF EXISTS consumables');
\DB::statement('drop table IF EXISTS custom_field_custom_fieldset');
\DB::statement('drop table IF EXISTS custom_fields');
\DB::statement('drop table IF EXISTS custom_fieldsets');
\DB::statement('drop table IF EXISTS depreciations');
\DB::statement('drop table IF EXISTS departments');
\DB::statement('drop table IF EXISTS groups');
\DB::statement('drop table IF EXISTS history');
\DB::statement('drop table IF EXISTS kits');
\DB::statement('drop table IF EXISTS kits_accessories');
\DB::statement('drop table IF EXISTS kits_consumables');
\DB::statement('drop table IF EXISTS kits_licenses');
\DB::statement('drop table IF EXISTS kits_models');
\DB::statement('drop table IF EXISTS models_custom_fields');
\DB::statement('drop table IF EXISTS permission_groups');
\DB::statement('drop table IF EXISTS license_seats');
\DB::statement('drop table IF EXISTS licenses');
\DB::statement('drop table IF EXISTS locations');
\DB::statement('drop table IF EXISTS login_attempts');
\DB::statement('drop table IF EXISTS manufacturers');
\DB::statement('drop table IF EXISTS models');
\DB::statement('drop table IF EXISTS migrations');
\DB::statement('drop table IF EXISTS oauth_access_tokens');
\DB::statement('drop table IF EXISTS oauth_auth_codes');
\DB::statement('drop table IF EXISTS oauth_clients');
\DB::statement('drop table IF EXISTS oauth_personal_access_clients');
\DB::statement('drop table IF EXISTS oauth_refresh_tokens');
\DB::statement('drop table IF EXISTS password_resets');
\DB::statement('drop table IF EXISTS requested_assets');
\DB::statement('drop table IF EXISTS requests');
\DB::statement('drop table IF EXISTS settings');
\DB::statement('drop table IF EXISTS status_labels');
\DB::statement('drop table IF EXISTS suppliers');
\DB::statement('drop table IF EXISTS throttle');
\DB::statement('drop table IF EXISTS users_groups');
\DB::statement('drop table IF EXISTS users');
\DB::statement('drop table IF EXISTS imports');
} }
} }
// List all the tables in the database so we don't have to worry about missing some as the app grows
$tables = DB::connection()->getDoctrineSchemaManager()->listTableNames();
$except_tables = [
'oauth_access_tokens',
'oauth_clients',
'oauth_personal_access_clients',
'migrations',
'settings',
'users',
];
// We only need to find out what these are so we can nuke these columns on the assets table.
$custom_fields = CustomField::get();
foreach ($custom_fields as $custom_field) {
$this->info('DROP the '.$custom_field->db_column.' column from assets as well.');
if (\Schema::hasColumn('assets', $custom_field->db_column)) {
\Schema::table('assets', function ($table) use ($custom_field) {
$table->dropColumn($custom_field->db_column);
});
}
}
foreach ($tables as $table) {
if (in_array($table, $except_tables)) {
$this->info($table. ' is SKIPPED.');
} else {
\DB::statement('truncate '.$table);
$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 id > 2');
} }
} }

View file

@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Setting; use App\Models\Setting;
use App\Models\User; use App\Models\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -39,6 +40,7 @@ class ResetDemoSettings extends Command
*/ */
public function handle() public function handle()
{ {
$this->info('Resetting the demo settings.'); $this->info('Resetting the demo settings.');
$settings = Setting::first(); $settings = Setting::first();
$settings->per_page = 20; $settings->per_page = 20;
@ -71,6 +73,7 @@ class ResetDemoSettings extends Command
$settings->saml_slo = null; $settings->saml_slo = null;
$settings->saml_custom_settings = null; $settings->saml_custom_settings = null;
$settings->save(); $settings->save();
if ($user = User::where('username', '=', 'admin')->first()) { if ($user = User::where('username', '=', 'admin')->first()) {
@ -78,7 +81,9 @@ class ResetDemoSettings extends Command
$user->save(); $user->save();
} }
\Storage::disk('local_public')->put('snipe-logo.png', file_get_contents(public_path('img/demo/snipe-logo.png'))); \Storage::disk('public')->put('snipe-logo.png', file_get_contents(public_path('img/demo/snipe-logo.png')));
\Storage::disk('local_public')->put('snipe-logo-lg.png', file_get_contents(public_path('img/demo/snipe-logo-lg.png'))); \Storage::disk('public')->put('snipe-logo-lg.png', file_get_contents(public_path('img/demo/snipe-logo-lg.png')));
} }
} }

View file

@ -60,7 +60,7 @@ class RestoreFromBackup extends Command
$za = new ZipArchive(); $za = new ZipArchive();
$errcode = $za->open($filename, ZipArchive::RDONLY); $errcode = $za->open($filename/* , ZipArchive::RDONLY */); // that constant only exists in PHP 7.4 and higher
if ($errcode !== true) { if ($errcode !== true) {
$errors = [ $errors = [
ZipArchive::ER_EXISTS => 'File already exists.', ZipArchive::ER_EXISTS => 'File already exists.',
@ -248,8 +248,13 @@ class RestoreFromBackup extends Command
} }
fclose($pipes[0]); fclose($pipes[0]);
fclose($sql_contents); fclose($sql_contents);
$this->line(stream_get_contents($pipes[1]));
fclose($pipes[1]); fclose($pipes[1]);
$this->error(stream_get_contents($pipes[2]));
fclose($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); $close_results = proc_close($proc_results);
if ($close_results != 0) { if ($close_results != 0) {

View file

@ -54,6 +54,7 @@ class Helper
return $cost; return $cost;
} }
/** /**
* Static colors for pie charts. * Static colors for pie charts.
* *
@ -332,6 +333,8 @@ class Helper
'#92896B', '#92896B',
]; ];
return $colors[$index]; return $colors[$index];
} }
@ -389,6 +392,7 @@ class Helper
return $colors; return $colors;
} }
/** /**
* Format currency using comma for thousands until local info is property used. * Format currency using comma for thousands until local info is property used.
* *
@ -398,6 +402,19 @@ class Helper
*/ */
public static function ParseFloat($floatString) public static function ParseFloat($floatString)
{ {
/*******
*
* WARNING: This does conversions based on *locale* - a Unix-ey-like thing.
*
* Everything else in the system tends to convert based on the Snipe-IT settings
*
* So it's very likely this is *not* what you want - instead look for the new
*
* ParseCurrency($currencyString)
*
* Which should be directly below here
*
*/
$LocaleInfo = localeconv(); $LocaleInfo = localeconv();
$floatString = str_replace(',', '', $floatString); $floatString = str_replace(',', '', $floatString);
$floatString = str_replace($LocaleInfo['decimal_point'], '.', $floatString); $floatString = str_replace($LocaleInfo['decimal_point'], '.', $floatString);
@ -412,6 +429,26 @@ class Helper
return floatval($floatString); return floatval($floatString);
} }
/**
* Format currency using comma or period for thousands, and period or comma for decimal, based on settings.
*
* @author [B. Wetherington] [<bwetherington@grokability.com>]
* @since [v5.2]
* @return Float
*/
public static function ParseCurrency($currencyString) {
$without_currency = str_replace(Setting::getSettings()->default_currency, '', $currencyString); //generally shouldn't come up, since we don't do this in fields, but just in case it does...
if(Setting::getSettings()->digit_separator=='1.234,56') {
//EU format
$without_thousands = str_replace('.', '', $without_currency);
$corrected_decimal = str_replace(',', '.', $without_thousands);
} else {
$without_thousands = str_replace(',', '', $without_currency);
$corrected_decimal = $without_thousands; // decimal is already OK
}
return floatval($corrected_decimal);
}
/** /**
* Get the list of status labels in an array to make a dropdown menu * Get the list of status labels in an array to make a dropdown menu
@ -780,12 +817,12 @@ class Helper
} catch (DecryptException $e) { } catch (DecryptException $e) {
return 'Error Decrypting: '.$e->getMessage(); return 'Error Decrypting: '.$e->getMessage();
} }
} }
return $string; return $string;
} }
public static function formatStandardApiResponse($status, $payload = null, $messages = null) public static function formatStandardApiResponse($status, $payload = null, $messages = null)
{ {
$array['status'] = $status; $array['status'] = $status;
$array['messages'] = $messages; $array['messages'] = $messages;
@ -869,7 +906,8 @@ class Helper
// If upload_max_size is less, then reduce. Except if upload_max_size is // If upload_max_size is less, then reduce. Except if upload_max_size is
// zero, which indicates no limit. // zero, which indicates no limit.
$upload_max = self::parse_size(ini_get('upload_max_filesize')); $upload_max = self::parse_size(ini_get('upload_max_filesize'));
if ($upload_max > 0 && $upload_max < $max_size) {
if ($upload_max > 0 && $upload_max < $post_max_size) {
$max_size = ini_get('upload_max_filesize'); $max_size = ini_get('upload_max_filesize');
} }
} }
@ -895,33 +933,33 @@ class Helper
$allowedExtensionMap = [ $allowedExtensionMap = [
// Images // Images
'jpg' => 'fa fa-file-image-o', 'jpg' => 'far fa-image',
'jpeg' => 'fa fa-file-image-o', 'jpeg' => 'far fa-image',
'gif' => 'fa fa-file-image-o', 'gif' => 'far fa-image',
'png' => 'fa fa-file-image-o', 'png' => 'far fa-image',
// word // word
'doc' => 'fa fa-file-word-o', 'doc' => 'far fa-file-word',
'docx' => 'fa fa-file-word-o', 'docx' => 'far fa-file-word',
// Excel // Excel
'xls' => 'fa fa-file-excel-o', 'xls' => 'far fa-file-excel',
'xlsx' => 'fa fa-file-excel-o', 'xlsx' => 'far fa-file-excel',
// archive // archive
'zip' => 'fa fa-file-archive-o', 'zip' => 'fas fa-file-archive',
'rar' => 'fa fa-file-archive-o', 'rar' => 'fas fa-file-archive',
//Text //Text
'txt' => 'fa fa-file-text-o', 'txt' => 'far fa-file-alt',
'rtf' => 'fa fa-file-text-o', 'rtf' => 'far fa-file-alt',
'xml' => 'fa fa-file-text-o', 'xml' => 'far fa-file-alt',
// Misc // Misc
'pdf' => 'fa fa-file-pdf-o', 'pdf' => 'far fa-file-pdf',
'lic' => 'fa fa-file-floppy-o', 'lic' => 'far fa-save',
]; ];
if ($extension && array_key_exists($extension, $allowedExtensionMap)) { if ($extension && array_key_exists($extension, $allowedExtensionMap)) {
return $allowedExtensionMap[$extension]; return $allowedExtensionMap[$extension];
} }
return 'fa fa-file-o'; return 'far fa-file';
} }
public static function show_file_inline($filename) public static function show_file_inline($filename)

View file

@ -66,19 +66,19 @@ class AccessoriesController extends Controller
$accessory = new Accessory(); $accessory = new Accessory();
// Update the accessory data // Update the accessory data
$accessory->name = request('name'); $accessory->name = request('name');
$accessory->category_id = request('category_id'); $accessory->category_id = request('category_id');
$accessory->location_id = request('location_id'); $accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt'); $accessory->min_amt = request('min_amt');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id')); $accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->order_number = request('order_number'); $accessory->order_number = request('order_number');
$accessory->manufacturer_id = request('manufacturer_id'); $accessory->manufacturer_id = request('manufacturer_id');
$accessory->model_number = request('model_number'); $accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date'); $accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseFloat(request('purchase_cost')); $accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty'); $accessory->qty = request('qty');
$accessory->user_id = Auth::user()->id; $accessory->user_id = Auth::user()->id;
$accessory->supplier_id = request('supplier_id'); $accessory->supplier_id = request('supplier_id');
$accessory = $request->handleImages($accessory); $accessory = $request->handleImages($accessory);
@ -101,6 +101,7 @@ class AccessoriesController extends Controller
*/ */
public function edit($accessoryId = null) public function edit($accessoryId = null)
{ {
if ($item = Accessory::find($accessoryId)) { if ($item = Accessory::find($accessoryId)) {
$this->authorize($item); $this->authorize($item);
@ -108,8 +109,10 @@ class AccessoriesController extends Controller
} }
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist')); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
} }
/** /**
* Save edited Accessory from form post * Save edited Accessory from form post
* *
@ -128,18 +131,18 @@ class AccessoriesController extends Controller
$this->authorize($accessory); $this->authorize($accessory);
// Update the accessory data // Update the accessory data
$accessory->name = request('name'); $accessory->name = request('name');
$accessory->location_id = request('location_id'); $accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt'); $accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id'); $accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id')); $accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id'); $accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number'); $accessory->order_number = request('order_number');
$accessory->model_number = request('model_number'); $accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date'); $accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost'); $accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty'); $accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id'); $accessory->supplier_id = request('supplier_id');
$accessory = $request->handleImages($accessory); $accessory = $request->handleImages($accessory);
@ -167,6 +170,7 @@ class AccessoriesController extends Controller
$this->authorize($accessory); $this->authorize($accessory);
if ($accessory->hasUsers() > 0) { if ($accessory->hasUsers() > 0) {
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()])); return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.assoc_users', ['count'=> $accessory->hasUsers()]));
} }
@ -184,6 +188,7 @@ class AccessoriesController extends Controller
return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success')); return redirect()->route('accessories.index')->with('success', trans('admin/accessories/message.delete.success'));
} }
/** /**
* Returns a view that invokes the ajax table which contains * Returns a view that invokes the ajax table which contains
* the content for the accessory detail view, which is generated in getDataView. * the content for the accessory detail view, which is generated in getDataView.

View file

@ -28,8 +28,23 @@ class AccessoriesController extends Controller
{ {
$this->authorize('view', Accessory::class); $this->authorize('view', Accessory::class);
$allowed_columns = ['id', 'name', 'model_number', 'eol', 'notes', 'created_at', 'min_amt', 'company_id']; $allowed_columns = ['id', 'name', 'model_number', 'eol', 'notes', 'created_at', 'min_amt', 'company_id'];
// 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 =
[
'id',
'name',
'model_number',
'eol',
'notes',
'created_at',
'min_amt',
'company_id'
];
$accessories = Accessory::with('category', 'company', 'manufacturer', 'users', 'location');
$accessories = Accessory::select('accessories.*')->with('category', 'company', 'manufacturer', 'users', 'location', 'supplier');
if ($request->filled('search')) { if ($request->filled('search')) {
$accessories = $accessories->TextSearch($request->input('search')); $accessories = $accessories->TextSearch($request->input('search'));
@ -51,6 +66,10 @@ class AccessoriesController extends Controller
$accessories->where('supplier_id', '=', $request->input('supplier_id')); $accessories->where('supplier_id', '=', $request->input('supplier_id'));
} }
if ($request->filled('location_id')) {
$accessories->where('location_id','=',$request->input('location_id'));
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items. // case we override with the actual count, so we should return 0 items.
$offset = (($accessories) && ($request->get('offset') > $accessories->count())) ? $accessories->count() : $request->get('offset', 0); $offset = (($accessories) && ($request->get('offset') > $accessories->count())) ? $accessories->count() : $request->get('offset', 0);
@ -59,28 +78,37 @@ class AccessoriesController extends Controller
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results'); ((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort) { switch ($sort_override) {
case 'category': case 'category':
$accessories = $accessories->OrderCategory($order); $accessories = $accessories->OrderCategory($order);
break; break;
case 'company': case 'company':
$accessories = $accessories->OrderCompany($order); $accessories = $accessories->OrderCompany($order);
break; break;
case 'location':
$accessories = $accessories->OrderLocation($order);
break;
case 'manufacturer':
$accessories = $accessories->OrderManufacturer($order);
break;
case 'supplier':
$accessories = $accessories->OrderSupplier($order);
break;
default: default:
$accessories = $accessories->orderBy($sort, $order); $accessories = $accessories->orderBy($column_sort, $order);
break; break;
} }
$accessories->orderBy($sort, $order);
$total = $accessories->count(); $total = $accessories->count();
$accessories = $accessories->skip($offset)->take($limit)->get(); $accessories = $accessories->skip($offset)->take($limit)->get();
return (new AccessoriesTransformer)->transformAccessories($accessories, $total); return (new AccessoriesTransformer)->transformAccessories($accessories, $total);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
@ -94,12 +122,14 @@ class AccessoriesController extends Controller
$this->authorize('create', Accessory::class); $this->authorize('create', Accessory::class);
$accessory = new Accessory; $accessory = new Accessory;
$accessory->fill($request->all()); $accessory->fill($request->all());
$accessory = $request->handleImages($accessory);
if ($accessory->save()) { if ($accessory->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $accessory, trans('admin/accessories/message.create.success'))); return response()->json(Helper::formatStandardApiResponse('success', $accessory, trans('admin/accessories/message.create.success')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $accessory->getErrors())); return response()->json(Helper::formatStandardApiResponse('error', null, $accessory->getErrors()));
} }
/** /**
@ -118,6 +148,7 @@ class AccessoriesController extends Controller
return (new AccessoriesTransformer)->transformAccessory($accessory); return (new AccessoriesTransformer)->transformAccessory($accessory);
} }
/** /**
* Display the specified resource. * Display the specified resource.
* *
@ -134,6 +165,7 @@ class AccessoriesController extends Controller
return (new AccessoriesTransformer)->transformAccessory($accessory); return (new AccessoriesTransformer)->transformAccessory($accessory);
} }
/** /**
* Display the specified resource. * Display the specified resource.
* *
@ -165,15 +197,20 @@ class AccessoriesController extends Controller
if ($request->filled('search')) { if ($request->filled('search')) {
$accessory_users = $accessory->users() $accessory_users = $accessory->users()
->where('first_name', 'like', '%'.$request->input('search').'%') ->where(function ($query) use ($request) {
->orWhere('last_name', 'like', '%'.$request->input('search').'%') $search_str = '%' . $request->input('search') . '%';
->get(); $query->where('first_name', 'like', $search_str)
->orWhere('last_name', 'like', $search_str)
->orWhere('note', 'like', $search_str);
})
->get();
$total = $accessory_users->count(); $total = $accessory_users->count();
} }
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total); return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
@ -220,6 +257,7 @@ class AccessoriesController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.delete.success'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.delete.success')));
} }
/** /**
* Save the Accessory checkout information. * Save the Accessory checkout information.
* *
@ -239,7 +277,9 @@ class AccessoriesController extends Controller
$this->authorize('checkout', $accessory); $this->authorize('checkout', $accessory);
if ($accessory->numRemaining() > 0) { if ($accessory->numRemaining() > 0) {
if (! $user = User::find($request->input('assigned_to'))) { if (! $user = User::find($request->input('assigned_to'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkout.user_does_not_exist')));
} }
@ -261,6 +301,7 @@ class AccessoriesController extends Controller
} }
return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining')); return response()->json(Helper::formatStandardApiResponse('error', null, 'No accessories remaining'));
} }
/** /**
@ -299,19 +340,23 @@ class AccessoriesController extends Controller
$data['item_tag'] = ''; $data['item_tag'] = '';
$data['note'] = $logaction->note; $data['note'] = $logaction->note;
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success'))); return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/accessories/message.checkin.success')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/accessories/message.checkin.error')));
} }
/** /**
* Gets a paginated collection for the select2 menus * Gets a paginated collection for the select2 menus
* *
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ *
*/
public function selectlist(Request $request) public function selectlist(Request $request)
{ {
$accessories = Accessory::select([ $accessories = Accessory::select([
'accessories.id', 'accessories.id',
'accessories.name', 'accessories.name',
@ -325,4 +370,5 @@ class AccessoriesController extends Controller
return (new SelectlistTransformer)->transformSelectlist($accessories); return (new SelectlistTransformer)->transformSelectlist($accessories);
} }
} }

View file

@ -21,6 +21,8 @@ use Illuminate\Support\Facades\Input;
*/ */
class AssetMaintenancesController extends Controller class AssetMaintenancesController extends Controller
{ {
/** /**
* Generates the JSON response for asset maintenances listing view. * Generates the JSON response for asset maintenances listing view.
* *
@ -82,10 +84,12 @@ class AssetMaintenancesController extends Controller
$total = $maintenances->count(); $total = $maintenances->count();
$maintenances = $maintenances->skip($offset)->take($limit)->get(); $maintenances = $maintenances->skip($offset)->take($limit)->get();
return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $total); return (new AssetMaintenancesTransformer())->transformAssetMaintenances($maintenances, $total);
} }
/** /**
* Validates and stores the new asset maintenance * Validates and stores the new asset maintenance
* *
@ -101,7 +105,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance = new AssetMaintenance(); $assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id'); $assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty'); $assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = e($request->input('cost')); $assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->notes = e($request->input('notes')); $assetMaintenance->notes = e($request->input('notes'));
$asset = Asset::find(e($request->input('asset_id'))); $asset = Asset::find(e($request->input('asset_id')));
@ -129,9 +133,11 @@ class AssetMaintenancesController extends Controller
// Was the asset maintenance created? // Was the asset maintenance created?
if ($assetMaintenance->save()) { if ($assetMaintenance->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.create.success'))); return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.create.success')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $assetMaintenance->getErrors())); return response()->json(Helper::formatStandardApiResponse('error', null, $assetMaintenance->getErrors()));
} }
/** /**
@ -155,7 +161,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->supplier_id = e($request->input('supplier_id')); $assetMaintenance->supplier_id = e($request->input('supplier_id'));
$assetMaintenance->is_warranty = e($request->input('is_warranty')); $assetMaintenance->is_warranty = e($request->input('is_warranty'));
$assetMaintenance->cost = Helper::ParseFloat(e($request->input('cost'))); $assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->notes = e($request->input('notes')); $assetMaintenance->notes = e($request->input('notes'));
$asset = Asset::find(request('asset_id')); $asset = Asset::find(request('asset_id'));
@ -192,6 +198,7 @@ class AssetMaintenancesController extends Controller
// Was the asset maintenance created? // Was the asset maintenance created?
if ($assetMaintenance->save()) { if ($assetMaintenance->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.edit.success'))); return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.edit.success')));
} }
return response()->json(Helper::formatStandardApiResponse('error', null, $assetMaintenance->getErrors())); return response()->json(Helper::formatStandardApiResponse('error', null, $assetMaintenance->getErrors()));
@ -218,6 +225,8 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->delete(); $assetMaintenance->delete();
return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success'))); return response()->json(Helper::formatStandardApiResponse('success', $assetMaintenance, trans('admin/asset_maintenances/message.delete.success')));
} }
/** /**
@ -237,5 +246,6 @@ class AssetMaintenancesController extends Controller
} }
return (new AssetMaintenancesTransformer())->transformAssetMaintenance($assetMaintenance); return (new AssetMaintenancesTransformer())->transformAssetMaintenance($assetMaintenance);
} }
} }

View file

@ -66,7 +66,7 @@ class AssetModelsController extends Controller
->with('category', 'depreciation', 'manufacturer', 'fieldset') ->with('category', 'depreciation', 'manufacturer', 'fieldset')
->withCount('assets as assets_count'); ->withCount('assets as assets_count');
if ($request->filled('status')) { if ($request->input('status')=='deleted') {
$assetmodels->onlyTrashed(); $assetmodels->onlyTrashed();
} }
@ -102,6 +102,7 @@ class AssetModelsController extends Controller
return (new AssetModelsTransformer)->transformAssetModels($assetmodels, $total); return (new AssetModelsTransformer)->transformAssetModels($assetmodels, $total);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
@ -120,8 +121,9 @@ class AssetModelsController extends Controller
if ($assetmodel->save()) { if ($assetmodel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.create.success'))); return response()->json(Helper::formatStandardApiResponse('success', $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()));
} }
/** /**
@ -156,6 +158,7 @@ class AssetModelsController extends Controller
return (new AssetsTransformer)->transformAssets($assets, $assets->count()); return (new AssetsTransformer)->transformAssets($assets, $assets->count());
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
@ -184,6 +187,7 @@ class AssetModelsController extends Controller
$assetmodel->fieldset_id = $request->get('custom_fieldset_id'); $assetmodel->fieldset_id = $request->get('custom_fieldset_id');
} }
if ($assetmodel->save()) { if ($assetmodel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success'))); return response()->json(Helper::formatStandardApiResponse('success', $assetmodel, trans('admin/models/message.update.success')));
} }

View file

@ -2,12 +2,16 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest; use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Transformers\AssetsTransformer; use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\DepreciationReportTransformer;
use App\Http\Transformers\LicensesTransformer; use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use App\Models\AssetModel; use App\Models\AssetModel;
use App\Models\Company; use App\Models\Company;
@ -20,7 +24,6 @@ use Auth;
use Carbon\Carbon; use Carbon\Carbon;
use DB; use DB;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Input; use Input;
use Paginator; use Paginator;
@ -28,6 +31,7 @@ use Slack;
use Str; use Str;
use TCPDF; use TCPDF;
use Validator; use Validator;
use Route;
/** /**
* This class controls all actions related to assets for * This class controls all actions related to assets for
@ -46,9 +50,32 @@ class AssetsController extends Controller
* @since [v4.0] * @since [v4.0]
* @return JsonResponse * @return JsonResponse
*/ */
public function index(Request $request, $audit = null) public function index(Request $request, $audit = null)
{ {
$this->authorize('index', Asset::class);
\Log::debug(Route::currentRouteName());
/**
* 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') {
$transformer = 'App\Http\Transformers\DepreciationReportTransformer';
$this->authorize('reports.view');
} else {
$transformer = 'App\Http\Transformers\AssetsTransformer';
$this->authorize('index', Asset::class);
}
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$allowed_columns = [ $allowed_columns = [
@ -161,6 +188,8 @@ class AssetsController extends Controller
} }
} }
// This is used by the sidenav, mostly // This is used by the sidenav, mostly
// We switched from using query scopes here because of a Laravel bug // We switched from using query scopes here because of a Laravel bug
@ -221,7 +250,7 @@ class AssetsController extends Controller
->where('status_alias.archived', '=', 0); ->where('status_alias.archived', '=', 0);
}); });
// If there is a status ID, don't take show_archived_in_list into consideration // If there is a status ID, don't take show_archived_in_list into consideration
} else { } else {
$assets->join('status_labels AS status_alias', function ($join) { $assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id'); $join->on('status_alias.id', '=', 'assets.status_id');
@ -230,12 +259,14 @@ class AssetsController extends Controller
} }
if ((! is_null($filter)) && (count($filter)) > 0) { if ((! is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter); $assets->ByFilter($filter);
} elseif ($request->filled('search')) { } elseif ($request->filled('search')) {
$assets->TextSearch($request->input('search')); $assets->TextSearch($request->input('search'));
} }
// This is kinda gross, but we need to do this because the Bootstrap Tables // This is kinda gross, but we need to do this because the Bootstrap Tables
// API passes custom field ordering as custom_fields.fieldname, and we have to strip // API passes custom field ordering as custom_fields.fieldname, and we have to strip
// that out to let the default sorter below order them correctly on the assets table. // that out to let the default sorter below order them correctly on the assets table.
@ -245,6 +276,7 @@ class AssetsController extends Controller
// in the allowed_columns array) // in the allowed_columns array)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at'; $column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
switch ($sort_override) { switch ($sort_override) {
case 'model': case 'model':
$assets->OrderModels($order); $assets->OrderModels($order);
@ -280,10 +312,28 @@ class AssetsController extends Controller
break; break;
} }
$total = $assets->count(); $total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get(); $assets = $assets->skip($offset)->take($limit)->get();
// dd($assets);
return (new AssetsTransformer)->transformAssets($assets, $total);
/**
* Include additional associated relationships
*/
if ($request->input('components')) {
$assets->loadMissing(['components' => function ($query) {
$query->orderBy('created_at', 'desc');
}]);
}
/**
* Here we're just determining which Transformer (via $transformer) to use based on the
* variables we set earlier on in this method - we default to AssetsTransformer.
*/
return (new $transformer)->transformAssets($assets, $total, $request);
} }
/** /**
@ -294,15 +344,16 @@ class AssetsController extends Controller
* @since [v4.2.1] * @since [v4.2.1]
* @return JsonResponse * @return JsonResponse
*/ */
public function showByTag($tag) public function showByTag(Request $request, $tag)
{ {
if ($asset = Asset::with('assetstatus')->with('assignedTo')->where('asset_tag', $tag)->first()) { if ($asset = Asset::with('assetstatus')->with('assignedTo')->where('asset_tag', $tag)->first()) {
$this->authorize('view', $asset); $this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset); return (new AssetsTransformer)->transformAsset($asset, $request);
} }
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200); return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
} }
/** /**
@ -313,17 +364,19 @@ class AssetsController extends Controller
* @since [v4.2.1] * @since [v4.2.1]
* @return JsonResponse * @return JsonResponse
*/ */
public function showBySerial($serial) public function showBySerial(Request $request, $serial)
{ {
$this->authorize('index', Asset::class); $this->authorize('index', Asset::class);
if ($assets = Asset::with('assetstatus')->with('assignedTo') if ($assets = Asset::with('assetstatus')->with('assignedTo')
->withTrashed()->where('serial', $serial)->get()) { ->withTrashed()->where('serial', $serial)->get()) {
return (new AssetsTransformer)->transformAssets($assets, $assets->count()); return (new AssetsTransformer)->transformAssets($assets, $assets->count());
} }
return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200); return response()->json(Helper::formatStandardApiResponse('error', null, 'Asset not found'), 200);
} }
/** /**
* Returns JSON with information about an asset for detail view. * Returns JSON with information about an asset for detail view.
* *
@ -332,17 +385,19 @@ class AssetsController extends Controller
* @since [v4.0] * @since [v4.0]
* @return JsonResponse * @return JsonResponse
*/ */
public function show($id) public function show(Request $request, $id)
{ {
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed() if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()
->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) { ->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) {
$this->authorize('view', $asset); $this->authorize('view', $asset);
return (new AssetsTransformer)->transformAsset($asset); return (new AssetsTransformer)->transformAsset($asset, $request->input('components') );
} }
} }
public function licenses($id) public function licenses(Request $request, $id)
{ {
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$this->authorize('view', License::class); $this->authorize('view', License::class);
@ -350,7 +405,8 @@ class AssetsController extends Controller
$licenses = $asset->licenses()->get(); $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 * Gets a paginated collection for the select2 menus
@ -358,9 +414,11 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16] * @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*
*/ */
public function selectlist(Request $request) public function selectlist(Request $request)
{ {
$assets = Company::scopeCompanyables(Asset::select([ $assets = Company::scopeCompanyables(Asset::select([
'assets.id', 'assets.id',
'assets.name', 'assets.name',
@ -379,18 +437,22 @@ class AssetsController extends Controller
$assets = $assets->AssignedSearch($request->input('search')); $assets = $assets->AssignedSearch($request->input('search'));
} }
$assets = $assets->paginate(50); $assets = $assets->paginate(50);
// Loop through and set some custom properties for the transformer to use. // Loop through and set some custom properties for the transformer to use.
// This lets us have more flexibility in special cases like assets, where // This lets us have more flexibility in special cases like assets, where
// they may not have a ->name value but we want to display something anyway // they may not have a ->name value but we want to display something anyway
foreach ($assets as $asset) { foreach ($assets as $asset) {
$asset->use_text = $asset->present()->fullName; $asset->use_text = $asset->present()->fullName;
if (($asset->checkedOutToUser()) && ($asset->assigned)) { if (($asset->checkedOutToUser()) && ($asset->assigned)) {
$asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute(); $asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute();
} }
if ($asset->assetstatus->getStatuslabelType() == 'pending') { if ($asset->assetstatus->getStatuslabelType() == 'pending') {
$asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')'; $asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
} }
@ -401,6 +463,7 @@ class AssetsController extends Controller
return (new SelectlistTransformer)->transformSelectlist($assets); return (new SelectlistTransformer)->transformSelectlist($assets);
} }
/** /**
* Accepts a POST request to create a new asset * Accepts a POST request to create a new asset
* *
@ -416,34 +479,34 @@ class AssetsController extends Controller
$asset = new Asset(); $asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id'))); $asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
$asset->name = $request->get('name'); $asset->name = $request->get('name');
$asset->serial = $request->get('serial'); $asset->serial = $request->get('serial');
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id')); $asset->company_id = Company::getIdForCurrentUser($request->get('company_id'));
$asset->model_id = $request->get('model_id'); $asset->model_id = $request->get('model_id');
$asset->order_number = $request->get('order_number'); $asset->order_number = $request->get('order_number');
$asset->notes = $request->get('notes'); $asset->notes = $request->get('notes');
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset()); $asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
$asset->user_id = Auth::id(); $asset->user_id = Auth::id();
$asset->archived = '0'; $asset->archived = '0';
$asset->physical = '1'; $asset->physical = '1';
$asset->depreciate = '0'; $asset->depreciate = '0';
$asset->status_id = $request->get('status_id', 0); $asset->status_id = $request->get('status_id', 0);
$asset->warranty_months = $request->get('warranty_months', null); $asset->warranty_months = $request->get('warranty_months', null);
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost')); $asset->purchase_cost = Helper::ParseCurrency($request->get('purchase_cost')); // this is the API's store method, so I don't know that I want to do this? Confusing. FIXME (or not?!)
$asset->purchase_date = $request->get('purchase_date', null); $asset->purchase_date = $request->get('purchase_date', null);
$asset->assigned_to = $request->get('assigned_to', null); $asset->assigned_to = $request->get('assigned_to', null);
$asset->supplier_id = $request->get('supplier_id', 0); $asset->supplier_id = $request->get('supplier_id', 0);
$asset->requestable = $request->get('requestable', 0); $asset->requestable = $request->get('requestable', 0);
$asset->rtd_location_id = $request->get('rtd_location_id', null); $asset->rtd_location_id = $request->get('rtd_location_id', null);
$asset->location_id = $request->get('rtd_location_id', null); $asset->location_id = $request->get('rtd_location_id', null);
/** /**
* this is here just legacy reasons. Api\AssetController * this is here just legacy reasons. Api\AssetController
* used image_source once to allow encoded image uploads. * used image_source once to allow encoded image uploads.
*/ */
if ($request->has('image_source')) { if ($request->has('image_source')) {
$request->offsetSet('image', $request->offsetGet('image_source')); $request->offsetSet('image', $request->offsetGet('image_source'));
} }
$asset = $request->handleImages($asset); $asset = $request->handleImages($asset);
@ -478,6 +541,7 @@ class AssetsController extends Controller
} }
} }
$asset->{$field->convertUnicodeDbSlug()} = $field_val; $asset->{$field->convertUnicodeDbSlug()} = $field_val;
} }
} }
@ -504,6 +568,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200); return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
} }
/** /**
* Accepts a POST request to update an asset * Accepts a POST request to update an asset
* *
@ -554,9 +619,10 @@ class AssetsController extends Controller
} }
} }
if ($asset->save()) { if ($asset->save()) {
if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) { if (($request->filled('assigned_user')) && ($target = User::find($request->get('assigned_user')))) {
$location = $target->location_id; $location = $target->location_id;
} elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) { } elseif (($request->filled('assigned_asset')) && ($target = Asset::find($request->get('assigned_asset')))) {
$location = $target->location_id; $location = $target->location_id;
@ -583,6 +649,7 @@ class AssetsController extends Controller
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);
} }
/** /**
* Delete a given asset (mark as deleted). * Delete a given asset (mark as deleted).
* *
@ -610,6 +677,39 @@ class AssetsController extends Controller
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);
} }
/**
* Restore a soft-deleted asset.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @param int $assetId
* @since [v5.1.18]
* @return JsonResponse
*/
public function restore($assetId = null)
{
// Get asset information
$asset = Asset::withTrashed()->find($assetId);
$this->authorize('delete', $asset);
if (isset($asset->id)) {
// Restore the asset
Asset::withTrashed()->where('id', $assetId)->restore();
$logaction = new Actionlog();
$logaction->item_type = Asset::class;
$logaction->item_id = $asset->id;
$logaction->created_at = date("Y-m-d H:i:s");
$logaction->user_id = Auth::user()->id;
$logaction->logaction('restored');
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/hardware/message.restore.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')), 200);
}
/** /**
* Checkout an asset * Checkout an asset
* *
@ -635,12 +735,14 @@ class AssetsController extends Controller
'asset_tag' => $asset->asset_tag, 'asset_tag' => $asset->asset_tag,
]; ];
// This item is checked out to a location // This item is checked out to a location
if (request('checkout_to_type') == 'location') { if (request('checkout_to_type') == 'location') {
$target = Location::find(request('assigned_location')); $target = Location::find(request('assigned_location'));
$asset->location_id = ($target) ? $target->id : ''; $asset->location_id = ($target) ? $target->id : '';
$error_payload['target_id'] = $request->input('assigned_location'); $error_payload['target_id'] = $request->input('assigned_location');
$error_payload['target_type'] = 'location'; $error_payload['target_type'] = 'location';
} elseif (request('checkout_to_type') == 'asset') { } elseif (request('checkout_to_type') == 'asset') {
$target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset')); $target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset'));
$asset->location_id = $target->rtd_location_id; $asset->location_id = $target->rtd_location_id;
@ -648,6 +750,7 @@ class AssetsController extends Controller
$asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : ''; $asset->location_id = (($target) && (isset($target->location_id))) ? $target->location_id : '';
$error_payload['target_id'] = $request->input('assigned_asset'); $error_payload['target_id'] = $request->input('assigned_asset');
$error_payload['target_type'] = 'asset'; $error_payload['target_type'] = 'asset';
} elseif (request('checkout_to_type') == 'user') { } elseif (request('checkout_to_type') == 'user') {
// Fetch the target and set the asset's new location_id // Fetch the target and set the asset's new location_id
$target = User::find(request('assigned_user')); $target = User::find(request('assigned_user'));
@ -656,10 +759,14 @@ class AssetsController extends Controller
$error_payload['target_type'] = 'user'; $error_payload['target_type'] = 'user';
} }
if (! isset($target)) { 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')); $checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
$expected_checkin = request('expected_checkin', null); $expected_checkin = request('expected_checkin', null);
$note = request('note', null); $note = request('note', null);
@ -667,12 +774,15 @@ class AssetsController extends Controller
// Set the location ID to the RTD location id if there is one // 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. // 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!='')) { // if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
// $asset->location_id = $target->rtd_location_id; // $asset->location_id = $target->rtd_location_id;
// } // }
if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) { if ($asset->checkOut($target, Auth::user(), $checkout_at, $expected_checkin, $note, $asset_name, $asset->location_id)) {
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success'))); return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.success')));
} }
@ -680,6 +790,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error'))); return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkout.error')));
} }
/** /**
* Checkin an asset * Checkin an asset
* *
@ -694,6 +805,7 @@ class AssetsController extends Controller
$asset = Asset::findOrFail($asset_id); $asset = Asset::findOrFail($asset_id);
$this->authorize('checkin', $asset); $this->authorize('checkin', $asset);
$user = $asset->assignedUser; $user = $asset->assignedUser;
if (is_null($target = $asset->assignedTo)) { if (is_null($target = $asset->assignedTo)) {
return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.already_checked_in'))); return response()->json(Helper::formatStandardApiResponse('error', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.already_checked_in')));
@ -708,7 +820,7 @@ class AssetsController extends Controller
if ($request->filled('name')) { if ($request->filled('name')) {
$asset->name = $request->input('name'); $asset->name = $request->input('name');
} }
$asset->location_id = $asset->rtd_location_id; $asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) { if ($request->filled('location_id')) {
@ -720,7 +832,7 @@ class AssetsController extends Controller
} }
if ($asset->save()) { if ($asset->save()) {
$asset->logCheckin($target, e($request->input('note'))); event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note')));
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success'))); return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.success')));
} }
@ -728,6 +840,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error'))); return response()->json(Helper::formatStandardApiResponse('success', ['asset'=> e($asset->asset_tag)], trans('admin/hardware/message.checkin.error')));
} }
/** /**
* Mark an asset as audited * Mark an asset as audited
* *
@ -738,6 +851,8 @@ class AssetsController extends Controller
*/ */
public function audit(Request $request) public function audit(Request $request)
{ {
$this->authorize('audit', Asset::class); $this->authorize('audit', Asset::class);
$rules = [ $rules = [
'asset_tag' => 'required', 'asset_tag' => 'required',
@ -755,6 +870,7 @@ class AssetsController extends Controller
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first(); $asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
if ($asset) { if ($asset) {
// We don't want to log this as a normal update, so let's bypass that // We don't want to log this as a normal update, so let's bypass that
$asset->unsetEventDispatcher(); $asset->unsetEventDispatcher();
@ -784,8 +900,15 @@ class AssetsController extends Controller
} }
return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.$request->input('asset_tag').' not found')); return response()->json(Helper::formatStandardApiResponse('error', ['asset_tag'=> e($request->input('asset_tag'))], 'Asset with tag '.$request->input('asset_tag').' not found'));
} }
/** /**
* Returns JSON listing of all requestable assets * Returns JSON listing of all requestable assets
* *

View file

@ -47,6 +47,7 @@ class CategoriesController extends Controller
$categories = $categories->skip($offset)->take($limit)->get(); $categories = $categories->skip($offset)->take($limit)->get();
return (new CategoriesTransformer)->transformCategories($categories, $total); return (new CategoriesTransformer)->transformCategories($categories, $total);
} }
/** /**
@ -62,6 +63,7 @@ class CategoriesController extends Controller
$this->authorize('create', Category::class); $this->authorize('create', Category::class);
$category = new Category; $category = new Category;
$category->fill($request->all()); $category->fill($request->all());
$category->category_type = strtolower($request->input('category_type'));
$category = $request->handleImages($category); $category = $request->handleImages($category);
if ($category->save()) { if ($category->save()) {
@ -101,6 +103,7 @@ class CategoriesController extends Controller
$this->authorize('update', Category::class); $this->authorize('update', Category::class);
$category = Category::findOrFail($id); $category = Category::findOrFail($id);
$category->fill($request->all()); $category->fill($request->all());
$category->category_type = strtolower($request->input('category_type'));
$category = $request->handleImages($category); $category = $request->handleImages($category);
if ($category->save()) { if ($category->save()) {

View file

@ -9,6 +9,9 @@ use App\Models\Company;
use App\Models\Component; use App\Models\Component;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
use App\Models\Asset;
class ComponentsController extends Controller class ComponentsController extends Controller
{ {
@ -23,8 +26,25 @@ class ComponentsController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('view', Component::class); $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 =
[
'id',
'name',
'min_amt',
'order_number',
'serial',
'purchase_date',
'purchase_cost',
'qty',
'image',
];
$components = Company::scopeCompanyables(Component::select('components.*') $components = Company::scopeCompanyables(Component::select('components.*')
->with('company', 'location', 'category')); ->with('company', 'location', 'category', 'assets'));
if ($request->filled('search')) { if ($request->filled('search')) {
$components = $components->TextSearch($request->input('search')); $components = $components->TextSearch($request->input('search'));
@ -49,11 +69,12 @@ class ComponentsController extends Controller
// Check to make sure the limit is not higher than the max allowed // Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results'); ((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
$allowed_columns = ['id', 'name', 'min_amt', 'order_number', 'serial', 'purchase_date', 'purchase_cost', 'company', 'category', 'qty', 'location', 'image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at'; $sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort) { switch ($sort_override) {
case 'category': case 'category':
$components = $components->OrderCategory($order); $components = $components->OrderCategory($order);
break; break;
@ -64,7 +85,7 @@ class ComponentsController extends Controller
$components = $components->OrderCompany($order); $components = $components->OrderCompany($order);
break; break;
default: default:
$components = $components->orderBy($sort, $order); $components = $components->orderBy($column_sort, $order);
break; break;
} }
@ -74,6 +95,7 @@ class ComponentsController extends Controller
return (new ComponentsTransformer)->transformComponents($components, $total); return (new ComponentsTransformer)->transformComponents($components, $total);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
@ -163,11 +185,11 @@ class ComponentsController extends Controller
* @param Request $request * @param Request $request
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function getAssets(Request $request, $id) public function getAssets(Request $request, $id)
{ {
$this->authorize('view', \App\Models\Asset::class); $this->authorize('view', \App\Models\Asset::class);
$component = Component::findOrFail($id); $component = Component::findOrFail($id);
$assets = $component->assets(); $assets = $component->assets();
@ -178,4 +200,119 @@ class ComponentsController extends Controller
return (new ComponentsTransformer)->transformCheckedoutComponents($assets, $total); 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
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function checkout(Request $request, $componentId)
{
// Check if the component exists
if (is_null($component = Component::find($componentId))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.does_not_exist')));
}
$this->authorize('checkout', $component);
if ($component->numRemaining() >= $request->get('assigned_qty')) {
if (!$asset = Asset::find($request->input('assigned_to'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/hardware/message.does_not_exist')));
}
// Update the accessory data
$component->assigned_to = $request->input('assigned_to');
$component->assets()->attach($component->id, [
'component_id' => $component->id,
'created_at' => \Carbon::now(),
'assigned_qty' => $request->get('assigned_qty', 1),
'user_id' => \Auth::id(),
'asset_id' => $request->get('assigned_to')
]);
$component->logCheckout($request->input('note'), $asset);
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkout.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'Not enough components remaining: '.$component->numRemaining().' remaining, '.$request->get('assigned_qty').' requested.'));
}
/**
* Validate and store checkin data.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.1.8]
* @param Request $request
* @param $component_asset_id
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function checkin(Request $request, $component_asset_id)
{
if ($component_assets = \DB::table('components_assets')->find($component_asset_id)) {
if (is_null($component = Component::find($component_assets->component_id))) {
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/components/message.not_found')));
}
$this->authorize('checkin', $component);
$max_to_checkin = $component_assets->assigned_qty;
if ($max_to_checkin > 1) {
$validator = \Validator::make($request->all(), [
"checkin_qty" => "required|numeric|between:1,$max_to_checkin"
]);
if ($validator->fails()) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Checkin quantity must be between 1 and '.$max_to_checkin));
}
}
// 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));
// We have to modify the record to reflect the new qty that's
// actually checked out.
$component_assets->assigned_qty = $qty_remaining_in_checkout;
\Log::debug($component_asset_id.' - '.$qty_remaining_in_checkout.' remaining in record '.$component_assets->id);
\DB::table('components_assets')->where('id',
$component_asset_id)->update(['assigned_qty' => $qty_remaining_in_checkout]);
// If the checked-in qty is exactly the same as the assigned_qty,
// we can simply delete the associated components_assets record
if ($qty_remaining_in_checkout == 0) {
\DB::table('components_assets')->where('id', '=', $component_asset_id)->delete();
}
$asset = Asset::find($component_assets->asset_id);
event(new CheckoutableCheckedIn($component, $asset, \Auth::user(), $request->input('note'), \Carbon::now()));
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/components/message.checkin.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, 'No matching checkouts for that component join record'));
}
} }

View file

@ -25,6 +25,26 @@ class ConsumablesController extends Controller
public function index(Request $request) public function index(Request $request)
{ {
$this->authorize('index', Consumable::class); $this->authorize('index', Consumable::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 =
[
'id',
'name',
'order_number',
'min_amt',
'purchase_date',
'purchase_cost',
'company',
'category',
'model_number',
'item_no',
'qty',
'image',
];
$consumables = Company::scopeCompanyables( $consumables = Company::scopeCompanyables(
Consumable::select('consumables.*') Consumable::select('consumables.*')
->with('company', 'location', 'category', 'users', 'manufacturer') ->with('company', 'location', 'category', 'users', 'manufacturer')
@ -42,10 +62,19 @@ class ConsumablesController extends Controller
$consumables->where('category_id', '=', $request->input('category_id')); $consumables->where('category_id', '=', $request->input('category_id'));
} }
if ($request->filled('model_number')) {
$consumables->where('model_number','=',$request->input('model_number'));
}
if ($request->filled('manufacturer_id')) { if ($request->filled('manufacturer_id')) {
$consumables->where('manufacturer_id', '=', $request->input('manufacturer_id')); $consumables->where('manufacturer_id', '=', $request->input('manufacturer_id'));
} }
if ($request->filled('location_id')) {
$consumables->where('location_id','=',$request->input('location_id'));
}
// Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which // Set the offset to the API call's offset, unless the offset is higher than the actual count of items in which
// case we override with the actual count, so we should return 0 items. // case we override with the actual count, so we should return 0 items.
$offset = (($consumables) && ($request->get('offset') > $consumables->count())) ? $consumables->count() : $request->get('offset', 0); $offset = (($consumables) && ($request->get('offset') > $consumables->count())) ? $consumables->count() : $request->get('offset', 0);
@ -55,9 +84,12 @@ class ConsumablesController extends Controller
$allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image']; $allowed_columns = ['id', 'name', 'order_number', 'min_amt', 'purchase_date', 'purchase_cost', 'company', 'category', 'model_number', 'item_no', 'manufacturer', 'location', 'qty', 'image'];
$order = $request->input('order') === 'asc' ? 'asc' : 'desc'; $order = $request->input('order') === 'asc' ? 'asc' : 'desc';
$sort = in_array($request->input('sort'), $allowed_columns) ? $request->input('sort') : 'created_at';
switch ($sort) { $sort_override = $request->input('sort');
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'created_at';
switch ($sort_override) {
case 'category': case 'category':
$consumables = $consumables->OrderCategory($order); $consumables = $consumables->OrderCategory($order);
break; break;
@ -71,7 +103,7 @@ class ConsumablesController extends Controller
$consumables = $consumables->OrderCompany($order); $consumables = $consumables->OrderCompany($order);
break; break;
default: default:
$consumables = $consumables->orderBy($sort, $order); $consumables = $consumables->orderBy($column_sort, $order);
break; break;
} }
@ -160,13 +192,13 @@ class ConsumablesController extends Controller
} }
/** /**
* Returns a JSON response containing details on the users associated with this consumable. * Returns a JSON response containing details on the users associated with this consumable.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form. * @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0] * @since [v1.0]
* @param int $consumableId * @param int $consumableId
* @return array * @return array
*/ */
public function getDataView($consumableId) public function getDataView($consumableId)
{ {
@ -251,10 +283,10 @@ class ConsumablesController extends Controller
} }
/** /**
* Gets a paginated collection for the select2 menus * Gets a paginated collection for the select2 menus
* *
* @see \App\Http\Transformers\SelectlistTransformer * @see \App\Http\Transformers\SelectlistTransformer
*/ */
public function selectlist(Request $request) public function selectlist(Request $request)
{ {
$consumables = Consumable::select([ $consumables = Consumable::select([

View file

@ -3,12 +3,12 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Helpers\Helper; use App\Helpers\Helper;
use App\Http\Requests\ImageUploadRequest;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Transformers\LocationsTransformer; use App\Http\Transformers\LocationsTransformer;
use App\Http\Transformers\SelectlistTransformer; use App\Http\Transformers\SelectlistTransformer;
use App\Models\Location; use App\Models\Location;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -79,6 +79,7 @@ class LocationsController extends Controller
return (new LocationsTransformer)->transformLocations($locations, $total); return (new LocationsTransformer)->transformLocations($locations, $total);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *

View file

@ -47,6 +47,7 @@ class StatuslabelsController extends Controller
return (new StatuslabelsTransformer)->transformStatuslabels($statuslabels, $total); return (new StatuslabelsTransformer)->transformStatuslabels($statuslabels, $total);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
@ -71,6 +72,10 @@ class StatuslabelsController extends Controller
$statuslabel->deployable = $statusType['deployable']; $statuslabel->deployable = $statusType['deployable'];
$statuslabel->pending = $statusType['pending']; $statuslabel->pending = $statusType['pending'];
$statuslabel->archived = $statusType['archived']; $statuslabel->archived = $statusType['archived'];
$statuslabel->color = $request->input('color');
$statuslabel->show_in_nav = $request->input('show_in_nav', 0);
$statuslabel->default_label = $request->input('default_label', 0);
if ($statuslabel->save()) { if ($statuslabel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $statuslabel, trans('admin/statuslabels/message.create.success'))); return response()->json(Helper::formatStandardApiResponse('success', $statuslabel, trans('admin/statuslabels/message.create.success')));
@ -108,7 +113,7 @@ class StatuslabelsController extends Controller
{ {
$this->authorize('update', Statuslabel::class); $this->authorize('update', Statuslabel::class);
$statuslabel = Statuslabel::findOrFail($id); $statuslabel = Statuslabel::findOrFail($id);
$request->except('deployable', 'pending', 'archived'); $request->except('deployable', 'pending', 'archived');
if (! $request->filled('type')) { if (! $request->filled('type')) {
@ -121,6 +126,9 @@ class StatuslabelsController extends Controller
$statuslabel->deployable = $statusType['deployable']; $statuslabel->deployable = $statusType['deployable'];
$statuslabel->pending = $statusType['pending']; $statuslabel->pending = $statusType['pending'];
$statuslabel->archived = $statusType['archived']; $statuslabel->archived = $statusType['archived'];
$statuslabel->color = $request->input('color');
$statuslabel->show_in_nav = $request->input('show_in_nav', 0);
$statuslabel->default_label = $request->input('default_label', 0);
if ($statuslabel->save()) { if ($statuslabel->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $statuslabel, trans('admin/statuslabels/message.update.success'))); return response()->json(Helper::formatStandardApiResponse('success', $statuslabel, trans('admin/statuslabels/message.update.success')));
@ -153,7 +161,7 @@ class StatuslabelsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/statuslabels/message.assoc_assets'))); return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/statuslabels/message.assoc_assets')));
} }
/** /**
* Show a count of assets by status label for pie chart * Show a count of assets by status label for pie chart
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
@ -164,10 +172,7 @@ class StatuslabelsController extends Controller
{ {
$this->authorize('view', Statuslabel::class); $this->authorize('view', Statuslabel::class);
$statuslabels = Statuslabel::with('assets') $statuslabels = Statuslabel::withCount('assets')->get();
->groupBy('id')
->withCount('assets as assets_count')
->get();
$labels = []; $labels = [];
$points = []; $points = [];
@ -183,8 +188,8 @@ class StatuslabelsController extends Controller
$colors_array[] = $statuslabel->color; $colors_array[] = $statuslabel->color;
} else { } else {
$colors_array[] = Helper::defaultChartColors($default_color_count); $colors_array[] = Helper::defaultChartColors($default_color_count);
$default_color_count++;
} }
$default_color_count++;
} }
} }
@ -228,9 +233,11 @@ class StatuslabelsController extends Controller
$total = $assets->count(); $total = $assets->count();
$assets = $assets->skip($offset)->take($limit)->get(); $assets = $assets->skip($offset)->take($limit)->get();
return (new AssetsTransformer)->transformAssets($assets, $total); return (new AssetsTransformer)->transformAssets($assets, $total);
} }
/** /**
* Returns a boolean response based on whether the status label * Returns a boolean response based on whether the status label
* is one that is deployable. * is one that is deployable.

View file

@ -8,7 +8,7 @@ use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\SuppliersTransformer; use App\Http\Transformers\SuppliersTransformer;
use App\Models\Supplier; use App\Models\Supplier;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
class SuppliersController extends Controller class SuppliersController extends Controller
@ -24,11 +24,12 @@ class SuppliersController extends Controller
{ {
$this->authorize('view', Supplier::class); $this->authorize('view', Supplier::class);
$allowed_columns = ['id', 'name', 'address', 'phone', 'contact', 'fax', 'email', 'image', 'assets_count', 'licenses_count', 'accessories_count', 'url']; $allowed_columns = ['id', 'name', 'address', 'phone', 'contact', 'fax', 'email', 'image', 'assets_count', 'licenses_count', 'accessories_count', 'url'];
$suppliers = Supplier::select( $suppliers = Supplier::select(
['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes'] ['id', 'name', 'address', 'address2', 'city', 'state', 'country', 'fax', 'phone', 'email', 'contact', 'created_at', 'updated_at', 'deleted_at', 'image', 'notes']
)->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('accessories as accessories_count'); )->withCount('assets as assets_count')->withCount('licenses as licenses_count')->withCount('accessories as accessories_count');
if ($request->filled('search')) { if ($request->filled('search')) {
$suppliers = $suppliers->TextSearch($request->input('search')); $suppliers = $suppliers->TextSearch($request->input('search'));
} }
@ -50,6 +51,7 @@ class SuppliersController extends Controller
return (new SuppliersTransformer)->transformSuppliers($suppliers, $total); return (new SuppliersTransformer)->transformSuppliers($suppliers, $total);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *

View file

@ -68,12 +68,17 @@ class UsersController extends Controller
->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count'); ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count');
$users = Company::scopeCompanyables($users); $users = Company::scopeCompanyables($users);
if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) { if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed(); $users = $users->onlyTrashed();
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) { } elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed(); $users = $users->withTrashed();
} }
if ($request->filled('activated')) {
$users = $users->where('users.activated', '=', $request->input('activated'));
}
if ($request->filled('company_id')) { if ($request->filled('company_id')) {
$users = $users->where('users.company_id', '=', $request->input('company_id')); $users = $users->where('users.company_id', '=', $request->input('company_id'));
} }
@ -90,6 +95,30 @@ class UsersController extends Controller
$users = $users->where('users.username', '=', $request->input('username')); $users = $users->where('users.username', '=', $request->input('username'));
} }
if ($request->filled('first_name')) {
$users = $users->where('users.first_name', '=', $request->input('first_name'));
}
if ($request->filled('last_name')) {
$users = $users->where('users.last_name', '=', $request->input('last_name'));
}
if ($request->filled('employee_num')) {
$users = $users->where('users.employee_num', '=', $request->input('employee_num'));
}
if ($request->filled('state')) {
$users = $users->where('users.state', '=', $request->input('state'));
}
if ($request->filled('country')) {
$users = $users->where('users.country', '=', $request->input('country'));
}
if ($request->filled('zip')) {
$users = $users->where('users.zip', '=', $request->input('zip'));
}
if ($request->filled('group_id')) { if ($request->filled('group_id')) {
$users = $users->ByGroup($request->get('group_id')); $users = $users->ByGroup($request->get('group_id'));
} }
@ -98,6 +127,10 @@ class UsersController extends Controller
$users = $users->where('users.department_id', '=', $request->input('department_id')); $users = $users->where('users.department_id', '=', $request->input('department_id'));
} }
if ($request->filled('manager_id')) {
$users = $users->where('users.manager_id','=',$request->input('manager_id'));
}
if ($request->filled('search')) { if ($request->filled('search')) {
$users = $users->TextSearch($request->input('search')); $users = $users->TextSearch($request->input('search'));
} }
@ -112,6 +145,7 @@ class UsersController extends Controller
// Check to make sure the limit is not higher than the max allowed // Check to make sure the limit is not higher than the max allowed
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results'); ((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
switch ($request->input('sort')) { switch ($request->input('sort')) {
case 'manager': case 'manager':
$users = $users->OrderManager($order); $users = $users->OrderManager($order);
@ -201,6 +235,8 @@ class UsersController extends Controller
return (new SelectlistTransformer)->transformSelectlist($users); return (new SelectlistTransformer)->transformSelectlist($users);
} }
/** /**
* Store a newly created resource in storage. * Store a newly created resource in storage.
* *
@ -259,6 +295,7 @@ class UsersController extends Controller
return (new UsersTransformer)->transformUser($user); return (new UsersTransformer)->transformUser($user);
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
* *
@ -274,14 +311,21 @@ class UsersController extends Controller
$user = User::findOrFail($id); $user = User::findOrFail($id);
// This is a janky hack to prevent people from changing admin demo user data on the public demo. /**
// The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder. * This is a janky hack to prevent people from changing admin demo user data on the public demo.
// Thanks, jerks. You are why we can't have nice things. - snipe *
* The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
*
* Thanks, jerks. You are why we can't have nice things. - snipe
*
*/
if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) { if ((($id == 1) || ($id == 2)) && (config('app.lock_passwords'))) {
return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.')); return response()->json(Helper::formatStandardApiResponse('error', null, 'Permission denied. You cannot update user information via API on the demo.'));
} }
$user->fill($request->all()); $user->fill($request->all());
if ($user->id == $request->input('manager_id')) { if ($user->id == $request->input('manager_id')) {
@ -305,6 +349,8 @@ class UsersController extends Controller
$user->permissions = $permissions_array; $user->permissions = $permissions_array;
} }
// Update the location of any assets checked out to this user // Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class) Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
@ -328,6 +374,7 @@ class UsersController extends Controller
$user->groups()->sync([]); $user->groups()->sync([]);
} }
return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update'))); return response()->json(Helper::formatStandardApiResponse('success', (new UsersTransformer)->transformUser($user), trans('admin/users/message.success.update')));
} }
@ -389,13 +436,13 @@ class UsersController extends Controller
* @param $userId * @param $userId
* @return string JSON * @return string JSON
*/ */
public function assets($id) public function assets(Request $request, $id)
{ {
$this->authorize('view', User::class); $this->authorize('view', User::class);
$this->authorize('view', Asset::class); $this->authorize('view', Asset::class);
$assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model')->get(); $assets = Asset::where('assigned_to', '=', $id)->where('assigned_type', '=', User::class)->with('model')->get();
return (new AssetsTransformer)->transformAssets($assets, $assets->count()); return (new AssetsTransformer)->transformAssets($assets, $assets->count(), $request);
} }
/** /**
@ -458,8 +505,9 @@ class UsersController extends Controller
return response()->json(['message' => trans('admin/settings/general.two_factor_reset_error')], 500); return response()->json(['message' => trans('admin/settings/general.two_factor_reset_error')], 500);
} }
} }
return response()->json(['message' => 'No ID provided'], 500); return response()->json(['message' => 'No ID provided'], 500);
} }
/** /**
@ -474,4 +522,28 @@ class UsersController extends Controller
{ {
return (new UsersTransformer)->transformUser($request->user()); return (new UsersTransformer)->transformUser($request->user());
} }
/**
* Restore a soft-deleted user.
*
* @author [E. Taylor] [<dev@evantaylor.name>]
* @param int $userId
* @since [v6.0.0]
* @return JsonResponse
*/
public function restore($userId = null)
{
// Get asset information
$user = User::withTrashed()->find($userId);
$this->authorize('delete', $user);
if (isset($user->id)) {
// Restore the user
User::withTrashed()->where('id', $userId)->restore();
return response()->json(Helper::formatStandardApiResponse('success', null, trans('admin/users/message.success.restored')));
}
$id = $userId;
return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id'))), 200);
}
} }

View file

@ -23,14 +23,14 @@ use View;
class AssetMaintenancesController extends Controller class AssetMaintenancesController extends Controller
{ {
/** /**
* Checks for permissions for this action. * Checks for permissions for this action.
* *
* @todo This should be replaced with middleware and/or policies * @todo This should be replaced with middleware and/or policies
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return View * @return View
*/ */
private static function getInsufficientPermissionsRedirect() private static function getInsufficientPermissionsRedirect()
{ {
return redirect()->route('maintenances.index') return redirect()->route('maintenances.index')
@ -38,16 +38,16 @@ class AssetMaintenancesController extends Controller
} }
/** /**
* Returns a view that invokes the ajax tables which actually contains * Returns a view that invokes the ajax tables which actually contains
* the content for the asset maintenances listing, which is generated in getDatatable. * the content for the asset maintenances listing, which is generated in getDatatable.
* *
* @todo This should be replaced with middleware and/or policies * @todo This should be replaced with middleware and/or policies
* @see AssetMaintenancesController::getDatatable() method that generates the JSON response * @see AssetMaintenancesController::getDatatable() method that generates the JSON response
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return View * @return View
*/ */
public function index() public function index()
{ {
return view('asset_maintenances/index'); return view('asset_maintenances/index');
@ -84,21 +84,21 @@ class AssetMaintenancesController extends Controller
} }
/** /**
* Validates and stores the new asset maintenance * Validates and stores the new asset maintenance
* *
* @see AssetMaintenancesController::getCreate() method for the form * @see AssetMaintenancesController::getCreate() method for the form
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return mixed * @return mixed
*/ */
public function store(Request $request) public function store(Request $request)
{ {
// create a new model instance // create a new model instance
$assetMaintenance = new AssetMaintenance(); $assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id'); $assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty'); $assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = $request->input('cost'); $assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->notes = $request->input('notes'); $assetMaintenance->notes = $request->input('notes');
$asset = Asset::find($request->input('asset_id')); $asset = Asset::find($request->input('asset_id'));
@ -134,15 +134,15 @@ class AssetMaintenancesController extends Controller
} }
/** /**
* Returns a form view to edit a selected asset maintenance. * Returns a form view to edit a selected asset maintenance.
* *
* @see AssetMaintenancesController::postEdit() method that stores the data * @see AssetMaintenancesController::postEdit() method that stores the data
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return mixed * @return mixed
*/ */
public function edit($assetMaintenanceId = null) public function edit($assetMaintenanceId = null)
{ {
// Check if the asset maintenance exists // Check if the asset maintenance exists
@ -204,10 +204,10 @@ class AssetMaintenancesController extends Controller
return static::getInsufficientPermissionsRedirect(); return static::getInsufficientPermissionsRedirect();
} }
$assetMaintenance->supplier_id = e($request->input('supplier_id')); $assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = e($request->input('is_warranty')); $assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = Helper::ParseFloat(e($request->input('cost'))); $assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->notes = e($request->input('notes')); $assetMaintenance->notes = $request->input('notes');
$asset = Asset::find(request('asset_id')); $asset = Asset::find(request('asset_id'));
@ -240,7 +240,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate); $assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
} }
// Was the asset maintenance created? // Was the asset maintenance created?
if ($assetMaintenance->save()) { if ($assetMaintenance->save()) {
// Redirect to the new asset maintenance page // Redirect to the new asset maintenance page
@ -252,14 +252,14 @@ class AssetMaintenancesController extends Controller
} }
/** /**
* Delete an asset maintenance * Delete an asset maintenance
* *
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return mixed * @return mixed
*/ */
public function destroy($assetMaintenanceId) public function destroy($assetMaintenanceId)
{ {
// Check if the asset maintenance exists // Check if the asset maintenance exists
@ -280,14 +280,14 @@ class AssetMaintenancesController extends Controller
} }
/** /**
* View an asset maintenance * View an asset maintenance
* *
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId * @param int $assetMaintenanceId
* @version v1.0 * @version v1.0
* @since [v1.8] * @since [v1.8]
* @return View * @return View
*/ */
public function show($assetMaintenanceId) public function show($assetMaintenanceId)
{ {
// Check if the asset maintenance exists // Check if the asset maintenance exists

View file

@ -7,6 +7,8 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckinRequest; use App\Http\Requests\AssetCheckinRequest;
use App\Models\Asset; use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
@ -109,6 +111,16 @@ class AssetCheckinController extends Controller
$checkin_at = $request->input('checkin_at'); $checkin_at = $request->input('checkin_at');
} }
// Get all pending Acceptances for this asset and delete them
$acceptances = CheckoutAcceptance::pending()->whereHasMorph('checkoutable',
[Asset::class],
function (Builder $query) use ($asset) {
$query->where('id', $asset->id);
})->get();
$acceptances->map(function($acceptance) {
$acceptance->delete();
});
// Was the asset updated? // Was the asset updated?
if ($asset->save()) { if ($asset->save()) {
event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at)); event(new CheckoutableCheckedIn($asset, $target, Auth::user(), $request->input('note'), $checkin_at));

View file

@ -9,6 +9,7 @@ use App\Models\Actionlog;
use App\Models\Asset; use App\Models\Asset;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use enshrined\svgSanitize\Sanitizer;
class AssetFilesController extends Controller class AssetFilesController extends Controller
{ {
@ -36,9 +37,29 @@ class AssetFilesController extends Controller
} }
foreach ($request->file('file') as $file) { foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension(); $extension = $file->getClientOriginalExtension();
$file_name = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension; $file_name = 'hardware-'.$asset->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension=='svg') {
\Log::debug('This is an SVG');
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/assets/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/assets/'.$file_name, file_get_contents($file)); Storage::put('private_uploads/assets/'.$file_name, file_get_contents($file));
}
$asset->logUpload($file_name, e($request->get('notes'))); $asset->logUpload($file_name, e($request->get('notes')));
} }
@ -127,7 +148,6 @@ class AssetFilesController extends Controller
return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success')); return redirect()->back()->with('success', trans('admin/hardware/message.deletefile.success'));
} }
$log->delete();
return redirect()->back() return redirect()->back()
->with('success', trans('admin/hardware/message.deletefile.success')); ->with('success', trans('admin/hardware/message.deletefile.success'));

View file

@ -129,22 +129,22 @@ class AssetsController extends Controller
$asset->asset_tag = $asset_tags[$a]; $asset->asset_tag = $asset_tags[$a];
} }
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id')); $asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id'); $asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number'); $asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes'); $asset->notes = $request->input('notes');
$asset->user_id = Auth::id(); $asset->user_id = Auth::id();
$asset->archived = '0'; $asset->archived = '0';
$asset->physical = '1'; $asset->physical = '1';
$asset->depreciate = '0'; $asset->depreciate = '0';
$asset->status_id = request('status_id', 0); $asset->status_id = request('status_id', 0);
$asset->warranty_months = request('warranty_months', null); $asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost')); $asset->purchase_cost = Helper::ParseCurrency($request->get('purchase_cost'));
$asset->purchase_date = request('purchase_date', null); $asset->purchase_date = request('purchase_date', null);
$asset->assigned_to = request('assigned_to', null); $asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', 0); $asset->supplier_id = request('supplier_id', 0);
$asset->requestable = request('requestable', 0); $asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null); $asset->rtd_location_id = request('rtd_location_id', null);
if (! empty($settings->audit_interval)) { if (! empty($settings->audit_interval)) {
$asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString(); $asset->next_audit_date = Carbon::now()->addMonths($settings->audit_interval)->toDateString();
@ -296,7 +296,7 @@ class AssetsController extends Controller
$asset->status_id = $request->input('status_id', null); $asset->status_id = $request->input('status_id', null);
$asset->warranty_months = $request->input('warranty_months', null); $asset->warranty_months = $request->input('warranty_months', null);
$asset->purchase_cost = Helper::ParseFloat($request->input('purchase_cost', null)); $asset->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost', null));
$asset->purchase_date = $request->input('purchase_date', null); $asset->purchase_date = $request->input('purchase_date', null);
$asset->supplier_id = $request->input('supplier_id', null); $asset->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null); $asset->expected_checkin = $request->input('expected_checkin', null);
@ -357,6 +357,7 @@ class AssetsController extends Controller
} }
} }
if ($asset->save()) { if ($asset->save()) {
return redirect()->route('hardware.show', $assetId) return redirect()->route('hardware.show', $assetId)
->with('success', trans('admin/hardware/message.update.success')); ->with('success', trans('admin/hardware/message.update.success'));
@ -828,8 +829,8 @@ class AssetsController extends Controller
Storage::putFileAs($path, $upload, $file_name); Storage::putFileAs($path, $upload, $file_name);
} }
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
$asset->logAudit($request->input('note'), $request->input('location_id'), $file_name);
return redirect()->to('hardware')->with('success', trans('admin/hardware/message.audit.success')); return redirect()->to('hardware')->with('success', trans('admin/hardware/message.audit.success'));
} }
} }

View file

@ -32,7 +32,8 @@ class BulkAssetsController extends Controller
return redirect()->back()->with('error', 'No assets selected'); return redirect()->back()->with('error', 'No assets selected');
} }
$asset_ids = array_keys($request->input('ids'));
$asset_ids = array_values(array_unique($request->input('ids')));
if ($request->filled('bulk_actions')) { if ($request->filled('bulk_actions')) {
switch ($request->input('bulk_actions')) { switch ($request->input('bulk_actions')) {
@ -51,7 +52,7 @@ class BulkAssetsController extends Controller
return view('hardware/bulk-delete')->with('assets', $assets); return view('hardware/bulk-delete')->with('assets', $assets);
case 'edit': case 'edit':
return view('hardware/bulk') return view('hardware/bulk')
->with('assets', request('ids')) ->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList()); ->with('statuslabel_list', Helper::statusLabelList());
} }
} }
@ -92,6 +93,7 @@ class BulkAssetsController extends Controller
|| ($request->filled('model_id')) || ($request->filled('model_id'))
) { ) {
foreach ($assets as $assetId) { foreach ($assets as $assetId) {
$this->update_array = []; $this->update_array = [];
$this->conditionallyAddItem('purchase_date') $this->conditionallyAddItem('purchase_date')
@ -104,7 +106,7 @@ class BulkAssetsController extends Controller
->conditionallyAddItem('warranty_months'); ->conditionallyAddItem('warranty_months');
if ($request->filled('purchase_cost')) { if ($request->filled('purchase_cost')) {
$this->update_array['purchase_cost'] = Helper::ParseFloat($request->input('purchase_cost')); $this->update_array['purchase_cost'] = Helper::ParseCurrency($request->input('purchase_cost'));
} }
if ($request->filled('company_id')) { if ($request->filled('company_id')) {

View file

@ -29,6 +29,7 @@ class ForgotPasswordController extends Controller
public function __construct() public function __construct()
{ {
$this->middleware('guest'); $this->middleware('guest');
$this->middleware('throttle:5,1', ['except' => 'showLinkRequestForm']);
} }
/** /**
@ -66,13 +67,21 @@ class ForgotPasswordController extends Controller
* Once we have attempted to send the link, we will examine the response * Once we have attempted to send the link, we will examine the response
* then see the message we need to show to the user. Finally, we'll send out a proper response. * then see the message we need to show to the user. Finally, we'll send out a proper response.
*/ */
$response = $this->broker()->sendResetLink(
array_merge( $response = null;
$request->only('username'),
['activated' => '1'], try {
['ldap_import' => '0'] $response = $this->broker()->sendResetLink(
) array_merge(
); $request->only('username'),
['activated' => '1'],
['ldap_import' => '0']
)
);
} catch(\Exception $e) {
\Log::info('Password reset attempt: User '.$request->input('username').'failed with exception: '.$e );
}
if ($response === \Password::RESET_LINK_SENT) { if ($response === \Password::RESET_LINK_SENT) {
\Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent'); \Log::info('Password reset attempt: User '.$request->input('username').' WAS found, password reset sent');
@ -91,10 +100,10 @@ class ForgotPasswordController extends Controller
* *
* Instead we tell the user we've sent an email even though we haven't. * Instead we tell the user we've sent an email even though we haven't.
* It's bad UX, but better security. The compromises we sometimes have to make. * It's bad UX, but better security. The compromises we sometimes have to make.
*/ */
// Regardless of response, we do not want to disclose the status of a user account, // Regardless of response, we do not want to disclose the status of a user account,
// so we give them a generic "If this exists, we're TOTALLY gonna email you" response // so we give them a generic "If this exists, we're TOTALLY gonna email you" response
return redirect()->route('login')->with('success', trans('passwords.sent')); return redirect()->route('login')->with('success', trans('passwords.sent'));
} }
} }

View file

@ -154,6 +154,9 @@ class LoginController extends Controller
{ {
$header_name = Setting::getSettings()->login_remote_user_header_name ?: 'REMOTE_USER'; $header_name = Setting::getSettings()->login_remote_user_header_name ?: 'REMOTE_USER';
$remote_user = $request->server($header_name); $remote_user = $request->server($header_name);
if (!isset($remote_user)) {
$remote_user = $request->server('REDIRECT_'.$header_name);
}
if (Setting::getSettings()->login_remote_user_enabled == '1' && isset($remote_user) && ! empty($remote_user)) { if (Setting::getSettings()->login_remote_user_enabled == '1' && isset($remote_user) && ! empty($remote_user)) {
Log::debug("Authenticating via HTTP header $header_name."); Log::debug("Authenticating via HTTP header $header_name.");

View file

@ -80,6 +80,11 @@ class BulkAssetModelsController extends Controller
$update_array['depreciation_id'] = $request->input('depreciation_id'); $update_array['depreciation_id'] = $request->input('depreciation_id');
} }
if ($request->filled('requestable') != '') {
$update_array['requestable'] = $request->input('requestable');
}
if (count($update_array) > 0) { if (count($update_array) > 0) {
AssetModel::whereIn('id', $models_raw_array)->update($update_array); AssetModel::whereIn('id', $models_raw_array)->update($update_array);

View file

@ -39,6 +39,8 @@ trait CheckInOutRequest
switch (request('checkout_to_type')) { switch (request('checkout_to_type')) {
case 'location': case 'location':
$asset->location_id = $target->id; $asset->location_id = $target->id;
Asset::where('assigned_type', 'App\Models\Asset')->where('assigned_to', $asset->id)
->update(['location_id' => $asset->location_id]);
break; break;
case 'asset': case 'asset':
$asset->location_id = $target->rtd_location_id; $asset->location_id = $target->rtd_location_id;

View file

@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ImageUploadRequest; use App\Http\Requests\ImageUploadRequest;
use App\Models\Company; use App\Models\Company;
use App\Models\Component; use App\Models\Component;
use App\Helpers\Helper;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@ -36,6 +37,7 @@ class ComponentsController extends Controller
return view('components/index'); return view('components/index');
} }
/** /**
* Returns a form to create a new component. * Returns a form to create a new component.
* *
@ -67,17 +69,17 @@ class ComponentsController extends Controller
{ {
$this->authorize('create', Component::class); $this->authorize('create', Component::class);
$component = new Component(); $component = new Component();
$component->name = $request->input('name'); $component->name = $request->input('name');
$component->category_id = $request->input('category_id'); $component->category_id = $request->input('category_id');
$component->location_id = $request->input('location_id'); $component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id')); $component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null); $component->order_number = $request->input('order_number', null);
$component->min_amt = $request->input('min_amt', null); $component->min_amt = $request->input('min_amt', null);
$component->serial = $request->input('serial', null); $component->serial = $request->input('serial', null);
$component->purchase_date = $request->input('purchase_date', null); $component->purchase_date = $request->input('purchase_date', null);
$component->purchase_cost = $request->input('purchase_cost', null); $component->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost', null));
$component->qty = $request->input('qty'); $component->qty = $request->input('qty');
$component->user_id = Auth::id(); $component->user_id = Auth::id();
$component = $request->handleImages($component); $component = $request->handleImages($component);
@ -109,6 +111,7 @@ class ComponentsController extends Controller
return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist')); return redirect()->route('components.index')->with('error', trans('admin/components/message.does_not_exist'));
} }
/** /**
* Return a view to edit a component. * Return a view to edit a component.
* *
@ -139,16 +142,16 @@ class ComponentsController extends Controller
$this->authorize('update', $component); $this->authorize('update', $component);
// Update the component data // Update the component data
$component->name = $request->input('name'); $component->name = $request->input('name');
$component->category_id = $request->input('category_id'); $component->category_id = $request->input('category_id');
$component->location_id = $request->input('location_id'); $component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id')); $component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number'); $component->order_number = $request->input('order_number');
$component->min_amt = $request->input('min_amt'); $component->min_amt = $request->input('min_amt');
$component->serial = $request->input('serial'); $component->serial = $request->input('serial');
$component->purchase_date = $request->input('purchase_date'); $component->purchase_date = $request->input('purchase_date');
$component->purchase_cost = request('purchase_cost'); $component->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$component->qty = $request->input('qty'); $component->qty = $request->input('qty');
$component = $request->handleImages($component); $component = $request->handleImages($component);

View file

@ -65,19 +65,19 @@ class ConsumablesController extends Controller
{ {
$this->authorize('create', Consumable::class); $this->authorize('create', Consumable::class);
$consumable = new Consumable(); $consumable = new Consumable();
$consumable->name = $request->input('name'); $consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id'); $consumable->category_id = $request->input('category_id');
$consumable->location_id = $request->input('location_id'); $consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id')); $consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number'); $consumable->order_number = $request->input('order_number');
$consumable->min_amt = $request->input('min_amt'); $consumable->min_amt = $request->input('min_amt');
$consumable->manufacturer_id = $request->input('manufacturer_id'); $consumable->manufacturer_id = $request->input('manufacturer_id');
$consumable->model_number = $request->input('model_number'); $consumable->model_number = $request->input('model_number');
$consumable->item_no = $request->input('item_no'); $consumable->item_no = $request->input('item_no');
$consumable->purchase_date = $request->input('purchase_date'); $consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = Helper::ParseFloat($request->input('purchase_cost')); $consumable->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$consumable->qty = $request->input('qty'); $consumable->qty = $request->input('qty');
$consumable->user_id = Auth::id(); $consumable->user_id = Auth::id();
$consumable = $request->handleImages($consumable); $consumable = $request->handleImages($consumable);
@ -128,18 +128,18 @@ class ConsumablesController extends Controller
$this->authorize($consumable); $this->authorize($consumable);
$consumable->name = $request->input('name'); $consumable->name = $request->input('name');
$consumable->category_id = $request->input('category_id'); $consumable->category_id = $request->input('category_id');
$consumable->location_id = $request->input('location_id'); $consumable->location_id = $request->input('location_id');
$consumable->company_id = Company::getIdForCurrentUser($request->input('company_id')); $consumable->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$consumable->order_number = $request->input('order_number'); $consumable->order_number = $request->input('order_number');
$consumable->min_amt = $request->input('min_amt'); $consumable->min_amt = $request->input('min_amt');
$consumable->manufacturer_id = $request->input('manufacturer_id'); $consumable->manufacturer_id = $request->input('manufacturer_id');
$consumable->model_number = $request->input('model_number'); $consumable->model_number = $request->input('model_number');
$consumable->item_no = $request->input('item_no'); $consumable->item_no = $request->input('item_no');
$consumable->purchase_date = $request->input('purchase_date'); $consumable->purchase_date = $request->input('purchase_date');
$consumable->purchase_cost = Helper::ParseFloat($request->input('purchase_cost')); $consumable->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$consumable->qty = Helper::ParseFloat($request->input('qty')); $consumable->qty = Helper::ParseFloat($request->input('qty'));
$consumable = $request->handleImages($consumable); $consumable = $request->handleImages($consumable);

View file

@ -125,8 +125,8 @@ class DepreciationsController extends Controller
$this->authorize('update', $depreciation); $this->authorize('update', $depreciation);
// Depreciation data // Depreciation data
$depreciation->name = $request->input('name'); $depreciation->name = $request->input('name');
$depreciation->months = $request->input('months'); $depreciation->months = $request->input('months');
$depreciation->depreciation_min = $request->input('depreciation_min'); $depreciation->depreciation_min = $request->input('depreciation_min');
// Was the asset created? // Was the asset created?

View file

@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
use App\Models\License; use App\Models\License;
use App\Models\LicenseSeat; use App\Models\LicenseSeat;
use App\Models\User; use App\Models\User;
use App\Models\Asset;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Input;
@ -80,7 +81,12 @@ class LicenseCheckinController extends Controller
// Ooops.. something went wrong // Ooops.. something went wrong
return redirect()->back()->withInput()->withErrors($validator); return redirect()->back()->withInput()->withErrors($validator);
} }
$return_to = User::find($licenseSeat->assigned_to);
if($licenseSeat->assigned_to != null){
$return_to = User::find($licenseSeat->assigned_to);
} else {
$return_to = Asset::find($licenseSeat->asset_id);
}
// Update the asset data // Update the asset data
$licenseSeat->assigned_to = null; $licenseSeat->assigned_to = null;

View file

@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest; use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog; use App\Models\Actionlog;
use App\Models\License; use App\Models\License;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class LicenseFilesController extends Controller class LicenseFilesController extends Controller
{ {
@ -37,26 +37,39 @@ class LicenseFilesController extends Controller
Storage::makeDirectory('private_uploads/licenses', 775); Storage::makeDirectory('private_uploads/licenses', 775);
} }
$upload_success = false;
foreach ($request->file('file') as $file) { foreach ($request->file('file') as $file) {
$file_name = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$file->getClientOriginalExtension())).'.'.$file->getClientOriginalExtension();
$upload_success = $file->storeAs('private_uploads/licenses', $file_name); $extension = $file->getClientOriginalExtension();
// $upload_success = $file->storeAs('private_uploads/licenses/'.$file_name, $file); $file_name = 'license-'.$license->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
// Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/licenses/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/licenses/'.$file_name, file_get_contents($file));
}
//Log the upload to the log //Log the upload to the log
$license->logUpload($file_name, e($request->input('notes'))); $license->logUpload($file_name, e($request->input('notes')));
} }
// This being called from a modal seems to confuse redirect()->back()
// It thinks we should go to the dashboard. As this is only used
// from the modal at present, hardcode the redirect. Longterm
// maybe we evaluate something else.
if ($upload_success) {
return redirect()->route('licenses.show', $license->id)->with('success', trans('admin/licenses/message.upload.success'));
}
return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.error')); return redirect()->route('licenses.show', $license->id)->with('success', trans('admin/licenses/message.upload.success'));
} }
return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.nofiles')); return redirect()->route('licenses.show', $license->id)->with('error', trans('admin/licenses/message.upload.nofiles'));
@ -153,6 +166,7 @@ class LicenseFilesController extends Controller
} }
return StorageHelper::downloader($file); return StorageHelper::downloader($file);
} }
} }
} }

View file

@ -76,27 +76,27 @@ class LicensesController extends Controller
// create a new model instance // create a new model instance
$license = new License(); $license = new License();
// Save the license data // Save the license data
$license->company_id = Company::getIdForCurrentUser($request->input('company_id')); $license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id'); $license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date'); $license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email'); $license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name'); $license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained', 0); $license->maintained = $request->input('maintained', 0);
$license->manufacturer_id = $request->input('manufacturer_id'); $license->manufacturer_id = $request->input('manufacturer_id');
$license->name = $request->input('name'); $license->name = $request->input('name');
$license->notes = $request->input('notes'); $license->notes = $request->input('notes');
$license->order_number = $request->input('order_number'); $license->order_number = $request->input('order_number');
$license->purchase_cost = $request->input('purchase_cost'); $license->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$license->purchase_date = $request->input('purchase_date'); $license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order'); $license->purchase_order = $request->input('purchase_order');
$license->purchase_order = $request->input('purchase_order'); $license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0); $license->reassignable = $request->input('reassignable', 0);
$license->seats = $request->input('seats'); $license->seats = $request->input('seats');
$license->serial = $request->input('serial'); $license->serial = $request->input('serial');
$license->supplier_id = $request->input('supplier_id'); $license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id'); $license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date'); $license->termination_date = $request->input('termination_date');
$license->user_id = Auth::id(); $license->user_id = Auth::id();
if ($license->save()) { if ($license->save()) {
return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success')); return redirect()->route('licenses.index')->with('success', trans('admin/licenses/message.create.success'));
@ -154,25 +154,25 @@ class LicensesController extends Controller
$this->authorize('update', $license); $this->authorize('update', $license);
$license->company_id = Company::getIdForCurrentUser($request->input('company_id')); $license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id'); $license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date'); $license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email'); $license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name'); $license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained', 0); $license->maintained = $request->input('maintained',0);
$license->name = $request->input('name'); $license->name = $request->input('name');
$license->notes = $request->input('notes'); $license->notes = $request->input('notes');
$license->order_number = $request->input('order_number'); $license->order_number = $request->input('order_number');
$license->purchase_cost = $request->input('purchase_cost'); $license->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$license->purchase_date = $request->input('purchase_date'); $license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order'); $license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0); $license->reassignable = $request->input('reassignable', 0);
$license->serial = $request->input('serial'); $license->serial = $request->input('serial');
$license->termination_date = $request->input('termination_date'); $license->termination_date = $request->input('termination_date');
$license->seats = e($request->input('seats')); $license->seats = e($request->input('seats'));
$license->manufacturer_id = $request->input('manufacturer_id'); $license->manufacturer_id = $request->input('manufacturer_id');
$license->supplier_id = $request->input('supplier_id'); $license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id'); $license->category_id = $request->input('category_id');
if ($license->save()) { if ($license->save()) {
return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success')); return redirect()->route('licenses.show', ['license' => $licenseId])->with('success', trans('admin/licenses/message.update.success'));

View file

@ -12,8 +12,10 @@ use App\Models\CustomField;
use App\Models\Depreciation; use App\Models\Depreciation;
use App\Models\License; use App\Models\License;
use App\Models\Setting; use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\View; use Illuminate\Support\Facades\View;
use Input; use Input;
@ -37,12 +39,12 @@ class ReportsController extends Controller
} }
/** /**
* Returns a view that displays the accessories report. * Returns a view that displays the accessories report.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View * @return View
*/ */
public function getAccessoryReport() public function getAccessoryReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@ -52,14 +54,14 @@ class ReportsController extends Controller
} }
/** /**
* Exports the accessories to CSV * Exports the accessories to CSV
* *
* @deprecated Server-side exports have been replaced by datatables export since v2. * @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::getDatatable() method that generates the JSON response * @see ManufacturersController::getDatatable() method that generates the JSON response
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function exportAccessoryReport() public function exportAccessoryReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@ -73,7 +75,7 @@ class ReportsController extends Controller
trans('admin/accessories/general.remaining'), trans('admin/accessories/general.remaining'),
]; ];
$header = array_map('trim', $header); $header = array_map('trim', $header);
$rows[] = implode($header, ', '); $rows[] = implode(', ', $header);
// Row per accessory // Row per accessory
foreach ($accessories as $accessory) { foreach ($accessories as $accessory) {
@ -83,10 +85,10 @@ class ReportsController extends Controller
$row[] = e($accessory->total); $row[] = e($accessory->total);
$row[] = e($accessory->remaining); $row[] = e($accessory->remaining);
$rows[] = implode($row, ','); $rows[] = implode(',', $row);
} }
$csv = implode($rows, "\n"); $csv = implode("\n", $rows);
$response = Response::make($csv, 200); $response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv'); $response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv'); $response->header('Content-disposition', 'attachment;filename=report.csv');
@ -95,31 +97,27 @@ class ReportsController extends Controller
} }
/** /**
* Show depreciation report for assets. * Show depreciation report for assets.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View * @return View
*/ */
public function getDeprecationReport() public function getDeprecationReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$depreciations = Depreciation::get(); $depreciations = Depreciation::get();
// Grab all the assets return view('reports/depreciation')->with('depreciations',$depreciations);
$assets = Asset::with('assignedTo', 'assetstatus', 'defaultLoc', 'location', 'company', 'model.category', 'model.depreciation')
->orderBy('created_at', 'DESC')->get();
return view('reports/depreciation', compact('assets'))->with('depreciations', $depreciations);
} }
/** /**
* Exports the depreciations to CSV * Exports the depreciations to CSV
* *
* @deprecated Server-side exports have been replaced by datatables export since v2. * @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function exportDeprecationReport() public function exportDeprecationReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@ -190,6 +188,7 @@ class ReportsController extends Controller
die; die;
} }
/** /**
* Displays audit report. * Displays audit report.
* *
@ -204,13 +203,14 @@ class ReportsController extends Controller
return view('reports/audit'); return view('reports/audit');
} }
/** /**
* Displays activity report. * Displays activity report.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return View * @return View
*/ */
public function getActivityReport() public function getActivityReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@ -259,22 +259,23 @@ class ReportsController extends Controller
->orderBy('created_at', 'DESC') ->orderBy('created_at', 'DESC')
->chunk(20, function ($actionlogs) use ($handle) { ->chunk(20, function ($actionlogs) use ($handle) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']; $executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
\Log::debug('Walking results: '.$executionTime); \Log::debug('Walking results: '.$executionTime);
$count = 0; $count = 0;
foreach ($actionlogs as $actionlog) { foreach ($actionlogs as $actionlog) {
$count++; $count++;
$target_name = ''; $target_name = '';
if ($actionlog->target) { if ($actionlog->target) {
if ($actionlog->targetType() == 'user') { if ($actionlog->targetType() == 'user') {
$target_name = $actionlog->target->getFullNameAttribute(); $target_name = $actionlog->target->getFullNameAttribute();
} else { } else {
$target_name = $actionlog->target->getDisplayNameAttribute(); $target_name = $actionlog->target->getDisplayNameAttribute();
}
} }
}
$row = [
$row = [
$actionlog->created_at, $actionlog->created_at,
($actionlog->user) ? e($actionlog->user->getFullNameAttribute()) : '', ($actionlog->user) ? e($actionlog->user->getFullNameAttribute()) : '',
$actionlog->present()->actionType(), $actionlog->present()->actionType(),
@ -284,9 +285,9 @@ class ReportsController extends Controller
($actionlog->note) ? e($actionlog->note) : '', ($actionlog->note) ? e($actionlog->note) : '',
$actionlog->log_meta, $actionlog->log_meta,
]; ];
fputcsv($handle, $row); fputcsv($handle, $row);
} }
}); });
// Close the output stream // Close the output stream
fclose($handle); fclose($handle);
@ -297,9 +298,11 @@ class ReportsController extends Controller
'Content-Disposition' => 'attachment; filename="activity-report-'.date('Y-m-d-his').'.csv"', 'Content-Disposition' => 'attachment; filename="activity-report-'.date('Y-m-d-his').'.csv"',
]); ]);
return $response; return $response;
} }
/** /**
* Displays license report * Displays license report
* *
@ -318,13 +321,13 @@ class ReportsController extends Controller
} }
/** /**
* Exports the licenses to CSV * Exports the licenses to CSV
* *
* @deprecated Server-side exports have been replaced by datatables export since v2. * @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function exportLicenseReport() public function exportLicenseReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@ -343,7 +346,7 @@ class ReportsController extends Controller
]; ];
$header = array_map('trim', $header); $header = array_map('trim', $header);
$rows[] = implode($header, ', '); $rows[] = implode(', ', $header);
// Row per license // Row per license
foreach ($licenses as $license) { foreach ($licenses as $license) {
@ -357,10 +360,11 @@ class ReportsController extends Controller
$row[] = ($license->depreciation != '') ? '' : e($license->depreciation->name); $row[] = ($license->depreciation != '') ? '' : e($license->depreciation->name);
$row[] = '"'.Helper::formatCurrencyOutput($license->purchase_cost).'"'; $row[] = '"'.Helper::formatCurrencyOutput($license->purchase_cost).'"';
$rows[] = implode($row, ','); $rows[] = implode(',', $row);
} }
$csv = implode($rows, "\n");
$csv = implode("\n", $rows);
$response = Response::make($csv, 200); $response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv'); $response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv'); $response->header('Content-disposition', 'attachment;filename=report.csv');
@ -369,13 +373,13 @@ class ReportsController extends Controller
} }
/** /**
* Returns a form that allows the user to generate a custom CSV report. * Returns a form that allows the user to generate a custom CSV report.
* *
* @author [A. Gianotto] [<snipe@snipe.net>] * @author [A. Gianotto] [<snipe@snipe.net>]
* @see ReportsController::postCustomReport() method that generates the CSV * @see ReportsController::postCustomReport() method that generates the CSV
* @since [v1.0] * @since [v1.0]
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function getCustomReport() public function getCustomReport()
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
@ -405,13 +409,14 @@ class ReportsController extends Controller
// Open output stream // Open output stream
$handle = fopen('php://output', 'w'); $handle = fopen('php://output', 'w');
stream_set_timeout($handle, 2000); stream_set_timeout($handle, 2000);
if ($request->filled('use_bom')) { if ($request->filled('use_bom')) {
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF)); fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
} }
$header = []; $header = [];
if ($request->filled('company')) { if ($request->filled('company')) {
$header[] = trans('general.company'); $header[] = trans('general.company');
} }
@ -475,7 +480,7 @@ class ReportsController extends Controller
if ($request->filled('rtd_location')) { if ($request->filled('rtd_location')) {
$header[] = trans('admin/hardware/form.default_location'); $header[] = trans('admin/hardware/form.default_location');
} }
if ($request->filled('rtd_location_address')) { if ($request->filled('rtd_location_address')) {
$header[] = trans('general.address'); $header[] = trans('general.address');
$header[] = trans('general.address'); $header[] = trans('general.address');
@ -485,6 +490,7 @@ class ReportsController extends Controller
$header[] = trans('general.zip'); $header[] = trans('general.zip');
} }
if ($request->filled('assigned_to')) { if ($request->filled('assigned_to')) {
$header[] = trans('admin/hardware/table.checkoutto'); $header[] = trans('admin/hardware/table.checkoutto');
$header[] = trans('general.type'); $header[] = trans('general.type');
@ -548,8 +554,9 @@ class ReportsController extends Controller
$header[] = trans('general.notes'); $header[] = trans('general.notes');
} }
foreach ($customfields as $customfield) { foreach ($customfields as $customfield) {
if (e($request->input($customfield->db_column_name())) == '1') { if ($request->input($customfield->db_column_name()) == '1') {
$header[] = $customfield->name; $header[] = $customfield->name;
} }
} }
@ -563,7 +570,7 @@ class ReportsController extends Controller
$assets = \App\Models\Company::scopeCompanyables(Asset::select('assets.*'))->with( $assets = \App\Models\Company::scopeCompanyables(Asset::select('assets.*'))->with(
'location', 'assetstatus', 'assetlog', 'company', 'defaultLoc', 'assignedTo', 'location', 'assetstatus', 'assetlog', 'company', 'defaultLoc', 'assignedTo',
'model.category', 'model.manufacturer', 'supplier'); 'model.category', 'model.manufacturer', 'supplier');
if ($request->filled('by_location_id')) { if ($request->filled('by_location_id')) {
$assets->where('assets.location_id', $request->input('by_location_id')); $assets->where('assets.location_id', $request->input('by_location_id'));
} }
@ -625,15 +632,15 @@ class ReportsController extends Controller
if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) { if (($request->filled('next_audit_start')) && ($request->filled('next_audit_end'))) {
$assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]); $assets->whereBetween('assets.next_audit_date', [$request->input('next_audit_start'), $request->input('next_audit_end')]);
} }
$assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) { $assets->orderBy('assets.id', 'ASC')->chunk(20, function ($assets) use ($handle, $customfields, $request) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']; $executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
\Log::debug('Walking results: '.$executionTime); \Log::debug('Walking results: '.$executionTime);
$count = 0; $count = 0;
foreach ($assets as $asset) { foreach ($assets as $asset) {
$count++; $count++;
$row = []; $row = [];
if ($request->filled('company')) { if ($request->filled('company')) {
$row[] = ($asset->company) ? $asset->company->name : ''; $row[] = ($asset->company) ? $asset->company->name : '';
} }
@ -682,7 +689,7 @@ class ReportsController extends Controller
if ($request->filled('supplier')) { if ($request->filled('supplier')) {
$row[] = ($asset->supplier) ? $asset->supplier->name : ''; $row[] = ($asset->supplier) ? $asset->supplier->name : '';
} }
if ($request->filled('location')) { if ($request->filled('location')) {
$row[] = ($asset->location) ? $asset->location->present()->name() : ''; $row[] = ($asset->location) ? $asset->location->present()->name() : '';
} }
@ -758,8 +765,8 @@ class ReportsController extends Controller
} }
if ($request->filled('depreciation')) { if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue(); $depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation); $diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation); $row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff); $row[] = Helper::formatCurrencyOutput($diff);
$row[] = ($asset->depreciation) ? $asset->depreciated_date()->format('Y-m-d') : ''; $row[] = ($asset->depreciation) ? $asset->depreciated_date()->format('Y-m-d') : '';
@ -865,7 +872,7 @@ class ReportsController extends Controller
]; ];
$header = array_map('trim', $header); $header = array_map('trim', $header);
$rows[] = implode($header, ','); $rows[] = implode(',', $header);
foreach ($assetMaintenances as $assetMaintenance) { foreach ($assetMaintenances as $assetMaintenance) {
$row = []; $row = [];
@ -882,13 +889,13 @@ class ReportsController extends Controller
} else { } else {
$improvementTime = intval($assetMaintenance->asset_maintenance_time); $improvementTime = intval($assetMaintenance->asset_maintenance_time);
} }
$row[] = $improvementTime; $row[] = $improvementTime;
$row[] = trans('general.currency').Helper::formatCurrencyOutput($assetMaintenance->cost); $row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
$rows[] = implode($row, ','); $rows[] = implode(',', $row);
} }
// spit out a csv // spit out a csv
$csv = implode($rows, "\n"); $csv = implode("\n", $rows);
$response = Response::make($csv, 200); $response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv'); $response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv'); $response->header('Content-disposition', 'attachment;filename=report.csv');
@ -900,42 +907,124 @@ class ReportsController extends Controller
* getAssetAcceptanceReport * getAssetAcceptanceReport
* *
* @return mixed * @return mixed
* @throws \Illuminate\Auth\Access\AuthorizationException
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
public function getAssetAcceptanceReport() public function getAssetAcceptanceReport($deleted = false)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
/** /**
* Get all assets with pending checkout acceptances * Get all assets with pending checkout acceptances
*/ */
$acceptances = CheckoutAcceptance::pending()->get(); if($showDeleted) {
$acceptances = CheckoutAcceptance::pending()->withTrashed()->with(['assignedTo' , 'checkoutable.assignedTo', 'checkoutable.model'])->get();
} else {
$acceptances = CheckoutAcceptance::pending()->with(['assignedTo' => function ($query) {
$query->withTrashed();
}, 'checkoutable.assignedTo', 'checkoutable.model'])->get();
}
$assetsForReport = $acceptances $assetsForReport = $acceptances
->filter(function ($acceptance) { ->filter(function ($acceptance) {
return $acceptance->checkoutable_type == \App\Models\Asset::class; return $acceptance->checkoutable_type == \App\Models\Asset::class;
}) })
->map(function ($acceptance) { ->map(function($acceptance) {
return $acceptance->checkoutable; return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
}); });
return view('reports/unaccepted_assets', compact('assetsForReport')); return view('reports/unaccepted_assets', compact('assetsForReport','showDeleted' ));
} }
/** /**
* exportAssetAcceptanceReport * sentAssetAcceptanceReminder
*
* @param integer|null $acceptanceId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @version v1.0
*/
public function sentAssetAcceptanceReminder($acceptanceId = null)
{
$this->authorize('reports.view');
if (!$acceptance = CheckoutAcceptance::pending()->find($acceptanceId)) {
// Redirect to the unaccepted assets report page with error
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
$assetItem = $acceptance->checkoutable;
$logItem = $assetItem->checkouts()->where('created_at', '=', $acceptance->created_at)->get()[0];
if(!$assetItem->assignedTo->locale){
Notification::locale(Setting::getSettings()->locale)->send(
$assetItem->assignedTo,
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
);
} else {
Notification::send(
$assetItem->assignedTo,
new CheckoutAssetNotification($assetItem, $assetItem->assignedTo, $logItem->user, $acceptance, $logItem->note)
);
}
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.reminder_sent'));
}
/**
* sentAssetAcceptanceReminder
*
* @param integer|null $acceptanceId
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @version v1.0
*/
public function deleteAssetAcceptance($acceptanceId = null)
{
$this->authorize('reports.view');
if (!$acceptance = CheckoutAcceptance::pending()->find($acceptanceId)) {
// Redirect to the unaccepted assets report page with error
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.bad_data'));
}
if($acceptance->delete()) {
return redirect()->route('reports/unaccepted_assets')->with('success', trans('admin/reports/general.acceptance_deleted'));
} else {
return redirect()->route('reports/unaccepted_assets')->with('error', trans('general.deletion_failed'));
}
}
/**
* Exports the AssetAcceptance report to CSV
* *
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
public function exportAssetAcceptanceReport() public function postAssetAcceptanceReport($deleted = false)
{ {
$this->authorize('reports.view'); $this->authorize('reports.view');
// Grab all the improvements $showDeleted = $deleted == 'deleted';
$assetsForReport = Actionlog::whereIn('id', $this->getAssetsNotAcceptedYet())
->get(); /**
* Get all assets with pending checkout acceptances
*/
if($showDeleted) {
$acceptances = CheckoutAcceptance::pending()->withTrashed()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
} else {
$acceptances = CheckoutAcceptance::pending()->with(['assignedTo', 'checkoutable.assignedTo', 'checkoutable.model'])->get();
}
$assetsForReport = $acceptances
->filter(function($acceptance) {
return $acceptance->checkoutable_type == 'App\Models\Asset';
})
->map(function($acceptance) {
return ['assetItem' => $acceptance->checkoutable, 'acceptance' => $acceptance];
});
$rows = []; $rows = [];
@ -948,20 +1037,20 @@ class ReportsController extends Controller
]; ];
$header = array_map('trim', $header); $header = array_map('trim', $header);
$rows[] = implode($header, ','); $rows[] = implode(',', $header);
foreach ($assetsForReport as $assetItem) { foreach ($assetsForReport as $item) {
$row = []; $row = [ ];
$row[] = str_replace(',', '', e($assetItem->assetlog->model->category->name)); $row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
$row[] = str_replace(',', '', e($assetItem->assetlog->model->name)); $row[] = str_replace(',', '', e($item['assetItem']->model->name));
$row[] = str_replace(',', '', e($assetItem->assetlog->present()->name())); $row[] = str_replace(',', '', e($item['assetItem']->name));
$row[] = str_replace(',', '', e($assetItem->assetlog->asset_tag)); $row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
$row[] = str_replace(',', '', e($assetItem->assetlog->assignedTo->present()->name())); $row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
$rows[] = implode($row, ','); $rows[] = implode(',', $row);
} }
// spit out a csv // spit out a csv
$csv = implode($rows, "\n"); $csv = implode("\n", $rows);
$response = Response::make($csv, 200); $response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv'); $response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv'); $response->header('Content-disposition', 'attachment;filename=report.csv');

View file

@ -360,6 +360,7 @@ class SettingsController extends Controller
return redirect()->back()->withInput()->withErrors($setting->getErrors()); return redirect()->back()->withInput()->withErrors($setting->getErrors());
} }
/** /**
* Return a form to allow a super admin to update settings. * Return a form to allow a super admin to update settings.
* *
@ -411,14 +412,15 @@ class SettingsController extends Controller
$setting = $request->handleImages($setting, 600, 'logo', '', 'logo'); $setting = $request->handleImages($setting, 600, 'logo', '', 'logo');
if ('1' == $request->input('clear_logo')) { if ('1' == $request->input('clear_logo')) {
Storage::disk('public')->delete($setting->logo); Storage::disk('public')->delete($setting->logo);
$setting->logo = null; $setting->logo = null;
$setting->brand = 1; $setting->brand = 1;
} }
$setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo'); $setting = $request->handleImages($setting, 600, 'email_logo', '', 'email_logo');
if ('1' == $request->input('clear_email_logo')) {
if ('1' == $request->input('clear_email_logo')) {
Storage::disk('public')->delete($setting->email_logo); Storage::disk('public')->delete($setting->email_logo);
$setting->email_logo = null; $setting->email_logo = null;
// If they are uploading an image, validate it and upload it // If they are uploading an image, validate it and upload it
@ -431,8 +433,9 @@ class SettingsController extends Controller
$setting->label_logo = null; $setting->label_logo = null;
} }
// If the user wants to clear the favicon... // If the user wants to clear the favicon...
if ($request->hasFile('favicon')) { if ($request->hasFile('favicon')) {
$favicon_image = $favicon_upload = $request->file('favicon'); $favicon_image = $favicon_upload = $request->file('favicon');
$favicon_ext = $favicon_image->getClientOriginalExtension(); $favicon_ext = $favicon_image->getClientOriginalExtension();
$setting->favicon = $favicon_file_name = 'favicon-uploaded.'.$favicon_ext; $setting->favicon = $favicon_file_name = 'favicon-uploaded.'.$favicon_ext;
@ -449,16 +452,17 @@ class SettingsController extends Controller
Storage::disk('public')->put($favicon_file_name, file_get_contents($request->file('favicon'))); Storage::disk('public')->put($favicon_file_name, file_get_contents($request->file('favicon')));
} }
// Remove Current image if exists // Remove Current image if exists
if (($setting->favicon) && (file_exists($favicon_file_name))) { if (($setting->favicon) && (file_exists($favicon_file_name))) {
Storage::disk('public')->delete($favicon_file_name); Storage::disk('public')->delete($favicon_file_name);
} }
} elseif ('1' == $request->input('clear_favicon')) { } elseif ('1' == $request->input('clear_favicon')) {
Storage::disk('public')->delete($setting->clear_favicon); Storage::disk('public')->delete($setting->clear_favicon);
$setting->favicon = null; $setting->favicon = null;
// If they are uploading an image, validate it and upload it // If they are uploading an image, validate it and upload it
} }
if ($setting->save()) { if ($setting->save()) {
return redirect()->route('settings.index') return redirect()->route('settings.index')
@ -516,6 +520,7 @@ class SettingsController extends Controller
$setting->pwd_secure_min = (int) $request->input('pwd_secure_min'); $setting->pwd_secure_min = (int) $request->input('pwd_secure_min');
$setting->pwd_secure_complexity = ''; $setting->pwd_secure_complexity = '';
if ($request->filled('pwd_secure_complexity')) { if ($request->filled('pwd_secure_complexity')) {
$setting->pwd_secure_complexity = implode('|', $request->input('pwd_secure_complexity')); $setting->pwd_secure_complexity = implode('|', $request->input('pwd_secure_complexity'));
} }
@ -928,9 +933,14 @@ class SettingsController extends Controller
$setting->ldap_jobtitle = $request->input('ldap_jobtitle'); $setting->ldap_jobtitle = $request->input('ldap_jobtitle');
$setting->ldap_country = $request->input('ldap_country'); $setting->ldap_country = $request->input('ldap_country');
$setting->ldap_dept = $request->input('ldap_dept'); $setting->ldap_dept = $request->input('ldap_dept');
$setting->ldap_client_tls_cert = $request->input('ldap_client_tls_cert');
$setting->ldap_client_tls_key = $request->input('ldap_client_tls_key');
} }
if ($setting->save()) { if ($setting->save()) {
$setting->update_client_side_cert_files();
return redirect()->route('settings.ldap.index') return redirect()->route('settings.ldap.index')
->with('success', trans('admin/settings/message.update.success')); ->with('success', trans('admin/settings/message.update.success'));
} }

View file

@ -33,7 +33,8 @@ class BulkUsersController extends Controller
// Make sure there were users selected // Make sure there were users selected
if (($request->filled('ids')) && (count($request->input('ids')) > 0)) { if (($request->filled('ids')) && (count($request->input('ids')) > 0)) {
// Get the list of affected users // Get the list of affected users
$users = User::whereIn('id', array_keys(request('ids'))) $user_raw_array = request('ids');
$users = User::whereIn('id', $user_raw_array)
->with('groups', 'assets', 'licenses', 'accessories')->get(); ->with('groups', 'assets', 'licenses', 'accessories')->get();
if ($request->input('bulk_actions') == 'edit') { if ($request->input('bulk_actions') == 'edit') {

View file

@ -10,6 +10,8 @@ use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input; use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
class UserFilesController extends Controller class UserFilesController extends Controller
{ {
@ -38,12 +40,32 @@ class UserFilesController extends Controller
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles')); return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
} }
foreach ($files as $file) { foreach ($files as $file) {
$extension = $file->getClientOriginalExtension(); $extension = $file->getClientOriginalExtension();
$filename = 'user-'.$user->id.'-'.str_random(8); $file_name = 'user-'.$user->id.'-'.str_random(8).'-'.str_slug(basename($file->getClientOriginalName(), '.'.$extension)).'.'.$extension;
$filename .= '-'.str_slug($file->getClientOriginalName()).'.'.$extension;
if (! $file->move($destinationPath, $filename)) {
return redirect()->back()->with('error', trans('admin/users/message.upload.invalidfiles')); // Check for SVG and sanitize it
if ($extension == 'svg') {
\Log::debug('This is an SVG');
\Log::debug($file_name);
$sanitizer = new Sanitizer();
$dirtySVG = file_get_contents($file->getRealPath());
$cleanSVG = $sanitizer->sanitize($dirtySVG);
try {
Storage::put('private_uploads/users/'.$file_name, $cleanSVG);
} catch (\Exception $e) {
\Log::debug('Upload no workie :( ');
\Log::debug($e);
}
} else {
Storage::put('private_uploads/users/'.$file_name, file_get_contents($file));
} }
//Log the uploaded file to the log //Log the uploaded file to the log
$logAction = new Actionlog(); $logAction = new Actionlog();
$logAction->item_id = $user->id; $logAction->item_id = $user->id;
@ -51,8 +73,8 @@ class UserFilesController extends Controller
$logAction->user_id = Auth::id(); $logAction->user_id = Auth::id();
$logAction->note = $request->input('notes'); $logAction->note = $request->input('notes');
$logAction->target_id = null; $logAction->target_id = null;
$logAction->created_at = date('Y-m-d H:i:s'); $logAction->created_at = date("Y-m-d H:i:s");
$logAction->filename = $filename; $logAction->filename = $file_name;
$logAction->action_type = 'uploaded'; $logAction->action_type = 'uploaded';
if (! $logAction->save()) { if (! $logAction->save()) {
@ -63,8 +85,9 @@ class UserFilesController extends Controller
// dd($logActions); // dd($logActions);
return redirect()->back()->with('success', trans('admin/users/message.upload.success')); return redirect()->back()->with('success', trans('admin/users/message.upload.success'));
} }
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles')); return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
} }
/** /**
@ -97,6 +120,7 @@ class UserFilesController extends Controller
$error = trans('admin/users/message.user_not_found', ['id' => $userId]); $error = trans('admin/users/message.user_not_found', ['id' => $userId]);
// Redirect to the licence management page // Redirect to the licence management page
return redirect()->route('users.index')->with('error', $error); return redirect()->route('users.index')->with('error', $error);
} }
/** /**
@ -128,4 +152,5 @@ class UserFilesController extends Controller
// Redirect to the licence management page // Redirect to the licence management page
return redirect()->route('users.index')->with('error', $error); return redirect()->route('users.index')->with('error', $error);
} }
} }

View file

@ -23,6 +23,7 @@ class Kernel extends HttpKernel
\App\Http\Middleware\CheckForDebug::class, \App\Http\Middleware\CheckForDebug::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\SecurityHeaders::class, \App\Http\Middleware\SecurityHeaders::class,
\App\Http\Middleware\PreventBackHistory::class,
]; ];

View file

@ -15,27 +15,21 @@ class CustomFieldSetDefaultValuesForModel extends Component
public $fields; public $fields;
public $model_id; public $model_id;
public function __construct()
{
\Log::info("INSTANTIATING A THING!!!"); // WORKS!
}
public function foo()
{
\Log::info("Uh, foo?");
}
public function mount() public function mount()
{ {
$this->fieldset_id = AssetModel::find($this->model_id)->fieldset_id; $this->model = AssetModel::find($this->model_id); // It's possible to do some clever route-model binding here, but let's keep it simple, shall we?
\Log::error("Mount at least fired, that's got to count for something, yeah?"); //WORKS! YAY! $this->fieldset_id = $this->model->fieldset_id;
$this->fields = CustomFieldset::find($this->fieldset_id)->fields;
$this->add_default_values = ( $this->model->defaultValues->count() > 0);
}
public function updatedFieldsetId()
{
$this->fields = CustomFieldset::find($this->fieldset_id)->fields;
} }
public function render() public function render()
{ {
// return 'fart<div>Hi: {{ $this->add_default_values }} yeah?</div>';
return view('livewire.custom-field-set-default-values-for-model'); return view('livewire.custom-field-set-default-values-for-model');
} }
} }

View file

@ -1,21 +0,0 @@
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Models\CustomFieldset;
class CustomFieldsForFieldset extends Component
{
public $fieldset_id;
public $fields;
public function render()
{
if($this->fieldset_id) {
$this->fields = CustomFieldset::find($this->fieldset_id)->fields()->get();
}
return view('livewire.custom-fields-for-fieldset');
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use Closure;
class PreventBackHistory
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$headers = [
'Cache-Control' => 'no-cache, no-store, max-age=0, must-revalidate',
'Pragma' => 'no-cache',
'Expires' => 'Sun, 02 Jan 1990 00:00:00 GMT'
];
$response = $next($request);
foreach($headers as $key => $value) {
$response->headers->set($key, $value);
}
return $response;
}
}

View file

@ -21,7 +21,7 @@ class AssetFileRequest extends Request
*/ */
public function rules() public function rules()
{ {
$max_file_size = \App\Helpers\Helper::file_upload_max_size(); $max_file_size = Helper::file_upload_max_size();
return [ return [
'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,webp|max:'.$max_file_size, 'file.*' => 'required|mimes:png,gif,jpg,svg,jpeg,doc,docx,pdf,txt,zip,rar,xls,xlsx,lic,xml,rtf,webp|max:'.$max_file_size,

View file

@ -123,7 +123,7 @@ class ImageUploadRequest extends Request
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) { $upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
$constraint->aspectRatio(); $constraint->aspectRatio();
$constraint->upsize(); $constraint->upsize();
}); });
// This requires a string instead of an object, so we use ($string) // This requires a string instead of an object, so we use ($string)
Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode()); Storage::disk('public')->put($path.'/'.$file_name, (string) $upload->encode());
@ -150,12 +150,12 @@ class ImageUploadRequest extends Request
} }
} }
// Remove Current image if exists // Remove Current image if exists
if (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field})) { if (Storage::disk('public')->exists($path.'/'.$item->{$use_db_field})) {
\Log::debug('A file already exists that we are replacing - we should delete the old one.'); \Log::debug('A file already exists that we are replacing - we should delete the old one.');
try { try {
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field}); Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.'); \Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?'); \Log::debug('Could not delete old file. '.$path.'/'.$file_name.' does not exist?');
} }
@ -164,14 +164,14 @@ class ImageUploadRequest extends Request
$item->{$use_db_field} = $file_name; $item->{$use_db_field} = $file_name;
} }
// If the user isn't uploading anything new but wants to delete their old image, do so // If the user isn't uploading anything new but wants to delete their old image, do so
} else { } else {
\Log::debug('No file passed for '.$form_fieldname); \Log::debug('No file passed for '.$form_fieldname);
if ($this->input('image_delete') == '1') { if ($this->input('image_delete') == '1') {
\Log::debug('Deleting image'); \Log::debug('Deleting image');
try { try {
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field}); Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
$item->{$use_db_field} = null; $item->{$use_db_field} = null;
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::debug($e); \Log::debug($e);
} }

View file

@ -0,0 +1,25 @@
<?php
namespace App\Http\Traits;
trait TwoColumnUniqueUndeletedTrait
{
/**
* Prepare a unique_ids rule, adding a model identifier if required.
*
* @param array $parameters
* @param string $field
* @return string
*/
protected function prepareTwoColumnUniqueUndeletedRule($parameters, $field)
{
$column = $parameters[0];
$value = $this->{$parameters[0]};
if ($this->exists) {
return 'two_column_unique_undeleted:'.$this->table.','.$this->getKey().','.$column.','.$value;
}
return 'two_column_unique_undeleted:'.$this->table.',0,'.$column.','.$value;
}
}

View file

@ -24,7 +24,7 @@ class ActionlogsTransformer
{ {
$icon = $actionlog->present()->icon(); $icon = $actionlog->present()->icon();
if ($actionlog->filename != '') { if ($actionlog->filename != '') {
$icon = e(\App\Helpers\Helper::filetype_icon($actionlog->filename)); $icon = e(Helper::filetype_icon($actionlog->filename));
} }
// This is necessary since we can't escape special characters within a JSON object // This is necessary since we can't escape special characters within a JSON object
@ -68,14 +68,14 @@ class ActionlogsTransformer
} }
} }
$array = [ $array = [
'id' => (int) $actionlog->id, 'id' => (int) $actionlog->id,
'icon' => $icon, 'icon' => $icon,
'file' => ($actionlog->filename != '') ? 'file' => ($actionlog->filename != '') ?
[ [
'url' => route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]), 'url' => route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]),
'filename' => $actionlog->filename, 'filename' => $actionlog->filename,
'inlineable' => (bool) \App\Helpers\Helper::show_file_inline($actionlog->filename), 'inlineable' => (bool) Helper::show_file_inline($actionlog->filename),
] : null, ] : null,
'item' => ($actionlog->item) ? [ 'item' => ($actionlog->item) ? [
@ -104,10 +104,10 @@ class ActionlogsTransformer
'type' => e($actionlog->targetType()), 'type' => e($actionlog->targetType()),
] : null, ] : null,
'note' => ($actionlog->note) ? e($actionlog->note) : null, 'note' => ($actionlog->note) ? e($actionlog->note): null,
'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature]) : null, 'signature_file' => ($actionlog->accept_signature) ? route('log.signature.view', ['filename' => $actionlog->accept_signature ]) : null,
'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta : null, 'log_meta' => ((isset($clean_meta)) && (is_array($clean_meta))) ? $clean_meta: null,
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime') : null, 'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime'): Helper::getFormattedDateObject($actionlog->created_at, 'datetime'),
]; ];

View file

@ -83,12 +83,13 @@ class AssetsTransformer
'user_can_checkout' => (bool) $asset->availableForCheckout(), 'user_can_checkout' => (bool) $asset->availableForCheckout(),
]; ];
if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) { if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
$fields_array = []; $fields_array = [];
foreach ($asset->model->fieldset->fields as $field) { foreach ($asset->model->fieldset->fields as $field) {
if ($field->isFieldDecryptable($asset->{$field->convertUnicodeDbSlug()})) { if ($field->isFieldDecryptable($asset->{$field->convertUnicodeDbSlug()})) {
$decrypted = \App\Helpers\Helper::gracefulDecrypt($field, $asset->{$field->convertUnicodeDbSlug()}); $decrypted = Helper::gracefulDecrypt($field, $asset->{$field->convertUnicodeDbSlug()});
$value = (Gate::allows('superadmin')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted')); $value = (Gate::allows('superadmin')) ? $decrypted : strtoupper(trans('admin/custom_fields/general.encrypted'));
$fields_array[$field->name] = [ $fields_array[$field->name] = [
@ -96,12 +97,15 @@ class AssetsTransformer
'value' => $value, 'value' => $value,
'field_format' => $field->format, 'field_format' => $field->format,
]; ];
} else { } else {
$fields_array[$field->name] = [ $fields_array[$field->name] = [
'field' => $field->convertUnicodeDbSlug(), 'field' => $field->convertUnicodeDbSlug(),
'value' => $asset->{$field->convertUnicodeDbSlug()}, 'value' => $asset->{$field->convertUnicodeDbSlug()},
'field_format' => $field->format, 'field_format' => $field->format,
]; ];
} }
$array['custom_fields'] = $fields_array; $array['custom_fields'] = $fields_array;
} }
@ -112,7 +116,7 @@ class AssetsTransformer
$permissions_array['available_actions'] = [ $permissions_array['available_actions'] = [
'checkout' => Gate::allows('checkout', Asset::class), 'checkout' => Gate::allows('checkout', Asset::class),
'checkin' => Gate::allows('checkin', Asset::class), 'checkin' => Gate::allows('checkin', Asset::class),
'clone' => Gate::allows('create', Asset::class), 'clone' => false,
'restore' => false, 'restore' => false,
'update' => (bool) Gate::allows('update', Asset::class), 'update' => (bool) Gate::allows('update', Asset::class),
'delete' => ($asset->assigned_to == '' && Gate::allows('delete', Asset::class)), 'delete' => ($asset->assigned_to == '' && Gate::allows('delete', Asset::class)),
@ -129,6 +133,29 @@ class AssetsTransformer
]; ];
} }
if (request('components')=='true') {
if ($asset->components) {
$array['components'] = [];
foreach ($asset->components as $component) {
$array['components'][] = [
'id' => $component->id,
'pivot_id' => $component->pivot->id,
'name' => $component->name,
'qty' => $component->pivot->assigned_qty,
'price_cost' => $component->purchase_cost,
'purchase_total' => $component->purchase_cost * $component->pivot->assigned_qty,
'checkout_date' => Helper::getFormattedDateObject($component->pivot->created_at, 'datetime') ,
];
}
}
}
$array += $permissions_array; $array += $permissions_array;
return $array; return $array;
@ -160,6 +187,7 @@ class AssetsTransformer
] : null; ] : null;
} }
public function transformRequestedAssets(Collection $assets, $total) public function transformRequestedAssets(Collection $assets, $total)
{ {
$array = []; $array = [];
@ -192,7 +220,8 @@ class AssetsTransformer
]; ];
$array += $permissions_array; $array += $permissions_array;
return $array; return $array;
} }
} }

View file

@ -29,6 +29,7 @@ class CategoriesTransformer
'image' => ($category->image) ? Storage::disk('public')->url('categories/'.e($category->image)) : null, 'image' => ($category->image) ? Storage::disk('public')->url('categories/'.e($category->image)) : null,
'category_type' => ucwords(e($category->category_type)), 'category_type' => ucwords(e($category->category_type)),
'has_eula' => ($category->getEula() ? true : false), 'has_eula' => ($category->getEula() ? true : false),
'use_default_eula' => ($category->use_default_eula=='1' ? true : false),
'eula' => ($category->getEula()), 'eula' => ($category->getEula()),
'checkin_email' => ($category->checkin_email == '1'), 'checkin_email' => ($category->checkin_email == '1'),
'require_acceptance' => ($category->require_acceptance == '1'), 'require_acceptance' => ($category->require_acceptance == '1'),

View file

@ -0,0 +1,120 @@
<?php
namespace App\Http\Transformers;
use App\Helpers\Helper;
use App\Models\Asset;
use Illuminate\Database\Eloquent\Collection;
/**
* This tranformer looks like it's extraneous, since we return as much or more
* info in the AssetsTransformer, but we want to flatten these results out so that they
* don't dislose more information than we want. Folks with depreciation powers don't necessaily
* have the right to see additional info, and inspecting the API call here could disclose
* info they're not supposed to see.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v5.2.0]
*/
class DepreciationReportTransformer
{
public function transformAssets(Collection $assets, $total)
{
$array = array();
foreach ($assets as $asset) {
$array[] = self::transformAsset($asset);
}
return (new DatatablesTransformer)->transformDatatables($array, $total);
}
public function transformAsset(Asset $asset)
{
/**
* Set some default values here
*/
$purchase_cost = null;
$depreciated_value = null;
$monthly_depreciation = null;
$diff = null;
$checkout_target = null;
/**
* If there is a location set and a currency set, use that for display
*/
if ($asset->location && $asset->location->currency) {
$purchase_cost_currency = $asset->location->currency;
} else {
$purchase_cost_currency = \App\Models\Setting::getSettings()->default_currency;
}
/**
* If there is a NOT an empty purchase cost (meaning not null or '' but it *could* be zero),
* format the purchase cost. We coould do this inline in the transformer, but we need that value
* for the other calculations that come after, like diff, etc.
*/
if ($asset->purchase_cost!='') {
$purchase_cost = $asset->purchase_cost;
}
/**
* Override the previously set null values if there is a valid model and associated depreciation
*/
if (($asset->model) && ($asset->model->depreciation)) {
$depreciated_value = Helper::formatCurrencyOutput($asset->getDepreciatedValue());
$monthly_depreciation = Helper::formatCurrencyOutput(($asset->model->eol > 0 ? ($asset->purchase_cost / $asset->model->eol) : 0));
$diff = Helper::formatCurrencyOutput(($asset->purchase_cost - $asset->getDepreciatedValue()));
}
if ($asset->assigned) {
$checkout_target = $asset->assigned->name;
if ($asset->checkedOutToUser()) {
$checkout_target = $asset->assigned->getFullNameAttribute();
}
}
$array = [
'company' => ($asset->company) ? e($asset->company->name) : null,
'name' => e($asset->name),
'asset_tag' => e($asset->asset_tag),
'serial' => e($asset->serial),
'model' => ($asset->model) ? e($asset->model->name) : null,
'model_number' => (($asset->model) && ($asset->model->model_number)) ? e($asset->model->model_number) : null,
'eol' => ($asset->purchase_date!='') ? Helper::getFormattedDateObject($asset->present()->eol_date(), 'date') : null ,
'status_label' => ($asset->assetstatus) ? e($asset->assetstatus->name) : null,
'status' => ($asset->assetstatus) ? e($asset->present()->statusMeta) : null,
'category' => (($asset->model) && ($asset->model->category)) ? e($asset->model->category->name) : null,
'manufacturer' => (($asset->model) && ($asset->model->manufacturer)) ? e($asset->model->manufacturer->name) : null,
'supplier' => ($asset->supplier) ? e($asset->supplier->name) : null,
'notes' => ($asset->notes) ? e($asset->notes) : null,
'order_number' => ($asset->order_number) ? e($asset->order_number) : null,
'location' => ($asset->location) ? e($asset->location->name) : null,
'warranty_expires' => ($asset->warranty_months > 0) ? Helper::getFormattedDateObject($asset->warranty_expires, 'date') : null,
'currency' => $purchase_cost_currency,
'purchase_date' => Helper::getFormattedDateObject($asset->purchase_date, 'date'),
'purchase_cost' => Helper::formatCurrencyOutput($asset->purchase_cost),
'book_value' => Helper::formatCurrencyOutput($depreciated_value),
'monthly_depreciation' => $monthly_depreciation,
'checked_out_to' => $checkout_target,
'diff' => Helper::formatCurrencyOutput($diff),
'number_of_months' => ($asset->model && $asset->model->depreciation) ? e($asset->model->depreciation->months) : null,
'depreciation' => (($asset->model) && ($asset->model->depreciation)) ? e($asset->model->depreciation->name) : null,
];
return $array;
}
public function transformAssetsDatatable($assets)
{
return (new DatatablesTransformer)->transformDatatables($assets);
}
}

View file

@ -25,6 +25,7 @@ class DepreciationsTransformer
'id' => (int) $depreciation->id, 'id' => (int) $depreciation->id,
'name' => e($depreciation->name), 'name' => e($depreciation->name),
'months' => $depreciation->months.' '.trans('general.months'), 'months' => $depreciation->months.' '.trans('general.months'),
'depreciation_min' => $depreciation->depreciation_min,
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'), 'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime'), 'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime'),
'depreciation_min' =>($depreciation->depreciation_min), 'depreciation_min' =>($depreciation->depreciation_min),

View file

@ -31,8 +31,9 @@ class LicensesTransformer
'purchase_order' => e($license->purchase_order), 'purchase_order' => e($license->purchase_order),
'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'), 'purchase_date' => Helper::getFormattedDateObject($license->purchase_date, 'date'),
'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'), 'termination_date' => Helper::getFormattedDateObject($license->termination_date, 'date'),
'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id, 'name'=> e($license->depreciation->name)] : null, 'depreciation' => ($license->depreciation) ? ['id' => (int) $license->depreciation->id,'name'=> e($license->depreciation->name)] : null,
'purchase_cost' => e($license->purchase_cost), 'purchase_cost' => Helper::formatCurrencyOutput($license->purchase_cost),
'purchase_cost_numeric' => $license->purchase_cost,
'notes' => e($license->notes), 'notes' => e($license->notes),
'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'), 'expiration_date' => Helper::getFormattedDateObject($license->expiration_date, 'date'),
'seats' => (int) $license->seats, 'seats' => (int) $license->seats,
@ -65,4 +66,7 @@ class LicensesTransformer
{ {
return (new DatatablesTransformer)->transformDatatables($licenses); return (new DatatablesTransformer)->transformDatatables($licenses);
} }
} }

View file

@ -14,7 +14,7 @@ class AccessoryImporter extends ItemImporter
protected function handle($row) protected function handle($row)
{ {
parent::handle($row); // TODO: Change the autogenerated stub parent::handle($row); // TODO: Change the autogenerated stub
$this->createAccessoryIfNotExists(); $this->createAccessoryIfNotExists($row);
} }
/** /**
@ -23,7 +23,7 @@ class AccessoryImporter extends ItemImporter
* @author Daniel Melzter * @author Daniel Melzter
* @since 3.0 * @since 3.0
*/ */
public function createAccessoryIfNotExists() public function createAccessoryIfNotExists($row)
{ {
$accessory = Accessory::where('name', $this->item['name'])->first(); $accessory = Accessory::where('name', $this->item['name'])->first();
if ($accessory) { if ($accessory) {
@ -34,6 +34,7 @@ class AccessoryImporter extends ItemImporter
} }
$this->log('Updating Accessory'); $this->log('Updating Accessory');
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
$accessory->update($this->sanitizeItemForUpdating($accessory)); $accessory->update($this->sanitizeItemForUpdating($accessory));
$accessory->save(); $accessory->save();
@ -41,6 +42,7 @@ class AccessoryImporter extends ItemImporter
} }
$this->log('No Matching Accessory, Creating a new one'); $this->log('No Matching Accessory, Creating a new one');
$accessory = new Accessory(); $accessory = new Accessory();
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
$accessory->fill($this->sanitizeItemForStoring($accessory)); $accessory->fill($this->sanitizeItemForStoring($accessory));
//FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything. //FIXME: this disables model validation. Need to find a way to avoid double-logs without breaking everything.

View file

@ -101,7 +101,7 @@ class ItemImporter extends Importer
return $this->createOrFetchUser($row); return $this->createOrFetchUser($row);
} }
if ($this->item['checkout_class'] === 'location') { if (strtolower($this->item['checkout_class']) === 'location') {
return Location::findOrFail($this->createOrFetchLocation($this->findCsvMatch($row, 'checkout_location'))); return Location::findOrFail($this->createOrFetchLocation($this->findCsvMatch($row, 'checkout_location')));
} }

View file

@ -74,6 +74,7 @@ class UserImporter extends ItemImporter
return; return;
} }
// This needs to be applied after the update logic, otherwise we'll overwrite user passwords // This needs to be applied after the update logic, otherwise we'll overwrite user passwords
// Issue #5408 // Issue #5408
$this->item['password'] = bcrypt($this->tempPassword); $this->item['password'] = bcrypt($this->tempPassword);
@ -106,6 +107,7 @@ class UserImporter extends ItemImporter
} }
$this->logError($user, 'User'); $this->logError($user, 'User');
return;
} }
/** /**
@ -118,13 +120,15 @@ class UserImporter extends ItemImporter
*/ */
public function createOrFetchDepartment($department_name) public function createOrFetchDepartment($department_name)
{ {
if (is_null($department_name) || $department_name == ''){
return null;
}
$department = Department::where(['name' => $department_name])->first(); $department = Department::where(['name' => $department_name])->first();
if ($department) { if ($department) {
$this->log('A matching department '.$department_name.' already exists'); $this->log('A matching department ' . $department_name . ' already exists');
return $department->id; return $department->id;
} else {
return null;
} }
$department = new department(); $department = new department();
@ -132,15 +136,14 @@ class UserImporter extends ItemImporter
$department->user_id = $this->user_id; $department->user_id = $this->user_id;
if ($department->save()) { if ($department->save()) {
$this->log('department '.$department_name.' was created'); $this->log('department ' . $department_name . ' was created');
return $department->id; return $department->id;
} }
$this->logError($department, 'Department'); $this->logError($department, 'Department');
return null; return null;
} }
public function sendWelcome($send = true) public function sendWelcome($send = true)
{ {

View file

@ -38,16 +38,16 @@ class CheckoutableListener
/** /**
* Make a checkout acceptance and attach it in the notification * Make a checkout acceptance and attach it in the notification
*/ */
$acceptance = $this->getCheckoutAcceptance($event); $acceptance = $this->getCheckoutAcceptance($event);
if (! $event->checkedOutTo->locale) { if (! $event->checkedOutTo->locale) {
Notification::locale(Setting::getSettings()->locale)->send( Notification::locale(Setting::getSettings()->locale)->send(
$this->getNotifiables($event), $this->getNotifiables($event),
$this->getCheckoutNotification($event, $acceptance) $this->getCheckoutNotification($event, $acceptance)
); );
} else { } else {
Notification::send( Notification::send(
$this->getNotifiables($event), $this->getNotifiables($event),
$this->getCheckoutNotification($event, $acceptance) $this->getCheckoutNotification($event, $acceptance)
); );
} }
@ -55,7 +55,7 @@ class CheckoutableListener
/** /**
* Notify the user about the checked in checkoutable * Notify the user about the checked in checkoutable
*/ */
public function onCheckedIn($event) public function onCheckedIn($event)
{ {
\Log::debug('checkin fired'); \Log::debug('checkin fired');
@ -72,6 +72,15 @@ class CheckoutableListener
/** /**
* Send the appropriate notification * Send the appropriate notification
*/ */
$acceptances = CheckoutAcceptance::where('checkoutable_id', $event->checkoutable->id)
->where('assigned_to_id', $event->checkedOutTo->id)
->get();
foreach($acceptances as $acceptance){
if($acceptance->isPending()){
$acceptance->delete();
}
}
\Log::debug('checked out to a user'); \Log::debug('checked out to a user');
if (! $event->checkedOutTo->locale) { if (! $event->checkedOutTo->locale) {
\Log::debug('Use default settings locale'); \Log::debug('Use default settings locale');
@ -87,7 +96,7 @@ class CheckoutableListener
$this->getCheckinNotification($event) $this->getCheckinNotification($event)
); );
} }
} }
/** /**
* Generates a checkout acceptance * Generates a checkout acceptance
@ -105,12 +114,12 @@ class CheckoutableListener
$acceptance->assignedTo()->associate($event->checkedOutTo); $acceptance->assignedTo()->associate($event->checkedOutTo);
$acceptance->save(); $acceptance->save();
return $acceptance; return $acceptance;
} }
/** /**
* Gets the entities to be notified of the passed event * Gets the entities to be notified of the passed event
* *
* @param Event $event * @param Event $event
* @return Collection * @return Collection
*/ */
@ -130,13 +139,13 @@ class CheckoutableListener
$notifiables->push(new AdminRecipient()); $notifiables->push(new AdminRecipient());
} }
return $notifiables; return $notifiables;
} }
/** /**
* Get the appropriate notification for the event * Get the appropriate notification for the event
* *
* @param CheckoutableCheckedIn $event * @param CheckoutableCheckedIn $event
* @return Notification * @return Notification
*/ */
private function getCheckinNotification($event) private function getCheckinNotification($event)
@ -144,6 +153,8 @@ class CheckoutableListener
// $model = get_class($event->checkoutable); // $model = get_class($event->checkoutable);
$notificationClass = null; $notificationClass = null;
switch (get_class($event->checkoutable)) { switch (get_class($event->checkoutable)) {
@ -152,7 +163,7 @@ class CheckoutableListener
break; break;
case Asset::class: case Asset::class:
$notificationClass = CheckinAssetNotification::class; $notificationClass = CheckinAssetNotification::class;
break; break;
case LicenseSeat::class: case LicenseSeat::class:
$notificationClass = CheckinLicenseSeatNotification::class; $notificationClass = CheckinLicenseSeatNotification::class;
break; break;
@ -160,14 +171,14 @@ class CheckoutableListener
\Log::debug('Notification class: '.$notificationClass); \Log::debug('Notification class: '.$notificationClass);
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note); return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedInBy, $event->note);
} }
/** /**
* Get the appropriate notification for the event * Get the appropriate notification for the event
* *
* @param CheckoutableCheckedIn $event * @param CheckoutableCheckedIn $event
* @param CheckoutAcceptance $acceptance * @param CheckoutAcceptance $acceptance
* @return Notification * @return Notification
*/ */
private function getCheckoutNotification($event, $acceptance) private function getCheckoutNotification($event, $acceptance)
@ -183,10 +194,10 @@ class CheckoutableListener
break; break;
case Consumable::class: case Consumable::class:
$notificationClass = CheckoutConsumableNotification::class; $notificationClass = CheckoutConsumableNotification::class;
break; break;
case LicenseSeat::class: case LicenseSeat::class:
$notificationClass = CheckoutLicenseSeatNotification::class; $notificationClass = CheckoutLicenseSeatNotification::class;
break; break;
} }
return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note); return new $notificationClass($event->checkoutable, $event->checkedOutTo, $event->checkedOutBy, $acceptance, $event->note);
@ -202,11 +213,11 @@ class CheckoutableListener
$events->listen( $events->listen(
\App\Events\CheckoutableCheckedIn::class, \App\Events\CheckoutableCheckedIn::class,
'App\Listeners\CheckoutableListener@onCheckedIn' 'App\Listeners\CheckoutableListener@onCheckedIn'
); );
$events->listen( $events->listen(
\App\Events\CheckoutableCheckedOut::class, \App\Events\CheckoutableCheckedOut::class,
'App\Listeners\CheckoutableListener@onCheckedOut' 'App\Listeners\CheckoutableListener@onCheckedOut'
); );
} }
} }

View file

@ -31,17 +31,17 @@ class Accessory extends SnipeModel
use Searchable; use Searchable;
use Acceptable; use Acceptable;
/** /**
* The attributes that should be included when searching the model. * The attributes that should be included when searching the model.
* *
* @var array * @var array
*/ */
protected $searchableAttributes = ['name', 'model_number', 'order_number', 'purchase_date']; protected $searchableAttributes = ['name', 'model_number', 'order_number', 'purchase_date'];
/** /**
* The relations and their attributes that should be included when searching the model. * The relations and their attributes that should be included when searching the model.
* *
* @var array * @var array
*/ */
protected $searchableRelations = [ protected $searchableRelations = [
@ -53,8 +53,8 @@ class Accessory extends SnipeModel
]; ];
/** /**
* Accessory validation rules * Accessory validation rules
*/ */
public $rules = [ public $rules = [
'name' => 'required|min:3|max:255', 'name' => 'required|min:3|max:255',
'qty' => 'required|integer|min:1', 'qty' => 'required|integer|min:1',
@ -65,12 +65,12 @@ class Accessory extends SnipeModel
]; ];
/** /**
* Whether the model should inject it's identifier to the unique * Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property * validation rules before attempting validation. If this property
* is not set in the model it will default to true. * is not set in the model it will default to true.
* *
* @var bool * @var bool
*/ */
protected $injectUniqueIdentifier = true; protected $injectUniqueIdentifier = true;
use ValidatingTrait; use ValidatingTrait;
@ -92,9 +92,12 @@ class Accessory extends SnipeModel
'supplier_id', 'supplier_id',
'image', 'image',
'qty', 'qty',
'min_amt',
'requestable', 'requestable',
]; ];
/** /**
* Establishes the accessory -> supplier relationship * Establishes the accessory -> supplier relationship
* *
@ -172,7 +175,7 @@ class Accessory extends SnipeModel
/** /**
* Get the LAST checkout for this accessory. * Get the LAST checkout for this accessory.
* *
* This is kinda gross, but is necessary for how the accessory * This is kinda gross, but is necessary for how the accessory
* pivot stuff works for now. * pivot stuff works for now.
* *
@ -201,6 +204,7 @@ class Accessory extends SnipeModel
return $this->assetlog()->where('action_type', '=', 'checkout')->take(1); return $this->assetlog()->where('action_type', '=', 'checkout')->take(1);
} }
/** /**
* Sets the full image url * Sets the full image url
* *
@ -300,7 +304,7 @@ class Accessory extends SnipeModel
return $Parsedown->text(e(Setting::getSettings()->default_eula_text)); return $Parsedown->text(e(Setting::getSettings()->default_eula_text));
} }
return null; return null;
} }
/** /**
@ -320,13 +324,13 @@ class Accessory extends SnipeModel
} }
/** /**
* Query builder scope to order on company * Query builder scope to order on company
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderCompany($query, $order) public function scopeOrderCompany($query, $order)
{ {
return $query->leftJoin('companies', 'accessories.company_id', '=', 'companies.id') return $query->leftJoin('companies', 'accessories.company_id', '=', 'companies.id')
@ -334,13 +338,13 @@ class Accessory extends SnipeModel
} }
/** /**
* Query builder scope to order on category * Query builder scope to order on category
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderCategory($query, $order) public function scopeOrderCategory($query, $order)
{ {
return $query->leftJoin('categories', 'accessories.category_id', '=', 'categories.id') return $query->leftJoin('categories', 'accessories.category_id', '=', 'categories.id')
@ -348,13 +352,13 @@ class Accessory extends SnipeModel
} }
/** /**
* Query builder scope to order on location * Query builder scope to order on location
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderLocation($query, $order) public function scopeOrderLocation($query, $order)
{ {
return $query->leftJoin('locations', 'accessories.location_id', '=', 'locations.id') return $query->leftJoin('locations', 'accessories.location_id', '=', 'locations.id')
@ -362,15 +366,28 @@ class Accessory extends SnipeModel
} }
/** /**
* Query builder scope to order on manufacturer * Query builder scope to order on manufacturer
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderManufacturer($query, $order) public function scopeOrderManufacturer($query, $order)
{ {
return $query->leftJoin('manufacturers', 'accessories.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order); return $query->leftJoin('manufacturers', 'accessories.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order);
} }
/**
* Query builder scope to order on supplier
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderSupplier($query, $order)
{
return $query->leftJoin('suppliers', 'accessories.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
}
} }

View file

@ -29,6 +29,8 @@ class Asset extends Depreciable
{ {
protected $presenter = \App\Presenters\AssetPresenter::class; protected $presenter = \App\Presenters\AssetPresenter::class;
use CompanyableTrait;
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait; use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait;
const LOCATION = 'location'; const LOCATION = 'location';
@ -39,35 +41,46 @@ class Asset extends Depreciable
/** /**
* Run after the checkout acceptance was declined by the user * Run after the checkout acceptance was declined by the user
* *
* @param User $acceptedBy * @param User $acceptedBy
* @param string $signature * @param string $signature
*/ */
public function declinedCheckout(User $declinedBy, $signature) public function declinedCheckout(User $declinedBy, $signature)
{ {
$this->assigned_to = null; $this->assigned_to = null;
$this->assigned_type = null; $this->assigned_type = null;
$this->accepted = null; $this->accepted = null;
$this->save(); $this->save();
} }
/** /**
* The database table used by the model. * The database table used by the model.
* *
* @var string * @var string
*/ */
protected $table = 'assets'; protected $table = 'assets';
/** /**
* Whether the model should inject it's identifier to the unique * Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property * validation rules before attempting validation. If this property
* is not set in the model it will default to true. * is not set in the model it will default to true.
* *
* @var bool * @var bool
*/ */
protected $injectUniqueIdentifier = true; protected $injectUniqueIdentifier = true;
// We set these as protected dates so that they will be easily accessible via Carbon // We set these as protected dates so that they will be easily accessible via Carbon
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
'purchase_date',
'last_checkout',
'expected_checkin',
'last_audit_date',
'next_audit_date'
];
protected $casts = [ protected $casts = [
'purchase_date' => 'datetime', 'purchase_date' => 'datetime',
@ -103,11 +116,11 @@ class Asset extends Depreciable
'last_audit_date' => 'date|nullable', 'last_audit_date' => 'date|nullable',
]; ];
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
* *
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'asset_tag', 'asset_tag',
'assigned_to', 'assigned_to',
@ -135,27 +148,27 @@ class Asset extends Depreciable
/** /**
* The attributes that should be included when searching the model. * The attributes that should be included when searching the model.
* *
* @var array * @var array
*/ */
protected $searchableAttributes = [ protected $searchableAttributes = [
'name', 'name',
'asset_tag', 'asset_tag',
'serial', 'serial',
'order_number', 'order_number',
'purchase_cost', 'purchase_cost',
'notes', 'notes',
'created_at', 'created_at',
'updated_at', 'updated_at',
'purchase_date', 'purchase_date',
'expected_checkin', 'expected_checkin',
'next_audit_date', 'next_audit_date',
'last_audit_date', 'last_audit_date',
]; ];
/** /**
* The relations and their attributes that should be included when searching the model. * The relations and their attributes that should be included when searching the model.
* *
* @var array * @var array
*/ */
protected $searchableRelations = [ protected $searchableRelations = [
@ -169,6 +182,7 @@ class Asset extends Depreciable
'model.manufacturer' => ['name'], 'model.manufacturer' => ['name'],
]; ];
/** /**
* This handles the custom field validation for assets * This handles the custom field validation for assets
* *
@ -187,6 +201,7 @@ class Asset extends Depreciable
return parent::save($params); return parent::save($params);
} }
public function getDisplayNameAttribute() public function getDisplayNameAttribute()
{ {
return $this->present()->name(); return $this->present()->name();
@ -212,6 +227,7 @@ class Asset extends Depreciable
return null; return null;
} }
/** /**
* Establishes the asset -> company relationship * Establishes the asset -> company relationship
* *
@ -242,7 +258,8 @@ class Asset extends Depreciable
// The asset status is not archived and is deployable // The asset status is not archived and is deployable
if (($this->assetstatus) && ($this->assetstatus->archived == '0') if (($this->assetstatus) && ($this->assetstatus->archived == '0')
&& ($this->assetstatus->deployable == '1')) { && ($this->assetstatus->deployable == '1'))
{
return true; return true;
} }
} }
@ -346,6 +363,7 @@ class Asset extends Depreciable
return $this->rules; return $this->rules;
} }
/** /**
* Establishes the asset -> depreciation relationship * Establishes the asset -> depreciation relationship
* *
@ -358,6 +376,7 @@ class Asset extends Depreciable
return $this->model->belongsTo(\App\Models\Depreciation::class, 'depreciation_id'); return $this->model->belongsTo(\App\Models\Depreciation::class, 'depreciation_id');
} }
/** /**
* Get components assigned to this asset * Get components assigned to this asset
* *
@ -367,9 +386,10 @@ class Asset extends Depreciable
*/ */
public function components() public function components()
{ {
return $this->belongsToMany(\App\Models\Component::class, 'components_assets', 'asset_id', 'component_id')->withPivot('id', 'assigned_qty')->withTrashed(); return $this->belongsToMany('\App\Models\Component', 'components_assets', 'asset_id', 'component_id')->withPivot('id', 'assigned_qty', 'created_at')->withTrashed();
} }
/** /**
* Get depreciation attribute from associated asset model * Get depreciation attribute from associated asset model
* *
@ -386,6 +406,7 @@ class Asset extends Depreciable
} }
} }
/** /**
* Get uploads for this asset * Get uploads for this asset
* *
@ -395,8 +416,8 @@ class Asset extends Depreciable
*/ */
public function uploads() public function uploads()
{ {
return $this->hasMany(\App\Models\Actionlog::class, 'item_id') return $this->hasMany('\App\Models\Actionlog', 'item_id')
->where('item_type', '=', self::class) ->where('item_type', '=', Asset::class)
->where('action_type', '=', 'uploaded') ->where('action_type', '=', 'uploaded')
->whereNotNull('filename') ->whereNotNull('filename')
->orderBy('created_at', 'desc'); ->orderBy('created_at', 'desc');
@ -414,7 +435,7 @@ class Asset extends Depreciable
*/ */
public function checkedOutToUser() public function checkedOutToUser()
{ {
return $this->assignedType() === self::USER; return $this->assignedType() === self::USER;
} }
/** /**
@ -443,6 +464,7 @@ class Asset extends Depreciable
return $this->morphMany(self::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed(); return $this->morphMany(self::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed();
} }
/** /**
* Get the asset's location based on the assigned user * Get the asset's location based on the assigned user
* *
@ -471,6 +493,7 @@ class Asset extends Depreciable
if ($this->assignedTo) { if ($this->assignedTo) {
return $this->assignedTo; return $this->assignedTo;
} }
} }
if ($this->assignedType() == self::USER) { if ($this->assignedType() == self::USER) {
if (($this->assignedTo) && $this->assignedTo->userLoc) { if (($this->assignedTo) && $this->assignedTo->userLoc) {
@ -479,7 +502,7 @@ class Asset extends Depreciable
//this makes no sense //this makes no sense
return $this->defaultLoc; return $this->defaultLoc;
} }
} }
return $this->defaultLoc; return $this->defaultLoc;
} }
@ -529,6 +552,7 @@ class Asset extends Depreciable
return false; return false;
} }
/** /**
* Get the asset's logs * Get the asset's logs
* *
@ -588,6 +612,7 @@ class Asset extends Depreciable
->withTrashed(); ->withTrashed();
} }
/** /**
* Get maintenances for this asset * Get maintenances for this asset
* *
@ -613,6 +638,8 @@ class Asset extends Depreciable
return $this->belongsTo(\App\Models\User::class, 'user_id'); return $this->belongsTo(\App\Models\User::class, 'user_id');
} }
/** /**
* Establishes the asset -> status relationship * Establishes the asset -> status relationship
* *
@ -658,6 +685,7 @@ class Asset extends Depreciable
->get(); ->get();
} }
/** /**
* Establishes the asset -> assigned licenses relationship * Establishes the asset -> assigned licenses relationship
* *
@ -706,6 +734,8 @@ class Asset extends Depreciable
return $this->belongsTo(\App\Models\Location::class, 'location_id'); return $this->belongsTo(\App\Models\Location::class, 'location_id');
} }
/** /**
* Get the next autoincremented asset tag * Get the next autoincremented asset tag
* *
@ -717,6 +747,7 @@ class Asset extends Depreciable
{ {
$settings = \App\Models\Setting::getSettings(); $settings = \App\Models\Setting::getSettings();
if ($settings->auto_increment_assets == '1') { if ($settings->auto_increment_assets == '1') {
$temp_asset_tag = \DB::table('assets') $temp_asset_tag = \DB::table('assets')
->where('physical', '=', '1') ->where('physical', '=', '1')
@ -735,6 +766,7 @@ class Asset extends Depreciable
} }
} }
/** /**
* Get the next base number for the auto-incrementer. * Get the next base number for the auto-incrementer.
* *
@ -746,23 +778,28 @@ class Asset extends Depreciable
*/ */
public static function nextAutoIncrement($assets) public static function nextAutoIncrement($assets)
{ {
$max = 1; $max = 1;
foreach ($assets as $asset) { foreach ($assets as $asset) {
$results = preg_match("/\d+$/", $asset['asset_tag'], $matches); $results = preg_match("/\d+$/", $asset['asset_tag'], $matches);
if ($results) { if ($results)
{
$number = $matches[0]; $number = $matches[0];
if ($number > $max) { if ($number > $max)
{
$max = $number; $max = $number;
} }
} }
} }
return $max + 1;
} }
/** /**
* Add zerofilling based on Settings * Add zerofilling based on Settings
* *
@ -802,6 +839,7 @@ class Asset extends Depreciable
if (($this->model) && ($this->model->category)) { if (($this->model) && ($this->model->category)) {
return $this->model->category->require_acceptance; return $this->model->category->require_acceptance;
} }
} }
/** /**
@ -815,7 +853,7 @@ class Asset extends Depreciable
public function getEula() public function getEula()
{ {
$Parsedown = new \Parsedown(); $Parsedown = new \Parsedown();
if (($this->model) && ($this->model->category)) { if (($this->model) && ($this->model->category)) {
if ($this->model->category->eula_text) { if ($this->model->category->eula_text) {
return $Parsedown->text(e($this->model->category->eula_text)); return $Parsedown->text(e($this->model->category->eula_text));
@ -829,11 +867,12 @@ class Asset extends Depreciable
return false; return false;
} }
/** /**
* ----------------------------------------------- * -----------------------------------------------
* BEGIN QUERY SCOPES * BEGIN QUERY SCOPES
* ----------------------------------------------- * -----------------------------------------------
**/ **/
/** /**
* Run additional, advanced searches. * Run additional, advanced searches.
@ -854,11 +893,13 @@ class Asset extends Depreciable
}); });
foreach ($terms as $term) { foreach ($terms as $term) {
$query = $query $query = $query
->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%') ->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%') ->orWhere('assets_users.last_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.username', 'LIKE', '%'.$term.'%') ->orWhere('assets_users.username', 'LIKE', '%'.$term.'%')
->orWhereRaw('CONCAT('.DB::getTablePrefix().'assets_users.first_name," ",'.DB::getTablePrefix().'assets_users.last_name) LIKE ?', ["%$term%", "%$term%"]); ->orWhereRaw('CONCAT('.DB::getTablePrefix().'assets_users.first_name," ",'.DB::getTablePrefix().'assets_users.last_name) LIKE ?', ["%$term%"]);
} }
/** /**
@ -870,6 +911,7 @@ class Asset extends Depreciable
}); });
foreach ($terms as $term) { foreach ($terms as $term) {
$query = $query->orWhere('assets_locations.name', 'LIKE', '%'.$term.'%'); $query = $query->orWhere('assets_locations.name', 'LIKE', '%'.$term.'%');
} }
@ -883,30 +925,34 @@ class Asset extends Depreciable
foreach ($terms as $term) { foreach ($terms as $term) {
$query = $query->orWhere('assigned_assets.name', 'LIKE', '%'.$term.'%'); $query = $query->orWhere('assigned_assets.name', 'LIKE', '%'.$term.'%');
} }
return $query; return $query;
} }
/** /**
* Query builder scope for hardware * Query builder scope for hardware
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeHardware($query) public function scopeHardware($query)
{ {
return $query->where('physical', '=', '1'); return $query->where('physical', '=', '1');
} }
/** /**
* Query builder scope for pending assets * Query builder scope for pending assets
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopePending($query) public function scopePending($query)
{ {
return $query->whereHas('assetstatus', function ($query) { return $query->whereHas('assetstatus', function ($query) {
@ -916,13 +962,15 @@ class Asset extends Depreciable
}); });
} }
/** /**
* Query builder scope for searching location * Query builder scope for searching location
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeAssetsByLocation($query, $location) public function scopeAssetsByLocation($query, $location)
{ {
return $query->where(function ($query) use ($location) { return $query->where(function ($query) use ($location) {
@ -944,13 +992,15 @@ class Asset extends Depreciable
}); });
} }
/** /**
* Query builder scope for RTD assets * Query builder scope for RTD assets
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeRTD($query) public function scopeRTD($query)
{ {
return $query->whereNull('assets.assigned_to') return $query->whereNull('assets.assigned_to')
@ -961,13 +1011,14 @@ class Asset extends Depreciable
}); });
} }
/** /**
* Query builder scope for Undeployable assets * Query builder scope for Undeployable assets
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeUndeployable($query) public function scopeUndeployable($query)
{ {
return $query->whereHas('assetstatus', function ($query) { return $query->whereHas('assetstatus', function ($query) {
@ -984,6 +1035,7 @@ class Asset extends Depreciable
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeNotArchived($query) public function scopeNotArchived($query)
{ {
return $query->whereHas('assetstatus', function ($query) { return $query->whereHas('assetstatus', function ($query) {
@ -1013,6 +1065,7 @@ class Asset extends Depreciable
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeDueForAudit($query, $settings) public function scopeDueForAudit($query, $settings)
{ {
$interval = $settings->audit_warning_days ?? 0; $interval = $settings->audit_warning_days ?? 0;
@ -1037,6 +1090,7 @@ class Asset extends Depreciable
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOverdueForAudit($query) public function scopeOverdueForAudit($query)
{ {
return $query->whereNotNull('assets.next_audit_date') return $query->whereNotNull('assets.next_audit_date')
@ -1058,6 +1112,7 @@ class Asset extends Depreciable
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeDueOrOverdueForAudit($query, $settings) public function scopeDueOrOverdueForAudit($query, $settings)
{ {
$interval = $settings->audit_warning_days ?? 0; $interval = $settings->audit_warning_days ?? 0;
@ -1068,13 +1123,15 @@ class Asset extends Depreciable
->NotArchived(); ->NotArchived();
} }
/**
* Query builder scope for Archived assets /**
* * Query builder scope for Archived assets
* @param \Illuminate\Database\Query\Builder $query Query builder instance *
* * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @return \Illuminate\Database\Query\Builder Modified query builder *
*/ * @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeArchived($query) public function scopeArchived($query)
{ {
return $query->whereHas('assetstatus', function ($query) { return $query->whereHas('assetstatus', function ($query) {
@ -1084,25 +1141,27 @@ class Asset extends Depreciable
}); });
} }
/** /**
* Query builder scope for Deployed assets * Query builder scope for Deployed assets
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeDeployed($query) public function scopeDeployed($query)
{ {
return $query->where('assigned_to', '>', '0'); return $query->where('assigned_to', '>', '0');
} }
/** /**
* Query builder scope for Requestable assets * Query builder scope for Requestable assets
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeRequestableAssets($query) public function scopeRequestableAssets($query)
{ {
return Company::scopeCompanyables($query->where('requestable', '=', 1)) return Company::scopeCompanyables($query->where('requestable', '=', 1))
@ -1113,53 +1172,55 @@ class Asset extends Depreciable
}); });
} }
/** /**
* scopeInModelList * scopeInModelList
* Get all assets in the provided listing of model ids * Get all assets in the provided listing of model ids
* *
* @param $query * @param $query
* @param array $modelIdListing * @param array $modelIdListing
* *
* @return mixed * @return mixed
* @author Vincent Sposato <vincent.sposato@gmail.com> * @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0 * @version v1.0
*/ */
public function scopeInModelList($query, array $modelIdListing) public function scopeInModelList($query, array $modelIdListing)
{ {
return $query->whereIn('assets.model_id', $modelIdListing); return $query->whereIn('assets.model_id', $modelIdListing);
} }
/** /**
* Query builder scope to get not-yet-accepted assets * Query builder scope to get not-yet-accepted assets
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeNotYetAccepted($query) public function scopeNotYetAccepted($query)
{ {
return $query->where('accepted', '=', 'pending'); return $query->where('accepted', '=', 'pending');
} }
/** /**
* Query builder scope to get rejected assets * Query builder scope to get rejected assets
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeRejected($query) public function scopeRejected($query)
{ {
return $query->where('accepted', '=', 'rejected'); return $query->where('accepted', '=', 'rejected');
} }
/**
* Query builder scope to get accepted assets /**
* * Query builder scope to get accepted assets
* @param \Illuminate\Database\Query\Builder $query Query builder instance *
* * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @return \Illuminate\Database\Query\Builder Modified query builder *
*/ * @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeAccepted($query) public function scopeAccepted($query)
{ {
return $query->where('accepted', '=', 'accepted'); return $query->where('accepted', '=', 'accepted');
@ -1205,7 +1266,7 @@ class Asset extends Depreciable
})->orWhere(function ($query) use ($search) { })->orWhere(function ($query) use ($search) {
$query->where('assets_users.first_name', 'LIKE', '%'.$search.'%') $query->where('assets_users.first_name', 'LIKE', '%'.$search.'%')
->orWhere('assets_users.last_name', 'LIKE', '%'.$search.'%') ->orWhere('assets_users.last_name', 'LIKE', '%'.$search.'%')
->orWhereRaw('CONCAT('.DB::getTablePrefix().'assets_users.first_name," ",'.DB::getTablePrefix().'assets_users.last_name) LIKE ?', ["%$search%", "%$search%"]) ->orWhereRaw('CONCAT('.DB::getTablePrefix().'assets_users.first_name," ",'.DB::getTablePrefix().'assets_users.last_name) LIKE ?', ["%$search%"])
->orWhere('assets_users.username', 'LIKE', '%'.$search.'%') ->orWhere('assets_users.username', 'LIKE', '%'.$search.'%')
->orWhere('assets_locations.name', 'LIKE', '%'.$search.'%') ->orWhere('assets_locations.name', 'LIKE', '%'.$search.'%')
->orWhere('assigned_assets.name', 'LIKE', '%'.$search.'%'); ->orWhere('assigned_assets.name', 'LIKE', '%'.$search.'%');
@ -1215,6 +1276,7 @@ class Asset extends Depreciable
->orWhere('assets.order_number', 'LIKE', '%'.$search.'%') ->orWhere('assets.order_number', 'LIKE', '%'.$search.'%')
->orWhere('assets.notes', 'LIKE', '%'.$search.'%'); ->orWhere('assets.notes', 'LIKE', '%'.$search.'%');
} }
})->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug })->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug
} }
@ -1233,10 +1295,13 @@ class Asset extends Depreciable
$leftJoin->on('assets_dept_users.id', '=', 'assets.assigned_to') $leftJoin->on('assets_dept_users.id', '=', 'assets.assigned_to')
->where('assets.assigned_type', '=', User::class); ->where('assets.assigned_type', '=', User::class);
})->where(function ($query) use ($search) { })->where(function ($query) use ($search) {
$query->where('assets_dept_users.department_id', '=', $search); $query->where('assets_dept_users.department_id', '=', $search);
})->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug })->withTrashed()->whereNull('assets.deleted_at'); //workaround for laravel bug
} }
/** /**
* Query builder scope to search on text filters for complex Bootstrap Tables API * Query builder scope to search on text filters for complex Bootstrap Tables API
* *
@ -1259,7 +1324,8 @@ class Asset extends Depreciable
$query->where('assets.name', 'LIKE', '%'.$search_val.'%'); $query->where('assets.name', 'LIKE', '%'.$search_val.'%');
} }
if ($fieldname == 'product_key') {
if ($fieldname =='serial') {
$query->where('assets.serial', 'LIKE', '%'.$search_val.'%'); $query->where('assets.serial', 'LIKE', '%'.$search_val.'%');
} }
@ -1300,6 +1366,7 @@ class Asset extends Depreciable
}); });
} }
if ($fieldname == 'manufacturer') { if ($fieldname == 'manufacturer') {
$query->whereHas('model', function ($query) use ($search_val) { $query->whereHas('model', function ($query) use ($search_val) {
$query->whereHas('manufacturer', function ($query) use ($search_val) { $query->whereHas('manufacturer', function ($query) use ($search_val) {
@ -1338,6 +1405,7 @@ class Asset extends Depreciable
}); });
} }
if ($fieldname == 'company') { if ($fieldname == 'company') {
$query->where(function ($query) use ($search_val) { $query->where(function ($query) use ($search_val) {
$query->whereHas('company', function ($query) use ($search_val) { $query->whereHas('company', function ($query) use ($search_val) {
@ -1374,80 +1442,90 @@ class Asset extends Depreciable
* assets.location would fail, as that field doesn't exist -- plus we're already searching * assets.location would fail, as that field doesn't exist -- plus we're already searching
* against those relationships earlier in this method. * against those relationships earlier in this method.
* *
* - snipe * - snipe
*
*/ */
if (($fieldname != 'category') && ($fieldname != 'model_number') && ($fieldname != 'rtd_location') && ($fieldname != 'location') && ($fieldname != 'supplier')
&& ($fieldname != 'status_label') && ($fieldname != 'model') && ($fieldname != 'company') && ($fieldname != 'manufacturer')) { if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier')
$query->orWhere('assets.'.$fieldname, 'LIKE', '%'.$search_val.'%'); && ($fieldname!='status_label') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer')) {
$query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
} }
}); });
} }
/** /**
* Query builder scope to order on model * Query builder scope to order on model
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderModels($query, $order) public function scopeOrderModels($query, $order)
{ {
return $query->join('models as asset_models', 'assets.model_id', '=', 'asset_models.id')->orderBy('asset_models.name', $order); return $query->join('models as asset_models', 'assets.model_id', '=', 'asset_models.id')->orderBy('asset_models.name', $order);
} }
/** /**
* Query builder scope to order on model number * Query builder scope to order on model number
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderModelNumber($query, $order) public function scopeOrderModelNumber($query, $order)
{ {
return $query->join('models', 'assets.model_id', '=', 'models.id')->orderBy('models.model_number', $order); return $query->join('models', 'assets.model_id', '=', 'models.id')->orderBy('models.model_number', $order);
} }
/** /**
* Query builder scope to order on assigned user * Query builder scope to order on assigned user
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderAssigned($query, $order) public function scopeOrderAssigned($query, $order)
{ {
return $query->leftJoin('users as users_sort', 'assets.assigned_to', '=', 'users_sort.id')->select('assets.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order); return $query->leftJoin('users as users_sort', 'assets.assigned_to', '=', 'users_sort.id')->select('assets.*')->orderBy('users_sort.first_name', $order)->orderBy('users_sort.last_name', $order);
} }
/** /**
* Query builder scope to order on status * Query builder scope to order on status
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderStatus($query, $order) public function scopeOrderStatus($query, $order)
{ {
return $query->join('status_labels as status_sort', 'assets.status_id', '=', 'status_sort.id')->orderBy('status_sort.name', $order); return $query->join('status_labels as status_sort', 'assets.status_id', '=', 'status_sort.id')->orderBy('status_sort.name', $order);
} }
/** /**
* Query builder scope to order on company * Query builder scope to order on company
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderCompany($query, $order) public function scopeOrderCompany($query, $order)
{ {
return $query->leftJoin('companies as company_sort', 'assets.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order); return $query->leftJoin('companies as company_sort', 'assets.company_id', '=', 'company_sort.id')->orderBy('company_sort.name', $order);
} }
/** /**
* Query builder scope to return results of a category * Query builder scope to return results of a category
* *
@ -1476,14 +1554,16 @@ class Asset extends Depreciable
->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->where('models.manufacturer_id', '=', $manufacturer_id); ->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->where('models.manufacturer_id', '=', $manufacturer_id);
} }
/** /**
* Query builder scope to order on category * Query builder scope to order on category
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderCategory($query, $order) public function scopeOrderCategory($query, $order)
{ {
return $query->join('models as order_model_category', 'assets.model_id', '=', 'order_model_category.id') return $query->join('models as order_model_category', 'assets.model_id', '=', 'order_model_category.id')
@ -1491,6 +1571,7 @@ class Asset extends Depreciable
->orderBy('category_order.name', $order); ->orderBy('category_order.name', $order);
} }
/** /**
* Query builder scope to order on manufacturer * Query builder scope to order on manufacturer
* *
@ -1506,14 +1587,14 @@ class Asset extends Depreciable
->orderBy('manufacturers.name', $order); ->orderBy('manufacturers.name', $order);
} }
/** /**
* Query builder scope to order on location * Query builder scope to order on location
* *
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order * @param text $order Order
* *
* @return \Illuminate\Database\Query\Builder Modified query builder * @return \Illuminate\Database\Query\Builder Modified query builder
*/ */
public function scopeOrderLocation($query, $order) public function scopeOrderLocation($query, $order)
{ {
return $query->leftJoin('locations as asset_locations', 'asset_locations.id', '=', 'assets.location_id')->orderBy('asset_locations.name', $order); return $query->leftJoin('locations as asset_locations', 'asset_locations.id', '=', 'assets.location_id')->orderBy('asset_locations.name', $order);
@ -1531,6 +1612,7 @@ class Asset extends Depreciable
return $query->leftJoin('locations as rtd_asset_locations', 'rtd_asset_locations.id', '=', 'assets.rtd_location_id')->orderBy('rtd_asset_locations.name', $order); return $query->leftJoin('locations as rtd_asset_locations', 'rtd_asset_locations.id', '=', 'assets.rtd_location_id')->orderBy('rtd_asset_locations.name', $order);
} }
/** /**
* Query builder scope to order on supplier name * Query builder scope to order on supplier name
* *
@ -1559,8 +1641,10 @@ class Asset extends Depreciable
$query->where('locations.id', '=', $search); $query->where('locations.id', '=', $search);
}); });
}); });
} }
/** /**
* Query builder scope to search on depreciation name * Query builder scope to search on depreciation name
* @param \Illuminate\Database\Query\Builder $query Query builder instance * @param \Illuminate\Database\Query\Builder $query Query builder instance
@ -1572,5 +1656,8 @@ class Asset extends Depreciable
{ {
return $query->join('models', 'assets.model_id', '=', 'models.id') return $query->join('models', 'assets.model_id', '=', 'models.id')
->join('depreciations', 'models.depreciation_id', '=', 'depreciations.id')->where('models.depreciation_id', '=', $search); ->join('depreciations', 'models.depreciation_id', '=', 'depreciations.id')->where('models.depreciation_id', '=', $search);
} }
} }

View file

@ -2,7 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Http\Traits\UniqueUndeletedTrait; use App\Http\Traits\TwoColumnUniqueUndeletedTrait;
use App\Models\Traits\Searchable; use App\Models\Traits\Searchable;
use App\Presenters\Presentable; use App\Presenters\Presentable;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -38,7 +38,7 @@ class Category extends SnipeModel
*/ */
public $rules = [ public $rules = [
'user_id' => 'numeric|nullable', 'user_id' => 'numeric|nullable',
'name' => 'required|min:1|max:255|unique_undeleted', 'name' => 'required|min:1|max:255|two_column_unique_undeleted:category_type',
'require_acceptance' => 'boolean', 'require_acceptance' => 'boolean',
'use_default_eula' => 'boolean', 'use_default_eula' => 'boolean',
'category_type' => 'required|in:asset,accessory,consumable,component,license', 'category_type' => 'required|in:asset,accessory,consumable,component,license',
@ -53,7 +53,8 @@ class Category extends SnipeModel
*/ */
protected $injectUniqueIdentifier = true; protected $injectUniqueIdentifier = true;
use ValidatingTrait; use ValidatingTrait;
use UniqueUndeletedTrait; use TwoColumnUniqueUndeletedTrait;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.

View file

@ -30,29 +30,29 @@ final class Company extends SnipeModel
use Presentable; use Presentable;
/** /**
* Whether the model should inject it's identifier to the unique * Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property * validation rules before attempting validation. If this property
* is not set in the model it will default to true. * is not set in the model it will default to true.
* *
* @var bool * @var bool
*/ */
protected $injectUniqueIdentifier = true; protected $injectUniqueIdentifier = true;
use ValidatingTrait; use ValidatingTrait;
use Searchable; use Searchable;
/** /**
* The attributes that should be included when searching the model. * The attributes that should be included when searching the model.
* *
* @var array * @var array
*/ */
protected $searchableAttributes = ['name', 'created_at', 'updated_at']; protected $searchableAttributes = ['name', 'created_at', 'updated_at'];
/** /**
* The relations and their attributes that should be included when searching the model. * The relations and their attributes that should be included when searching the model.
* *
* @var array * @var array
*/ */
protected $searchableRelations = []; protected $searchableRelations = [];
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -81,10 +81,10 @@ final class Company extends SnipeModel
$company_id = null; $company_id = null;
} }
$table = ($table_name) ? $table_name.'.' : ''; $table = ($table_name) ? $table_name."." : $query->getModel()->getTable().".";
if (\Schema::hasColumn($query->getModel()->getTable(), $column)) { if (\Schema::hasColumn($query->getModel()->getTable(), $column)) {
return $query->where($table.$column, '=', $company_id); return $query->where($table.$column, '=', $company_id);
} else { } else {
return $query->join('users as users_comp', 'users_comp.id', 'user_id')->where('users_comp.company_id', '=', $company_id); return $query->join('users as users_comp', 'users_comp.id', 'user_id')->where('users_comp.company_id', '=', $company_id);
} }

View file

@ -78,7 +78,7 @@ class Consumable extends SnipeModel
* *
* @var array * @var array
*/ */
protected $searchableAttributes = ['name', 'order_number', 'purchase_cost', 'purchase_date', 'item_no']; protected $searchableAttributes = ['name', 'order_number', 'purchase_cost', 'purchase_date', 'item_no', 'model_number'];
/** /**
* The relations and their attributes that should be included when searching the model. * The relations and their attributes that should be included when searching the model.

View file

@ -47,13 +47,20 @@ class Ldap extends Model
ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, $ldap_version); ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, $ldap_version);
ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, 20); ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, 20);
if ($ldap_use_tls == '1') { if (Setting::getSettings()->ldap_client_tls_cert && Setting::getSettings()->ldap_client_tls_key) {
ldap_set_option($connection, LDAP_OPT_X_TLS_CERTFILE, Setting::get_client_side_cert_path());
ldap_set_option($connection, LDAP_OPT_X_TLS_KEYFILE, Setting::get_client_side_key_path());
}
if ($ldap_use_tls=='1') {
ldap_start_tls($connection); ldap_start_tls($connection);
} }
return $connection; return $connection;
} }
/** /**
* Binds/authenticates the user to LDAP, and returns their attributes. * Binds/authenticates the user to LDAP, and returns their attributes.
* *
@ -95,7 +102,7 @@ class Ldap extends Model
if (! $ldapbind = @ldap_bind($connection, $userDn, $password)) { if (! $ldapbind = @ldap_bind($connection, $userDn, $password)) {
if (! $ldapbind = self::bindAdminToLdap($connection)) { if (! $ldapbind = self::bindAdminToLdap($connection)) {
return false; return false;
} }
} }
@ -128,6 +135,8 @@ class Ldap extends Model
{ {
$ldap_username = Setting::getSettings()->ldap_uname; $ldap_username = Setting::getSettings()->ldap_uname;
$ldap_username = Setting::getSettings()->ldap_uname;
// Lets return some nicer messages for users who donked their app key, and disable LDAP // Lets return some nicer messages for users who donked their app key, and disable LDAP
try { try {
$ldap_pass = \Crypt::decrypt(Setting::getSettings()->ldap_pword); $ldap_pass = \Crypt::decrypt(Setting::getSettings()->ldap_pword);
@ -140,6 +149,7 @@ class Ldap extends Model
} }
} }
/** /**
* Parse and map LDAP attributes based on settings * Parse and map LDAP attributes based on settings
* *

View file

@ -341,8 +341,60 @@ class Setting extends Model
'is_ad', 'is_ad',
'ad_domain', 'ad_domain',
'ad_append_domain', 'ad_append_domain',
'ldap_client_tls_key',
'ldap_client_tls_cert'
])->first()->getAttributes(); ])->first()->getAttributes();
return collect($ldapSettings); return collect($ldapSettings);
} }
/**
* Return the filename for the client-side SSL cert
*
* @var string
*/
public static function get_client_side_cert_path()
{
return storage_path().'/ldap_client_tls.cert';
}
/**
* Return the filename for the client-side SSL key
*
* @var string
*/
public static function get_client_side_key_path()
{
return storage_path().'/ldap_client_tls.key';
}
public function update_client_side_cert_files()
{
/**
* I'm not sure if it makes sense to have a cert but no key
* nor vice versa, but for now I'm just leaving it like this.
*
* Also, we could easily set this up with an event handler and
* self::saved() or something like that but there's literally only
* one place where we will do that, so I'll just explicitly call
* this method at that spot instead. It'll be easier to debug and understand.
*/
if ($this->ldap_client_tls_cert) {
file_put_contents(self::get_client_side_cert_path(), $this->ldap_client_tls_cert);
} else {
if (file_exists(self::get_client_side_cert_path())) {
unlink(self::get_client_side_cert_path());
}
}
if ($this->ldap_client_tls_key) {
file_put_contents(self::get_client_side_key_path(), $this->ldap_client_tls_key);
} else {
if (file_exists(self::get_client_side_key_path())) {
unlink(self::get_client_side_key_path());
}
}
}
} }

View file

@ -103,7 +103,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
/** /**
* The relations and their attributes that should be included when searching the model. * The relations and their attributes that should be included when searching the model.
* *
* @var array * @var array
*/ */
protected $searchableRelations = [ protected $searchableRelations = [
@ -122,8 +122,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
protected function checkPermissionSection($section) protected function checkPermissionSection($section)
{ {
$user_groups = $this->groups; $user_groups = $this->groups;
if (($this->permissions == '') && (count($user_groups) == 0)) { if (($this->permissions == '') && (count($user_groups) == 0)) {
return false; return false;
} }
@ -181,6 +181,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->checkPermissionSection('superuser'); return $this->checkPermissionSection('superuser');
} }
/** /**
* Establishes the user -> company relationship * Establishes the user -> company relationship
* *
@ -439,6 +440,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at'); return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at');
} }
/** /**
* Query builder scope to return NOT-deleted users * Query builder scope to return NOT-deleted users
* @author A. Gianotto <snipe@snipe.net> * @author A. Gianotto <snipe@snipe.net>
@ -472,7 +474,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
/** /**
* Generate email from full name * Generate email from full name
* *
* @author A. Gianotto <snipe@snipe.net> * @author A. Gianotto <snipe@snipe.net>
* @since [v2.0] * @since [v2.0]
* *
@ -518,7 +520,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
$username = str_slug($first_name).str_slug($last_name); $username = str_slug($first_name).str_slug($last_name);
} elseif ($format == 'firstnamelastinitial') { } elseif ($format == 'firstnamelastinitial') {
$username = str_slug(($first_name.substr($last_name, 0, 1))); $username = str_slug(($first_name.substr($last_name, 0, 1)));
} }
} }
$user['first_name'] = $first_name; $user['first_name'] = $first_name;
@ -583,6 +585,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return false; return false;
} }
public function decodePermissions() public function decodePermissions()
{ {
return json_decode($this->permissions, true); return json_decode($this->permissions, true);
@ -599,13 +602,14 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/ */
public function scopeSimpleNameSearch($query, $search) public function scopeSimpleNameSearch($query, $search)
{ {
$query = $query->where('first_name', 'LIKE', '%'.$search.'%') $query = $query->where('first_name', 'LIKE', '%'.$search.'%')
->orWhere('last_name', 'LIKE', '%'.$search.'%') ->orWhere('last_name', 'LIKE', '%'.$search.'%')
->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$search%", "%$search%"]); ->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$search%"]);
return $query; return $query;
} }
/** /**
* Run additional, advanced searches. * Run additional, advanced searches.
* *
@ -613,10 +617,10 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
* @param array $terms The search terms * @param array $terms The search terms
* @return \Illuminate\Database\Eloquent\Builder * @return \Illuminate\Database\Eloquent\Builder
*/ */
public function advancedTextSearch(Builder $query, array $terms) public function advancedTextSearch(Builder $query, array $terms) {
{
foreach ($terms as $term) { foreach($terms as $term) {
$query = $query->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$term%", "%$term%"]); $query = $query->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$term%"]);
} }
return $query; return $query;

View file

@ -30,7 +30,7 @@ class CheckinAssetNotification extends Notification
$this->expected_checkin = ''; $this->expected_checkin = '';
if ($this->item->expected_checkin) { if ($this->item->expected_checkin) {
$this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date', $this->expected_checkin = Helper::getFormattedDateObject($this->item->expected_checkin, 'date',
false); false);
} }
} }

View file

@ -2,6 +2,7 @@
namespace App\Notifications; namespace App\Notifications;
use App\Helpers\Helper;
use App\Models\Asset; use App\Models\Asset;
use App\Models\Setting; use App\Models\Setting;
use App\Models\User; use App\Models\User;
@ -33,12 +34,12 @@ class CheckoutAssetNotification extends Notification
$this->expected_checkin = ''; $this->expected_checkin = '';
if ($this->item->last_checkout) { if ($this->item->last_checkout) {
$this->last_checkout = \App\Helpers\Helper::getFormattedDateObject($this->item->last_checkout, 'date', $this->last_checkout = Helper::getFormattedDateObject($this->item->last_checkout, 'date',
false); false);
} }
if ($this->item->expected_checkin) { if ($this->item->expected_checkin) {
$this->expected_checkin = \App\Helpers\Helper::getFormattedDateObject($this->item->expected_checkin, 'date', $this->expected_checkin = Helper::getFormattedDateObject($this->item->expected_checkin, 'date',
false); false);
} }
} }

View file

@ -38,21 +38,20 @@ class ActionlogPresenter extends Presenter
public function icon() public function icon()
{ {
$itemicon = 'fa fa-paperclip'; $itemicon = 'fas fa-paperclip';
if ($this->itemType() == 'asset') { if ($this->itemType() == 'asset') {
$itemicon = 'fa fa-barcode'; return 'fas fa-barcode';
} elseif ($this->itemType() == 'accessory') { } elseif ($this->itemType() == 'accessory') {
$itemicon = 'fa fa-keyboard-o'; return 'far fa-keyboard';
} elseif ($this->itemType() == 'consumable') { } elseif ($this->itemType() == 'consumable') {
$itemicon = 'fa fa-tint'; return 'fas fa-tint';
} elseif ($this->itemType() == 'license') { } elseif ($this->itemType() == 'license') {
$itemicon = 'fa fa-floppy-o'; return 'far fa-save';
} elseif ($this->itemType() == 'component') { } elseif ($this->itemType() == 'component') {
$itemicon = 'fa fa-hdd-o'; return 'far fa-hdd';
} }
return $itemicon;
} }
public function actionType() public function actionType()

View file

@ -248,7 +248,7 @@ class AssetAuditPresenter extends Presenter
'sortable' => true, 'sortable' => true,
'visible' => false, 'visible' => false,
'switchable' => true, 'switchable' => true,
'title' => ($field->field_encrypted == '1') ? '<i class="fa fa-lock"></i> '.e($field->name) : e($field->name), 'title' => ($field->field_encrypted == '1') ? '<i class="fas fa-lock"></i> '.e($field->name) : e($field->name),
'formatter' => 'customFieldsFormatter', 'formatter' => 'customFieldsFormatter',
]; ];
} }

View file

@ -106,6 +106,14 @@ class AssetModelPresenter extends Presenter
'visible' => true, 'visible' => true,
'formatter' => 'fieldsetsLinkObjFormatter', 'formatter' => 'fieldsetsLinkObjFormatter',
], ],
[
'field' => 'requestable',
'searchable' => false,
'sortable' => true,
'visible' => false,
'title' => trans('admin/hardware/general.requestable'),
'formatter' => 'trueFalseFormatter',
],
[ [
'field' => 'notes', 'field' => 'notes',
'searchable' => true, 'searchable' => true,

View file

@ -523,6 +523,6 @@ class AssetPresenter extends Presenter
public function glyph() public function glyph()
{ {
return '<i class="fa fa-barcode" aria-hidden="true"></i>'; return '<i class="fas fa-barcode" aria-hidden="true"></i>';
} }
} }

View file

@ -75,7 +75,25 @@ class CategoryPresenter extends Presenter
'sortable' => false, 'sortable' => false,
'switchable' => false, 'switchable' => false,
'title' => trans('table.actions'), 'title' => trans('table.actions'),
],[
"field" => "use_default_eula",
"searchable" => false,
"sortable" => true,
"title" => trans('admin/categories/general.use_default_eula_column'),
'visible' => true, 'visible' => true,
"formatter" => 'trueFalseFormatter',
],[
"field" => "checkin_email",
"searchable" => false,
"sortable" => true,
"class" => 'css-envelope',
"title" => 'Send Email',
"visible" => true,
"formatter" => 'trueFalseFormatter',
],[
"field" => "require_acceptance",
"searchable" => false,
"sortable" => true,
'formatter' => 'categoriesActionsFormatter', 'formatter' => 'categoriesActionsFormatter',
], ],
[ [

View file

@ -41,14 +41,14 @@ class CompanyPresenter extends Presenter
'field' => 'users_count', 'field' => 'users_count',
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
'title' => '<span class="hidden-xs"><i class="fa fa-users"></i></span><span class="hidden-md hidden-lg">'.trans('general.users').'</span></th>', 'title' => '<span class="hidden-xs"><i class="fas fa-users"></i></span><span class="hidden-md hidden-lg">'.trans('general.users').'</span></th>',
'visible' => true, 'visible' => true,
], [ ], [
'field' => 'assets_count', 'field' => 'assets_count',
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
'title' => '<span class="hidden-xs"><i class="fa fa-barcode" aria-hidden="true"></i></span><span class="hidden-md hidden-lg">'.trans('general.assets').'</span>', 'title' => '<span class="hidden-xs"><i class="fas fa-barcode" aria-hidden="true"></i></span><span class="hidden-md hidden-lg">'.trans('general.assets').'</span>',
'visible' => true, 'visible' => true,
], [ ], [
@ -56,25 +56,25 @@ class CompanyPresenter extends Presenter
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
'visible' => true, 'visible' => true,
'title' => ' <span class="hidden-xs"><i class="fa fa-floppy-o"></i></span><span class="hidden-md hidden-lg">'.trans('general.licenses').'</span>', 'title' => ' <span class="hidden-xs"><i class="far fa-save"></i></span><span class="hidden-md hidden-lg">'.trans('general.licenses').'</span>',
], [ ], [
'field' => 'accessories_count', 'field' => 'accessories_count',
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
'visible' => true, 'visible' => true,
'title' => ' <span class="hidden-xs"><i class="fa fa-keyboard-o"></i></span><span class="hidden-md hidden-lg">'.trans('general.accessories').'</span>', 'title' => ' <span class="hidden-xs"><i class="far fa-keyboard"></i></span><span class="hidden-md hidden-lg">'.trans('general.accessories').'</span>',
], [ ], [
'field' => 'consumables_count', 'field' => 'consumables_count',
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
'visible' => true, 'visible' => true,
'title' => ' <span class="hidden-xs"><i class="fa fa-tint"></i></span><span class="hidden-md hidden-lg">'.trans('general.consumables').'</span>', 'title' => ' <span class="hidden-xs"><i class="fas fa-tint"></i></span><span class="hidden-md hidden-lg">'.trans('general.consumables').'</span>',
], [ ], [
'field' => 'components_count', 'field' => 'components_count',
'searchable' => false, 'searchable' => false,
'sortable' => true, 'sortable' => true,
'visible' => true, 'visible' => true,
'title' => ' <span class="hidden-xs"><i class="fa fa-hdd-o"></i></span><span class="hidden-md hidden-lg">'.trans('general.components').'</span>', 'title' => ' <span class="hidden-xs"><i class="far fa-hdd"></i></span><span class="hidden-md hidden-lg">'.trans('general.components').'</span>',
], [ ], [
'field' => 'updated_at', 'field' => 'updated_at',
'searchable' => false, 'searchable' => false,

View file

@ -0,0 +1,401 @@
<?php
namespace App\Presenters;
use DateTime;
/**
* Class DepreciationReportPresenter
* @package App\Presenters
*/
class DepreciationReportPresenter extends Presenter
{
/**
* Json Column Layout for bootstrap table
* @return string
*/
public static function dataTableLayout()
{
$layout = [
[
"field" => "company",
"searchable" => true,
"sortable" => true,
"switchable" => true,
"title" => trans('general.company'),
"visible" => false,
], [
"field" => "category",
"searchable" => true,
"sortable" => true,
"title" => trans('general.category'),
"visible" => true,
], [
"field" => "name",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/form.name'),
"visible" => false,
], [
"field" => "asset_tag",
"searchable" => true,
"sortable" => true,
"title" => trans('general.asset_tag'),
"visible" => true,
],[
"field" => "model",
"searchable" => true,
"sortable" => true,
"title" => trans('general.asset_model'),
"visible" => true,
], [
"field" => "model",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/form.model'),
"visible" => true,
], [
"field" => "model_number",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/models/table.modelnumber'),
"visible" => false
], [
"field" => "serial",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/form.serial'),
"visible" => true,
], [
"field" => "depreciation",
"searchable" => true,
"sortable" => true,
"title" => trans('general.depreciation'),
"visible" => true,
], [
"field" => "number_of_months",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/depreciations/general.number_of_months'),
"visible" => true,
], [
"field" => "status",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/table.status'),
"visible" => true,
], [
"field" => "checked_out_to",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/table.checkoutto'),
"visible" => false,
], [
"field" => "location",
"searchable" => true,
"sortable" => true,
"title" => trans('admin/hardware/table.location'),
"visible" => true,
], [
"field" => "manufacturer",
"searchable" => true,
"sortable" => true,
"title" => trans('general.manufacturer'),
"visible" => false,
],[
"field" => "supplier",
"searchable" => true,
"sortable" => true,
"title" => trans('general.supplier'),
"visible" => false,
], [
"field" => "purchase_date",
"searchable" => true,
"sortable" => true,
"visible" => true,
"title" => trans('general.purchase_date'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "currency",
"searchable" => false,
"sortable" => false,
"visible" => false,
"title" => 'Currency',
], [
"field" => "purchase_cost",
"searchable" => true,
"sortable" => true,
"visible" => true,
"title" => trans('general.purchase_cost'),
"footerFormatter" => 'sumFormatter',
], [
"field" => "order_number",
"searchable" => true,
"sortable" => true,
"visible" => false,
"title" => trans('general.order_number'),
], [
"field" => "eol",
"searchable" => false,
"sortable" => false,
"visible" => false,
"title" => trans('general.eol'),
"formatter" => "dateDisplayFormatter"
], [
"field" => "book_value",
"searchable" => true,
"sortable" => true,
"visible" => true,
"title" => trans('admin/hardware/table.book_value'),
"footerFormatter" => 'sumFormatter',
], [
"field" => "monthly_depreciation",
"searchable" => true,
"sortable" => true,
"visible" => true,
"title" => trans('admin/hardware/table.monthly_depreciation')
],[
"field" => "diff",
"searchable" => false,
"sortable" => false,
"visible" => true,
"title" => trans('admin/hardware/table.diff'),
"footerFormatter" => 'sumFormatter',
],[
"field" => "warranty_expires",
"searchable" => false,
"sortable" => false,
"visible" => false,
"title" => trans('admin/hardware/form.warranty_expires'),
"formatter" => "dateDisplayFormatter"
],
];
return json_encode($layout);
}
/**
* Generate html link to this items name.
* @return string
*/
public function nameUrl()
{
return (string) link_to_route('hardware.show', e($this->name), $this->id);
}
public function modelUrl()
{
if ($this->model->model) {
return $this->model->model->present()->nameUrl();
}
return '';
}
/**
* Generate img tag to this items image.
* @return mixed|string
*/
public function imageUrl()
{
$imagePath = '';
if ($this->image && !empty($this->image)) {
$imagePath = $this->image;
$imageAlt = $this->name;
} elseif ($this->model && !empty($this->model->image)) {
$imagePath = $this->model->image;
$imageAlt = $this->model->name;
}
$url = config('app.url');
if (!empty($imagePath)) {
$imagePath = '<img src="'.$url.'/uploads/assets/'.$imagePath.' height="50" width="50" alt="'.$imageAlt.'">';
}
return $imagePath;
}
/**
* Generate img tag to this items image.
* @return mixed|string
*/
public function imageSrc()
{
$imagePath = '';
if ($this->image && !empty($this->image)) {
$imagePath = $this->image;
} elseif ($this->model && !empty($this->model->image)) {
$imagePath = $this->model->image;
}
if (!empty($imagePath)) {
return config('app.url').'/uploads/assets/'.$imagePath;
}
return $imagePath;
}
/**
* Get Displayable Name
* @return string
*
* @todo this should be factored out - it should be subsumed by fullName (below)
*
**/
public function name()
{
return $this->fullName;
}
/**
* Helper for notification polymorphism.
* @return mixed
*/
public function fullName()
{
$str = '';
// Asset name
if ($this->model->name) {
$str .= $this->model->name;
}
// Asset tag
if ($this->asset_tag) {
$str .= ' ('.$this->model->asset_tag.')';
}
// Asset Model name
if ($this->model->model) {
$str .= ' - '.$this->model->model->name;
}
return $str;
}
/**
* Returns the date this item hits EOL.
* @return false|string
*/
public function eol_date()
{
if (( $this->purchase_date ) && ( $this->model->model ) && ($this->model->model->eol) ) {
$date = date_create($this->purchase_date);
date_add($date, date_interval_create_from_date_string($this->model->model->eol . ' months'));
return date_format($date, 'Y-m-d');
}
}
/**
* How many months until this asset hits EOL.
* @return null
*/
public function months_until_eol()
{
$today = date("Y-m-d");
$d1 = new DateTime($today);
$d2 = new DateTime($this->eol_date());
if ($this->eol_date() > $today) {
$interval = $d2->diff($d1);
} else {
$interval = null;
}
return $interval;
}
/**
* @return string
* This handles the status label "meta" status of "deployed" if
* it's assigned. Should maybe deprecate.
*/
public function statusMeta()
{
if ($this->model->assigned) {
return 'deployed';
}
return $this->model->assetstatus->getStatuslabelType();
}
/**
* @return string
* This handles the status label "meta" status of "deployed" if
* it's assigned. Should maybe deprecate.
*/
public function statusText()
{
if ($this->model->assigned) {
return trans('general.deployed');
}
return $this->model->assetstatus->name;
}
/**
* @return string
* This handles the status label "meta" status of "deployed" if
* it's assigned. Results look like:
*
* (if assigned and the status label is "Ready to Deploy"):
* (Deployed)
*
* (f assigned and status label is not "Ready to Deploy":)
* Deployed (Another Status Label)
*
* (if not deployed:)
* Another Status Label
*/
public function fullStatusText() {
// Make sure the status is valid
if ($this->assetstatus) {
// If the status is assigned to someone or something...
if ($this->model->assigned) {
// If it's assigned and not set to the default "ready to deploy" status
if ($this->assetstatus->name != trans('general.ready_to_deploy')) {
return trans('general.deployed'). ' (' . $this->model->assetstatus->name.')';
}
// If it's assigned to the default "ready to deploy" status, just
// say it's deployed - otherwise it's confusing to have a status that is
// both "ready to deploy" and deployed at the same time.
return trans('general.deployed');
}
// Return just the status name
return $this->model->assetstatus->name;
}
// This status doesn't seem valid - either data has been manually edited or
// the status label was deleted.
return 'Invalid status';
}
/**
* Date the warantee expires.
* @return false|string
*/
public function warrantee_expires()
{
if (($this->purchase_date) && ($this->warranty_months)) {
$date = date_create($this->purchase_date);
date_add($date, date_interval_create_from_date_string($this->warranty_months . ' months'));
return date_format($date, 'Y-m-d');
}
return false;
}
/**
* Url to view this item.
* @return string
*/
public function viewUrl()
{
return route('hardware.show', $this->id);
}
public function glyph()
{
return '<i class="fas fa-barcode" aria-hidden="true"></i>';
}
}

View file

@ -193,7 +193,7 @@ class LocationPresenter extends Presenter
public function glyph() public function glyph()
{ {
return '<i class="fa fa-map-marker" aria-hidden="true"></i>'; return '<i class="fas fa-map-marker-alt" aria-hidden="true"></i>';
} }
public function fullName() public function fullName()

View file

@ -85,7 +85,7 @@ class ManufacturerPresenter extends Presenter
'sortable' => true, 'sortable' => true,
'switchable' => true, 'switchable' => true,
'title' => ' <span class="hidden-md hidden-lg">Assets</span>' 'title' => ' <span class="hidden-md hidden-lg">Assets</span>'
.'<span class="hidden-xs"><i class="fa fa-barcode fa-lg"></i></span>', .'<span class="hidden-xs"><i class="fas fa-barcode fa-lg"></i></span>',
'visible' => true, 'visible' => true,
], ],
[ [
@ -94,7 +94,7 @@ class ManufacturerPresenter extends Presenter
'sortable' => true, 'sortable' => true,
'switchable' => true, 'switchable' => true,
'title' => ' <span class="hidden-md hidden-lg">Licenses</span>' 'title' => ' <span class="hidden-md hidden-lg">Licenses</span>'
.'<span class="hidden-xs"><i class="fa fa-floppy-o fa-lg"></i></span>', .'<span class="hidden-xs"><i class="far fa-save fa-lg"></i></span>',
'visible' => true, 'visible' => true,
], ],
[ [
@ -103,7 +103,7 @@ class ManufacturerPresenter extends Presenter
'sortable' => true, 'sortable' => true,
'switchable' => true, 'switchable' => true,
'title' => ' <span class="hidden-md hidden-lg">Consumables</span>' 'title' => ' <span class="hidden-md hidden-lg">Consumables</span>'
.'<span class="hidden-xs"><i class="fa fa-tint fa-lg"></i></span>', .'<span class="hidden-xs"><i class="fas fa-tint fa-lg"></i></span>',
'visible' => true, 'visible' => true,
], ],
[ [
@ -112,7 +112,7 @@ class ManufacturerPresenter extends Presenter
'sortable' => true, 'sortable' => true,
'switchable' => true, 'switchable' => true,
'title' => ' <span class="hidden-md hidden-lg">Accessories</span>' 'title' => ' <span class="hidden-md hidden-lg">Accessories</span>'
.'<span class="hidden-xs"><i class="fa fa-keyboard-o fa-lg"></i></span>', .'<span class="hidden-xs"><i class="far fa-keyboard fa-lg"></i></span>',
'visible' => true, 'visible' => true,
], ],
[ [

View file

@ -312,7 +312,7 @@ class UserPresenter extends Presenter
{ {
if ($this->email) { if ($this->email) {
return '<a href="mailto:'.$this->email.'">'.$this->email.'</a>' return '<a href="mailto:'.$this->email.'">'.$this->email.'</a>'
.'<a href="mailto:'.$this->email.'" class="hidden-xs hidden-sm"><i class="fa fa-envelope"></i></a>'; .'<a href="mailto:'.$this->email.'" class="hidden-xs hidden-sm"><i class="far fa-envelope"></i></a>';
} }
return ''; return '';
@ -386,6 +386,6 @@ class UserPresenter extends Presenter
public function glyph() public function glyph()
{ {
return '<i class="fa fa-user" aria-hidden="true"></i>'; return '<i class="fas fa-user" aria-hidden="true"></i>';
} }
} }

View file

@ -54,6 +54,20 @@ class ValidationServiceProvider extends ServiceProvider
} }
}); });
// Unique if undeleted for two columns
// Same as unique_undeleted but taking the combination of two columns as unique constrain.
Validator::extend('two_column_unique_undeleted', function ($attribute, $value, $parameters, $validator) {
if (count($parameters)) {
$count = DB::table($parameters[0])
->select('id')->where($attribute, '=', $value)
->whereNull('deleted_at')
->where('id', '!=', $parameters[1])
->where($parameters[2], $parameters[3])->count();
return $count < 1;
}
});
// Prevent circular references // Prevent circular references
// //
// Example usage in Location model where parent_id references another Location: // Example usage in Location model where parent_id references another Location:

View file

@ -157,7 +157,7 @@ class LdapAdConfiguration
private function setLdapConnectionConfiguration(): array private function setLdapConnectionConfiguration(): array
{ {
// Create the configuration array. // Create the configuration array.
return [ $ldap_settings = [
// Mandatory Configuration Options // Mandatory Configuration Options
'hosts' => $this->getServerUrlBase(), 'hosts' => $this->getServerUrlBase(),
'base_dn' => $this->ldapSettings['ldap_basedn'], 'base_dn' => $this->ldapSettings['ldap_basedn'],
@ -181,6 +181,14 @@ class LdapAdConfiguration
// LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD, // LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_HARD,
], ],
]; ];
if($this->ldapSettings['ldap_client_tls_cert'] || $this->ldapSettings['ldap_client_tls_key']) {
$ldap_settings['custom_options'] = [
LDAP_OPT_X_TLS_CERTFILE => Setting::get_client_side_cert_path(),
LDAP_OPT_X_TLS_KEYFILE => Setting::get_client_side_key_path()
];
}
return $ldap_settings;
} }
/** /**

1036
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -250,6 +250,9 @@ return [
'enable_csp' => env('ENABLE_CSP', false), 'enable_csp' => env('ENABLE_CSP', false),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Demo Mode Lockdown | Demo Mode Lockdown
@ -273,6 +276,7 @@ return [
'min_php' => '7.2.5', 'min_php' => '7.2.5',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Autoloaded Service Providers | Autoloaded Service Providers
@ -397,6 +401,8 @@ return [
'Google2FA' => PragmaRX\Google2FALaravel\Facade::class, 'Google2FA' => PragmaRX\Google2FALaravel\Facade::class,
'Image' => Intervention\Image\ImageServiceProvider::class, 'Image' => Intervention\Image\ImageServiceProvider::class,
'Carbon' => Carbon\Carbon::class, 'Carbon' => Carbon\Carbon::class,
'Helper' => App\Helpers\Helper::class, // makes it much easier to use 'Helper::blah' in blades (which is where we usually use this)
], ],

View file

@ -54,6 +54,7 @@ class UserFactory extends Factory
'first_name' => 'Admin', 'first_name' => 'Admin',
'last_name' => 'User', 'last_name' => 'User',
'username' => 'admin', 'username' => 'admin',
'avatar' => '1.jpg',
'permissions' => '{"superuser":"1"}', 'permissions' => '{"superuser":"1"}',
]; ];
}); });
@ -66,6 +67,7 @@ class UserFactory extends Factory
'first_name' => 'Snipe E.', 'first_name' => 'Snipe E.',
'last_name' => 'Head', 'last_name' => 'Head',
'username' => 'snipe', 'username' => 'snipe',
'avatar' => '2.jpg',
'email' => 'snipe@snipe.net', 'email' => 'snipe@snipe.net',
'permissions' => '{"superuser":"1"}', 'permissions' => '{"superuser":"1"}',
]; ];

View file

@ -1,41 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
class CreateLicensesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('licenses', function ($table) {
$table->increments('id');
$table->string('name');
$table->integer('model_id');
$table->text('serial');
$table->string('license_email');
$table->string('license_name');
$table->date('purchase_date')->nullable();
$table->decimal('purchase_cost', 8, 2)->nullable();
$table->string('order_number');
$table->integer('assigned_to');
$table->text('notes');
$table->integer('user_id')->nullable();
$table->timestamps();
$table->engine = 'InnoDB';
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('licenses');
}
}

View file

@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
class CreateTempLicensesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (!Schema::hasTable('licenses')) {
Schema::create('licenses', function ($table) {
$table->increments('id');
$table->string('name');
$table->integer('model_id');
$table->text('serial');
$table->string('license_email');
$table->string('license_name');
$table->date('purchase_date')->nullable();
$table->decimal('purchase_cost', 8, 2)->nullable();
$table->string('order_number');
$table->integer('assigned_to');
$table->text('notes');
$table->integer('user_id')->nullable();
$table->timestamps();
$table->engine = 'InnoDB';
});
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('licenses');
}
}

View file

@ -11,7 +11,7 @@ class DropLicensesTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::drop('licenses'); Schema::dropIfExists('licenses');
} }
/** /**

View file

@ -13,7 +13,7 @@ class AddShowInNavToStatusLabels extends Migration
public function up() public function up()
{ {
Schema::table('status_labels', function (Blueprint $table) { Schema::table('status_labels', function (Blueprint $table) {
$table->boolean('show_in_nav')->default(0); $table->boolean('show_in_nav')->nullable()->default(0);
}); });
} }

View file

@ -14,7 +14,7 @@ class AddDefaultFlagOnStatuslabels extends Migration
public function up() public function up()
{ {
Schema::table('status_labels', function (Blueprint $table) { Schema::table('status_labels', function (Blueprint $table) {
$table->boolean('default_label')->default(0); $table->boolean('default_label')->nullable()->default(0);
}); });
} }

View file

@ -15,9 +15,16 @@ class MoveAccessoryCheckoutNoteToJoinTable extends Migration
*/ */
public function up() public function up()
{ {
Schema::table('accessories_users', function (Blueprint $table) {
$table->string('note')->nullable(true)->default(null); if (!Schema::hasColumn('accessories_users', 'note'))
}); {
Schema::table('accessories_users', function (Blueprint $table) {
$table->string('note')->nullable(true)->default(null);
});
}
// Loop through the checked out accessories, find their related action_log entry, and copy over the note // Loop through the checked out accessories, find their related action_log entry, and copy over the note
// to the newly created note field // to the newly created note field
@ -44,7 +51,9 @@ class MoveAccessoryCheckoutNoteToJoinTable extends Migration
->where('target_id', '=', $join_log->assigned_to) ->where('target_id', '=', $join_log->assigned_to)
->where('item_id', '=', $accessory->id) ->where('item_id', '=', $accessory->id)
->where('target_type', '=', \App\Models\User::class) ->where('target_type', '=', \App\Models\User::class)
->where('item_type', '=','App\\Models\\Accessory')
->where('action_type', '=', 'checkout') ->where('action_type', '=', 'checkout')
->where('note', '!=', '')
->orderBy('created_at', 'DESC')->get(); ->orderBy('created_at', 'DESC')->get();
\Log::debug($action_log_entries->count().' matching entries in the action_logs table'); \Log::debug($action_log_entries->count().' matching entries in the action_logs table');
@ -74,8 +83,15 @@ class MoveAccessoryCheckoutNoteToJoinTable extends Migration
*/ */
public function down() public function down()
{ {
Schema::table('accessories_users', function (Blueprint $table) {
$table->dropColumn('note'); if (Schema::hasColumn('accessories_users', 'note'))
}); {
Schema::table('accessories_users', function (Blueprint $table)
{
$table->dropColumn('note');
});
}
} }
} }

View file

@ -14,7 +14,7 @@ class AddDigitSeparatorToSettings extends Migration
public function up() public function up()
{ {
Schema::table('settings', function (Blueprint $table) { Schema::table('settings', function (Blueprint $table) {
$table->char('digit_separator')->nullable()->default('1234.56'); $table->char('digit_separator')->nullable()->default('1,234.56');
}); });
} }

View file

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

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddClientSideLDAPKeyToSettings extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('settings', function (Blueprint $table) {
$table->text("ldap_client_tls_key")->nullable()->default(null);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('settings', function (Blueprint $table) {
$table->dropColumn("ldap_client_tls_key");
});
}
}

View file

@ -29,4 +29,4 @@ class AddDepreciationMinimumValue extends Migration
$table->dropColumn('depreciation_min'); $table->dropColumn('depreciation_min');
}); });
} }
} }

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