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": [
"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
_ide_helper_models.php
/.phplint-cache
storage/ldap_client_tls.cert
storage/ldap_client_tls.key

View file

@ -1,5 +1,5 @@
FROM ubuntu:bionic
LABEL maintainer Brady Wetherington <uberbrady@gmail.com>
FROM ubuntu:20.04
LABEL maintainer="Brady Wetherington <bwetherington@grokability.com>"
# No need to add `apt-get clean` here, reference:
# - https://github.com/snipe/snipe-it/pull/9201
@ -14,15 +14,15 @@ RUN export DEBIAN_FRONTEND=noninteractive; \
apt-utils \
apache2 \
apache2-bin \
libapache2-mod-php7.2 \
php7.2-curl \
php7.2-ldap \
php7.2-mysql \
php7.2-gd \
php7.2-xml \
php7.2-mbstring \
php7.2-zip \
php7.2-bcmath \
libapache2-mod-php7.4 \
php7.4-curl \
php7.4-ldap \
php7.4-mysql \
php7.4-gd \
php7.4-xml \
php7.4-mbstring \
php7.4-zip \
php7.4-bcmath \
patch \
curl \
wget \
@ -38,7 +38,7 @@ autoconf \
libc-dev \
pkg-config \
libmcrypt-dev \
php7.2-dev \
php7.4-dev \
ca-certificates \
unzip \
&& 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 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 gd
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.2/cli/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.4/cli/php.ini
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"]
EXPOSE 80
EXPOSE 443
EXPOSE 443

View file

