mirror of
https://github.com/snipe/snipe-it.git
synced 2024-12-26 14:09:43 -08:00
081bd69d5a
Signed-off-by: snipe <snipe@snipe.net>
1411 lines
42 KiB
PHP
1411 lines
42 KiB
PHP
<?php
|
|
|
|
namespace App\Helpers;
|
|
use App\Models\Accessory;
|
|
use App\Models\Asset;
|
|
use App\Models\AssetModel;
|
|
use App\Models\Component;
|
|
use App\Models\Consumable;
|
|
use App\Models\CustomField;
|
|
use App\Models\CustomFieldset;
|
|
use App\Models\Depreciation;
|
|
use App\Models\Setting;
|
|
use App\Models\Statuslabel;
|
|
use Crypt;
|
|
use Illuminate\Contracts\Encryption\DecryptException;
|
|
use Image;
|
|
use Carbon\Carbon;
|
|
|
|
class Helper
|
|
{
|
|
/**
|
|
* Simple helper to invoke the markdown parser
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v2.0]
|
|
* @return string
|
|
*/
|
|
public static function parseEscapedMarkedown($str = null)
|
|
{
|
|
$Parsedown = new \Parsedown();
|
|
$Parsedown->setSafeMode(true);
|
|
|
|
if ($str) {
|
|
return $Parsedown->text($str);
|
|
}
|
|
}
|
|
|
|
public static function parseEscapedMarkedownInline($str = null)
|
|
{
|
|
$Parsedown = new \Parsedown();
|
|
$Parsedown->setSafeMode(true);
|
|
|
|
if ($str) {
|
|
return $Parsedown->line($str);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The importer has formatted number strings since v3,
|
|
* so the value might be a string, or an integer.
|
|
* If it's a number, format it as a string.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v2.0]
|
|
* @return string
|
|
*/
|
|
public static function formatCurrencyOutput($cost)
|
|
{
|
|
if (is_numeric($cost)) {
|
|
|
|
if (Setting::getSettings()->digit_separator=='1.234,56') {
|
|
return number_format($cost, 2, ',', '.');
|
|
}
|
|
return number_format($cost, 2, '.', ',');
|
|
}
|
|
// It's already been parsed.
|
|
return $cost;
|
|
}
|
|
|
|
|
|
/**
|
|
* Static colors for pie charts.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v3.3]
|
|
* @return string
|
|
*/
|
|
public static function defaultChartColors(int $index = 0)
|
|
{
|
|
if ($index < 0) {
|
|
$index = 0;
|
|
}
|
|
|
|
$colors = [
|
|
'#008941',
|
|
'#FF4A46',
|
|
'#006FA6',
|
|
'#A30059',
|
|
'#1CE6FF',
|
|
'#FFDBE5',
|
|
'#7A4900',
|
|
'#0000A6',
|
|
'#63FFAC',
|
|
'#B79762',
|
|
'#004D43',
|
|
'#8FB0FF',
|
|
'#997D87',
|
|
'#5A0007',
|
|
'#809693',
|
|
'#FEFFE6',
|
|
'#1B4400',
|
|
'#4FC601',
|
|
'#3B5DFF',
|
|
'#4A3B53',
|
|
'#FF2F80',
|
|
'#61615A',
|
|
'#BA0900',
|
|
'#6B7900',
|
|
'#00C2A0',
|
|
'#FFAA92',
|
|
'#FF90C9',
|
|
'#B903AA',
|
|
'#D16100',
|
|
'#DDEFFF',
|
|
'#000035',
|
|
'#7B4F4B',
|
|
'#A1C299',
|
|
'#300018',
|
|
'#0AA6D8',
|
|
'#013349',
|
|
'#00846F',
|
|
'#372101',
|
|
'#FFB500',
|
|
'#C2FFED',
|
|
'#A079BF',
|
|
'#CC0744',
|
|
'#C0B9B2',
|
|
'#C2FF99',
|
|
'#001E09',
|
|
'#00489C',
|
|
'#6F0062',
|
|
'#0CBD66',
|
|
'#EEC3FF',
|
|
'#456D75',
|
|
'#B77B68',
|
|
'#7A87A1',
|
|
'#788D66',
|
|
'#885578',
|
|
'#FAD09F',
|
|
'#FF8A9A',
|
|
'#D157A0',
|
|
'#BEC459',
|
|
'#456648',
|
|
'#0086ED',
|
|
'#886F4C',
|
|
'#34362D',
|
|
'#B4A8BD',
|
|
'#00A6AA',
|
|
'#452C2C',
|
|
'#636375',
|
|
'#A3C8C9',
|
|
'#FF913F',
|
|
'#938A81',
|
|
'#575329',
|
|
'#00FECF',
|
|
'#B05B6F',
|
|
'#8CD0FF',
|
|
'#3B9700',
|
|
'#04F757',
|
|
'#C8A1A1',
|
|
'#1E6E00',
|
|
'#7900D7',
|
|
'#A77500',
|
|
'#6367A9',
|
|
'#A05837',
|
|
'#6B002C',
|
|
'#772600',
|
|
'#D790FF',
|
|
'#9B9700',
|
|
'#549E79',
|
|
'#FFF69F',
|
|
'#201625',
|
|
'#72418F',
|
|
'#BC23FF',
|
|
'#99ADC0',
|
|
'#3A2465',
|
|
'#922329',
|
|
'#5B4534',
|
|
'#FDE8DC',
|
|
'#404E55',
|
|
'#0089A3',
|
|
'#CB7E98',
|
|
'#A4E804',
|
|
'#324E72',
|
|
'#6A3A4C',
|
|
'#83AB58',
|
|
'#001C1E',
|
|
'#D1F7CE',
|
|
'#004B28',
|
|
'#C8D0F6',
|
|
'#A3A489',
|
|
'#806C66',
|
|
'#222800',
|
|
'#BF5650',
|
|
'#E83000',
|
|
'#66796D',
|
|
'#DA007C',
|
|
'#FF1A59',
|
|
'#8ADBB4',
|
|
'#1E0200',
|
|
'#5B4E51',
|
|
'#C895C5',
|
|
'#320033',
|
|
'#FF6832',
|
|
'#66E1D3',
|
|
'#CFCDAC',
|
|
'#D0AC94',
|
|
'#7ED379',
|
|
'#012C58',
|
|
'#7A7BFF',
|
|
'#D68E01',
|
|
'#353339',
|
|
'#78AFA1',
|
|
'#FEB2C6',
|
|
'#75797C',
|
|
'#837393',
|
|
'#943A4D',
|
|
'#B5F4FF',
|
|
'#D2DCD5',
|
|
'#9556BD',
|
|
'#6A714A',
|
|
'#001325',
|
|
'#02525F',
|
|
'#0AA3F7',
|
|
'#E98176',
|
|
'#DBD5DD',
|
|
'#5EBCD1',
|
|
'#3D4F44',
|
|
'#7E6405',
|
|
'#02684E',
|
|
'#962B75',
|
|
'#8D8546',
|
|
'#9695C5',
|
|
'#E773CE',
|
|
'#D86A78',
|
|
'#3E89BE',
|
|
'#CA834E',
|
|
'#518A87',
|
|
'#5B113C',
|
|
'#55813B',
|
|
'#E704C4',
|
|
'#00005F',
|
|
'#A97399',
|
|
'#4B8160',
|
|
'#59738A',
|
|
'#FF5DA7',
|
|
'#F7C9BF',
|
|
'#643127',
|
|
'#513A01',
|
|
'#6B94AA',
|
|
'#51A058',
|
|
'#A45B02',
|
|
'#1D1702',
|
|
'#E20027',
|
|
'#E7AB63',
|
|
'#4C6001',
|
|
'#9C6966',
|
|
'#64547B',
|
|
'#97979E',
|
|
'#006A66',
|
|
'#391406',
|
|
'#F4D749',
|
|
'#0045D2',
|
|
'#006C31',
|
|
'#DDB6D0',
|
|
'#7C6571',
|
|
'#9FB2A4',
|
|
'#00D891',
|
|
'#15A08A',
|
|
'#BC65E9',
|
|
'#FFFFFE',
|
|
'#C6DC99',
|
|
'#203B3C',
|
|
'#671190',
|
|
'#6B3A64',
|
|
'#F5E1FF',
|
|
'#FFA0F2',
|
|
'#CCAA35',
|
|
'#374527',
|
|
'#8BB400',
|
|
'#797868',
|
|
'#C6005A',
|
|
'#3B000A',
|
|
'#C86240',
|
|
'#29607C',
|
|
'#402334',
|
|
'#7D5A44',
|
|
'#CCB87C',
|
|
'#B88183',
|
|
'#AA5199',
|
|
'#B5D6C3',
|
|
'#A38469',
|
|
'#9F94F0',
|
|
'#A74571',
|
|
'#B894A6',
|
|
'#71BB8C',
|
|
'#00B433',
|
|
'#789EC9',
|
|
'#6D80BA',
|
|
'#953F00',
|
|
'#5EFF03',
|
|
'#E4FFFC',
|
|
'#1BE177',
|
|
'#BCB1E5',
|
|
'#76912F',
|
|
'#003109',
|
|
'#0060CD',
|
|
'#D20096',
|
|
'#895563',
|
|
'#29201D',
|
|
'#5B3213',
|
|
'#A76F42',
|
|
'#89412E',
|
|
'#1A3A2A',
|
|
'#494B5A',
|
|
'#A88C85',
|
|
'#F4ABAA',
|
|
'#A3F3AB',
|
|
'#00C6C8',
|
|
'#EA8B66',
|
|
'#958A9F',
|
|
'#BDC9D2',
|
|
'#9FA064',
|
|
'#BE4700',
|
|
'#658188',
|
|
'#83A485',
|
|
'#453C23',
|
|
'#47675D',
|
|
'#3A3F00',
|
|
'#061203',
|
|
'#DFFB71',
|
|
'#868E7E',
|
|
'#98D058',
|
|
'#6C8F7D',
|
|
'#D7BFC2',
|
|
'#3C3E6E',
|
|
'#D83D66',
|
|
'#2F5D9B',
|
|
'#6C5E46',
|
|
'#D25B88',
|
|
'#5B656C',
|
|
'#00B57F',
|
|
'#545C46',
|
|
'#866097',
|
|
'#365D25',
|
|
'#252F99',
|
|
'#00CCFF',
|
|
'#674E60',
|
|
'#FC009C',
|
|
'#92896B',
|
|
];
|
|
|
|
$total_colors = count($colors);
|
|
|
|
if ($index >= $total_colors) {
|
|
|
|
\Log::info('Status label count is '.$index.' and exceeds the allowed count of 266.');
|
|
//patch fix for array key overflow (color count starts at 1, array starts at 0)
|
|
$index = $index - $total_colors - 1;
|
|
|
|
//constraints to keep result in 0-265 range. This should never be needed, but if something happens
|
|
//to create this many status labels and it DOES happen, this will keep it from failing at least.
|
|
if($index < 0) {
|
|
$index = 0;
|
|
}
|
|
elseif($index >($total_colors - 1)) {
|
|
$index = $total_colors - 1;
|
|
}
|
|
}
|
|
|
|
return $colors[$index];
|
|
}
|
|
|
|
/**
|
|
* Increases or decreases the brightness of a color by a percentage of the current brightness.
|
|
*
|
|
* @param string $hexCode Supported formats: `#FFF`, `#FFFFFF`, `FFF`, `FFFFFF`
|
|
* @param float $adjustPercent A number between -1 and 1. E.g. 0.3 = 30% lighter; -0.4 = 40% darker.
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function adjustBrightness($hexCode, $adjustPercent)
|
|
{
|
|
$hexCode = ltrim($hexCode, '#');
|
|
|
|
if (strlen($hexCode) == 3) {
|
|
$hexCode = $hexCode[0].$hexCode[0].$hexCode[1].$hexCode[1].$hexCode[2].$hexCode[2];
|
|
}
|
|
|
|
$hexCode = array_map('hexdec', str_split($hexCode, 2));
|
|
|
|
foreach ($hexCode as &$color) {
|
|
$adjustableLimit = $adjustPercent < 0 ? $color : 255 - $color;
|
|
$adjustAmount = ceil($adjustableLimit * $adjustPercent);
|
|
|
|
$color = str_pad(dechex($color + $adjustAmount), 2, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
return '#'.implode($hexCode);
|
|
}
|
|
|
|
/**
|
|
* Static background (highlight) colors for pie charts
|
|
* This is inelegant, and could be refactored later.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v3.2]
|
|
* @return array
|
|
*/
|
|
public static function chartBackgroundColors()
|
|
{
|
|
$colors = [
|
|
'#f56954',
|
|
'#00a65a',
|
|
'#f39c12',
|
|
'#00c0ef',
|
|
'#3c8dbc',
|
|
'#d2d6de',
|
|
'#3c8dbc',
|
|
'#3c8dbc',
|
|
'#3c8dbc',
|
|
|
|
];
|
|
|
|
return $colors;
|
|
}
|
|
|
|
|
|
/**
|
|
* Format currency using comma for thousands until local info is property used.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v2.7]
|
|
* @return string
|
|
*/
|
|
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);
|
|
// Strip Currency symbol
|
|
// If no currency symbol is set, default to $ because Murica
|
|
$currencySymbol = $LocaleInfo['currency_symbol'];
|
|
if (empty($currencySymbol)) {
|
|
$currencySymbol = '$';
|
|
}
|
|
|
|
$floatString = str_replace($currencySymbol, '', $floatString);
|
|
|
|
return floatval($floatString);
|
|
}
|
|
|
|
/**
|
|
* Format currency using comma or period for thousands, and period or comma for decimal, based on settings.
|
|
*
|
|
* @author [B. Wetherington] [<bwetherington@grokability.com>]
|
|
* @since [v5.2]
|
|
* @return Float
|
|
*/
|
|
public static function ParseCurrency($currencyString) {
|
|
$without_currency = str_replace(Setting::getSettings()->default_currency, '', $currencyString); //generally shouldn't come up, since we don't do this in fields, but just in case it does...
|
|
if(Setting::getSettings()->digit_separator=='1.234,56') {
|
|
//EU format
|
|
$without_thousands = str_replace('.', '', $without_currency);
|
|
$corrected_decimal = str_replace(',', '.', $without_thousands);
|
|
} else {
|
|
$without_thousands = str_replace(',', '', $without_currency);
|
|
$corrected_decimal = $without_thousands; // decimal is already OK
|
|
}
|
|
return floatval($corrected_decimal);
|
|
}
|
|
|
|
/**
|
|
* Get the list of status labels in an array to make a dropdown menu
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v2.5]
|
|
* @return array
|
|
*/
|
|
public static function statusLabelList()
|
|
{
|
|
$statuslabel_list = ['' => trans('general.select_statuslabel')] + Statuslabel::orderBy('default_label', 'desc')->orderBy('name', 'asc')->orderBy('deployable', 'desc')
|
|
->pluck('name', 'id')->toArray();
|
|
|
|
return $statuslabel_list;
|
|
}
|
|
|
|
/**
|
|
* Get the list of deployable status labels in an array to make a dropdown menu
|
|
*
|
|
* @todo This should probably be a selectlist, same as the other endpoints
|
|
* and we should probably add to the API controllers to make sure that
|
|
* the status_id submitted is actually really deployable.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v5.1.0]
|
|
* @return array
|
|
*/
|
|
public static function deployableStatusLabelList()
|
|
{
|
|
$statuslabel_list = Statuslabel::where('deployable', '=', '1')->orderBy('default_label', 'desc')
|
|
->orderBy('name', 'asc')
|
|
->orderBy('deployable', 'desc')
|
|
->pluck('name', 'id')->toArray();
|
|
|
|
return $statuslabel_list;
|
|
}
|
|
|
|
/**
|
|
* Get the list of status label types in an array to make a dropdown menu
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v2.5]
|
|
* @return array
|
|
*/
|
|
public static function statusTypeList()
|
|
{
|
|
$statuslabel_types =
|
|
['' => trans('admin/hardware/form.select_statustype')]
|
|
+ ['deployable' => trans('admin/hardware/general.deployable')]
|
|
+ ['pending' => trans('admin/hardware/general.pending')]
|
|
+ ['undeployable' => trans('admin/hardware/general.undeployable')]
|
|
+ ['archived' => trans('admin/hardware/general.archived')];
|
|
|
|
return $statuslabel_types;
|
|
}
|
|
|
|
/**
|
|
* Get the list of depreciations in an array to make a dropdown menu
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v2.5]
|
|
* @return array
|
|
*/
|
|
public static function depreciationList()
|
|
{
|
|
$depreciation_list = ['' => 'Do Not Depreciate'] + Depreciation::orderBy('name', 'asc')
|
|
->pluck('name', 'id')->toArray();
|
|
|
|
return $depreciation_list;
|
|
}
|
|
|
|
/**
|
|
* Get the list of category types in an array to make a dropdown menu
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v2.5]
|
|
* @return array
|
|
*/
|
|
public static function categoryTypeList($selection=null)
|
|
{
|
|
$category_types = [
|
|
'' => '',
|
|
'accessory' => trans('general.accessory'),
|
|
'asset' => trans('general.asset'),
|
|
'consumable' => trans('general.consumable'),
|
|
'component' => trans('general.component'),
|
|
'license' => trans('general.license'),
|
|
];
|
|
|
|
if ($selection != null){
|
|
return $category_types[strtolower($selection)];
|
|
}
|
|
else
|
|
return $category_types;
|
|
}
|
|
/**
|
|
* Get the list of custom fields in an array to make a dropdown menu
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v2.5]
|
|
* @return array
|
|
*/
|
|
public static function customFieldsetList()
|
|
{
|
|
$customfields = ['' => trans('admin/models/general.no_custom_field')] + CustomFieldset::pluck('name', 'id')->toArray();
|
|
|
|
return $customfields;
|
|
}
|
|
|
|
/**
|
|
* Get the list of custom field formats in an array to make a dropdown menu
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v3.4]
|
|
* @return array
|
|
*/
|
|
public static function predefined_formats()
|
|
{
|
|
$keys = array_keys(CustomField::PREDEFINED_FORMATS);
|
|
$stuff = array_combine($keys, $keys);
|
|
|
|
return $stuff;
|
|
}
|
|
|
|
/**
|
|
* Get the list of barcode dimensions
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v3.3]
|
|
* @return array
|
|
*/
|
|
public static function barcodeDimensions($barcode_type = 'QRCODE')
|
|
{
|
|
if ($barcode_type == 'C128') {
|
|
$size['height'] = '-1';
|
|
$size['width'] = '-10';
|
|
} elseif ($barcode_type == 'PDF417') {
|
|
$size['height'] = '-3';
|
|
$size['width'] = '-10';
|
|
} else {
|
|
$size['height'] = '-3';
|
|
$size['width'] = '-3';
|
|
}
|
|
|
|
return $size;
|
|
}
|
|
|
|
/**
|
|
* Generates a random string
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v3.0]
|
|
* @return array
|
|
*/
|
|
public static function generateRandomString($length = 10)
|
|
{
|
|
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
$charactersLength = strlen($characters);
|
|
$randomString = '';
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$randomString .= $characters[rand(0, $charactersLength - 1)];
|
|
}
|
|
|
|
return $randomString;
|
|
}
|
|
|
|
/**
|
|
* This nasty little method gets the low inventory info for the
|
|
* alert dropdown
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v3.0]
|
|
* @return array
|
|
*/
|
|
public static function checkLowInventory()
|
|
{
|
|
$consumables = Consumable::withCount('consumableAssignments as consumable_assignments_count')->whereNotNull('min_amt')->get();
|
|
$accessories = Accessory::withCount('users as users_count')->whereNotNull('min_amt')->get();
|
|
$components = Component::whereNotNull('min_amt')->get();
|
|
$asset_models = AssetModel::where('min_amt', '>', 0)->get();
|
|
|
|
$avail_consumables = 0;
|
|
$items_array = [];
|
|
$all_count = 0;
|
|
|
|
foreach ($consumables as $consumable) {
|
|
$avail = $consumable->numRemaining();
|
|
if ($avail < ($consumable->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
|
if ($consumable->qty > 0) {
|
|
$percent = number_format((($avail / $consumable->qty) * 100), 0);
|
|
} else {
|
|
$percent = 100;
|
|
}
|
|
|
|
$items_array[$all_count]['id'] = $consumable->id;
|
|
$items_array[$all_count]['name'] = $consumable->name;
|
|
$items_array[$all_count]['type'] = 'consumables';
|
|
$items_array[$all_count]['percent'] = $percent;
|
|
$items_array[$all_count]['remaining'] = $avail;
|
|
$items_array[$all_count]['min_amt'] = $consumable->min_amt;
|
|
$all_count++;
|
|
}
|
|
}
|
|
|
|
foreach ($accessories as $accessory) {
|
|
$avail = $accessory->qty - $accessory->users_count;
|
|
if ($avail < ($accessory->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
|
if ($accessory->qty > 0) {
|
|
$percent = number_format((($avail / $accessory->qty) * 100), 0);
|
|
} else {
|
|
$percent = 100;
|
|
}
|
|
|
|
$items_array[$all_count]['id'] = $accessory->id;
|
|
$items_array[$all_count]['name'] = $accessory->name;
|
|
$items_array[$all_count]['type'] = 'accessories';
|
|
$items_array[$all_count]['percent'] = $percent;
|
|
$items_array[$all_count]['remaining'] = $avail;
|
|
$items_array[$all_count]['min_amt'] = $accessory->min_amt;
|
|
$all_count++;
|
|
}
|
|
}
|
|
|
|
foreach ($components as $component) {
|
|
$avail = $component->numRemaining();
|
|
if ($avail < ($component->min_amt) + \App\Models\Setting::getSettings()->alert_threshold) {
|
|
if ($component->qty > 0) {
|
|
$percent = number_format((($avail / $component->qty) * 100), 0);
|
|
} else {
|
|
$percent = 100;
|
|
}
|
|
|
|
$items_array[$all_count]['id'] = $component->id;
|
|
$items_array[$all_count]['name'] = $component->name;
|
|
$items_array[$all_count]['type'] = 'components';
|
|
$items_array[$all_count]['percent'] = $percent;
|
|
$items_array[$all_count]['remaining'] = $avail;
|
|
$items_array[$all_count]['min_amt'] = $component->min_amt;
|
|
$all_count++;
|
|
}
|
|
}
|
|
|
|
foreach ($asset_models as $asset_model){
|
|
|
|
$asset = new Asset();
|
|
$total_owned = $asset->where('model_id', '=', $asset_model->id)->count();
|
|
$avail = $asset->where('model_id', '=', $asset_model->id)->whereNull('assigned_to')->count();
|
|
|
|
if ($avail < ($asset_model->min_amt)+ \App\Models\Setting::getSettings()->alert_threshold) {
|
|
if ($avail > 0) {
|
|
$percent = number_format((($avail / $total_owned) * 100), 0);
|
|
} else {
|
|
$percent = 100;
|
|
}
|
|
$items_array[$all_count]['id'] = $asset_model->id;
|
|
$items_array[$all_count]['name'] = $asset_model->name;
|
|
$items_array[$all_count]['type'] = 'models';
|
|
$items_array[$all_count]['percent'] = $percent;
|
|
$items_array[$all_count]['remaining'] = $avail;
|
|
$items_array[$all_count]['min_amt'] = $asset_model->min_amt;
|
|
$all_count++;
|
|
}
|
|
}
|
|
|
|
return $items_array;
|
|
}
|
|
|
|
/**
|
|
* Check if the file is an image, so we can show a preview
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v3.0]
|
|
* @param File $file
|
|
* @return string | Boolean
|
|
*/
|
|
public static function checkUploadIsImage($file)
|
|
{
|
|
$finfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
|
|
$filetype = @finfo_file($finfo, $file);
|
|
finfo_close($finfo);
|
|
|
|
if (($filetype == 'image/jpeg') || ($filetype == 'image/jpg') || ($filetype == 'image/png') || ($filetype == 'image/bmp') || ($filetype == 'image/gif')) {
|
|
return $filetype;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Walks through the permissions in the permissions config file and determines if
|
|
* permissions are granted based on a $selected_arr array.
|
|
*
|
|
* The $permissions array is a multidimensional array broke down by section.
|
|
* (Licenses, Assets, etc)
|
|
*
|
|
* The $selected_arr should be a flattened array that contains just the
|
|
* corresponding permission name and a true or false boolean to determine
|
|
* if that group/user has been granted that permission.
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net]
|
|
* @param array $permissions
|
|
* @param array $selected_arr
|
|
* @since [v1.0]
|
|
* @return array
|
|
*/
|
|
public static function selectedPermissionsArray($permissions, $selected_arr = [])
|
|
{
|
|
$permissions_arr = [];
|
|
|
|
foreach ($permissions as $permission) {
|
|
for ($x = 0; $x < count($permission); $x++) {
|
|
$permission_name = $permission[$x]['permission'];
|
|
|
|
if ($permission[$x]['display'] === true) {
|
|
if ($selected_arr) {
|
|
if (array_key_exists($permission_name, $selected_arr)) {
|
|
$permissions_arr[$permission_name] = $selected_arr[$permission_name];
|
|
} else {
|
|
$permissions_arr[$permission_name] = '0';
|
|
}
|
|
} else {
|
|
$permissions_arr[$permission_name] = '0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $permissions_arr;
|
|
}
|
|
|
|
/**
|
|
* Introspects into the model validation to see if the field passed is required.
|
|
* This is used by the blades to add a required class onto the HTML element.
|
|
* This isn't critical, but is helpful to keep form fields in sync with the actual
|
|
* model level validation.
|
|
*
|
|
* This does not currently handle form request validation requiredness :(
|
|
*
|
|
* @author [A. Gianotto] [<snipe@snipe.net>]
|
|
* @since [v3.0]
|
|
* @return bool
|
|
*/
|
|
public static function checkIfRequired($class, $field)
|
|
{
|
|
$rules = $class::rules();
|
|
foreach ($rules as $rule_name => $rule) {
|
|
if ($rule_name == $field) {
|
|
if (strpos($rule, 'required') === false) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check to see if the given key exists in the array, and trim excess white space before returning it
|
|
*
|
|
* @author Daniel Melzter
|
|
* @since 3.0
|
|
* @param $array array
|
|
* @param $key string
|
|
* @param $default string
|
|
* @return string
|
|
*/
|
|
public static function array_smart_fetch(array $array, $key, $default = '')
|
|
{
|
|
array_change_key_case($array, CASE_LOWER);
|
|
|
|
return array_key_exists(strtolower($key), array_change_key_case($array)) ? e(trim($array[$key])) : $default;
|
|
}
|
|
|
|
/**
|
|
* Gracefully handle decrypting encrypted fields (custom fields, etc).
|
|
*
|
|
* @todo allow this to handle more than just strings (arrays, etc)
|
|
*
|
|
* @author A. Gianotto
|
|
* @since 3.6
|
|
* @param CustomField $field
|
|
* @param string $string
|
|
* @return string
|
|
*/
|
|
public static function gracefulDecrypt(CustomField $field, $string)
|
|
{
|
|
if ($field->isFieldDecryptable($string)) {
|
|
try {
|
|
Crypt::decrypt($string);
|
|
|
|
return Crypt::decrypt($string);
|
|
} 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;
|
|
if (($messages) && (is_array($messages)) && (count($messages) > 0)) {
|
|
$array['messages'] = $messages;
|
|
}
|
|
($payload) ? $array['payload'] = $payload : $array['payload'] = null;
|
|
|
|
return $array;
|
|
}
|
|
|
|
/*
|
|
Possible solution for unicode fieldnames
|
|
*/
|
|
public static function make_slug($string)
|
|
{
|
|
return preg_replace('/\s+/u', '_', trim($string));
|
|
}
|
|
|
|
/**
|
|
* Return an array (or null) of the the raw and formatted date object for easy use in
|
|
* the API and the bootstrap table listings.
|
|
*
|
|
* @param $date
|
|
* @param $type
|
|
* @param $array
|
|
* @return array|string|null
|
|
*/
|
|
|
|
public static function getFormattedDateObject($date, $type = 'datetime', $array = true)
|
|
{
|
|
if ($date == '') {
|
|
return null;
|
|
}
|
|
|
|
$settings = Setting::getSettings();
|
|
|
|
/**
|
|
* Wrap this in a try/catch so that if Carbon crashes, for example if the $date value
|
|
* isn't actually valid, we don't crash out completely.
|
|
*
|
|
* While this *shouldn't* typically happen since we validate dates before entering them
|
|
* into the database (and we use date/datetime fields for native fields in the system),
|
|
* it is a possible scenario that a custom field could be created as an "ANY" field, data gets
|
|
* added, and then the custom field format gets edited later. If someone put bad data in the
|
|
* database before then - or if they manually edited the field's value - it will crash.
|
|
*
|
|
*/
|
|
|
|
|
|
try {
|
|
$tmp_date = new \Carbon($date);
|
|
|
|
if ($type == 'datetime') {
|
|
$dt['datetime'] = $tmp_date->format('Y-m-d H:i:s');
|
|
$dt['formatted'] = $tmp_date->format($settings->date_display_format.' '.$settings->time_display_format);
|
|
} else {
|
|
$dt['date'] = $tmp_date->format('Y-m-d');
|
|
$dt['formatted'] = $tmp_date->format($settings->date_display_format);
|
|
}
|
|
|
|
if ($array == 'true') {
|
|
return $dt;
|
|
}
|
|
|
|
return $dt['formatted'];
|
|
|
|
} catch (\Exception $e) {
|
|
\Log::warning($e);
|
|
return $date.' (Invalid '.$type.' value.)';
|
|
}
|
|
|
|
}
|
|
|
|
// Nicked from Drupal :)
|
|
// Returns a file size limit in bytes based on the PHP upload_max_filesize
|
|
// and post_max_size
|
|
public static function file_upload_max_size()
|
|
{
|
|
static $max_size = -1;
|
|
|
|
if ($max_size < 0) {
|
|
|
|
// Start with post_max_size.
|
|
$post_max_size = self::parse_size(ini_get('post_max_size'));
|
|
if ($post_max_size > 0) {
|
|
$max_size = $post_max_size;
|
|
}
|
|
|
|
// 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) {
|
|
$max_size = $upload_max;
|
|
}
|
|
}
|
|
|
|
return $max_size;
|
|
}
|
|
|
|
public static function file_upload_max_size_readable()
|
|
{
|
|
static $max_size = -1;
|
|
|
|
if ($max_size < 0) {
|
|
// Start with post_max_size.
|
|
$post_max_size = self::parse_size(ini_get('post_max_size'));
|
|
if ($post_max_size > 0) {
|
|
$max_size = ini_get('post_max_size');
|
|
}
|
|
|
|
// 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 < $post_max_size) {
|
|
$max_size = ini_get('upload_max_filesize');
|
|
}
|
|
}
|
|
|
|
return $max_size;
|
|
}
|
|
|
|
public static function parse_size($size)
|
|
{
|
|
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
|
|
$size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
|
|
if ($unit) {
|
|
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
|
|
return round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
|
|
} else {
|
|
return round($size);
|
|
}
|
|
}
|
|
|
|
public static function filetype_icon($filename)
|
|
{
|
|
$extension = substr(strrchr($filename, '.'), 1);
|
|
|
|
$allowedExtensionMap = [
|
|
// Images
|
|
'jpg' => 'far fa-image',
|
|
'jpeg' => 'far fa-image',
|
|
'gif' => 'far fa-image',
|
|
'png' => 'far fa-image',
|
|
// word
|
|
'doc' => 'far fa-file-word',
|
|
'docx' => 'far fa-file-word',
|
|
// Excel
|
|
'xls' => 'far fa-file-excel',
|
|
'xlsx' => 'far fa-file-excel',
|
|
// archive
|
|
'zip' => 'fas fa-file-archive',
|
|
'rar' => 'fas fa-file-archive',
|
|
//Text
|
|
'txt' => 'far fa-file-alt',
|
|
'rtf' => 'far fa-file-alt',
|
|
'xml' => 'far fa-file-alt',
|
|
// Misc
|
|
'pdf' => 'far fa-file-pdf',
|
|
'lic' => 'far fa-save',
|
|
];
|
|
|
|
if ($extension && array_key_exists($extension, $allowedExtensionMap)) {
|
|
return $allowedExtensionMap[$extension];
|
|
}
|
|
|
|
return 'far fa-file';
|
|
}
|
|
|
|
public static function show_file_inline($filename)
|
|
{
|
|
$extension = substr(strrchr($filename, '.'), 1);
|
|
|
|
if ($extension) {
|
|
switch ($extension) {
|
|
case 'jpg':
|
|
case 'jpeg':
|
|
case 'gif':
|
|
case 'png':
|
|
return true;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Generate a random encrypted password.
|
|
*
|
|
* @author Wes Hulette <jwhulette@gmail.com>
|
|
*
|
|
* @since 5.0.0
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function generateEncyrptedPassword(): string
|
|
{
|
|
return bcrypt(self::generateUnencryptedPassword());
|
|
}
|
|
|
|
/**
|
|
* Get a random unencrypted password.
|
|
*
|
|
* @author Steffen Buehl <sb@sbuehl.com>
|
|
*
|
|
* @since 5.0.0
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function generateUnencryptedPassword(): string
|
|
{
|
|
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
|
|
$password = '';
|
|
for ($i = 0; $i < 20; $i++) {
|
|
$password .= substr($chars, random_int(0, strlen($chars) - 1), 1);
|
|
}
|
|
|
|
return $password;
|
|
}
|
|
|
|
/**
|
|
* Process base64 encoded image data and save it on supplied path
|
|
*
|
|
* @param string $image_data base64 encoded image data with mime type
|
|
* @param string $save_path path to a folder where the image should be saved
|
|
* @return string path to uploaded image or false if something went wrong
|
|
*/
|
|
public static function processUploadedImage(String $image_data, String $save_path)
|
|
{
|
|
if ($image_data == null || $save_path == null) {
|
|
return false;
|
|
}
|
|
|
|
// After modification, the image is prefixed by mime info like the following:
|
|
// data:image/jpeg;base64,; This causes the image library to be unhappy, so we need to remove it.
|
|
$header = explode(';', $image_data, 2)[0];
|
|
// Grab the image type from the header while we're at it.
|
|
$extension = substr($header, strpos($header, '/') + 1);
|
|
// Start reading the image after the first comma, postceding the base64.
|
|
$image = substr($image_data, strpos($image_data, ',') + 1);
|
|
|
|
$file_name = str_random(25).'.'.$extension;
|
|
|
|
$directory = public_path($save_path);
|
|
// Check if the uploads directory exists. If not, try to create it.
|
|
if (! file_exists($directory)) {
|
|
mkdir($directory, 0755, true);
|
|
}
|
|
|
|
$path = public_path($save_path.$file_name);
|
|
|
|
try {
|
|
Image::make($image)->resize(500, 500, function ($constraint) {
|
|
$constraint->aspectRatio();
|
|
$constraint->upsize();
|
|
})->save($path);
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
|
|
return $file_name;
|
|
}
|
|
|
|
|
|
/**
|
|
* Universal helper to show file size in human-readable formats
|
|
*
|
|
* @author A. Gianotto <snipe@snipe.net>
|
|
* @since 5.0
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public static function formatFilesizeUnits($bytes)
|
|
{
|
|
if ($bytes >= 1073741824)
|
|
{
|
|
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
|
|
}
|
|
elseif ($bytes >= 1048576)
|
|
{
|
|
$bytes = number_format($bytes / 1048576, 2) . ' MB';
|
|
}
|
|
elseif ($bytes >= 1024)
|
|
{
|
|
$bytes = number_format($bytes / 1024, 2) . ' KB';
|
|
}
|
|
elseif ($bytes > 1)
|
|
{
|
|
$bytes = $bytes . ' bytes';
|
|
}
|
|
elseif ($bytes == 1)
|
|
{
|
|
$bytes = $bytes . ' byte';
|
|
}
|
|
else
|
|
{
|
|
$bytes = '0 bytes';
|
|
}
|
|
|
|
return $bytes;
|
|
}
|
|
|
|
/**
|
|
* This is weird but used by the side nav to determine which URL to point the user to
|
|
*
|
|
* @author A. Gianotto <snipe@snipe.net>
|
|
* @since 5.0
|
|
*
|
|
* @return string[]
|
|
*/
|
|
public static function SettingUrls(){
|
|
$settings=['#','fields.index', 'statuslabels.index', 'models.index', 'categories.index', 'manufacturers.index', 'suppliers.index', 'departments.index', 'locations.index', 'companies.index', 'depreciations.index'];
|
|
|
|
return $settings;
|
|
}
|
|
|
|
|
|
/**
|
|
* Generic helper (largely used by livewire right now) that returns the font-awesome icon
|
|
* for the object type.
|
|
*
|
|
* @author A. Gianotto <snipe@snipe.net>
|
|
* @since 6.1.0
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function iconTypeByItem($item) {
|
|
|
|
switch ($item) {
|
|
case 'asset':
|
|
return 'fas fa-barcode';
|
|
break;
|
|
case 'accessory':
|
|
return 'fas fa-keyboard';
|
|
break;
|
|
case 'component':
|
|
return 'fas fa-hdd';
|
|
break;
|
|
case 'consumable':
|
|
return 'fas fa-tint';
|
|
break;
|
|
case 'license':
|
|
return 'far fa-save';
|
|
break;
|
|
case 'location':
|
|
return 'fas fa-map-marker-alt';
|
|
break;
|
|
case 'user':
|
|
return 'fas fa-user';
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* This is a shorter way to see if the app is in demo mode.
|
|
*
|
|
* This makes it cleanly available in blades and in controllers, e.g.
|
|
*
|
|
* Blade:
|
|
* {{ Helper::isDemoMode() ? ' disabled' : ''}} for form blades where we need to disable a form
|
|
*
|
|
* Controller:
|
|
* if (Helper::isDemoMode()) {
|
|
* // don't allow the thing
|
|
* }
|
|
* @todo - use this everywhere else in the app where we have very long if/else config('app.lock_passwords') stuff
|
|
*/
|
|
public static function isDemoMode() {
|
|
if (config('app.lock_passwords') === true) {
|
|
return true;
|
|
\Log::debug('app locked!');
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Conversion between units of measurement
|
|
*
|
|
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
|
|
* @since 5.0
|
|
* @param float $value Measurement value to convert
|
|
* @param string $srcUnit Source unit of measurement
|
|
* @param string $dstUnit Destination unit of measurement
|
|
* @param int $round Round the result to decimals (Default false - No rounding)
|
|
* @return float
|
|
*/
|
|
public static function convertUnit($value, $srcUnit, $dstUnit, $round=false) {
|
|
$srcFactor = static::getUnitConversionFactor($srcUnit);
|
|
$dstFactor = static::getUnitConversionFactor($dstUnit);
|
|
$output = $value * $srcFactor / $dstFactor;
|
|
return ($round !== false) ? round($output, $round) : $output;
|
|
}
|
|
|
|
/**
|
|
* Get conversion factor from unit of measurement to mm
|
|
*
|
|
* @author Grant Le Roux <grant.leroux+snipe-it@gmail.com>
|
|
* @since 5.0
|
|
* @param string $unit Unit of measurement
|
|
* @return float
|
|
*/
|
|
public static function getUnitConversionFactor($unit) {
|
|
switch (strtolower($unit)) {
|
|
case 'mm':
|
|
return 1.0;
|
|
case 'cm':
|
|
return 10.0;
|
|
case 'm':
|
|
return 1000.0;
|
|
case 'in':
|
|
return 25.4;
|
|
case 'ft':
|
|
return 12 * static::getUnitConversionFactor('in');
|
|
case 'yd':
|
|
return 3 * static::getUnitConversionFactor('ft');
|
|
case 'pt':
|
|
return (1 / 72) * static::getUnitConversionFactor('in');
|
|
default:
|
|
throw new \InvalidArgumentException('Unit: \'' . $unit . '\' is not supported');
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* I know it's gauche to return a shitty HTML string, but this is just a helper and since it will be the same every single time,
|
|
* it seemed pretty safe to do here. Don't you judge me.
|
|
*/
|
|
public static function showDemoModeFieldWarning() {
|
|
if (Helper::isDemoMode()) {
|
|
return "<p class=\"text-warning\"><i class=\"fas fa-lock\"></i>" . trans('general.feature_disabled') . "</p>";
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Ah, legacy code.
|
|
*
|
|
* This corrects the original mistakes from 2013 where we used the wrong locale codes. Hopefully we
|
|
* can get rid of this in a future version, but this should at least give us the belt and suspenders we need
|
|
* to be sure this change is not too disruptive.
|
|
*
|
|
* In this array, we ONLY include the older languages where we weren't using the correct locale codes.
|
|
*
|
|
* @author A. Gianotto <snipe@snipe.net>
|
|
* @since 6.3.0
|
|
*
|
|
* @param $language_code
|
|
* @return string []
|
|
*/
|
|
public static function mapLegacyLocale($language_code = null)
|
|
{
|
|
|
|
if (strlen($language_code) > 4) {
|
|
return $language_code;
|
|
}
|
|
|
|
$languages = [
|
|
'af' => 'af-ZA', // Afrikaans
|
|
'am' => 'am-ET', // Amharic
|
|
'ar' => 'ar-SA', // Arabic
|
|
'bg' => 'bg-BG', // Bulgarian
|
|
'ca' => 'ca-ES', // Catalan
|
|
'cs' => 'cs-CZ', // Czech
|
|
'cy' => 'cy-GB', // Welsh
|
|
'da' => 'da-DK', // Danish
|
|
'de-i' => 'de-if', // German informal
|
|
'de' => 'de-DE', // German
|
|
'el' => 'el-GR', // Greek
|
|
'en' => 'en-US', // English
|
|
'et' => 'et-EE', // Estonian
|
|
'fa' => 'fa-IR', // Persian
|
|
'fi' => 'fi-FI', // Finnish
|
|
'fil' => 'fil-PH', // Filipino
|
|
'fr' => 'fr-FR', // French
|
|
'he' => 'he-IL', // Hebrew
|
|
'hr' => 'hr-HR', // Croatian
|
|
'hu' => 'hu-HU', // Hungarian
|
|
'id' => 'id-ID', // Indonesian
|
|
'is' => 'is-IS', // Icelandic
|
|
'it' => 'it-IT', // Italian
|
|
'iu' => 'iu-NU', // Inuktitut
|
|
'ja' => 'ja-JP', // Japanese
|
|
'ko' => 'ko-KR', // Korean
|
|
'lt' => 'lt-LT', // Lithuanian
|
|
'lv' => 'lv-LV', // Latvian
|
|
'mi' => 'mi-NZ', // Maori
|
|
'mk' => 'mk-MK', // Macedonian
|
|
'mn' => 'mn-MN', // Mongolian
|
|
'ms' => 'ms-MY', // Malay
|
|
'nl' => 'nl-NL', // Dutch
|
|
'no' => 'no-NO', // Norwegian
|
|
'pl' => 'pl-PL', // Polish
|
|
'ro' => 'ro-RO', // Romanian
|
|
'ru' => 'ru-RU', // Russian
|
|
'sk' => 'sk-SK', // Slovak
|
|
'sl' => 'sl-SI', // Slovenian
|
|
'so' => 'so-SO', // Somali
|
|
'ta' => 'ta-IN', // Tamil
|
|
'th' => 'th-TH', // Thai
|
|
'tl' => 'tl-PH', // Tagalog
|
|
'tr' => 'tr-TR', // Turkish
|
|
'uk' => 'uk-UA', // Ukrainian
|
|
'vi' => 'vi-VN', // Vietnamese
|
|
'zu' => 'zu-ZA', // Zulu
|
|
];
|
|
|
|
foreach ($languages as $legacy => $new) {
|
|
if ($language_code == $legacy) {
|
|
\Log::debug('Current language is '.$legacy.', using '.$new.' instead');
|
|
return $new;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|