@ -1,4 +1,4 @@
FROM alpine:3.13
FROM alpine:3.14.2
# Apache + PHP
RUN apk add --no-cache \
apache2 \
@ -82,4 +82,4 @@ ENTRYPOINT ["/sbin/tini", "--"]
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)
[![All Contributors](https://img.shields.io/badge/all_contributors-259-orange.svg?style=flat-square)](#contributors)
![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-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
@ -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.
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
-----
### User's Manual
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)
- [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
- [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
- [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.
- [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.
@ -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/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/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 -->
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;
use App\Models\Accessory;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Category;
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 App\Models\CustomField;
use Schema;
use DB;
use Illuminate\Console\Command;
@ -29,15 +15,14 @@ class PaveIt extends Command
*
* @var string
*/
protected $signature = 'snipeit:pave
{--soft : Perform a "Soft" Delete, leaving all migrations, table structure, and the first user in place.}';
protected $signature = 'snipeit:pave {--force : Skip the interactive yes/no prompt for confirmation}';
/**
* The console command description.
*
* @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.
@ -56,106 +41,51 @@ class PaveIt extends Command
*/
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');
DB::statement('delete from asset_logs');
DB::statement('delete from asset_maintenances');
DB::statement('delete from login_attempts');
DB::statement('delete from asset_uploads');
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');
if (!$this->option('force')) {
$confirmation = $this->confirm("\n****************************************************\nTHIS WILL DELETE ALL OF THE DATA IN YOUR DATABASE. \nThere is NO undo. This WILL destroy ALL of your data, \nINCLUDING ANY non-Snipe-IT tables you have in this database. \n****************************************************\n\nDo you wish to continue? No backsies! ");
if (!$confirmation) {
$this->error('ABORTING');
exit(-1);
}
}
// 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;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Console\Command;
@ -39,6 +40,7 @@ class ResetDemoSettings extends Command
*/
public function handle()
{
$this->info('Resetting the demo settings.');
$settings = Setting::first();
$settings->per_page = 20;
@ -71,6 +73,7 @@ class ResetDemoSettings extends Command
$settings->saml_slo = null;
$settings->saml_custom_settings = null;
$settings->save();
if ($user = User::where('username', '=', 'admin')->first()) {
@ -78,7 +81,9 @@ class ResetDemoSettings extends Command
$user->save();
}
\Storage::disk('local_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.png', file_get_contents(public_path('img/demo/snipe-logo.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();
$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) {
$errors = [
ZipArchive::ER_EXISTS => 'File already exists.',
@ -248,8 +248,13 @@ class RestoreFromBackup extends Command
}
fclose($pipes[0]);
fclose($sql_contents);
$this->line(stream_get_contents($pipes[1]));
fclose($pipes[1]);
$this->error(stream_get_contents($pipes[2]));
fclose($pipes[2]);
//wait, have to do fclose() on all pipes first?
$close_results = proc_close($proc_results);
if ($close_results != 0) {

View file

@ -54,6 +54,7 @@ class Helper
return $cost;
}
/**
* Static colors for pie charts.
*
@ -332,6 +333,8 @@ class Helper
'#92896B',
];
return $colors[$index];
}
@ -389,6 +392,7 @@ class Helper
return $colors;
}
/**
* Format currency using comma for thousands until local info is property used.
*
@ -398,6 +402,19 @@ class Helper
*/
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();
$floatString = str_replace(',', '', $floatString);
$floatString = str_replace($LocaleInfo['decimal_point'], '.', $floatString);
@ -412,6 +429,26 @@ class Helper
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
@ -780,12 +817,12 @@ class Helper
} catch (DecryptException $e) {
return 'Error Decrypting: '.$e->getMessage();
}
}
}
return $string;
}
public static function formatStandardApiResponse($status, $payload = null, $messages = null)
{
$array['status'] = $status;
$array['messages'] = $messages;
@ -869,7 +906,8 @@ class Helper
// If upload_max_size is less, then reduce. Except if upload_max_size is
// zero, which indicates no limit.
$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');
}
}
@ -895,33 +933,33 @@ class Helper
$allowedExtensionMap = [
// Images
'jpg' => 'fa fa-file-image-o',
'jpeg' => 'fa fa-file-image-o',
'gif' => 'fa fa-file-image-o',
'png' => 'fa fa-file-image-o',
'jpg' => 'far fa-image',
'jpeg' => 'far fa-image',
'gif' => 'far fa-image',
'png' => 'far fa-image',
// word
'doc' => 'fa fa-file-word-o',
'docx' => 'fa fa-file-word-o',
'doc' => 'far fa-file-word',
'docx' => 'far fa-file-word',
// Excel
'xls' => 'fa fa-file-excel-o',
'xlsx' => 'fa fa-file-excel-o',
'xls' => 'far fa-file-excel',
'xlsx' => 'far fa-file-excel',
// archive
'zip' => 'fa fa-file-archive-o',
'rar' => 'fa fa-file-archive-o',
'zip' => 'fas fa-file-archive',
'rar' => 'fas fa-file-archive',
//Text
'txt' => 'fa fa-file-text-o',
'rtf' => 'fa fa-file-text-o',
'xml' => 'fa fa-file-text-o',
'txt' => 'far fa-file-alt',
'rtf' => 'far fa-file-alt',
'xml' => 'far fa-file-alt',
// Misc
'pdf' => 'fa fa-file-pdf-o',
'lic' => 'fa fa-file-floppy-o',
'pdf' => 'far fa-file-pdf',
'lic' => 'far fa-save',
];
if ($extension && array_key_exists($extension, $allowedExtensionMap)) {
return $allowedExtensionMap[$extension];
}
return 'fa fa-file-o';
return 'far fa-file';
}
public static function show_file_inline($filename)

View file

@ -66,19 +66,19 @@ class AccessoriesController extends Controller
$accessory = new Accessory();
// Update the accessory data
$accessory->name = request('name');
$accessory->category_id = request('category_id');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->order_number = request('order_number');
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseFloat(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->user_id = Auth::user()->id;
$accessory->supplier_id = request('supplier_id');
$accessory->name = request('name');
$accessory->category_id = request('category_id');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->order_number = request('order_number');
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->user_id = Auth::user()->id;
$accessory->supplier_id = request('supplier_id');
$accessory = $request->handleImages($accessory);
@ -101,6 +101,7 @@ class AccessoriesController extends Controller
*/
public function edit($accessoryId = null)
{
if ($item = Accessory::find($accessoryId)) {
$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'));
}
/**
* Save edited Accessory from form post
*
@ -128,18 +131,18 @@ class AccessoriesController extends Controller
$this->authorize($accessory);
// Update the accessory data
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = request('purchase_cost');
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory->name = request('name');
$accessory->location_id = request('location_id');
$accessory->min_amt = request('min_amt');
$accessory->category_id = request('category_id');
$accessory->company_id = Company::getIdForCurrentUser(request('company_id'));
$accessory->manufacturer_id = request('manufacturer_id');
$accessory->order_number = request('order_number');
$accessory->model_number = request('model_number');
$accessory->purchase_date = request('purchase_date');
$accessory->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$accessory->qty = request('qty');
$accessory->supplier_id = request('supplier_id');
$accessory = $request->handleImages($accessory);
@ -167,6 +170,7 @@ class AccessoriesController extends Controller
$this->authorize($accessory);
if ($accessory->hasUsers() > 0) {
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'));
}
/**
* Returns a view that invokes the ajax table which contains
* 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);
$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')) {
$accessories = $accessories->TextSearch($request->input('search'));
@ -51,6 +66,10 @@ class AccessoriesController extends Controller
$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
// 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);
@ -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');
$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':
$accessories = $accessories->OrderCategory($order);
break;
case 'company':
$accessories = $accessories->OrderCompany($order);
break;
case 'location':
$accessories = $accessories->OrderLocation($order);
break;
case 'manufacturer':
$accessories = $accessories->OrderManufacturer($order);
break;
case 'supplier':
$accessories = $accessories->OrderSupplier($order);
break;
default:
$accessories = $accessories->orderBy($sort, $order);
$accessories = $accessories->orderBy($column_sort, $order);
break;
}
$accessories->orderBy($sort, $order);
$total = $accessories->count();
$accessories = $accessories->skip($offset)->take($limit)->get();
return (new AccessoriesTransformer)->transformAccessories($accessories, $total);
}
/**
* Store a newly created resource in storage.
*
@ -94,12 +122,14 @@ class AccessoriesController extends Controller
$this->authorize('create', Accessory::class);
$accessory = new Accessory;
$accessory->fill($request->all());
$accessory = $request->handleImages($accessory);
if ($accessory->save()) {
return response()->json(Helper::formatStandardApiResponse('success', $accessory, trans('admin/accessories/message.create.success')));
}
return response()->json(Helper::formatStandardApiResponse('error', null, $accessory->getErrors()));
}
/**
@ -118,6 +148,7 @@ class AccessoriesController extends Controller
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
/**
* Display the specified resource.
*
@ -134,6 +165,7 @@ class AccessoriesController extends Controller
return (new AccessoriesTransformer)->transformAccessory($accessory);
}
/**
* Display the specified resource.
*
@ -165,15 +197,20 @@ class AccessoriesController extends Controller
if ($request->filled('search')) {
$accessory_users = $accessory->users()
->where('first_name', 'like', '%'.$request->input('search').'%')
->orWhere('last_name', 'like', '%'.$request->input('search').'%')
->get();
->where(function ($query) use ($request) {
$search_str = '%' . $request->input('search') . '%';
$query->where('first_name', 'like', $search_str)
->orWhere('last_name', 'like', $search_str)
->orWhere('note', 'like', $search_str);
})
->get();
$total = $accessory_users->count();
}
return (new AccessoriesTransformer)->transformCheckedoutAccessory($accessory, $accessory_users, $total);
}
/**
* 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')));
}
/**
* Save the Accessory checkout information.
*
@ -239,7 +277,9 @@ class AccessoriesController extends Controller
$this->authorize('checkout', $accessory);
if ($accessory->numRemaining() > 0) {
if (! $user = User::find($request->input('assigned_to'))) {
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'));
}
/**
@ -299,19 +340,23 @@ class AccessoriesController extends Controller
$data['item_tag'] = '';
$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')));
}
/**
* Gets a paginated collection for the select2 menus
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
* Gets a paginated collection for the select2 menus
*
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$accessories = Accessory::select([
'accessories.id',
'accessories.name',
@ -325,4 +370,5 @@ class AccessoriesController extends Controller
return (new SelectlistTransformer)->transformSelectlist($accessories);
}
}

View file

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

View file

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

View file

@ -2,12 +2,16 @@
namespace App\Http\Controllers\Api;
use App\Events\CheckoutableCheckedIn;
use Illuminate\Support\Facades\Gate;
use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckoutRequest;
use App\Http\Transformers\AssetsTransformer;
use App\Http\Transformers\DepreciationReportTransformer;
use App\Http\Transformers\LicensesTransformer;
use App\Http\Transformers\SelectlistTransformer;
use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\AssetModel;
use App\Models\Company;
@ -20,7 +24,6 @@ use Auth;
use Carbon\Carbon;
use DB;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use App\Http\Requests\ImageUploadRequest;
use Input;
use Paginator;
@ -28,6 +31,7 @@ use Slack;
use Str;
use TCPDF;
use Validator;
use Route;
/**
* This class controls all actions related to assets for
@ -46,9 +50,32 @@ class AssetsController extends Controller
* @since [v4.0]
* @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();
$allowed_columns = [
@ -161,6 +188,8 @@ class AssetsController extends Controller
}
}
// This is used by the sidenav, mostly
// 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);
});
// 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 {
$assets->join('status_labels AS status_alias', function ($join) {
$join->on('status_alias.id', '=', 'assets.status_id');
@ -230,12 +259,14 @@ class AssetsController extends Controller
}
if ((! is_null($filter)) && (count($filter)) > 0) {
$assets->ByFilter($filter);
} elseif ($request->filled('search')) {
$assets->TextSearch($request->input('search'));
}
// 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
// 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)
$column_sort = in_array($sort_override, $allowed_columns) ? $sort_override : 'assets.created_at';
switch ($sort_override) {
case 'model':
$assets->OrderModels($order);
@ -280,10 +312,28 @@ class AssetsController extends Controller
break;
}
$total = $assets->count();
$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]
* @return JsonResponse
*/
public function showByTag($tag)
public function showByTag(Request $request, $tag)
{
if ($asset = Asset::with('assetstatus')->with('assignedTo')->where('asset_tag', $tag)->first()) {
$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);
}
/**
@ -313,17 +364,19 @@ class AssetsController extends Controller
* @since [v4.2.1]
* @return JsonResponse
*/
public function showBySerial($serial)
public function showBySerial(Request $request, $serial)
{
$this->authorize('index', Asset::class);
if ($assets = Asset::with('assetstatus')->with('assignedTo')
->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);
}
/**
* Returns JSON with information about an asset for detail view.
*
@ -332,17 +385,19 @@ class AssetsController extends Controller
* @since [v4.0]
* @return JsonResponse
*/
public function show($id)
public function show(Request $request, $id)
{
if ($asset = Asset::with('assetstatus')->with('assignedTo')->withTrashed()
->withCount('checkins as checkins_count', 'checkouts as checkouts_count', 'userRequests as user_requests_count')->findOrFail($id)) {
$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', License::class);
@ -350,7 +405,8 @@ class AssetsController extends Controller
$licenses = $asset->licenses()->get();
return (new LicensesTransformer())->transformLicenses($licenses, $licenses->count());
}
}
/**
* Gets a paginated collection for the select2 menus
@ -358,9 +414,11 @@ class AssetsController extends Controller
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v4.0.16]
* @see \App\Http\Transformers\SelectlistTransformer
*
*/
public function selectlist(Request $request)
{
$assets = Company::scopeCompanyables(Asset::select([
'assets.id',
'assets.name',
@ -379,18 +437,22 @@ class AssetsController extends Controller
$assets = $assets->AssignedSearch($request->input('search'));
}
$assets = $assets->paginate(50);
// Loop through and set some custom properties for the transformer to use.
// 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
foreach ($assets as $asset) {
$asset->use_text = $asset->present()->fullName;
if (($asset->checkedOutToUser()) && ($asset->assigned)) {
$asset->use_text .= ' → '.$asset->assigned->getFullNameAttribute();
}
if ($asset->assetstatus->getStatuslabelType() == 'pending') {
$asset->use_text .= '('.$asset->assetstatus->getStatuslabelType().')';
}
@ -401,6 +463,7 @@ class AssetsController extends Controller
return (new SelectlistTransformer)->transformSelectlist($assets);
}
/**
* Accepts a POST request to create a new asset
*
@ -416,34 +479,34 @@ class AssetsController extends Controller
$asset = new Asset();
$asset->model()->associate(AssetModel::find((int) $request->get('model_id')));
$asset->name = $request->get('name');
$asset->serial = $request->get('serial');
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id'));
$asset->model_id = $request->get('model_id');
$asset->order_number = $request->get('order_number');
$asset->notes = $request->get('notes');
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
$asset->depreciate = '0';
$asset->status_id = $request->get('status_id', 0);
$asset->warranty_months = $request->get('warranty_months', null);
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost'));
$asset->purchase_date = $request->get('purchase_date', null);
$asset->assigned_to = $request->get('assigned_to', null);
$asset->supplier_id = $request->get('supplier_id', 0);
$asset->requestable = $request->get('requestable', 0);
$asset->rtd_location_id = $request->get('rtd_location_id', null);
$asset->location_id = $request->get('rtd_location_id', null);
$asset->name = $request->get('name');
$asset->serial = $request->get('serial');
$asset->company_id = Company::getIdForCurrentUser($request->get('company_id'));
$asset->model_id = $request->get('model_id');
$asset->order_number = $request->get('order_number');
$asset->notes = $request->get('notes');
$asset->asset_tag = $request->get('asset_tag', Asset::autoincrement_asset());
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
$asset->depreciate = '0';
$asset->status_id = $request->get('status_id', 0);
$asset->warranty_months = $request->get('warranty_months', null);
$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->assigned_to = $request->get('assigned_to', null);
$asset->supplier_id = $request->get('supplier_id', 0);
$asset->requestable = $request->get('requestable', 0);
$asset->rtd_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
* used image_source once to allow encoded image uploads.
*/
if ($request->has('image_source')) {
$request->offsetSet('image', $request->offsetGet('image_source'));
}
}
$asset = $request->handleImages($asset);
@ -478,6 +541,7 @@ class AssetsController extends Controller
}
}
$asset->{$field->convertUnicodeDbSlug()} = $field_val;
}
}
@ -504,6 +568,7 @@ class AssetsController extends Controller
return response()->json(Helper::formatStandardApiResponse('error', null, $asset->getErrors()), 200);
}
/**
* Accepts a POST request to update an asset
*
@ -554,9 +619,10 @@ class AssetsController extends Controller
}
}
if ($asset->save()) {
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')))) {
$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);
}
/**
* 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);
}
/**
* 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
*
@ -635,12 +735,14 @@ class AssetsController extends Controller
'asset_tag' => $asset->asset_tag,
];
// This item is checked out to a location
if (request('checkout_to_type') == 'location') {
$target = Location::find(request('assigned_location'));
$asset->location_id = ($target) ? $target->id : '';
$error_payload['target_id'] = $request->input('assigned_location');
$error_payload['target_type'] = 'location';
} elseif (request('checkout_to_type') == 'asset') {
$target = Asset::where('id', '!=', $asset_id)->find(request('assigned_asset'));
$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 : '';
$error_payload['target_id'] = $request->input('assigned_asset');
$error_payload['target_type'] = 'asset';
} elseif (request('checkout_to_type') == 'user') {
// Fetch the target and set the asset's new location_id
$target = User::find(request('assigned_user'));
@ -656,10 +759,14 @@ class AssetsController extends Controller
$error_payload['target_type'] = 'user';
}
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.'));
}
$checkout_at = request('checkout_at', date('Y-m-d H:i:s'));
$expected_checkin = request('expected_checkin', 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
// Wait, why are we doing this? This overrides the stuff we set further up, which makes no sense.
// TODO: Follow up here. WTF. Commented out for now.
// TODO: Follow up here. WTF. Commented out for now.
// if ((isset($target->rtd_location_id)) && ($asset->rtd_location_id!='')) {
// $asset->location_id = $target->rtd_location_id;
// }
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')));
}
@ -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')));
}
/**
* Checkin an asset
*
@ -694,6 +805,7 @@ class AssetsController extends Controller
$asset = Asset::findOrFail($asset_id);
$this->authorize('checkin', $asset);
$user = $asset->assignedUser;
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')));
@ -708,7 +820,7 @@ class AssetsController extends Controller
if ($request->filled('name')) {
$asset->name = $request->input('name');
}
$asset->location_id = $asset->rtd_location_id;
if ($request->filled('location_id')) {
@ -720,7 +832,7 @@ class AssetsController extends Controller
}
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')));
}
@ -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')));
}
/**
* Mark an asset as audited
*
@ -738,6 +851,8 @@ class AssetsController extends Controller
*/
public function audit(Request $request)
{
$this->authorize('audit', Asset::class);
$rules = [
'asset_tag' => 'required',
@ -755,6 +870,7 @@ class AssetsController extends Controller
$asset = Asset::where('asset_tag', '=', $request->input('asset_tag'))->first();
if ($asset) {
// We don't want to log this as a normal update, so let's bypass that
$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'));
}
/**
* Returns JSON listing of all requestable assets
*

View file

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

View file

@ -9,6 +9,9 @@ use App\Models\Company;
use App\Models\Component;
use Illuminate\Http\Request;
use App\Http\Requests\ImageUploadRequest;
use App\Events\CheckoutableCheckedIn;
use App\Events\ComponentCheckedIn;
use App\Models\Asset;
class ComponentsController extends Controller
{
@ -23,8 +26,25 @@ class ComponentsController extends Controller
public function index(Request $request)
{
$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.*')
->with('company', 'location', 'category'));
->with('company', 'location', 'category', 'assets'));
if ($request->filled('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
((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';
$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':
$components = $components->OrderCategory($order);
break;
@ -64,7 +85,7 @@ class ComponentsController extends Controller
$components = $components->OrderCompany($order);
break;
default:
$components = $components->orderBy($sort, $order);
$components = $components->orderBy($column_sort, $order);
break;
}
@ -74,6 +95,7 @@ class ComponentsController extends Controller
return (new ComponentsTransformer)->transformComponents($components, $total);
}
/**
* Store a newly created resource in storage.
*
@ -163,11 +185,11 @@ class ComponentsController extends Controller
* @param Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
*/
public function getAssets(Request $request, $id)
{
$this->authorize('view', \App\Models\Asset::class);
$component = Component::findOrFail($id);
$assets = $component->assets();
@ -178,4 +200,119 @@ class ComponentsController extends Controller
return (new ComponentsTransformer)->transformCheckedoutComponents($assets, $total);
}
/**
* Validate and checkout the component.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* t
* @since [v5.1.8]
* @param Request $request
* @param int $componentId
* @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)
{
$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(
Consumable::select('consumables.*')
->with('company', 'location', 'category', 'users', 'manufacturer')
@ -42,10 +62,19 @@ class ConsumablesController extends Controller
$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')) {
$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
// 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);
@ -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'];
$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':
$consumables = $consumables->OrderCategory($order);
break;
@ -71,7 +103,7 @@ class ConsumablesController extends Controller
$consumables = $consumables->OrderCompany($order);
break;
default:
$consumables = $consumables->orderBy($sort, $order);
$consumables = $consumables->orderBy($column_sort, $order);
break;
}
@ -160,13 +192,13 @@ class ConsumablesController extends Controller
}
/**
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return array
* Returns a JSON response containing details on the users associated with this consumable.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see \App\Http\Controllers\Consumables\ConsumablesController::getView() method that returns the form.
* @since [v1.0]
* @param int $consumableId
* @return array
*/
public function getDataView($consumableId)
{
@ -251,10 +283,10 @@ class ConsumablesController extends Controller
}
/**
* Gets a paginated collection for the select2 menus
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
* Gets a paginated collection for the select2 menus
*
* @see \App\Http\Transformers\SelectlistTransformer
*/
public function selectlist(Request $request)
{
$consumables = Consumable::select([

View file

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

View file

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

View file

@ -8,7 +8,7 @@ use App\Http\Transformers\SelectlistTransformer;
use App\Http\Transformers\SuppliersTransformer;
use App\Models\Supplier;
use Illuminate\Http\Request;
use App\http\Requests\ImageUploadRequest;
use App\Http\Requests\ImageUploadRequest;
use Illuminate\Support\Facades\Storage;
class SuppliersController extends Controller
@ -24,11 +24,12 @@ class SuppliersController extends Controller
{
$this->authorize('view', Supplier::class);
$allowed_columns = ['id', 'name', 'address', 'phone', 'contact', 'fax', 'email', 'image', 'assets_count', 'licenses_count', 'accessories_count', 'url'];
$suppliers = Supplier::select(
['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');
if ($request->filled('search')) {
$suppliers = $suppliers->TextSearch($request->input('search'));
}
@ -50,6 +51,7 @@ class SuppliersController extends Controller
return (new SuppliersTransformer)->transformSuppliers($suppliers, $total);
}
/**
* 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');
$users = Company::scopeCompanyables($users);
if (($request->filled('deleted')) && ($request->input('deleted') == 'true')) {
$users = $users->onlyTrashed();
} elseif (($request->filled('all')) && ($request->input('all') == 'true')) {
$users = $users->withTrashed();
}
if ($request->filled('activated')) {
$users = $users->where('users.activated', '=', $request->input('activated'));
}
if ($request->filled('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'));
}
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')) {
$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'));
}
if ($request->filled('manager_id')) {
$users = $users->where('users.manager_id','=',$request->input('manager_id'));
}
if ($request->filled('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
((config('app.max_results') >= $request->input('limit')) && ($request->filled('limit'))) ? $limit = $request->input('limit') : $limit = config('app.max_results');
switch ($request->input('sort')) {
case 'manager':
$users = $users->OrderManager($order);
@ -201,6 +235,8 @@ class UsersController extends Controller
return (new SelectlistTransformer)->transformSelectlist($users);
}
/**
* Store a newly created resource in storage.
*
@ -259,6 +295,7 @@ class UsersController extends Controller
return (new UsersTransformer)->transformUser($user);
}
/**
* Update the specified resource in storage.
*
@ -274,14 +311,21 @@ class UsersController extends Controller
$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.
// Thanks, jerks. You are why we can't have nice things. - snipe
/**
* This is a janky hack to prevent people from changing admin demo user data on the public demo.
*
* The $ids 1 and 2 are special since they are seeded as superadmins in the demo seeder.
*
* Thanks, jerks. You are why we can't have nice things. - snipe
*
*/
if ((($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.'));
}
$user->fill($request->all());
if ($user->id == $request->input('manager_id')) {
@ -305,6 +349,8 @@ class UsersController extends Controller
$user->permissions = $permissions_array;
}
// Update the location of any assets checked out to this user
Asset::where('assigned_type', User::class)
->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]);
@ -328,6 +374,7 @@ class UsersController extends Controller
$user->groups()->sync([]);
}
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
* @return string JSON
*/
public function assets($id)
public function assets(Request $request, $id)
{
$this->authorize('view', User::class);
$this->authorize('view', Asset::class);
$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' => 'No ID provided'], 500);
}
/**
@ -474,4 +522,28 @@ class UsersController extends Controller
{
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
{
/**
* Checks for permissions for this action.
*
* @todo This should be replaced with middleware and/or policies
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return View
*/
* Checks for permissions for this action.
*
* @todo This should be replaced with middleware and/or policies
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return View
*/
private static function getInsufficientPermissionsRedirect()
{
return redirect()->route('maintenances.index')
@ -38,16 +38,16 @@ class AssetMaintenancesController extends Controller
}
/**
* Returns a view that invokes the ajax tables which actually contains
* the content for the asset maintenances listing, which is generated in getDatatable.
*
* @todo This should be replaced with middleware and/or policies
* @see AssetMaintenancesController::getDatatable() method that generates the JSON response
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return View
*/
* Returns a view that invokes the ajax tables which actually contains
* the content for the asset maintenances listing, which is generated in getDatatable.
*
* @todo This should be replaced with middleware and/or policies
* @see AssetMaintenancesController::getDatatable() method that generates the JSON response
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return View
*/
public function index()
{
return view('asset_maintenances/index');
@ -84,21 +84,21 @@ class AssetMaintenancesController extends Controller
}
/**
* Validates and stores the new asset maintenance
*
* @see AssetMaintenancesController::getCreate() method for the form
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return mixed
*/
* Validates and stores the new asset maintenance
*
* @see AssetMaintenancesController::getCreate() method for the form
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
* @since [v1.8]
* @return mixed
*/
public function store(Request $request)
{
// create a new model instance
$assetMaintenance = new AssetMaintenance();
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = $request->input('cost');
$assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->notes = $request->input('notes');
$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.
*
* @see AssetMaintenancesController::postEdit() method that stores the data
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
* @return mixed
*/
* Returns a form view to edit a selected asset maintenance.
*
* @see AssetMaintenancesController::postEdit() method that stores the data
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
* @return mixed
*/
public function edit($assetMaintenanceId = null)
{
// Check if the asset maintenance exists
@ -204,10 +204,10 @@ class AssetMaintenancesController extends Controller
return static::getInsufficientPermissionsRedirect();
}
$assetMaintenance->supplier_id = e($request->input('supplier_id'));
$assetMaintenance->is_warranty = e($request->input('is_warranty'));
$assetMaintenance->cost = Helper::ParseFloat(e($request->input('cost')));
$assetMaintenance->notes = e($request->input('notes'));
$assetMaintenance->supplier_id = $request->input('supplier_id');
$assetMaintenance->is_warranty = $request->input('is_warranty');
$assetMaintenance->cost = Helper::ParseCurrency($request->input('cost'));
$assetMaintenance->notes = $request->input('notes');
$asset = Asset::find(request('asset_id'));
@ -240,7 +240,7 @@ class AssetMaintenancesController extends Controller
$assetMaintenance->asset_maintenance_time = $completionDate->diffInDays($startDate);
}
// Was the asset maintenance created?
// Was the asset maintenance created?
if ($assetMaintenance->save()) {
// Redirect to the new asset maintenance page
@ -252,14 +252,14 @@ class AssetMaintenancesController extends Controller
}
/**
* Delete an asset maintenance
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
* @return mixed
*/
* Delete an asset maintenance
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
* @return mixed
*/
public function destroy($assetMaintenanceId)
{
// Check if the asset maintenance exists
@ -280,14 +280,14 @@ class AssetMaintenancesController extends Controller
}
/**
* View an asset maintenance
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
* @return View
*/
* View an asset maintenance
*
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @param int $assetMaintenanceId
* @version v1.0
* @since [v1.8]
* @return View
*/
public function show($assetMaintenanceId)
{
// Check if the asset maintenance exists

View file

@ -7,6 +7,8 @@ use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Http\Requests\AssetCheckinRequest;
use App\Models\Asset;
use App\Models\CheckoutAcceptance;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View;
@ -109,6 +111,16 @@ class AssetCheckinController extends Controller
$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?
if ($asset->save()) {
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 Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use enshrined\svgSanitize\Sanitizer;
class AssetFilesController extends Controller
{
@ -36,9 +37,29 @@ class AssetFilesController extends Controller
}
foreach ($request->file('file') as $file) {
$extension = $file->getClientOriginalExtension();
$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));
}
$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'));
}
$log->delete();
return redirect()->back()
->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->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
$asset->depreciate = '0';
$asset->status_id = request('status_id', 0);
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = Helper::ParseFloat($request->get('purchase_cost'));
$asset->purchase_date = request('purchase_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', 0);
$asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null);
$asset->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$asset->model_id = $request->input('model_id');
$asset->order_number = $request->input('order_number');
$asset->notes = $request->input('notes');
$asset->user_id = Auth::id();
$asset->archived = '0';
$asset->physical = '1';
$asset->depreciate = '0';
$asset->status_id = request('status_id', 0);
$asset->warranty_months = request('warranty_months', null);
$asset->purchase_cost = Helper::ParseCurrency($request->get('purchase_cost'));
$asset->purchase_date = request('purchase_date', null);
$asset->assigned_to = request('assigned_to', null);
$asset->supplier_id = request('supplier_id', 0);
$asset->requestable = request('requestable', 0);
$asset->rtd_location_id = request('rtd_location_id', null);
if (! empty($settings->audit_interval)) {
$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->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->supplier_id = $request->input('supplier_id', null);
$asset->expected_checkin = $request->input('expected_checkin', null);
@ -357,6 +357,7 @@ class AssetsController extends Controller
}
}
if ($asset->save()) {
return redirect()->route('hardware.show', $assetId)
->with('success', trans('admin/hardware/message.update.success'));
@ -828,8 +829,8 @@ class AssetsController extends Controller
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'));
}
}

View file

@ -32,7 +32,8 @@ class BulkAssetsController extends Controller
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')) {
switch ($request->input('bulk_actions')) {
@ -51,7 +52,7 @@ class BulkAssetsController extends Controller
return view('hardware/bulk-delete')->with('assets', $assets);
case 'edit':
return view('hardware/bulk')
->with('assets', request('ids'))
->with('assets', $asset_ids)
->with('statuslabel_list', Helper::statusLabelList());
}
}
@ -92,6 +93,7 @@ class BulkAssetsController extends Controller
|| ($request->filled('model_id'))
) {
foreach ($assets as $assetId) {
$this->update_array = [];
$this->conditionallyAddItem('purchase_date')
@ -104,7 +106,7 @@ class BulkAssetsController extends Controller
->conditionallyAddItem('warranty_months');
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')) {

View file

@ -29,6 +29,7 @@ class ForgotPasswordController extends Controller
public function __construct()
{
$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
* 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(
$request->only('username'),
['activated' => '1'],
['ldap_import' => '0']
)
);
$response = null;
try {
$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) {
\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.
* 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,
// 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'));
}
}
}

View file

@ -154,6 +154,9 @@ class LoginController extends Controller
{
$header_name = Setting::getSettings()->login_remote_user_header_name ?: 'REMOTE_USER';
$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)) {
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');
}
if ($request->filled('requestable') != '') {
$update_array['requestable'] = $request->input('requestable');
}
if (count($update_array) > 0) {
AssetModel::whereIn('id', $models_raw_array)->update($update_array);

View file

@ -39,6 +39,8 @@ trait CheckInOutRequest
switch (request('checkout_to_type')) {
case 'location':
$asset->location_id = $target->id;
Asset::where('assigned_type', 'App\Models\Asset')->where('assigned_to', $asset->id)
->update(['location_id' => $asset->location_id]);
break;
case 'asset':
$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\Models\Company;
use App\Models\Component;
use App\Helpers\Helper;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Storage;
@ -36,6 +37,7 @@ class ComponentsController extends Controller
return view('components/index');
}
/**
* Returns a form to create a new component.
*
@ -67,17 +69,17 @@ class ComponentsController extends Controller
{
$this->authorize('create', Component::class);
$component = new Component();
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null);
$component->min_amt = $request->input('min_amt', null);
$component->serial = $request->input('serial', null);
$component->purchase_date = $request->input('purchase_date', null);
$component->purchase_cost = $request->input('purchase_cost', null);
$component->qty = $request->input('qty');
$component->user_id = Auth::id();
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number', null);
$component->min_amt = $request->input('min_amt', null);
$component->serial = $request->input('serial', null);
$component->purchase_date = $request->input('purchase_date', null);
$component->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost', null));
$component->qty = $request->input('qty');
$component->user_id = Auth::id();
$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 a view to edit a component.
*
@ -139,16 +142,16 @@ class ComponentsController extends Controller
$this->authorize('update', $component);
// Update the component data
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number');
$component->min_amt = $request->input('min_amt');
$component->serial = $request->input('serial');
$component->purchase_date = $request->input('purchase_date');
$component->purchase_cost = request('purchase_cost');
$component->qty = $request->input('qty');
$component->name = $request->input('name');
$component->category_id = $request->input('category_id');
$component->location_id = $request->input('location_id');
$component->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$component->order_number = $request->input('order_number');
$component->min_amt = $request->input('min_amt');
$component->serial = $request->input('serial');
$component->purchase_date = $request->input('purchase_date');
$component->purchase_cost = Helper::ParseCurrency(request('purchase_cost'));
$component->qty = $request->input('qty');
$component = $request->handleImages($component);

View file

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

View file

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

View file

@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
use App\Models\License;
use App\Models\LicenseSeat;
use App\Models\User;
use App\Models\Asset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
@ -80,7 +81,12 @@ class LicenseCheckinController extends Controller
// Ooops.. something went wrong
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
$licenseSeat->assigned_to = null;

View file

@ -7,10 +7,10 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\AssetFileRequest;
use App\Models\Actionlog;
use App\Models\License;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
class LicenseFilesController extends Controller
{
@ -37,26 +37,39 @@ class LicenseFilesController extends Controller
Storage::makeDirectory('private_uploads/licenses', 775);
}
$upload_success = false;
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);
// $upload_success = $file->storeAs('private_uploads/licenses/'.$file_name, $file);
$extension = $file->getClientOriginalExtension();
$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
$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'));
@ -153,6 +166,7 @@ class LicenseFilesController extends Controller
}
return StorageHelper::downloader($file);
}
}
}

View file

@ -76,27 +76,27 @@ class LicensesController extends Controller
// create a new model instance
$license = new License();
// Save the license data
$license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained', 0);
$license->manufacturer_id = $request->input('manufacturer_id');
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = $request->input('purchase_cost');
$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->seats = $request->input('seats');
$license->serial = $request->input('serial');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date');
$license->user_id = Auth::id();
$license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained', 0);
$license->manufacturer_id = $request->input('manufacturer_id');
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$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->seats = $request->input('seats');
$license->serial = $request->input('serial');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
$license->termination_date = $request->input('termination_date');
$license->user_id = Auth::id();
if ($license->save()) {
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);
$license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained', 0);
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = $request->input('purchase_cost');
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0);
$license->serial = $request->input('serial');
$license->termination_date = $request->input('termination_date');
$license->seats = e($request->input('seats'));
$license->manufacturer_id = $request->input('manufacturer_id');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
$license->company_id = Company::getIdForCurrentUser($request->input('company_id'));
$license->depreciation_id = $request->input('depreciation_id');
$license->expiration_date = $request->input('expiration_date');
$license->license_email = $request->input('license_email');
$license->license_name = $request->input('license_name');
$license->maintained = $request->input('maintained',0);
$license->name = $request->input('name');
$license->notes = $request->input('notes');
$license->order_number = $request->input('order_number');
$license->purchase_cost = Helper::ParseCurrency($request->input('purchase_cost'));
$license->purchase_date = $request->input('purchase_date');
$license->purchase_order = $request->input('purchase_order');
$license->reassignable = $request->input('reassignable', 0);
$license->serial = $request->input('serial');
$license->termination_date = $request->input('termination_date');
$license->seats = e($request->input('seats'));
$license->manufacturer_id = $request->input('manufacturer_id');
$license->supplier_id = $request->input('supplier_id');
$license->category_id = $request->input('category_id');
if ($license->save()) {
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\License;
use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\View;
use Input;
@ -37,12 +39,12 @@ class ReportsController extends Controller
}
/**
* Returns a view that displays the accessories report.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
* Returns a view that displays the accessories report.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
public function getAccessoryReport()
{
$this->authorize('reports.view');
@ -52,14 +54,14 @@ class ReportsController extends Controller
}
/**
* Exports the accessories to CSV
*
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
* Exports the accessories to CSV
*
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ManufacturersController::getDatatable() method that generates the JSON response
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function exportAccessoryReport()
{
$this->authorize('reports.view');
@ -73,7 +75,7 @@ class ReportsController extends Controller
trans('admin/accessories/general.remaining'),
];
$header = array_map('trim', $header);
$rows[] = implode($header, ', ');
$rows[] = implode(', ', $header);
// Row per accessory
foreach ($accessories as $accessory) {
@ -83,10 +85,10 @@ class ReportsController extends Controller
$row[] = e($accessory->total);
$row[] = e($accessory->remaining);
$rows[] = implode($row, ',');
$rows[] = implode(',', $row);
}
$csv = implode($rows, "\n");
$csv = implode("\n", $rows);
$response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@ -95,31 +97,27 @@ class ReportsController extends Controller
}
/**
* Show depreciation report for assets.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
* Show depreciation report for assets.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
public function getDeprecationReport()
{
$this->authorize('reports.view');
$depreciations = Depreciation::get();
// Grab all the assets
$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);
return view('reports/depreciation')->with('depreciations',$depreciations);
}
/**
* Exports the depreciations to CSV
*
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
* Exports the depreciations to CSV
*
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function exportDeprecationReport()
{
$this->authorize('reports.view');
@ -190,6 +188,7 @@ class ReportsController extends Controller
die;
}
/**
* Displays audit report.
*
@ -204,13 +203,14 @@ class ReportsController extends Controller
return view('reports/audit');
}
/**
* Displays activity report.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
* Displays activity report.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return View
*/
public function getActivityReport()
{
$this->authorize('reports.view');
@ -259,22 +259,23 @@ class ReportsController extends Controller
->orderBy('created_at', 'DESC')
->chunk(20, function ($actionlogs) use ($handle) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
\Log::debug('Walking results: '.$executionTime);
$count = 0;
\Log::debug('Walking results: '.$executionTime);
$count = 0;
foreach ($actionlogs as $actionlog) {
$count++;
$target_name = '';
foreach ($actionlogs as $actionlog) {
$count++;
$target_name = '';
if ($actionlog->target) {
if ($actionlog->target) {
if ($actionlog->targetType() == 'user') {
$target_name = $actionlog->target->getFullNameAttribute();
} else {
$target_name = $actionlog->target->getDisplayNameAttribute();
}
} else {
$target_name = $actionlog->target->getDisplayNameAttribute();
}
}
$row = [
$row = [
$actionlog->created_at,
($actionlog->user) ? e($actionlog->user->getFullNameAttribute()) : '',
$actionlog->present()->actionType(),
@ -284,9 +285,9 @@ class ReportsController extends Controller
($actionlog->note) ? e($actionlog->note) : '',
$actionlog->log_meta,
];
fputcsv($handle, $row);
}
});
fputcsv($handle, $row);
}
});
// Close the output stream
fclose($handle);
@ -297,9 +298,11 @@ class ReportsController extends Controller
'Content-Disposition' => 'attachment; filename="activity-report-'.date('Y-m-d-his').'.csv"',
]);
return $response;
}
/**
* Displays license report
*
@ -318,13 +321,13 @@ class ReportsController extends Controller
}
/**
* Exports the licenses to CSV
*
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
* Exports the licenses to CSV
*
* @deprecated Server-side exports have been replaced by datatables export since v2.
* @author [A. Gianotto] [<snipe@snipe.net>]
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function exportLicenseReport()
{
$this->authorize('reports.view');
@ -343,7 +346,7 @@ class ReportsController extends Controller
];
$header = array_map('trim', $header);
$rows[] = implode($header, ', ');
$rows[] = implode(', ', $header);
// Row per license
foreach ($licenses as $license) {
@ -357,10 +360,11 @@ class ReportsController extends Controller
$row[] = ($license->depreciation != '') ? '' : e($license->depreciation->name);
$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->header('Content-Type', 'text/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.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ReportsController::postCustomReport() method that generates the CSV
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
* Returns a form that allows the user to generate a custom CSV report.
*
* @author [A. Gianotto] [<snipe@snipe.net>]
* @see ReportsController::postCustomReport() method that generates the CSV
* @since [v1.0]
* @return \Illuminate\Http\Response
*/
public function getCustomReport()
{
$this->authorize('reports.view');
@ -405,13 +409,14 @@ class ReportsController extends Controller
// Open output stream
$handle = fopen('php://output', 'w');
stream_set_timeout($handle, 2000);
if ($request->filled('use_bom')) {
fprintf($handle, chr(0xEF).chr(0xBB).chr(0xBF));
}
$header = [];
if ($request->filled('company')) {
$header[] = trans('general.company');
}
@ -475,7 +480,7 @@ class ReportsController extends Controller
if ($request->filled('rtd_location')) {
$header[] = trans('admin/hardware/form.default_location');
}
if ($request->filled('rtd_location_address')) {
$header[] = trans('general.address');
$header[] = trans('general.address');
@ -485,6 +490,7 @@ class ReportsController extends Controller
$header[] = trans('general.zip');
}
if ($request->filled('assigned_to')) {
$header[] = trans('admin/hardware/table.checkoutto');
$header[] = trans('general.type');
@ -548,8 +554,9 @@ class ReportsController extends Controller
$header[] = trans('general.notes');
}
foreach ($customfields as $customfield) {
if (e($request->input($customfield->db_column_name())) == '1') {
if ($request->input($customfield->db_column_name()) == '1') {
$header[] = $customfield->name;
}
}
@ -563,7 +570,7 @@ class ReportsController extends Controller
$assets = \App\Models\Company::scopeCompanyables(Asset::select('assets.*'))->with(
'location', 'assetstatus', 'assetlog', 'company', 'defaultLoc', 'assignedTo',
'model.category', 'model.manufacturer', 'supplier');
if ($request->filled('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'))) {
$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) {
$executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
\Log::debug('Walking results: '.$executionTime);
$count = 0;
foreach ($assets as $asset) {
$count++;
$row = [];
if ($request->filled('company')) {
$row[] = ($asset->company) ? $asset->company->name : '';
}
@ -682,7 +689,7 @@ class ReportsController extends Controller
if ($request->filled('supplier')) {
$row[] = ($asset->supplier) ? $asset->supplier->name : '';
}
if ($request->filled('location')) {
$row[] = ($asset->location) ? $asset->location->present()->name() : '';
}
@ -758,8 +765,8 @@ class ReportsController extends Controller
}
if ($request->filled('depreciation')) {
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$depreciation = $asset->getDepreciatedValue();
$diff = ($asset->purchase_cost - $depreciation);
$row[] = Helper::formatCurrencyOutput($depreciation);
$row[] = Helper::formatCurrencyOutput($diff);
$row[] = ($asset->depreciation) ? $asset->depreciated_date()->format('Y-m-d') : '';
@ -865,7 +872,7 @@ class ReportsController extends Controller
];
$header = array_map('trim', $header);
$rows[] = implode($header, ',');
$rows[] = implode(',', $header);
foreach ($assetMaintenances as $assetMaintenance) {
$row = [];
@ -882,13 +889,13 @@ class ReportsController extends Controller
} else {
$improvementTime = intval($assetMaintenance->asset_maintenance_time);
}
$row[] = $improvementTime;
$row[] = trans('general.currency').Helper::formatCurrencyOutput($assetMaintenance->cost);
$rows[] = implode($row, ',');
$row[] = $improvementTime;
$row[] = trans('general.currency') . Helper::formatCurrencyOutput($assetMaintenance->cost);
$rows[] = implode(',', $row);
}
// spit out a csv
$csv = implode($rows, "\n");
$csv = implode("\n", $rows);
$response = Response::make($csv, 200);
$response->header('Content-Type', 'text/csv');
$response->header('Content-disposition', 'attachment;filename=report.csv');
@ -900,42 +907,124 @@ class ReportsController extends Controller
* getAssetAcceptanceReport
*
* @return mixed
* @throws \Illuminate\Auth\Access\AuthorizationException
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
*/
public function getAssetAcceptanceReport()
public function getAssetAcceptanceReport($deleted = false)
{
$this->authorize('reports.view');
$showDeleted = $deleted == 'deleted';
/**
* 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
->filter(function ($acceptance) {
return $acceptance->checkoutable_type == \App\Models\Asset::class;
})
->map(function ($acceptance) {
return $acceptance->checkoutable;
->map(function($acceptance) {
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
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
*/
public function exportAssetAcceptanceReport()
public function postAssetAcceptanceReport($deleted = false)
{
$this->authorize('reports.view');
// Grab all the improvements
$assetsForReport = Actionlog::whereIn('id', $this->getAssetsNotAcceptedYet())
->get();
$showDeleted = $deleted == 'deleted';
/**
* 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 = [];
@ -948,20 +1037,20 @@ class ReportsController extends Controller
];
$header = array_map('trim', $header);
$rows[] = implode($header, ',');
$rows[] = implode(',', $header);
foreach ($assetsForReport as $assetItem) {
$row = [];
$row[] = str_replace(',', '', e($assetItem->assetlog->model->category->name));
$row[] = str_replace(',', '', e($assetItem->assetlog->model->name));
$row[] = str_replace(',', '', e($assetItem->assetlog->present()->name()));
$row[] = str_replace(',', '', e($assetItem->assetlog->asset_tag));
$row[] = str_replace(',', '', e($assetItem->assetlog->assignedTo->present()->name()));
$rows[] = implode($row, ',');
foreach ($assetsForReport as $item) {
$row = [ ];
$row[] = str_replace(',', '', e($item['assetItem']->model->category->name));
$row[] = str_replace(',', '', e($item['assetItem']->model->name));
$row[] = str_replace(',', '', e($item['assetItem']->name));
$row[] = str_replace(',', '', e($item['assetItem']->asset_tag));
$row[] = str_replace(',', '', e(($item['acceptance']->assignedTo) ? $item['acceptance']->assignedTo->present()->name() : trans('admin/reports/general.deleted_user')));
$rows[] = implode(',', $row);
}
// spit out a csv
$csv = implode($rows, "\n");
$csv = implode("\n", $rows);
$response = Response::make($csv, 200);
$response->header('Content-Type', 'text/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 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');
if ('1' == $request->input('clear_logo')) {
Storage::disk('public')->delete($setting->logo);
Storage::disk('public')->delete($setting->logo);
$setting->logo = null;
$setting->brand = 1;
$setting->brand = 1;
}
$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);
$setting->email_logo = null;
// If they are uploading an image, validate it and upload it
@ -431,8 +433,9 @@ class SettingsController extends Controller
$setting->label_logo = null;
}
// If the user wants to clear the favicon...
if ($request->hasFile('favicon')) {
if ($request->hasFile('favicon')) {
$favicon_image = $favicon_upload = $request->file('favicon');
$favicon_ext = $favicon_image->getClientOriginalExtension();
$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')));
}
// Remove Current image if exists
if (($setting->favicon) && (file_exists($favicon_file_name))) {
Storage::disk('public')->delete($favicon_file_name);
}
} elseif ('1' == $request->input('clear_favicon')) {
Storage::disk('public')->delete($setting->clear_favicon);
Storage::disk('public')->delete($setting->clear_favicon);
$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()) {
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_complexity = '';
if ($request->filled('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_country = $request->input('ldap_country');
$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()) {
$setting->update_client_side_cert_files();
return redirect()->route('settings.ldap.index')
->with('success', trans('admin/settings/message.update.success'));
}

View file

@ -33,7 +33,8 @@ class BulkUsersController extends Controller
// Make sure there were users selected
if (($request->filled('ids')) && (count($request->input('ids')) > 0)) {
// 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();
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\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use enshrined\svgSanitize\Sanitizer;
use Illuminate\Support\Facades\Storage;
class UserFilesController extends Controller
{
@ -38,12 +40,32 @@ class UserFilesController extends Controller
return redirect()->back()->with('error', trans('admin/users/message.upload.nofiles'));
}
foreach ($files as $file) {
$extension = $file->getClientOriginalExtension();
$filename = 'user-'.$user->id.'-'.str_random(8);
$filename .= '-'.str_slug($file->getClientOriginalName()).'.'.$extension;
if (! $file->move($destinationPath, $filename)) {
return redirect()->back()->with('error', trans('admin/users/message.upload.invalidfiles'));
$file_name = 'user-'.$user->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/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
$logAction = new Actionlog();
$logAction->item_id = $user->id;
@ -51,8 +73,8 @@ class UserFilesController extends Controller
$logAction->user_id = Auth::id();
$logAction->note = $request->input('notes');
$logAction->target_id = null;
$logAction->created_at = date('Y-m-d H:i:s');
$logAction->filename = $filename;
$logAction->created_at = date("Y-m-d H:i:s");
$logAction->filename = $file_name;
$logAction->action_type = 'uploaded';
if (! $logAction->save()) {
@ -63,8 +85,9 @@ class UserFilesController extends Controller
// dd($logActions);
return redirect()->back()->with('success', trans('admin/users/message.upload.success'));
}
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]);
// Redirect to the licence management page
return redirect()->route('users.index')->with('error', $error);
}
/**
@ -128,4 +152,5 @@ class UserFilesController extends Controller
// Redirect to the licence management page
return redirect()->route('users.index')->with('error', $error);
}
}

View file

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

View file

@ -15,27 +15,21 @@ class CustomFieldSetDefaultValuesForModel extends Component
public $fields;
public $model_id;
public function __construct()
{
\Log::info("INSTANTIATING A THING!!!"); // WORKS!
}
public function foo()
{
\Log::info("Uh, foo?");
}
public function mount()
{
$this->fieldset_id = AssetModel::find($this->model_id)->fieldset_id;
\Log::error("Mount at least fired, that's got to count for something, yeah?"); //WORKS! YAY!
$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?
$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()
{
// return 'fart<div>Hi: {{ $this->add_default_values }} yeah?</div>';
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()
{
$max_file_size = \App\Helpers\Helper::file_upload_max_size();
$max_file_size = Helper::file_upload_max_size();
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,

View file

@ -123,7 +123,7 @@ class ImageUploadRequest extends Request
$upload = Image::make($image->getRealPath())->resize(null, $w, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
});
// This requires a string instead of an object, so we use ($string)
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})) {
\Log::debug('A file already exists that we are replacing - we should delete the old one.');
try {
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
\Log::debug('Old file '.$path.'/'.$file_name.' has been deleted.');
} catch (\Exception $e) {
\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;
}
// 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 {
\Log::debug('No file passed for '.$form_fieldname);
if ($this->input('image_delete') == '1') {
\Log::debug('Deleting image');
try {
Storage::disk('public')->delete($path.'/'.$item->{$use_db_field});
$item->{$use_db_field} = null;
$item->{$use_db_field} = null;
} catch (\Exception $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();
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
@ -68,14 +68,14 @@ class ActionlogsTransformer
}
}
$array = [
$array = [
'id' => (int) $actionlog->id,
'icon' => $icon,
'file' => ($actionlog->filename != '') ?
[
'url' => route('show/assetfile', ['assetId' => $actionlog->item->id, 'fileId' => $actionlog->id]),
'filename' => $actionlog->filename,
'inlineable' => (bool) \App\Helpers\Helper::show_file_inline($actionlog->filename),
'inlineable' => (bool) Helper::show_file_inline($actionlog->filename),
] : null,
'item' => ($actionlog->item) ? [
@ -104,10 +104,10 @@ class ActionlogsTransformer
'type' => e($actionlog->targetType()),
] : null,
'note' => ($actionlog->note) ? e($actionlog->note) : 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,
'action_date' => ($actionlog->action_date) ? Helper::getFormattedDateObject($actionlog->action_date, 'datetime') : null,
'note' => ($actionlog->note) ? e($actionlog->note): 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,
'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(),
];
if (($asset->model) && ($asset->model->fieldset) && ($asset->model->fieldset->fields->count() > 0)) {
$fields_array = [];
foreach ($asset->model->fieldset->fields as $field) {
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'));
$fields_array[$field->name] = [
@ -96,12 +97,15 @@ class AssetsTransformer
'value' => $value,
'field_format' => $field->format,
];
} else {
$fields_array[$field->name] = [
'field' => $field->convertUnicodeDbSlug(),
'value' => $asset->{$field->convertUnicodeDbSlug()},
'field_format' => $field->format,
];
}
$array['custom_fields'] = $fields_array;
}
@ -112,7 +116,7 @@ class AssetsTransformer
$permissions_array['available_actions'] = [
'checkout' => Gate::allows('checkout', Asset::class),
'checkin' => Gate::allows('checkin', Asset::class),
'clone' => Gate::allows('create', Asset::class),
'clone' => false,
'restore' => false,
'update' => (bool) Gate::allows('update', 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;
return $array;
@ -160,6 +187,7 @@ class AssetsTransformer
] : null;
}
public function transformRequestedAssets(Collection $assets, $total)
{
$array = [];
@ -192,7 +220,8 @@ class AssetsTransformer
];
$array += $permissions_array;
return $array;
}
}

View file

@ -29,6 +29,7 @@ class CategoriesTransformer
'image' => ($category->image) ? Storage::disk('public')->url('categories/'.e($category->image)) : null,
'category_type' => ucwords(e($category->category_type)),
'has_eula' => ($category->getEula() ? true : false),
'use_default_eula' => ($category->use_default_eula=='1' ? true : false),
'eula' => ($category->getEula()),
'checkin_email' => ($category->checkin_email == '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,
'name' => e($depreciation->name),
'months' => $depreciation->months.' '.trans('general.months'),
'depreciation_min' => $depreciation->depreciation_min,
'created_at' => Helper::getFormattedDateObject($depreciation->created_at, 'datetime'),
'updated_at' => Helper::getFormattedDateObject($depreciation->updated_at, 'datetime'),
'depreciation_min' =>($depreciation->depreciation_min),

View file

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

View file

@ -14,7 +14,7 @@ class AccessoryImporter extends ItemImporter
protected function handle($row)
{
parent::handle($row); // TODO: Change the autogenerated stub
$this->createAccessoryIfNotExists();
$this->createAccessoryIfNotExists($row);
}
/**
@ -23,7 +23,7 @@ class AccessoryImporter extends ItemImporter
* @author Daniel Melzter
* @since 3.0
*/
public function createAccessoryIfNotExists()
public function createAccessoryIfNotExists($row)
{
$accessory = Accessory::where('name', $this->item['name'])->first();
if ($accessory) {
@ -34,6 +34,7 @@ class AccessoryImporter extends ItemImporter
}
$this->log('Updating Accessory');
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
$accessory->update($this->sanitizeItemForUpdating($accessory));
$accessory->save();
@ -41,6 +42,7 @@ class AccessoryImporter extends ItemImporter
}
$this->log('No Matching Accessory, Creating a new one');
$accessory = new Accessory();
$this->item['model_number'] = $this->findCsvMatch($row, "model_number");
$accessory->fill($this->sanitizeItemForStoring($accessory));
//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);
}
if ($this->item['checkout_class'] === 'location') {
if (strtolower($this->item['checkout_class']) === 'location') {
return Location::findOrFail($this->createOrFetchLocation($this->findCsvMatch($row, 'checkout_location')));
}

View file

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

View file

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

View file

@ -31,17 +31,17 @@ class Accessory extends SnipeModel
use Searchable;
use Acceptable;
/**
* The attributes that should be included when searching the model.
*
*
* @var array
*/
protected $searchableAttributes = ['name', 'model_number', 'order_number', 'purchase_date'];
/**
* The relations and their attributes that should be included when searching the model.
*
*
* @var array
*/
protected $searchableRelations = [
@ -53,8 +53,8 @@ class Accessory extends SnipeModel
];
/**
* Accessory validation rules
*/
* Accessory validation rules
*/
public $rules = [
'name' => 'required|min:3|max:255',
'qty' => 'required|integer|min:1',
@ -65,12 +65,12 @@ class Accessory extends SnipeModel
];
/**
* Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property
* is not set in the model it will default to true.
*
* Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property
* is not set in the model it will default to true.
*
* @var bool
*/
*/
protected $injectUniqueIdentifier = true;
use ValidatingTrait;
@ -92,9 +92,12 @@ class Accessory extends SnipeModel
'supplier_id',
'image',
'qty',
'min_amt',
'requestable',
];
/**
* Establishes the accessory -> supplier relationship
*
@ -172,7 +175,7 @@ class Accessory extends SnipeModel
/**
* Get the LAST checkout for this accessory.
*
*
* This is kinda gross, but is necessary for how the accessory
* pivot stuff works for now.
*
@ -201,6 +204,7 @@ class Accessory extends SnipeModel
return $this->assetlog()->where('action_type', '=', 'checkout')->take(1);
}
/**
* Sets the full image url
*
@ -300,7 +304,7 @@ class Accessory extends SnipeModel
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
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on company
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderCompany($query, $order)
{
return $query->leftJoin('companies', 'accessories.company_id', '=', 'companies.id')
@ -334,13 +338,13 @@ class Accessory extends SnipeModel
}
/**
* Query builder scope to order on category
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on category
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderCategory($query, $order)
{
return $query->leftJoin('categories', 'accessories.category_id', '=', 'categories.id')
@ -348,13 +352,13 @@ class Accessory extends SnipeModel
}
/**
* Query builder scope to order on location
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on location
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderLocation($query, $order)
{
return $query->leftJoin('locations', 'accessories.location_id', '=', 'locations.id')
@ -362,15 +366,28 @@ class Accessory extends SnipeModel
}
/**
* Query builder scope to order on manufacturer
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on manufacturer
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderManufacturer($query, $order)
{
return $query->leftJoin('manufacturers', '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;
use CompanyableTrait;
use HasFactory, Loggable, Requestable, Presentable, SoftDeletes, ValidatingTrait, UniqueUndeletedTrait, UniqueSerialTrait;
const LOCATION = 'location';
@ -39,35 +41,46 @@ class Asset extends Depreciable
/**
* Run after the checkout acceptance was declined by the user
*
*
* @param User $acceptedBy
* @param string $signature
*/
*/
public function declinedCheckout(User $declinedBy, $signature)
{
$this->assigned_to = null;
$this->assigned_type = null;
$this->accepted = null;
$this->save();
$this->assigned_to = null;
$this->assigned_type = null;
$this->accepted = null;
$this->save();
}
/**
* The database table used by the model.
*
* @var string
*/
* The database table used by the model.
*
* @var string
*/
protected $table = 'assets';
/**
* Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property
* is not set in the model it will default to true.
*
* Whether the model should inject it's identifier to the unique
* validation rules before attempting validation. If this property
* is not set in the model it will default to true.
*
* @var bool
*/
*/
protected $injectUniqueIdentifier = true;
// 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 = [
'purchase_date' => 'datetime',
@ -103,11 +116,11 @@ class Asset extends Depreciable
'last_audit_date' => 'date|nullable',
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'asset_tag',
'assigned_to',
@ -135,27 +148,27 @@ class Asset extends Depreciable
/**
* The attributes that should be included when searching the model.
*
*
* @var array
*/
protected $searchableAttributes = [
'name',
'asset_tag',
'serial',
'order_number',
'purchase_cost',
'notes',
'name',
'asset_tag',
'serial',
'order_number',
'purchase_cost',
'notes',
'created_at',
'updated_at',
'purchase_date',
'expected_checkin',
'next_audit_date',
'updated_at',
'purchase_date',
'expected_checkin',
'next_audit_date',
'last_audit_date',
];
/**
* The relations and their attributes that should be included when searching the model.
*
*
* @var array
*/
protected $searchableRelations = [
@ -169,6 +182,7 @@ class Asset extends Depreciable
'model.manufacturer' => ['name'],
];
/**
* This handles the custom field validation for assets
*
@ -187,6 +201,7 @@ class Asset extends Depreciable
return parent::save($params);
}
public function getDisplayNameAttribute()
{
return $this->present()->name();
@ -212,6 +227,7 @@ class Asset extends Depreciable
return null;
}
/**
* Establishes the asset -> company relationship
*
@ -242,7 +258,8 @@ class Asset extends Depreciable
// The asset status is not archived and is deployable
if (($this->assetstatus) && ($this->assetstatus->archived == '0')
&& ($this->assetstatus->deployable == '1')) {
&& ($this->assetstatus->deployable == '1'))
{
return true;
}
}
@ -346,6 +363,7 @@ class Asset extends Depreciable
return $this->rules;
}
/**
* Establishes the asset -> depreciation relationship
*
@ -358,6 +376,7 @@ class Asset extends Depreciable
return $this->model->belongsTo(\App\Models\Depreciation::class, 'depreciation_id');
}
/**
* Get components assigned to this asset
*
@ -367,9 +386,10 @@ class Asset extends Depreciable
*/
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
*
@ -386,6 +406,7 @@ class Asset extends Depreciable
}
}
/**
* Get uploads for this asset
*
@ -395,8 +416,8 @@ class Asset extends Depreciable
*/
public function uploads()
{
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
->where('item_type', '=', self::class)
return $this->hasMany('\App\Models\Actionlog', 'item_id')
->where('item_type', '=', Asset::class)
->where('action_type', '=', 'uploaded')
->whereNotNull('filename')
->orderBy('created_at', 'desc');
@ -414,7 +435,7 @@ class Asset extends Depreciable
*/
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();
}
/**
* Get the asset's location based on the assigned user
*
@ -471,6 +493,7 @@ class Asset extends Depreciable
if ($this->assignedTo) {
return $this->assignedTo;
}
}
if ($this->assignedType() == self::USER) {
if (($this->assignedTo) && $this->assignedTo->userLoc) {
@ -479,7 +502,7 @@ class Asset extends Depreciable
//this makes no sense
return $this->defaultLoc;
}
}
}
return $this->defaultLoc;
}
@ -529,6 +552,7 @@ class Asset extends Depreciable
return false;
}
/**
* Get the asset's logs
*
@ -588,6 +612,7 @@ class Asset extends Depreciable
->withTrashed();
}
/**
* Get maintenances for this asset
*
@ -613,6 +638,8 @@ class Asset extends Depreciable
return $this->belongsTo(\App\Models\User::class, 'user_id');
}
/**
* Establishes the asset -> status relationship
*
@ -658,6 +685,7 @@ class Asset extends Depreciable
->get();
}
/**
* Establishes the asset -> assigned licenses relationship
*
@ -706,6 +734,8 @@ class Asset extends Depreciable
return $this->belongsTo(\App\Models\Location::class, 'location_id');
}
/**
* Get the next autoincremented asset tag
*
@ -717,6 +747,7 @@ class Asset extends Depreciable
{
$settings = \App\Models\Setting::getSettings();
if ($settings->auto_increment_assets == '1') {
$temp_asset_tag = \DB::table('assets')
->where('physical', '=', '1')
@ -735,6 +766,7 @@ class Asset extends Depreciable
}
}
/**
* Get the next base number for the auto-incrementer.
*
@ -746,23 +778,28 @@ class Asset extends Depreciable
*/
public static function nextAutoIncrement($assets)
{
$max = 1;
foreach ($assets as $asset) {
$results = preg_match("/\d+$/", $asset['asset_tag'], $matches);
if ($results) {
if ($results)
{
$number = $matches[0];
if ($number > $max) {
if ($number > $max)
{
$max = $number;
}
}
}
return $max + 1;
}
/**
* Add zerofilling based on Settings
*
@ -802,6 +839,7 @@ class Asset extends Depreciable
if (($this->model) && ($this->model->category)) {
return $this->model->category->require_acceptance;
}
}
/**
@ -815,7 +853,7 @@ class Asset extends Depreciable
public function getEula()
{
$Parsedown = new \Parsedown();
if (($this->model) && ($this->model->category)) {
if ($this->model->category->eula_text) {
return $Parsedown->text(e($this->model->category->eula_text));
@ -829,11 +867,12 @@ class Asset extends Depreciable
return false;
}
/**
* -----------------------------------------------
* BEGIN QUERY SCOPES
* -----------------------------------------------
**/
* -----------------------------------------------
* BEGIN QUERY SCOPES
* -----------------------------------------------
**/
/**
* Run additional, advanced searches.
@ -854,11 +893,13 @@ class Asset extends Depreciable
});
foreach ($terms as $term) {
$query = $query
->orWhere('assets_users.first_name', 'LIKE', '%'.$term.'%')
->orWhere('assets_users.last_name', '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) {
$query = $query->orWhere('assets_locations.name', 'LIKE', '%'.$term.'%');
}
@ -883,30 +925,34 @@ class Asset extends Depreciable
foreach ($terms as $term) {
$query = $query->orWhere('assigned_assets.name', 'LIKE', '%'.$term.'%');
}
return $query;
}
/**
* Query builder scope for hardware
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope for hardware
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeHardware($query)
{
return $query->where('physical', '=', '1');
}
/**
* Query builder scope for pending assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope for pending assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopePending($query)
{
return $query->whereHas('assetstatus', function ($query) {
@ -916,13 +962,15 @@ class Asset extends Depreciable
});
}
/**
* Query builder scope for searching location
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope for searching location
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeAssetsByLocation($query, $location)
{
return $query->where(function ($query) use ($location) {
@ -944,13 +992,15 @@ class Asset extends Depreciable
});
}
/**
* Query builder scope for RTD assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope for RTD assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeRTD($query)
{
return $query->whereNull('assets.assigned_to')
@ -961,13 +1011,14 @@ class Asset extends Depreciable
});
}
/**
* Query builder scope for Undeployable assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
/**
* Query builder scope for Undeployable assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeUndeployable($query)
{
return $query->whereHas('assetstatus', function ($query) {
@ -984,6 +1035,7 @@ class Asset extends Depreciable
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeNotArchived($query)
{
return $query->whereHas('assetstatus', function ($query) {
@ -1013,6 +1065,7 @@ class Asset extends Depreciable
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeDueForAudit($query, $settings)
{
$interval = $settings->audit_warning_days ?? 0;
@ -1037,6 +1090,7 @@ class Asset extends Depreciable
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOverdueForAudit($query)
{
return $query->whereNotNull('assets.next_audit_date')
@ -1058,6 +1112,7 @@ class Asset extends Depreciable
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeDueOrOverdueForAudit($query, $settings)
{
$interval = $settings->audit_warning_days ?? 0;
@ -1068,13 +1123,15 @@ class Asset extends Depreciable
->NotArchived();
}
/**
* Query builder scope for Archived assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
/**
* Query builder scope for Archived assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeArchived($query)
{
return $query->whereHas('assetstatus', function ($query) {
@ -1084,25 +1141,27 @@ class Asset extends Depreciable
});
}
/**
* Query builder scope for Deployed assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
/**
* Query builder scope for Deployed assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeDeployed($query)
{
return $query->where('assigned_to', '>', '0');
}
/**
* Query builder scope for Requestable assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
/**
* Query builder scope for Requestable assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeRequestableAssets($query)
{
return Company::scopeCompanyables($query->where('requestable', '=', 1))
@ -1113,53 +1172,55 @@ class Asset extends Depreciable
});
}
/**
* scopeInModelList
* Get all assets in the provided listing of model ids
*
* @param $query
* @param array $modelIdListing
*
* @return mixed
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
*/
* scopeInModelList
* Get all assets in the provided listing of model ids
*
* @param $query
* @param array $modelIdListing
*
* @return mixed
* @author Vincent Sposato <vincent.sposato@gmail.com>
* @version v1.0
*/
public function scopeInModelList($query, array $modelIdListing)
{
return $query->whereIn('assets.model_id', $modelIdListing);
}
/**
* Query builder scope to get not-yet-accepted assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
/**
* Query builder scope to get not-yet-accepted assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeNotYetAccepted($query)
{
return $query->where('accepted', '=', 'pending');
}
/**
* Query builder scope to get rejected assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
/**
* Query builder scope to get rejected assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeRejected($query)
{
return $query->where('accepted', '=', 'rejected');
}
/**
* Query builder scope to get accepted assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
/**
* Query builder scope to get accepted assets
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeAccepted($query)
{
return $query->where('accepted', '=', 'accepted');
@ -1205,7 +1266,7 @@ class Asset extends Depreciable
})->orWhere(function ($query) use ($search) {
$query->where('assets_users.first_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_locations.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.notes', 'LIKE', '%'.$search.'%');
}
})->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')
->where('assets.assigned_type', '=', User::class);
})->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
}
/**
* 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.'%');
}
if ($fieldname == 'product_key') {
if ($fieldname =='serial') {
$query->where('assets.serial', 'LIKE', '%'.$search_val.'%');
}
@ -1300,6 +1366,7 @@ class Asset extends Depreciable
});
}
if ($fieldname == 'manufacturer') {
$query->whereHas('model', function ($query) use ($search_val) {
$query->whereHas('manufacturer', function ($query) use ($search_val) {
@ -1338,6 +1405,7 @@ class Asset extends Depreciable
});
}
if ($fieldname == 'company') {
$query->where(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
* 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')) {
$query->orWhere('assets.'.$fieldname, 'LIKE', '%'.$search_val.'%');
if (($fieldname!='category') && ($fieldname!='model_number') && ($fieldname!='rtd_location') && ($fieldname!='location') && ($fieldname!='supplier')
&& ($fieldname!='status_label') && ($fieldname!='model') && ($fieldname!='company') && ($fieldname!='manufacturer')) {
$query->where('assets.'.$fieldname, 'LIKE', '%' . $search_val . '%');
}
});
}
/**
* Query builder scope to order on model
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on model
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderModels($query, $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
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on model number
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderModelNumber($query, $order)
{
return $query->join('models', 'assets.model_id', '=', 'models.id')->orderBy('models.model_number', $order);
}
/**
* Query builder scope to order on assigned user
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on assigned user
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
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);
}
/**
* Query builder scope to order on status
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on status
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderStatus($query, $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
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on company
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderCompany($query, $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
*
@ -1476,14 +1554,16 @@ class Asset extends Depreciable
->join('manufacturers', 'models.manufacturer_id', '=', 'manufacturers.id')->where('models.manufacturer_id', '=', $manufacturer_id);
}
/**
* Query builder scope to order on category
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
* Query builder scope to order on category
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderCategory($query, $order)
{
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);
}
/**
* Query builder scope to order on manufacturer
*
@ -1506,14 +1587,14 @@ class Asset extends Depreciable
->orderBy('manufacturers.name', $order);
}
/**
* Query builder scope to order on location
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
/**
* Query builder scope to order on location
*
* @param \Illuminate\Database\Query\Builder $query Query builder instance
* @param text $order Order
*
* @return \Illuminate\Database\Query\Builder Modified query builder
*/
public function scopeOrderLocation($query, $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);
}
/**
* Query builder scope to order on supplier name
*
@ -1559,8 +1641,10 @@ class Asset extends Depreciable
$query->where('locations.id', '=', $search);
});
});
}
/**
* Query builder scope to search on depreciation name
* @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')
->join('depreciations', 'models.depreciation_id', '=', 'depreciations.id')->where('models.depreciation_id', '=', $search);
}
}

View file

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

View file

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

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_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);
}
return $connection;
}
/**
* 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 = 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;
// Lets return some nicer messages for users who donked their app key, and disable LDAP
try {
$ldap_pass = \Crypt::decrypt(Setting::getSettings()->ldap_pword);
@ -140,6 +149,7 @@ class Ldap extends Model
}
}
/**
* Parse and map LDAP attributes based on settings
*

View file

@ -341,8 +341,60 @@ class Setting extends Model
'is_ad',
'ad_domain',
'ad_append_domain',
'ldap_client_tls_key',
'ldap_client_tls_cert'
])->first()->getAttributes();
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.
*
*
* @var array
*/
protected $searchableRelations = [
@ -122,8 +122,8 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
protected function checkPermissionSection($section)
{
$user_groups = $this->groups;
if (($this->permissions == '') && (count($user_groups) == 0)) {
return false;
}
@ -181,6 +181,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return $this->checkPermissionSection('superuser');
}
/**
* 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');
}
/**
* Query builder scope to return NOT-deleted users
* @author A. Gianotto <snipe@snipe.net>
@ -472,7 +474,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
/**
* Generate email from full name
*
*
* @author A. Gianotto <snipe@snipe.net>
* @since [v2.0]
*
@ -518,7 +520,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
$username = str_slug($first_name).str_slug($last_name);
} elseif ($format == 'firstnamelastinitial') {
$username = str_slug(($first_name.substr($last_name, 0, 1)));
}
}
}
$user['first_name'] = $first_name;
@ -583,6 +585,7 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
return false;
}
public function decodePermissions()
{
return json_decode($this->permissions, true);
@ -599,13 +602,14 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
*/
public function scopeSimpleNameSearch($query, $search)
{
$query = $query->where('first_name', 'LIKE', '%'.$search.'%')
$query = $query->where('first_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;
}
/**
* Run additional, advanced searches.
*
@ -613,10 +617,10 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
* @param array $terms The search terms
* @return \Illuminate\Database\Eloquent\Builder
*/
public function advancedTextSearch(Builder $query, array $terms)
{
foreach ($terms as $term) {
$query = $query->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$term%", "%$term%"]);
public function advancedTextSearch(Builder $query, array $terms) {
foreach($terms as $term) {
$query = $query->orWhereRaw('CONCAT('.DB::getTablePrefix().'users.first_name," ",'.DB::getTablePrefix().'users.last_name) LIKE ?', ["%$term%"]);
}
return $query;

View file

@ -30,7 +30,7 @@ class CheckinAssetNotification extends Notification
$this->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);
}
}

View file

@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Helpers\Helper;
use App\Models\Asset;
use App\Models\Setting;
use App\Models\User;
@ -33,12 +34,12 @@ class CheckoutAssetNotification extends Notification
$this->expected_checkin = '';
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);
}
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);
}
}

View file

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

View file

@ -248,7 +248,7 @@ class AssetAuditPresenter extends Presenter
'sortable' => true,
'visible' => false,
'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',
];
}

View file

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

View file

@ -523,6 +523,6 @@ class AssetPresenter extends Presenter
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,
'switchable' => false,
'title' => trans('table.actions'),
],[
"field" => "use_default_eula",
"searchable" => false,
"sortable" => true,
"title" => trans('admin/categories/general.use_default_eula_column'),
'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',
],
[

View file

@ -41,14 +41,14 @@ class CompanyPresenter extends Presenter
'field' => 'users_count',
'searchable' => false,
'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,
], [
'field' => 'assets_count',
'searchable' => false,
'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,
], [
@ -56,25 +56,25 @@ class CompanyPresenter extends Presenter
'searchable' => false,
'sortable' => 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',
'searchable' => false,
'sortable' => 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',
'searchable' => false,
'sortable' => 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',
'searchable' => false,
'sortable' => 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',
'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()
{
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()

View file

@ -85,7 +85,7 @@ class ManufacturerPresenter extends Presenter
'sortable' => true,
'switchable' => true,
'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,
],
[
@ -94,7 +94,7 @@ class ManufacturerPresenter extends Presenter
'sortable' => true,
'switchable' => true,
'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,
],
[
@ -103,7 +103,7 @@ class ManufacturerPresenter extends Presenter
'sortable' => true,
'switchable' => true,
'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,
],
[
@ -112,7 +112,7 @@ class ManufacturerPresenter extends Presenter
'sortable' => true,
'switchable' => true,
'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,
],
[

View file

@ -312,7 +312,7 @@ class UserPresenter extends Presenter
{
if ($this->email) {
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 '';
@ -386,6 +386,6 @@ class UserPresenter extends Presenter
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
//
// Example usage in Location model where parent_id references another Location:

View file

@ -157,7 +157,7 @@ class LdapAdConfiguration
private function setLdapConnectionConfiguration(): array
{
// Create the configuration array.
return [
$ldap_settings = [
// Mandatory Configuration Options
'hosts' => $this->getServerUrlBase(),
'base_dn' => $this->ldapSettings['ldap_basedn'],
@ -181,6 +181,14 @@ class LdapAdConfiguration
// 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),
/*
|--------------------------------------------------------------------------
| Demo Mode Lockdown
@ -273,6 +276,7 @@ return [
'min_php' => '7.2.5',
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
@ -397,6 +401,8 @@ return [
'Google2FA' => PragmaRX\Google2FALaravel\Facade::class,
'Image' => Intervention\Image\ImageServiceProvider::class,
'Carbon' => Carbon\Carbon::class,
'Helper' => App\Helpers\Helper::class, // makes it much easier to use 'Helper::blah' in blades (which is where we usually use this)
],

View file

@ -54,6 +54,7 @@ class UserFactory extends Factory
'first_name' => 'Admin',
'last_name' => 'User',
'username' => 'admin',
'avatar' => '1.jpg',
'permissions' => '{"superuser":"1"}',
];
});
@ -66,6 +67,7 @@ class UserFactory extends Factory
'first_name' => 'Snipe E.',
'last_name' => 'Head',
'username' => 'snipe',
'avatar' => '2.jpg',
'email' => 'snipe@snipe.net',
'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()
{
Schema::drop('licenses');
Schema::dropIfExists('licenses');
}
/**

View file

@ -13,7 +13,7 @@ class AddShowInNavToStatusLabels extends Migration
public function up()
{
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()
{
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()
{
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
// to the newly created note field
@ -44,7 +51,9 @@ class MoveAccessoryCheckoutNoteToJoinTable extends Migration
->where('target_id', '=', $join_log->assigned_to)
->where('item_id', '=', $accessory->id)
->where('target_type', '=', \App\Models\User::class)
->where('item_type', '=','App\\Models\\Accessory')
->where('action_type', '=', 'checkout')
->where('note', '!=', '')
->orderBy('created_at', 'DESC')->get();
\Log::debug($action_log_entries->count().' matching entries in the action_logs table');
@ -74,8 +83,15 @@ class MoveAccessoryCheckoutNoteToJoinTable extends Migration
*/
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()
{
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');
});
}
}
}

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