ajout app
This commit is contained in:
464
SNIPE-IT/app/Models/Accessory.php
Normal file
464
SNIPE-IT/app/Models/Accessory.php
Normal file
@ -0,0 +1,464 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Acceptable;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
/**
|
||||
* Model for Accessories.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class Accessory extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\AccessoryPresenter::class;
|
||||
use CompanyableTrait;
|
||||
use Loggable, Presentable;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'accessories';
|
||||
protected $casts = [
|
||||
'purchase_date' => 'datetime',
|
||||
'requestable' => 'boolean', ];
|
||||
|
||||
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', 'notes'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'category' => ['name'],
|
||||
'company' => ['name'],
|
||||
'manufacturer' => ['name'],
|
||||
'supplier' => ['name'],
|
||||
'location' => ['name'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Accessory validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'name' => 'required|min:3|max:255',
|
||||
'qty' => 'required|integer|min:1',
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'company_id' => 'integer|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
'company_id',
|
||||
'location_id',
|
||||
'name',
|
||||
'order_number',
|
||||
'purchase_cost',
|
||||
'purchase_date',
|
||||
'model_number',
|
||||
'manufacturer_id',
|
||||
'supplier_id',
|
||||
'image',
|
||||
'qty',
|
||||
'min_amt',
|
||||
'requestable',
|
||||
'notes',
|
||||
];
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the accessories -> action logs -> uploads relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.13]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the accessory -> supplier relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function supplier()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the requestable attribute on the accessory
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return void
|
||||
*/
|
||||
public function setRequestableAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['requestable'] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the accessory -> company relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Company::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the accessory -> location relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function location()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Location::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the accessory -> category relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Category::class, 'category_id')->where('category_type', '=', 'accessory');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the action logs associated with the accessory
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assetlog()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LAST checkout for this accessory.
|
||||
*
|
||||
* This is kinda gross, but is necessary for how the accessory
|
||||
* pivot stuff works for now.
|
||||
*
|
||||
* It looks like we should be able to use ->first() here and
|
||||
* return an object instead of a collection, but we actually
|
||||
* cannot.
|
||||
*
|
||||
* In short, you cannot execute the query defined when you're eager loading.
|
||||
* and in order to avoid 1001 query problems when displaying the most
|
||||
* recent checkout note, we have to eager load this.
|
||||
*
|
||||
* This means we technically return a collection of one here, and then
|
||||
* in the controller, we convert that collection to an array, so we can
|
||||
* use it in the transformer to display only the notes of the LAST
|
||||
* checkout.
|
||||
*
|
||||
* It's super-mega-assy, but it's the best I could do for now.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since v5.0.0
|
||||
*
|
||||
* @see \App\Http\Controllers\Api\AccessoriesController\checkedout()
|
||||
*/
|
||||
public function lastCheckout()
|
||||
{
|
||||
return $this->assetlog()->where('action_type', '=', 'checkout')->take(1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the full image url
|
||||
*
|
||||
* @todo this should probably be moved out of the model and into a
|
||||
* presenter or service provider
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return string
|
||||
*/
|
||||
public function getImageUrl()
|
||||
{
|
||||
if ($this->image) {
|
||||
return Storage::disk('public')->url(app('accessories_upload_path').$this->image);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the accessory -> users relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->withPivot('id', 'created_at', 'note')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the accessory has users
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return int
|
||||
*/
|
||||
public function hasUsers()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'accessories_users', 'accessory_id', 'assigned_to')->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the accessory -> manufacturer relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function manufacturer()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determins whether or not an email should be sent for checkin/checkout of this
|
||||
* accessory based on the category it belongs to.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function checkin_email()
|
||||
{
|
||||
return $this->category->checkin_email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the accessory should require the user to
|
||||
* accept it via email.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function requireAcceptance()
|
||||
{
|
||||
return $this->category->require_acceptance ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
||||
* checks for a settings level EULA
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return string
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
|
||||
if ($this->category->eula_text) {
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check how many items of an accessory remain.
|
||||
*
|
||||
* In order to use this model method, you MUST call withCount('users as users_count')
|
||||
* on the eloquent query in the controller, otherwise $this->>users_count will be null and
|
||||
* bad things happen.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numRemaining()
|
||||
{
|
||||
$checkedout = $this->users_count;
|
||||
$total = $this->qty;
|
||||
$remaining = $total - $checkedout;
|
||||
|
||||
return (int) $remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run after the checkout acceptance was declined by the user
|
||||
*
|
||||
* @param User $acceptedBy
|
||||
* @param string $signature
|
||||
*/
|
||||
public function declinedCheckout(User $declinedBy, $signature)
|
||||
{
|
||||
if (is_null($accessory_user = \DB::table('accessories_users')->where('assigned_to', $declinedBy->id)->where('accessory_id', $this->id)->latest('created_at'))) {
|
||||
// Redirect to the accessory management page with error
|
||||
return redirect()->route('accessories.index')->with('error', trans('admin/accessories/message.does_not_exist'));
|
||||
}
|
||||
|
||||
$accessory_user->limit(1)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN MUTATORS
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* This sets a value for qty if no value is given. The database does not allow this
|
||||
* field to be null, and in the other areas of the code, we set a default, but the importer
|
||||
* does not.
|
||||
*
|
||||
* This simply checks that there is a value for quantity, and if there isn't, set it to 0.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since v6.3.4
|
||||
* @param $value
|
||||
* @return void
|
||||
*/
|
||||
public function setQtyAttribute($value)
|
||||
{
|
||||
$this->attributes['qty'] = (!$value) ? 0 : intval($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* 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')
|
||||
->orderBy('companies.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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')
|
||||
->orderBy('categories.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
|
||||
*/
|
||||
public function scopeOrderLocation($query, $order)
|
||||
{
|
||||
return $query->leftJoin('locations', 'accessories.location_id', '=', 'locations.id')
|
||||
->orderBy('locations.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on manufacturer
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderManufacturer($query, $order)
|
||||
{
|
||||
return $query->leftJoin('manufacturers', '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);
|
||||
}
|
||||
}
|
375
SNIPE-IT/app/Models/Actionlog.php
Normal file
375
SNIPE-IT/app/Models/Actionlog.php
Normal file
@ -0,0 +1,375 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
/**
|
||||
* Model for the Actionlog (the table that keeps a historical log of
|
||||
* checkouts, checkins, and updates).
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class Actionlog extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// This is to manually set the source (via setActionSource()) for determineActionSource()
|
||||
protected ?string $source = null;
|
||||
|
||||
protected $presenter = \App\Presenters\ActionlogPresenter::class;
|
||||
use SoftDeletes;
|
||||
use Presentable;
|
||||
|
||||
protected $table = 'action_logs';
|
||||
public $timestamps = true;
|
||||
protected $fillable = [
|
||||
'created_at',
|
||||
'item_type',
|
||||
'user_id',
|
||||
'item_id',
|
||||
'action_type',
|
||||
'note',
|
||||
'target_id',
|
||||
'target_type',
|
||||
'stored_eula'
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = [
|
||||
'action_type',
|
||||
'note',
|
||||
'log_meta',
|
||||
'user_id',
|
||||
'remote_ip',
|
||||
'user_agent',
|
||||
'action_source'
|
||||
];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'company' => ['name'],
|
||||
'admin' => ['first_name','last_name','username', 'email'],
|
||||
'user' => ['first_name','last_name','username', 'email'],
|
||||
'assets' => ['asset_tag','name'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Override from Builder to automatically add the company
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
static::creating(function (self $actionlog) {
|
||||
// If the admin is a superadmin, let's see if the target instead has a company.
|
||||
if (Auth::user() && Auth::user()->isSuperUser()) {
|
||||
if ($actionlog->target) {
|
||||
$actionlog->company_id = $actionlog->target->company_id;
|
||||
} elseif ($actionlog->item) {
|
||||
$actionlog->company_id = $actionlog->item->company_id;
|
||||
}
|
||||
} elseif (Auth::user() && Auth::user()->company) {
|
||||
$actionlog->company_id = Auth::user()->company_id;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> item relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function item()
|
||||
{
|
||||
return $this->morphTo('item')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> company relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Company::class, 'id', 'company_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> asset relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'id', 'item_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> item type relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function itemType()
|
||||
{
|
||||
if ($this->item_type == AssetModel::class) {
|
||||
return 'model';
|
||||
}
|
||||
|
||||
return camel_case(class_basename($this->item_type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> target type relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function targetType()
|
||||
{
|
||||
if ($this->target_type == User::class) {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
return camel_case(class_basename($this->target_type));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> uploads relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->morphTo('item')
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> userlog relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function userlog()
|
||||
{
|
||||
return $this->target();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> admin user relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> user relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'target_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> target relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function target()
|
||||
{
|
||||
return $this->morphTo('target')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the actionlog -> location relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function location()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Location::class, 'location_id')->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the file exists, and if it does, force a download
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return string | false
|
||||
*/
|
||||
public function get_src($type = 'assets', $fieldname = 'filename')
|
||||
{
|
||||
if ($this->filename != '') {
|
||||
$file = config('app.private_uploads').'/'.$type.'/'.$this->{$fieldname};
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the log record with the action type
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function logaction($actiontype)
|
||||
{
|
||||
$this->action_type = $actiontype;
|
||||
$this->remote_ip = request()->ip();
|
||||
$this->user_agent = request()->header('User-Agent');
|
||||
$this->action_source = $this->determineActionSource();
|
||||
|
||||
if ($this->save()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of days until the next audit
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return int
|
||||
*/
|
||||
public function daysUntilNextAudit($monthInterval = 12, $asset = null)
|
||||
{
|
||||
$now = Carbon::now();
|
||||
$last_audit_date = $this->created_at;
|
||||
$next_audit = $last_audit_date->addMonth($monthInterval);
|
||||
$next_audit_days = $now->diffInDays($next_audit);
|
||||
|
||||
// Override the default setting for interval if the asset has its own next audit date
|
||||
if (($asset) && ($asset->next_audit_date)) {
|
||||
$override_default_next = \Carbon::parse($asset->next_audit_date);
|
||||
$next_audit_days = $override_default_next->diffInDays($now);
|
||||
}
|
||||
|
||||
return $next_audit_days;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the date of the next audit
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return \Datetime
|
||||
*/
|
||||
public function calcNextAuditDate($monthInterval = 12, $asset = null)
|
||||
{
|
||||
$last_audit_date = Carbon::parse($this->created_at);
|
||||
// If there is an asset-specific next date already given,
|
||||
if (($asset) && ($asset->next_audit_date)) {
|
||||
return \Carbon::parse($asset->next_audit_date);
|
||||
}
|
||||
|
||||
return \Carbon::parse($last_audit_date)->addMonths($monthInterval)->toDateString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets action logs in chronological order, excluding uploads
|
||||
*
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @since v1.0
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
public function getListingOfActionLogsChronologicalOrder()
|
||||
{
|
||||
return $this->all()
|
||||
->where('action_type', '!=', 'uploaded')
|
||||
->orderBy('item_id', 'asc')
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what the type of request is so we can log it to the action_log
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since v6.3.0
|
||||
* @return string
|
||||
*/
|
||||
public function determineActionSource(): string
|
||||
{
|
||||
// This is a manually set source
|
||||
if($this->source) {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
// This is an API call
|
||||
if (((request()->header('content-type') && (request()->header('accept'))=='application/json'))
|
||||
&& (starts_with(request()->header('authorization'), 'Bearer '))) {
|
||||
return 'api';
|
||||
}
|
||||
|
||||
// This is probably NOT an API call
|
||||
if (request()->filled('_token')) {
|
||||
return 'gui';
|
||||
}
|
||||
|
||||
// We're not sure, probably cli
|
||||
return 'cli/unknown';
|
||||
|
||||
}
|
||||
|
||||
// Manually sets $this->source for determineActionSource()
|
||||
public function setActionSource($source = null): void
|
||||
{
|
||||
$this->source = $source;
|
||||
}
|
||||
}
|
1796
SNIPE-IT/app/Models/Asset.php
Normal file
1796
SNIPE-IT/app/Models/Asset.php
Normal file
File diff suppressed because it is too large
Load Diff
281
SNIPE-IT/app/Models/AssetMaintenance.php
Normal file
281
SNIPE-IT/app/Models/AssetMaintenance.php
Normal file
@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Searchable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
/**
|
||||
* Model for Asset Maintenances.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class AssetMaintenance extends Model implements ICompanyableChild
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
use CompanyableChildTrait;
|
||||
use ValidatingTrait;
|
||||
|
||||
|
||||
|
||||
protected $table = 'asset_maintenances';
|
||||
protected $rules = [
|
||||
'asset_id' => 'required|integer',
|
||||
'supplier_id' => 'required|integer',
|
||||
'asset_maintenance_type' => 'required',
|
||||
'title' => 'required|max:100',
|
||||
'is_warranty' => 'boolean',
|
||||
'start_date' => 'required|date_format:Y-m-d',
|
||||
'completion_date' => 'date_format:Y-m-d|nullable',
|
||||
'notes' => 'string|nullable',
|
||||
'cost' => 'numeric|nullable',
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'asset_id',
|
||||
'supplier_id',
|
||||
'asset_maintenance_type',
|
||||
'is_warranty',
|
||||
'start_date',
|
||||
'completion_date',
|
||||
'asset_maintenance_time',
|
||||
'notes',
|
||||
'cost',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes =
|
||||
[
|
||||
'title',
|
||||
'notes',
|
||||
'asset_maintenance_type',
|
||||
'cost',
|
||||
'start_date',
|
||||
'completion_date'
|
||||
];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'asset' => ['name', 'asset_tag', 'serial'],
|
||||
'asset.model' => ['name', 'model_number'],
|
||||
'asset.supplier' => ['name'],
|
||||
'asset.assetstatus' => ['name'],
|
||||
'supplier' => ['name'],
|
||||
];
|
||||
|
||||
public function getCompanyableParents()
|
||||
{
|
||||
return ['asset'];
|
||||
}
|
||||
|
||||
/**
|
||||
* getImprovementOptions
|
||||
*
|
||||
* @return array
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
*/
|
||||
public static function getImprovementOptions()
|
||||
{
|
||||
return [
|
||||
trans('admin/asset_maintenances/general.maintenance') => trans('admin/asset_maintenances/general.maintenance'),
|
||||
trans('admin/asset_maintenances/general.repair') => trans('admin/asset_maintenances/general.repair'),
|
||||
trans('admin/asset_maintenances/general.upgrade') => trans('admin/asset_maintenances/general.upgrade'),
|
||||
trans('admin/asset_maintenances/general.pat_test') => trans('admin/asset_maintenances/general.pat_test'),
|
||||
trans('admin/asset_maintenances/general.calibration') => trans('admin/asset_maintenances/general.calibration'),
|
||||
trans('admin/asset_maintenances/general.software_support') => trans('admin/asset_maintenances/general.software_support'),
|
||||
trans('admin/asset_maintenances/general.hardware_support') => trans('admin/asset_maintenances/general.hardware_support'),
|
||||
trans('admin/asset_maintenances/general.configuration_change') => trans('admin/asset_maintenances/general.configuration_change'),
|
||||
];
|
||||
}
|
||||
|
||||
public function setIsWarrantyAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = 0;
|
||||
}
|
||||
$this->attributes['is_warranty'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*/
|
||||
public function setCostAttribute($value)
|
||||
{
|
||||
$value = Helper::ParseCurrency($value);
|
||||
if ($value == 0) {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['cost'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*/
|
||||
public function setNotesAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['notes'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*/
|
||||
public function setCompletionDateAttribute($value)
|
||||
{
|
||||
if ($value == '' || $value == '0000-00-00') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['completion_date'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* asset
|
||||
* Get asset for this improvement
|
||||
*
|
||||
* @return mixed
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
*/
|
||||
public function asset()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Asset::class, 'asset_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the admin who created the maintenance
|
||||
*
|
||||
* @return mixed
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @version v3.0
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'user_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
public function supplier()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id')
|
||||
->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* Query builder scope to order on a supplier
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderBySupplier($query, $order)
|
||||
{
|
||||
return $query->leftJoin('suppliers as suppliers_maintenances', 'asset_maintenances.supplier_id', '=', 'suppliers_maintenances.id')
|
||||
->orderBy('suppliers_maintenances.name', $order);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope to order on admin user
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderAdmin($query, $order)
|
||||
{
|
||||
return $query->leftJoin('users', 'asset_maintenances.user_id', '=', 'users.id')
|
||||
->orderBy('users.first_name', $order)
|
||||
->orderBy('users.last_name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on asset tag
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderByTag($query, $order)
|
||||
{
|
||||
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
|
||||
->orderBy('assets.asset_tag', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on asset tag
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderByAssetName($query, $order)
|
||||
{
|
||||
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
|
||||
->orderBy('assets.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on serial
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderByAssetSerial($query, $order)
|
||||
{
|
||||
return $query->leftJoin('assets', 'asset_maintenances.asset_id', '=', 'assets.id')
|
||||
->orderBy('assets.serial', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on status label name
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderStatusName($query, $order)
|
||||
{
|
||||
return $query->join('assets as maintained_asset', 'asset_maintenances.asset_id', '=', 'maintained_asset.id')
|
||||
->leftjoin('status_labels as maintained_asset_status', 'maintained_asset_status.id', '=', 'maintained_asset.status_id')
|
||||
->orderBy('maintained_asset_status.name', $order);
|
||||
}
|
||||
}
|
306
SNIPE-IT/app/Models/AssetModel.php
Normal file
306
SNIPE-IT/app/Models/AssetModel.php
Normal file
@ -0,0 +1,306 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
/**
|
||||
* Model for Asset Models. Asset Models contain higher level
|
||||
* attributes that are common among the same type of asset.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class AssetModel extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
protected $presenter = \App\Presenters\AssetModelPresenter::class;
|
||||
use Loggable, Requestable, Presentable;
|
||||
|
||||
protected $table = 'models';
|
||||
protected $hidden = ['user_id', 'deleted_at'];
|
||||
|
||||
// Declare the rules for the model validation
|
||||
protected $rules = [
|
||||
'name' => 'required|min:1|max:255',
|
||||
'model_number' => 'max:255|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'manufacturer_id' => 'integer|exists:manufacturers,id|nullable',
|
||||
'eol' => 'integer:min:0|max:240|nullable',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
'depreciation_id',
|
||||
'eol',
|
||||
'fieldset_id',
|
||||
'image',
|
||||
'manufacturer_id',
|
||||
'min_amt',
|
||||
'model_number',
|
||||
'name',
|
||||
'notes',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'model_number', 'notes', 'eol'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'depreciation' => ['name'],
|
||||
'category' => ['name'],
|
||||
'manufacturer' => ['name'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Establishes the model -> assets relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'model_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the model -> category relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Category::class, 'category_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the model -> depreciation relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function depreciation()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Depreciation::class, 'depreciation_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the model -> manufacturer relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function manufacturer()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the model -> fieldset relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function fieldset()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\CustomFieldset::class, 'fieldset_id');
|
||||
}
|
||||
|
||||
public function customFields()
|
||||
{
|
||||
return $this->fieldset()->first()->fields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the model -> custom field default values relationship
|
||||
*
|
||||
* @author hannah tinkler
|
||||
* @since [v4.3]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function defaultValues()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\CustomField::class, 'models_custom_fields')->withPivot('default_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full url for the image
|
||||
*
|
||||
* @todo this should probably be moved
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function getImageUrl()
|
||||
{
|
||||
if ($this->image) {
|
||||
return Storage::disk('public')->url(app('models_upload_path').$this->image);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the model is deletable
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.3.4]
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets_count == 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uploads for this model
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany('\App\Models\Actionlog', 'item_id')
|
||||
->where('item_type', '=', AssetModel::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* scopeInCategory
|
||||
* Get all models that are in the array of category ids
|
||||
*
|
||||
* @param $query
|
||||
* @param array $categoryIdListing
|
||||
*
|
||||
* @return mixed
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
* @version v1.0
|
||||
*/
|
||||
public function scopeInCategory($query, array $categoryIdListing)
|
||||
{
|
||||
return $query->whereIn('category_id', $categoryIdListing);
|
||||
}
|
||||
|
||||
/**
|
||||
* scopeRequestable
|
||||
* Get all models that are requestable by a user.
|
||||
*
|
||||
* @param $query
|
||||
*
|
||||
* @return $query
|
||||
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
|
||||
* @version v3.5
|
||||
*/
|
||||
public function scopeRequestableModels($query)
|
||||
{
|
||||
return $query->where('requestable', '1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to search on text, including catgeory and manufacturer name
|
||||
*
|
||||
* @param Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $search Search term
|
||||
*
|
||||
* @return Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeSearchByManufacturerOrCat($query, $search)
|
||||
{
|
||||
return $query->where('models.name', 'LIKE', "%$search%")
|
||||
->orWhere('model_number', 'LIKE', "%$search%")
|
||||
->orWhere(function ($query) use ($search) {
|
||||
$query->whereHas('category', function ($query) use ($search) {
|
||||
$query->where('categories.name', 'LIKE', '%'.$search.'%');
|
||||
});
|
||||
})
|
||||
->orWhere(function ($query) use ($search) {
|
||||
$query->whereHas('manufacturer', function ($query) use ($search) {
|
||||
$query->where('manufacturers.name', 'LIKE', '%'.$search.'%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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', 'models.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on category name
|
||||
*
|
||||
* @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', 'models.category_id', '=', 'categories.id')->orderBy('categories.name', $order);
|
||||
}
|
||||
|
||||
public function scopeOrderFieldset($query, $order)
|
||||
{
|
||||
return $query->leftJoin('custom_fieldsets', 'models.fieldset_id', '=', 'custom_fieldsets.id')->orderBy('custom_fieldsets.name', $order);
|
||||
}
|
||||
}
|
289
SNIPE-IT/app/Models/Category.php
Normal file
289
SNIPE-IT/app/Models/Category.php
Normal file
@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Traits\TwoColumnUniqueUndeletedTrait;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Model for Categories. Categories are a higher-level group
|
||||
* than Asset Models, and handle things like whether or not
|
||||
* to require acceptance from the user, whether or not to
|
||||
* send a EULA to the user, etc.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class Category extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\CategoryPresenter::class;
|
||||
use Presentable;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'categories';
|
||||
protected $hidden = ['user_id', 'deleted_at'];
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Category validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'user_id' => 'numeric|nullable',
|
||||
'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',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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 TwoColumnUniqueUndeletedTrait;
|
||||
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'category_type',
|
||||
'checkin_email',
|
||||
'eula_text',
|
||||
'name',
|
||||
'require_acceptance',
|
||||
'use_default_eula',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'category_type'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
/**
|
||||
* Checks if category can be deleted
|
||||
*
|
||||
* @author [Dan Meltzer] [<dmeltzer.devel@gmail.com>]
|
||||
* @since [v5.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->itemCount() == 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the category -> accessories relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Accessory::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the category -> licenses relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.3]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenses()
|
||||
{
|
||||
return $this->hasMany(\App\Models\License::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the category -> consumables relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Consumable::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the category -> consumables relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function components()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Component::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of items in the category. This should NEVER be used in
|
||||
* a collection of categories, as you'll end up with an n+1 query problem.
|
||||
*
|
||||
* It should only be used in a single category context.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return int
|
||||
*/
|
||||
public function itemCount()
|
||||
{
|
||||
|
||||
if (isset($this->{Str::plural($this->category_type).'_count'})) {
|
||||
return $this->{Str::plural($this->category_type).'_count'};
|
||||
}
|
||||
|
||||
switch ($this->category_type) {
|
||||
case 'asset':
|
||||
return $this->assets()->count();
|
||||
case 'accessory':
|
||||
return $this->accessories()->count();
|
||||
case 'component':
|
||||
return $this->components()->count();
|
||||
case 'consumable':
|
||||
return $this->consumables()->count();
|
||||
case 'license':
|
||||
return $this->licenses()->count();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the category -> assets relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasManyThrough(Asset::class, \App\Models\AssetModel::class, 'category_id', 'model_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the category -> assets relationship but also takes into consideration
|
||||
* the setting to show archived in lists.
|
||||
*
|
||||
* We could have complicated the assets() method above, but keeping this separate
|
||||
* should give us more flexibility if we need to return actually archived assets
|
||||
* by their category.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.1.0]
|
||||
* @see \App\Models\Asset::scopeAssetsForShow()
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function showableAssets()
|
||||
{
|
||||
return $this->hasManyThrough(Asset::class, \App\Models\AssetModel::class, 'category_id', 'model_id')->AssetsForShow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the category -> models relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function models()
|
||||
{
|
||||
return $this->hasMany(\App\Models\AssetModel::class, 'category_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
||||
* checks for a settings level EULA
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v2.0]
|
||||
* @return string | null
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
|
||||
if ($this->eula_text) {
|
||||
return Helper::parseEscapedMarkedown($this->eula_text);
|
||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->use_default_eula == '1')) {
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN MUTATORS
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* This sets the checkin_value to a boolean 0 or 1. This accounts for forms or API calls that
|
||||
* explicitly pass the checkin_email field but it has a null or empty value.
|
||||
*
|
||||
* This will also correctly parse a 1/0 if "true"/"false" is passed.
|
||||
*
|
||||
* @param $value
|
||||
* @return void
|
||||
*/
|
||||
public function setCheckinEmailAttribute($value)
|
||||
{
|
||||
$this->attributes['checkin_email'] = (int) filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* Query builder scope for whether or not the category requires acceptance
|
||||
*
|
||||
* @author Vincent Sposato <vincent.sposato@gmail.com>
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeRequiresAcceptance($query)
|
||||
{
|
||||
return $query->where('require_acceptance', '=', true);
|
||||
}
|
||||
}
|
134
SNIPE-IT/app/Models/CheckoutAcceptance.php
Normal file
134
SNIPE-IT/app/Models/CheckoutAcceptance.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class CheckoutAcceptance extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes, Notifiable;
|
||||
|
||||
protected $casts = [
|
||||
'accepted_at' => 'datetime',
|
||||
'declined_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the mail recipient from the config
|
||||
*
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
public function routeNotificationForMail()
|
||||
{
|
||||
// At this point the endpoint is the same for everything.
|
||||
// In the future this may want to be adapted for individual notifications.
|
||||
$recipients_string = explode(',', Setting::getSettings()->alert_email);
|
||||
$recipients = array_map('trim', $recipients_string);
|
||||
|
||||
return array_filter($recipients);
|
||||
}
|
||||
|
||||
/**
|
||||
* The resource that was is out
|
||||
*
|
||||
* @return Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
public function checkoutable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user that the checkoutable was checked out to
|
||||
*
|
||||
* @return Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function assignedTo()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this checkout acceptance pending?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isPending()
|
||||
{
|
||||
return $this->accepted_at == null && $this->declined_at == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Was the checkoutable checked out to this user?
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function isCheckedOutTo(User $user)
|
||||
{
|
||||
return $this->assignedTo->is($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a record to the checkout_acceptance table ONLY.
|
||||
* Do not add stuff here that doesn't have a corresponding column in the
|
||||
* checkout_acceptances table or you'll get an error.
|
||||
*
|
||||
* @param string $signature_filename
|
||||
*/
|
||||
public function accept($signature_filename, $eula = null, $filename = null)
|
||||
{
|
||||
$this->accepted_at = now();
|
||||
$this->signature_filename = $signature_filename;
|
||||
$this->stored_eula = $eula;
|
||||
$this->stored_eula_file = $filename;
|
||||
$this->save();
|
||||
|
||||
/**
|
||||
* Update state for the checked out item
|
||||
*/
|
||||
$this->checkoutable->acceptedCheckout($this->assignedTo, $signature_filename, $filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decline the checkout acceptance
|
||||
*
|
||||
* @param string $signature_filename
|
||||
*/
|
||||
public function decline($signature_filename)
|
||||
{
|
||||
$this->declined_at = now();
|
||||
$this->signature_filename = $signature_filename;
|
||||
$this->save();
|
||||
|
||||
/**
|
||||
* Update state for the checked out item
|
||||
*/
|
||||
$this->checkoutable->declinedCheckout($this->assignedTo, $signature_filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter checkout acceptences by the user
|
||||
* @param Illuminate\Database\Eloquent\Builder $query
|
||||
* @param User $user
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeForUser(Builder $query, User $user)
|
||||
{
|
||||
return $query->where('assigned_to_id', $user->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to only get pending acceptances
|
||||
* @param Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopePending(Builder $query)
|
||||
{
|
||||
return $query->whereNull('accepted_at')->whereNull('declined_at');
|
||||
}
|
||||
}
|
52
SNIPE-IT/app/Models/CheckoutRequest.php
Normal file
52
SNIPE-IT/app/Models/CheckoutRequest.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class CheckoutRequest extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
protected $fillable = ['user_id'];
|
||||
protected $table = 'checkout_requests';
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function requestingUser()
|
||||
{
|
||||
return $this->user()->withTrashed()->first();
|
||||
}
|
||||
|
||||
public function requestedItem()
|
||||
{
|
||||
return $this->morphTo('requestable');
|
||||
}
|
||||
|
||||
public function itemRequested() // Workaround for laravel polymorphic issue that's not being solved :(
|
||||
{
|
||||
return $this->requestedItem()->first();
|
||||
}
|
||||
|
||||
public function itemType()
|
||||
{
|
||||
return snake_case(class_basename($this->requestable_type));
|
||||
}
|
||||
|
||||
public function location()
|
||||
{
|
||||
return $this->itemRequested()->location;
|
||||
}
|
||||
|
||||
public function name()
|
||||
{
|
||||
if ($this->itemType() == 'asset') {
|
||||
return $this->itemRequested()->present()->name();
|
||||
}
|
||||
|
||||
return $this->itemRequested()->name;
|
||||
}
|
||||
}
|
264
SNIPE-IT/app/Models/Company.php
Normal file
264
SNIPE-IT/app/Models/Company.php
Normal file
@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Auth;
|
||||
use DB;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
/**
|
||||
* Model for Companies.
|
||||
*
|
||||
* @version v1.8
|
||||
*/
|
||||
final class Company extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'companies';
|
||||
|
||||
// Declare the rules for the model validation
|
||||
protected $rules = [
|
||||
'name' => 'required|min:1|max:255|unique:companies,name',
|
||||
'fax' => 'min:7|max:35|nullable',
|
||||
'phone' => 'min:7|max:35|nullable',
|
||||
'email' => 'email|max:150|nullable',
|
||||
];
|
||||
|
||||
protected $presenter = \App\Presenters\CompanyPresenter::class;
|
||||
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.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $injectUniqueIdentifier = true;
|
||||
use ValidatingTrait;
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'phone', 'fax', 'email', 'created_at', 'updated_at'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'phone',
|
||||
'fax',
|
||||
'email',
|
||||
];
|
||||
|
||||
private static function isFullMultipleCompanySupportEnabled()
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
// NOTE: this can happen when seeding the database
|
||||
if (is_null($settings)) {
|
||||
return false;
|
||||
} else {
|
||||
return $settings->full_multiple_companies_support == 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scoping table queries, determining if a logged in user is part of a company, and only allows
|
||||
* that user to see items associated with that company
|
||||
*/
|
||||
private static function scopeCompanyablesDirectly($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
if (Auth::user()) {
|
||||
$company_id = Auth::user()->company_id;
|
||||
} else {
|
||||
$company_id = null;
|
||||
}
|
||||
|
||||
$table = ($table_name) ? $table_name."." : $query->getModel()->getTable().".";
|
||||
|
||||
if (\Schema::hasColumn($query->getModel()->getTable(), $column)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getIdFromInput($unescaped_input)
|
||||
{
|
||||
$escaped_input = e($unescaped_input);
|
||||
|
||||
if ($escaped_input == '0') {
|
||||
return null;
|
||||
} else {
|
||||
return $escaped_input;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the company id for the current user taking into
|
||||
* account the full multiple company support setting
|
||||
* and if the current user is a super user.
|
||||
*
|
||||
* @param $unescaped_input
|
||||
* @return int|mixed|string|null
|
||||
*/
|
||||
public static function getIdForCurrentUser($unescaped_input)
|
||||
{
|
||||
if (! static::isFullMultipleCompanySupportEnabled()) {
|
||||
return static::getIdFromInput($unescaped_input);
|
||||
} else {
|
||||
$current_user = Auth::user();
|
||||
|
||||
// Super users should be able to set a company to whatever they need
|
||||
if ($current_user->isSuperUser()) {
|
||||
return static::getIdFromInput($unescaped_input);
|
||||
} else {
|
||||
if ($current_user->company_id != null) {
|
||||
return $current_user->company_id;
|
||||
} else {
|
||||
return static::getIdFromInput($unescaped_input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function isCurrentUserHasAccess($companyable)
|
||||
{
|
||||
if (is_null($companyable)) {
|
||||
return false;
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled()) {
|
||||
return true;
|
||||
} elseif (!$companyable instanceof Company && !\Schema::hasColumn($companyable->getModel()->getTable(), 'company_id')) {
|
||||
// This is primary for the gate:allows-check in location->isDeletable()
|
||||
// Locations don't have a company_id so without this it isn't possible to delete locations with FullMultipleCompanySupport enabled
|
||||
// because this function is called by SnipePermissionsPolicy->before()
|
||||
return true;
|
||||
} else {
|
||||
if (Auth::user()) {
|
||||
$current_user_company_id = Auth::user()->company_id;
|
||||
$companyable_company_id = $companyable->company_id;
|
||||
|
||||
return $current_user_company_id == null || $current_user_company_id == $companyable_company_id || Auth::user()->isSuperUser();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function isCurrentUserAuthorized()
|
||||
{
|
||||
return (! static::isFullMultipleCompanySupportEnabled()) || (Auth::user()->isSuperUser());
|
||||
}
|
||||
|
||||
public static function canManageUsersCompanies()
|
||||
{
|
||||
return ! static::isFullMultipleCompanySupportEnabled() || Auth::user()->isSuperUser() ||
|
||||
Auth::user()->company_id == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if company can be deleted
|
||||
*
|
||||
* @author [Dan Meltzer] [<dmeltzer.devel@gmail.com>]
|
||||
* @since [v5.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets()->count() === 0)
|
||||
&& ($this->accessories()->count() === 0)
|
||||
&& ($this->consumables()->count() === 0)
|
||||
&& ($this->components()->count() === 0)
|
||||
&& ($this->users()->count() === 0);
|
||||
}
|
||||
|
||||
public static function getIdForUser($unescaped_input)
|
||||
{
|
||||
if (! static::isFullMultipleCompanySupportEnabled() || Auth::user()->isSuperUser()) {
|
||||
return static::getIdFromInput($unescaped_input);
|
||||
} else {
|
||||
return static::getIdForCurrentUser($unescaped_input);
|
||||
}
|
||||
}
|
||||
|
||||
public static function scopeCompanyables($query, $column = 'company_id', $table_name = null)
|
||||
{
|
||||
// If not logged in and hitting this, assume we are on the command line and don't scope?'
|
||||
if (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser()) || (! Auth::check())) {
|
||||
return $query;
|
||||
} else {
|
||||
return static::scopeCompanyablesDirectly($query, $column, $table_name);
|
||||
}
|
||||
}
|
||||
|
||||
public static function scopeCompanyableChildren(array $companyable_names, $query)
|
||||
{
|
||||
if (count($companyable_names) == 0) {
|
||||
throw new Exception('No Companyable Children to scope');
|
||||
} elseif (! static::isFullMultipleCompanySupportEnabled() || (Auth::check() && Auth::user()->isSuperUser())) {
|
||||
return $query;
|
||||
} else {
|
||||
$f = function ($q) {
|
||||
static::scopeCompanyablesDirectly($q);
|
||||
};
|
||||
|
||||
$q = $query->where(function ($q) use ($companyable_names, $f) {
|
||||
$q2 = $q->whereHas($companyable_names[0], $f);
|
||||
|
||||
for ($i = 1; $i < count($companyable_names); $i++) {
|
||||
$q2 = $q2->orWhereHas($companyable_names[$i], $f);
|
||||
}
|
||||
});
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany(User::class, 'company_id');
|
||||
}
|
||||
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(Asset::class, 'company_id');
|
||||
}
|
||||
|
||||
public function licenses()
|
||||
{
|
||||
return $this->hasMany(License::class, 'company_id');
|
||||
}
|
||||
|
||||
public function accessories()
|
||||
{
|
||||
return $this->hasMany(Accessory::class, 'company_id');
|
||||
}
|
||||
|
||||
public function consumables()
|
||||
{
|
||||
return $this->hasMany(Consumable::class, 'company_id');
|
||||
}
|
||||
|
||||
public function components()
|
||||
{
|
||||
return $this->hasMany(Component::class, 'company_id');
|
||||
}
|
||||
}
|
40
SNIPE-IT/app/Models/CompanyableChildScope.php
Normal file
40
SNIPE-IT/app/Models/CompanyableChildScope.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
/**
|
||||
* Handle query scoping for full company support.
|
||||
*
|
||||
* @todo Move this to a more Laravel 5.2 esque way
|
||||
* @version v1.0
|
||||
*/
|
||||
final class CompanyableChildScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model)
|
||||
{
|
||||
$model = $builder->getModel();
|
||||
|
||||
return Company::scopeCompanyableChildren($model->getCompanyableParents(), $builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo IMPLEMENT
|
||||
* Remove the scope from the given Eloquent query builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
public function remove(Builder $builder)
|
||||
{
|
||||
}
|
||||
}
|
16
SNIPE-IT/app/Models/CompanyableChildTrait.php
Normal file
16
SNIPE-IT/app/Models/CompanyableChildTrait.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
trait CompanyableChildTrait
|
||||
{
|
||||
/**
|
||||
* Boot the companyable trait for a model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootCompanyableChildTrait()
|
||||
{
|
||||
static::addGlobalScope(new CompanyableChildScope);
|
||||
}
|
||||
}
|
38
SNIPE-IT/app/Models/CompanyableScope.php
Normal file
38
SNIPE-IT/app/Models/CompanyableScope.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Scope;
|
||||
|
||||
/**
|
||||
* Handle query scoping for full company support.
|
||||
*
|
||||
* @todo Move this to a more Laravel 5.2 esque way
|
||||
* @version v1.0
|
||||
*/
|
||||
final class CompanyableScope implements Scope
|
||||
{
|
||||
/**
|
||||
* Apply the scope to a given Eloquent query builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
public function apply(Builder $builder, Model $model)
|
||||
{
|
||||
return Company::scopeCompanyables($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo IMPLEMENT
|
||||
* Remove the scope from the given Eloquent query builder.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
public function remove(Builder $builder)
|
||||
{
|
||||
}
|
||||
}
|
16
SNIPE-IT/app/Models/CompanyableTrait.php
Normal file
16
SNIPE-IT/app/Models/CompanyableTrait.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
trait CompanyableTrait
|
||||
{
|
||||
/**
|
||||
* Boot the companyable trait for a model.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootCompanyableTrait()
|
||||
{
|
||||
static::addGlobalScope(new CompanyableScope);
|
||||
}
|
||||
}
|
309
SNIPE-IT/app/Models/Component.php
Normal file
309
SNIPE-IT/app/Models/Component.php
Normal file
@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
/**
|
||||
* Model for Components.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
class Component extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\ComponentPresenter::class;
|
||||
use CompanyableTrait;
|
||||
use Loggable, Presentable;
|
||||
use SoftDeletes;
|
||||
protected $casts = [
|
||||
'purchase_date' => 'datetime',
|
||||
];
|
||||
protected $table = 'components';
|
||||
|
||||
/**
|
||||
* Category validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'name' => 'required|min:3|max:255',
|
||||
'qty' => 'required|integer|min:1',
|
||||
'category_id' => 'required|integer|exists:categories,id',
|
||||
'supplier_id' => 'nullable|integer|exists:suppliers,id',
|
||||
'company_id' => 'integer|nullable|exists:companies,id',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
'company_id',
|
||||
'supplier_id',
|
||||
'location_id',
|
||||
'name',
|
||||
'purchase_cost',
|
||||
'purchase_date',
|
||||
'min_amt',
|
||||
'order_number',
|
||||
'qty',
|
||||
'serial',
|
||||
'notes',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'order_number', 'serial', 'purchase_cost', 'purchase_date', 'notes'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'category' => ['name'],
|
||||
'company' => ['name'],
|
||||
'location' => ['name'],
|
||||
'supplier' => ['name'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the components -> action logs -> uploads relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.13]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the component -> location relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function location()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Location::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> assets relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Asset::class, 'components_assets')->withPivot('id', 'assigned_qty', 'created_at', 'user_id', 'note');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> admin user relationship
|
||||
*
|
||||
* @todo this is probably not needed - refactor
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> company relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Company::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> category relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Category::class, 'category_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the item -> supplier relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.1.1]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function supplier()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> action logs relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assetlog()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many items within a component are checked out
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numCheckedOut()
|
||||
{
|
||||
$checkedout = 0;
|
||||
foreach ($this->assets as $checkout) {
|
||||
$checkedout += $checkout->pivot->assigned_qty;
|
||||
}
|
||||
|
||||
return $checkedout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many items within a component are remaining
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numRemaining()
|
||||
{
|
||||
return $this->qty - $this->numCheckedOut();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN MUTATORS
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* This sets a value for qty if no value is given. The database does not allow this
|
||||
* field to be null, and in the other areas of the code, we set a default, but the importer
|
||||
* does not.
|
||||
*
|
||||
* This simply checks that there is a value for quantity, and if there isn't, set it to 0.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since v6.3.4
|
||||
* @param $value
|
||||
* @return void
|
||||
*/
|
||||
public function setQtyAttribute($value)
|
||||
{
|
||||
$this->attributes['qty'] = (!$value) ? 0 : intval($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope to order on company
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderCategory($query, $order)
|
||||
{
|
||||
return $query->join('categories', 'components.category_id', '=', 'categories.id')->orderBy('categories.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on company
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderLocation($query, $order)
|
||||
{
|
||||
return $query->leftJoin('locations', 'components.location_id', '=', 'locations.id')->orderBy('locations.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on company
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderCompany($query, $order)
|
||||
{
|
||||
return $query->leftJoin('companies', 'components.company_id', '=', 'companies.id')->orderBy('companies.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', 'components.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
|
||||
}
|
||||
}
|
435
SNIPE-IT/app/Models/Consumable.php
Normal file
435
SNIPE-IT/app/Models/Consumable.php
Normal file
@ -0,0 +1,435 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Acceptable;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class Consumable extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\ConsumablePresenter::class;
|
||||
use CompanyableTrait;
|
||||
use Loggable, Presentable;
|
||||
use SoftDeletes;
|
||||
use Acceptable;
|
||||
|
||||
protected $table = 'consumables';
|
||||
protected $casts = [
|
||||
'purchase_date' => 'datetime',
|
||||
'requestable' => 'boolean',
|
||||
'category_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
'supplier_id',
|
||||
'qty' => 'integer',
|
||||
'min_amt' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Category validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'name' => 'required|min:3|max:255',
|
||||
'qty' => 'required|integer|min:0',
|
||||
'category_id' => 'required|integer',
|
||||
'company_id' => 'integer|nullable',
|
||||
'min_amt' => 'integer|min:0|nullable',
|
||||
'purchase_cost' => 'numeric|nullable|gte:0',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'category_id',
|
||||
'company_id',
|
||||
'item_no',
|
||||
'location_id',
|
||||
'manufacturer_id',
|
||||
'name',
|
||||
'order_number',
|
||||
'model_number',
|
||||
'purchase_cost',
|
||||
'purchase_date',
|
||||
'qty',
|
||||
'min_amt',
|
||||
'requestable',
|
||||
'notes',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'order_number', 'purchase_cost', 'purchase_date', 'item_no', 'model_number', 'notes'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'category' => ['name'],
|
||||
'company' => ['name'],
|
||||
'location' => ['name'],
|
||||
'manufacturer' => ['name'],
|
||||
'supplier' => ['name'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the components -> action logs -> uploads relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.13]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the attribute of whether or not the consumable is requestable
|
||||
*
|
||||
* This isn't really implemented yet, as you can't currently request a consumable
|
||||
* however it will be implemented in the future, and we needed to include
|
||||
* this method here so all of our polymorphic methods don't break.
|
||||
*
|
||||
* @todo Update this comment once it's been implemented
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function setRequestableAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['requestable'] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the consumable -> admin user relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> assignments relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumableAssignments()
|
||||
{
|
||||
return $this->hasMany(\App\Models\ConsumableAssignment::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> company relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Company::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> manufacturer relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function manufacturer()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> location relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function location()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Location::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> category relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Category::class, 'category_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the component -> action logs relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assetlog()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')->where('item_type', self::class)->orderBy('created_at', 'desc')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full image url for the consumable
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return string | false
|
||||
*/
|
||||
public function getImageUrl()
|
||||
{
|
||||
if ($this->image) {
|
||||
return Storage::disk('public')->url(app('consumables_upload_path').$this->image);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> users relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'consumables_users', 'consumable_id', 'assigned_to')->withPivot('user_id')->withTrashed()->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the item -> supplier relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.1.1]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function supplier()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether to send a checkin/checkout email based on
|
||||
* asset model category
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function checkin_email()
|
||||
{
|
||||
return $this->category->checkin_email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this asset requires acceptance by the assigned user
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function requireAcceptance()
|
||||
{
|
||||
return $this->category->require_acceptance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
||||
* checks for a settings level EULA
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return string | false
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
if ($this->category->eula_text) {
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ((Setting::getSettings()->default_eula_text) && ($this->category->use_default_eula == '1')) {
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how many items within a consumable are checked out
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v5.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numCheckedOut()
|
||||
{
|
||||
$checkedout = 0;
|
||||
$checkedout = $this->users->count();
|
||||
|
||||
return $checkedout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the number of available consumables
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return int
|
||||
*/
|
||||
public function numRemaining()
|
||||
{
|
||||
$checkedout = $this->users->count();
|
||||
$total = $this->qty;
|
||||
$remaining = $total - $checkedout;
|
||||
|
||||
return $remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN MUTATORS
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* This sets a value for qty if no value is given. The database does not allow this
|
||||
* field to be null, and in the other areas of the code, we set a default, but the importer
|
||||
* does not.
|
||||
*
|
||||
* This simply checks that there is a value for quantity, and if there isn't, set it to 0.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since v6.3.4
|
||||
* @param $value
|
||||
* @return void
|
||||
*/
|
||||
public function setQtyAttribute($value)
|
||||
{
|
||||
$this->attributes['qty'] = (!$value) ? 0 : intval($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
|
||||
/**
|
||||
* Query builder scope to order on company
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderCategory($query, $order)
|
||||
{
|
||||
return $query->join('categories', 'consumables.category_id', '=', 'categories.id')->orderBy('categories.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
|
||||
*/
|
||||
public function scopeOrderLocation($query, $order)
|
||||
{
|
||||
return $query->leftJoin('locations', 'consumables.location_id', '=', 'locations.id')->orderBy('locations.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on manufacturer
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderManufacturer($query, $order)
|
||||
{
|
||||
return $query->leftJoin('manufacturers', 'consumables.manufacturer_id', '=', 'manufacturers.id')->orderBy('manufacturers.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on company
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderCompany($query, $order)
|
||||
{
|
||||
return $query->leftJoin('companies', 'consumables.company_id', '=', 'companies.id')->orderBy('companies.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', 'consumables.supplier_id', '=', 'suppliers.id')->orderBy('suppliers.name', $order);
|
||||
}
|
||||
}
|
27
SNIPE-IT/app/Models/ConsumableAssignment.php
Normal file
27
SNIPE-IT/app/Models/ConsumableAssignment.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ConsumableAssignment extends Model
|
||||
{
|
||||
use CompanyableTrait;
|
||||
|
||||
protected $table = 'consumables_users';
|
||||
|
||||
public function consumable()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Consumable::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'assigned_to');
|
||||
}
|
||||
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'user_id');
|
||||
}
|
||||
}
|
402
SNIPE-IT/app/Models/CustomField.php
Normal file
402
SNIPE-IT/app/Models/CustomField.php
Normal file
@ -0,0 +1,402 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use EasySlugger\Utf8Slugger;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Schema;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
class CustomField extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use ValidatingTrait,
|
||||
UniqueUndeletedTrait;
|
||||
|
||||
/**
|
||||
* Custom field predfined formats
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const PREDEFINED_FORMATS = [
|
||||
'ANY' => '',
|
||||
'CUSTOM REGEX' => '',
|
||||
'ALPHA' => 'alpha',
|
||||
'ALPHA-DASH' => 'alpha_dash',
|
||||
'NUMERIC' => 'numeric',
|
||||
'ALPHA-NUMERIC' => 'alpha_num',
|
||||
'EMAIL' => 'email',
|
||||
'DATE' => 'date',
|
||||
'URL' => 'url',
|
||||
'IP' => 'ip',
|
||||
'IPV4' => 'ipv4',
|
||||
'IPV6' => 'ipv6',
|
||||
'MAC' => 'regex:/^[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}:[a-fA-F0-9]{2}$/',
|
||||
'BOOLEAN' => 'boolean',
|
||||
];
|
||||
|
||||
public $guarded = [
|
||||
'id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Validation rules.
|
||||
* At least empty array must be provided if using ValidatingTrait.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rules = [
|
||||
'name' => 'required|unique:custom_fields',
|
||||
'element' => 'required|in:text,listbox,textarea,checkbox,radio',
|
||||
'field_encrypted' => 'nullable|boolean',
|
||||
'auto_add_to_fieldsets' => 'boolean',
|
||||
'show_in_listview' => 'boolean',
|
||||
'show_in_requestable_list' => 'boolean',
|
||||
'show_in_email' => 'boolean',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'show_in_requestable_list' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'element',
|
||||
'format',
|
||||
'field_values',
|
||||
'field_encrypted',
|
||||
'help_text',
|
||||
'show_in_email',
|
||||
'is_unique',
|
||||
'display_in_user_view',
|
||||
'auto_add_to_fieldsets',
|
||||
'show_in_listview',
|
||||
'show_in_email',
|
||||
'show_in_requestable_list',
|
||||
];
|
||||
|
||||
/**
|
||||
* This is confusing, since it's actually the custom fields table that
|
||||
* we're usually modifying, but since we alter the assets table, we have to
|
||||
* say that here, otherwise the new fields get added onto the custom fields
|
||||
* table instead of the assets table.
|
||||
*
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @since [v3.0]
|
||||
*/
|
||||
public static $table_name = 'assets';
|
||||
|
||||
/**
|
||||
* Convert the custom field's name property to a db-safe string.
|
||||
*
|
||||
* We could probably have used str_slug() here but not sure what it would
|
||||
* do with previously existing values. - @snipe
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.4]
|
||||
* @return string
|
||||
*/
|
||||
public static function name_to_db_name($name)
|
||||
{
|
||||
return '_snipeit_'.preg_replace('/[^a-zA-Z0-9]/', '_', strtolower($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set some boot methods for creating and updating.
|
||||
*
|
||||
* There is never ever a time when we wouldn't want to be updating those asset
|
||||
* column names and the values of the db column name in the custom fields table
|
||||
* if they have changed, so we handle that here so that we don't have to remember
|
||||
* to do it in the controllers.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.4]
|
||||
* @return bool
|
||||
*/
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
self::created(function ($custom_field) {
|
||||
|
||||
// Column already exists on the assets table - nothing to do here.
|
||||
// This *shouldn't* happen in the wild.
|
||||
if (Schema::hasColumn(self::$table_name, $custom_field->db_column)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update the column name in the assets table
|
||||
Schema::table(self::$table_name, function ($table) use ($custom_field) {
|
||||
$table->text($custom_field->convertUnicodeDbSlug())->nullable();
|
||||
});
|
||||
|
||||
// Update the db_column property in the custom fields table
|
||||
$custom_field->db_column = $custom_field->convertUnicodeDbSlug();
|
||||
$custom_field->save();
|
||||
});
|
||||
|
||||
self::updating(function ($custom_field) {
|
||||
|
||||
// Column already exists on the assets table - nothing to do here.
|
||||
if ($custom_field->isDirty('name')) {
|
||||
if (Schema::hasColumn(self::$table_name, $custom_field->convertUnicodeDbSlug())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is just a dumb thing we have to include because Laraval/Doctrine doesn't
|
||||
// play well with enums or a table that EVER had enums. :(
|
||||
$platform = Schema::getConnection()->getDoctrineSchemaManager()->getDatabasePlatform();
|
||||
$platform->registerDoctrineTypeMapping('enum', 'string');
|
||||
|
||||
// Rename the field if the name has changed
|
||||
Schema::table(self::$table_name, function ($table) use ($custom_field) {
|
||||
$table->renameColumn($custom_field->convertUnicodeDbSlug($custom_field->getOriginal('name')), $custom_field->convertUnicodeDbSlug());
|
||||
});
|
||||
|
||||
// Save the updated column name to the custom fields table
|
||||
$custom_field->db_column = $custom_field->convertUnicodeDbSlug();
|
||||
$custom_field->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Drop the assets column if we've deleted it from custom fields
|
||||
self::deleting(function ($custom_field) {
|
||||
return Schema::table(self::$table_name, function ($table) use ($custom_field) {
|
||||
$table->dropColumn($custom_field->db_column);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the customfield -> fieldset relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function fieldset()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\CustomFieldset::class);
|
||||
}
|
||||
|
||||
public function assetModels()
|
||||
{
|
||||
return $this->fieldset()->with('models')->get()->pluck('models')->flatten()->unique('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the customfield -> admin user relationship
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the customfield -> default values relationship
|
||||
*
|
||||
* @author Hannah Tinkler
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function defaultValues()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\AssetModel::class, 'models_custom_fields')->withPivot('default_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default value for a given model using the defaultValues
|
||||
* relationship
|
||||
*
|
||||
* @param int $modelId
|
||||
* @return string
|
||||
*/
|
||||
public function defaultValue($modelId)
|
||||
{
|
||||
return $this->defaultValues->filter(function ($item) use ($modelId) {
|
||||
return $item->pivot->asset_model_id == $modelId;
|
||||
})->map(function ($item) {
|
||||
return $item->pivot->default_value;
|
||||
})->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the format of the attribute
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @param $value string
|
||||
* @since [v3.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function check_format($value)
|
||||
{
|
||||
return preg_match('/^'.$this->attributes['format'].'$/', $value) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DB column name.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return string
|
||||
*/
|
||||
public function db_column_name()
|
||||
{
|
||||
return $this->db_column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutator for the 'format' attribute.
|
||||
*
|
||||
* This is used by the dropdown to store the laravel-specific
|
||||
* validator strings in the database but still return the
|
||||
* user-friendly text in the dropdowns, and in the custom fields display.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.4]
|
||||
* @return string
|
||||
*/
|
||||
public function getFormatAttribute($value)
|
||||
{
|
||||
foreach (self::PREDEFINED_FORMATS as $name => $pattern) {
|
||||
if ($pattern === $value || $name === $value) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a value string as an array for select boxes and checkboxes.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.4]
|
||||
* @return array
|
||||
*/
|
||||
public function setFormatAttribute($value)
|
||||
{
|
||||
if (isset(self::PREDEFINED_FORMATS[$value])) {
|
||||
$this->attributes['format'] = self::PREDEFINED_FORMATS[$value];
|
||||
} else {
|
||||
$this->attributes['format'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a value string as an array for select boxes and checkboxes.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.4]
|
||||
* @return array
|
||||
*/
|
||||
public function formatFieldValuesAsArray()
|
||||
{
|
||||
$result = [];
|
||||
$arr = preg_split('/\\r\\n|\\r|\\n/', $this->field_values);
|
||||
|
||||
if (($this->element != 'checkbox') && ($this->element != 'radio')) {
|
||||
$result[''] = 'Select '.strtolower($this->format);
|
||||
}
|
||||
|
||||
for ($x = 0; $x < count($arr); $x++) {
|
||||
$arr_parts = explode('|', $arr[$x]);
|
||||
if ($arr_parts[0] != '') {
|
||||
if (array_key_exists('1', $arr_parts)) {
|
||||
$result[$arr_parts[0]] = trim($arr_parts[1]);
|
||||
} else {
|
||||
$result[$arr_parts[0]] = trim($arr_parts[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the field is encrypted
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.4]
|
||||
* @return bool
|
||||
*/
|
||||
public function isFieldDecryptable($string)
|
||||
{
|
||||
if (($this->field_encrypted == '1') && ($string != '')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert non-UTF-8 or weirdly encoded text into something that
|
||||
* won't break the database.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.4]
|
||||
* @return string
|
||||
*/
|
||||
public function convertUnicodeDbSlug($original = null)
|
||||
{
|
||||
$name = $original ? $original : $this->name;
|
||||
$id = $this->id ? $this->id : 'xx';
|
||||
|
||||
if (! function_exists('transliterator_transliterate')) {
|
||||
$long_slug = '_snipeit_'.str_slug(mb_convert_encoding(trim($name),"UTF-8"), '_');
|
||||
} else {
|
||||
$long_slug = '_snipeit_'.Utf8Slugger::slugify($name, '_');
|
||||
}
|
||||
|
||||
return substr($long_slug, 0, 50).'_'.$id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rules for custom fields to use with Validator
|
||||
* @author [V. Cordes] [<volker@fdatek.de>]
|
||||
* @param int $id
|
||||
* @since [v4.1.10]
|
||||
* @return array
|
||||
*/
|
||||
public function validationRules($regex_format = null)
|
||||
{
|
||||
return [
|
||||
'format' => [
|
||||
Rule::in(array_merge(array_keys(self::PREDEFINED_FORMATS), self::PREDEFINED_FORMATS, [$regex_format])),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if there is a custom regex format type
|
||||
* @see https://github.com/snipe/snipe-it/issues/5896
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFormatType()
|
||||
{
|
||||
if (stripos($this->format, 'regex') === 0 && ($this->format !== self::PREDEFINED_FORMATS['MAC'])) {
|
||||
return 'CUSTOM REGEX';
|
||||
}
|
||||
|
||||
return $this->format;
|
||||
}
|
||||
}
|
114
SNIPE-IT/app/Models/CustomFieldset.php
Normal file
114
SNIPE-IT/app/Models/CustomFieldset.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Gate;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class CustomFieldset extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use ValidatingTrait;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
/**
|
||||
* Validation rules
|
||||
* @var array
|
||||
*/
|
||||
public $rules = [
|
||||
'name' => 'required|unique:custom_fieldsets',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Establishes the fieldset -> field relationship
|
||||
*
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function fields()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\CustomField::class)->withPivot(['required', 'order'])->orderBy('pivot_order');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the fieldset -> models relationship
|
||||
*
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function models()
|
||||
{
|
||||
return $this->hasMany(\App\Models\AssetModel::class, 'fieldset_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the fieldset -> admin user relationship
|
||||
*
|
||||
* @author [Brady Wetherington] [<uberbrady@gmail.com>]
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class); //WARNING - not all CustomFieldsets have a User!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the validation rules we should apply based on the
|
||||
* custom field format
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return array
|
||||
*/
|
||||
public function validation_rules()
|
||||
{
|
||||
$rules = [];
|
||||
foreach ($this->fields as $field) {
|
||||
$rule = [];
|
||||
|
||||
if (($field->field_encrypted != '1') ||
|
||||
(($field->field_encrypted == '1') && (Gate::allows('admin')))) {
|
||||
$rule[] = ($field->pivot->required == '1') ? 'required' : 'nullable';
|
||||
}
|
||||
|
||||
if ($field->is_unique == '1') {
|
||||
$rule[] = 'unique_undeleted';
|
||||
}
|
||||
|
||||
array_push($rule, $field->attributes['format']);
|
||||
$rules[$field->db_column_name()] = $rule;
|
||||
|
||||
// add not_array to rules for all fields but checkboxes
|
||||
if ($field->element != 'checkbox') {
|
||||
$rules[$field->db_column_name()][] = 'not_array';
|
||||
}
|
||||
|
||||
if ($field->element == 'checkbox') {
|
||||
$rules[$field->db_column_name()][] = 'checkboxes';
|
||||
}
|
||||
|
||||
if ($field->element == 'radio') {
|
||||
$rules[$field->db_column_name()][] = 'radio_buttons';
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
144
SNIPE-IT/app/Models/Department.php
Normal file
144
SNIPE-IT/app/Models/Department.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Traits\Searchable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class Department extends SnipeModel
|
||||
{
|
||||
use CompanyableTrait;
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* 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, UniqueUndeletedTrait;
|
||||
|
||||
protected $casts = [
|
||||
'manager_id' => 'integer',
|
||||
'location_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|max:255|is_unique_department',
|
||||
'location_id' => 'numeric|nullable',
|
||||
'company_id' => 'numeric|nullable',
|
||||
'manager_id' => 'numeric|nullable',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'name',
|
||||
'phone',
|
||||
'fax',
|
||||
'location_id',
|
||||
'company_id',
|
||||
'manager_id',
|
||||
'notes',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'notes', 'phone', 'fax'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
/**
|
||||
* Establishes the department -> company relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Company::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the department -> users relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany(\App\Models\User::class, 'department_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the department -> manager relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function manager()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'manager_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the department -> location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function location()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Location::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on location name
|
||||
*
|
||||
* @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 department_location', 'departments.location_id', '=', 'department_location.id')->orderBy('department_location.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on manager name
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderManager($query, $order)
|
||||
{
|
||||
return $query->leftJoin('users as department_user', 'departments.manager_id', '=', 'department_user.id')->orderBy('department_user.first_name', $order)->orderBy('department_user.last_name', $order);
|
||||
}
|
||||
}
|
187
SNIPE-IT/app/Models/Depreciable.php
Normal file
187
SNIPE-IT/app/Models/Depreciable.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Depreciable extends SnipeModel
|
||||
{
|
||||
/**
|
||||
* Depreciation Relation, and associated helper methods
|
||||
*/
|
||||
|
||||
//REQUIRES a purchase_date field
|
||||
// and a purchase_cost field
|
||||
|
||||
//REQUIRES a get_depreciation method,
|
||||
//which will return the deprecation.
|
||||
//this is needed because assets get
|
||||
//their depreciation from a model,
|
||||
//whereas licenses have deprecations
|
||||
//directly associated with them.
|
||||
|
||||
//assets will override the following
|
||||
//two methods in order to inherit from
|
||||
//their model instead of directly (like
|
||||
//here)
|
||||
|
||||
public function depreciation()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Depreciation::class, 'depreciation_id');
|
||||
}
|
||||
|
||||
public function get_depreciation()
|
||||
{
|
||||
return $this->depreciation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int
|
||||
*/
|
||||
public function getDepreciatedValue()
|
||||
{
|
||||
if (! $this->get_depreciation()) { // will never happen
|
||||
return $this->purchase_cost;
|
||||
}
|
||||
|
||||
if ($this->get_depreciation()->months <= 0) {
|
||||
return $this->purchase_cost;
|
||||
}
|
||||
$depreciation = 0;
|
||||
$setting = Setting::getSettings();
|
||||
switch ($setting->depreciation_method) {
|
||||
case 'half_1':
|
||||
$depreciation = $this->getHalfYearDepreciatedValue(true);
|
||||
break;
|
||||
|
||||
case 'half_2':
|
||||
$depreciation = $this->getHalfYearDepreciatedValue(false);
|
||||
break;
|
||||
|
||||
default:
|
||||
$depreciation = $this->getLinearDepreciatedValue();
|
||||
}
|
||||
|
||||
return $depreciation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|int
|
||||
*/
|
||||
public function getLinearDepreciatedValue() // TODO - for testing it might be nice to have an optional $relative_to param here, defaulted to 'now'
|
||||
{
|
||||
if (($this->get_depreciation()) && ($this->purchase_date)) {
|
||||
$months_passed = ($this->purchase_date->diff(now())->m)+($this->purchase_date->diff(now())->y*12);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($months_passed >= $this->get_depreciation()->months){
|
||||
//if there is a floor use it
|
||||
if(!$this->get_depreciation()->depreciation_min == null) {
|
||||
|
||||
$current_value = $this->get_depreciation()->depreciation_min;
|
||||
|
||||
}else{
|
||||
$current_value = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// The equation here is (Purchase_Cost-Floor_min)*(Months_passed/Months_til_depreciated)
|
||||
$current_value = round(($this->purchase_cost-($this->purchase_cost - ($this->get_depreciation()->depreciation_min)) * ($months_passed / $this->get_depreciation()->months)), 2);
|
||||
|
||||
}
|
||||
|
||||
return $current_value;
|
||||
}
|
||||
|
||||
public function getMonthlyDepreciation(){
|
||||
|
||||
return ($this->purchase_cost-$this->get_depreciation()->depreciation_min)/$this->get_depreciation()->months;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onlyHalfFirstYear Boolean always applied only second half of the first year
|
||||
* @return float|int
|
||||
*/
|
||||
public function getHalfYearDepreciatedValue($onlyHalfFirstYear = false)
|
||||
{
|
||||
// @link http://www.php.net/manual/en/class.dateinterval.php
|
||||
$current_date = $this->getDateTime();
|
||||
$purchase_date = date_create($this->purchase_date);
|
||||
$currentYear = $this->get_fiscal_year($current_date);
|
||||
$purchaseYear = $this->get_fiscal_year($purchase_date);
|
||||
$yearsPast = $currentYear - $purchaseYear;
|
||||
$deprecationYears = ceil($this->get_depreciation()->months / 12);
|
||||
if ($onlyHalfFirstYear) {
|
||||
$yearsPast -= 0.5;
|
||||
} elseif (! $this->is_first_half_of_year($purchase_date)) {
|
||||
$yearsPast -= 0.5;
|
||||
}
|
||||
if (! $this->is_first_half_of_year($current_date)) {
|
||||
$yearsPast += 0.5;
|
||||
}
|
||||
|
||||
if ($yearsPast >= $deprecationYears) {
|
||||
$yearsPast = $deprecationYears;
|
||||
} elseif ($yearsPast < 0) {
|
||||
$yearsPast = 0;
|
||||
}
|
||||
|
||||
return $this->purchase_cost - round($yearsPast / $deprecationYears * $this->purchase_cost, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return int
|
||||
*/
|
||||
protected function get_fiscal_year($date)
|
||||
{
|
||||
$year = intval($date->format('Y'));
|
||||
// also, maybe it'll have to set fiscal year date
|
||||
if ($date->format('nj') === '1231') {
|
||||
return $year;
|
||||
} else {
|
||||
return $year - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DateTime $date
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_first_half_of_year($date)
|
||||
{
|
||||
$date0m0d = intval($date->format('md'));
|
||||
|
||||
return ($date0m0d < 601) || ($date0m0d >= 1231);
|
||||
}
|
||||
|
||||
public function time_until_depreciated()
|
||||
{
|
||||
// @link http://www.php.net/manual/en/class.datetime.php
|
||||
$d1 = new \DateTime();
|
||||
$d2 = $this->depreciated_date();
|
||||
|
||||
// @link http://www.php.net/manual/en/class.dateinterval.php
|
||||
$interval = $d1->diff($d2);
|
||||
if (! $interval->invert) {
|
||||
return $interval;
|
||||
} else {
|
||||
return new \DateInterval('PT0S'); //null interval (zero seconds from now)
|
||||
}
|
||||
}
|
||||
|
||||
public function depreciated_date()
|
||||
{
|
||||
$date = date_create($this->purchase_date);
|
||||
date_add($date, date_interval_create_from_date_string($this->get_depreciation()->months.' months'));
|
||||
|
||||
return $date; //date_format($date, 'Y-m-d'); //don't bake-in format, for internationalization
|
||||
}
|
||||
|
||||
// it's necessary for unit tests
|
||||
protected function getDateTime($time = null)
|
||||
{
|
||||
return new \DateTime($time);
|
||||
}
|
||||
}
|
78
SNIPE-IT/app/Models/Depreciation.php
Normal file
78
SNIPE-IT/app/Models/Depreciation.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class Depreciation extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\DepreciationPresenter::class;
|
||||
use Presentable;
|
||||
// Declare the rules for the form validation
|
||||
protected $rules = [
|
||||
'name' => 'required|min:3|max:255|unique:depreciations,name',
|
||||
'months' => 'required|max:3600|integer|gt:0',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name', 'months'];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'months'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
/**
|
||||
* Establishes the depreciation -> models relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v5.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function models()
|
||||
{
|
||||
return $this->hasMany(\App\Models\AssetModel::class, 'depreciation_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the depreciation -> licenses relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v5.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenses()
|
||||
{
|
||||
return $this->hasMany(\App\Models\License::class, 'depreciation_id');
|
||||
}
|
||||
}
|
84
SNIPE-IT/app/Models/Group.php
Normal file
84
SNIPE-IT/app/Models/Group.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Searchable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class Group extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'permission_groups';
|
||||
|
||||
public $rules = [
|
||||
'name' => 'required|min:2|max:255',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'permissions'
|
||||
];
|
||||
|
||||
/**
|
||||
* 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'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
/**
|
||||
* Establishes the groups -> users relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'users_groups');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that created the group
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode JSON permissions into array
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return array
|
||||
*/
|
||||
public function decodePermissions()
|
||||
{
|
||||
return json_decode($this->permissions, true);
|
||||
}
|
||||
}
|
8
SNIPE-IT/app/Models/ICompanyableChild.php
Normal file
8
SNIPE-IT/app/Models/ICompanyableChild.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
interface ICompanyableChild
|
||||
{
|
||||
public function getCompanyableParents();
|
||||
}
|
14
SNIPE-IT/app/Models/Import.php
Normal file
14
SNIPE-IT/app/Models/Import.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Import extends Model
|
||||
{
|
||||
protected $casts = [
|
||||
'header_row' => 'array',
|
||||
'first_row' => 'array',
|
||||
'field_map' => 'json',
|
||||
];
|
||||
}
|
186
SNIPE-IT/app/Models/Labels/DefaultLabel.php
Normal file
186
SNIPE-IT/app/Models/Labels/DefaultLabel.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Setting;
|
||||
|
||||
class DefaultLabel extends RectangleSheet
|
||||
{
|
||||
private const BARCODE1D_SIZE = 0.15;
|
||||
|
||||
private const BARCODE2D_SIZE = 0.76;
|
||||
private const BARCODE2D_MARGIN = 0.075;
|
||||
|
||||
private const LOGO_SIZE = [0.75, 0.50];
|
||||
private const LOGO_MARGIN = 0.05;
|
||||
|
||||
private const TEXT_MARGIN = 0.04;
|
||||
|
||||
|
||||
private float $textSize;
|
||||
|
||||
private float $labelWidth;
|
||||
private float $labelHeight;
|
||||
|
||||
private float $labelSpacingH;
|
||||
private float $labelSpacingV;
|
||||
|
||||
private float $pageMarginTop;
|
||||
private float $pageMarginBottom;
|
||||
private float $pageMarginLeft;
|
||||
private float $pageMarginRight;
|
||||
|
||||
private float $pageWidth;
|
||||
private float $pageHeight;
|
||||
|
||||
private int $columns;
|
||||
private int $rows;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$this->textSize = Helper::convertUnit($settings->labels_fontsize, 'pt', 'in');
|
||||
|
||||
$this->labelWidth = $settings->labels_width;
|
||||
$this->labelHeight = $settings->labels_height;
|
||||
|
||||
$this->labelSpacingH = $settings->labels_display_sgutter;
|
||||
$this->labelSpacingV = $settings->labels_display_bgutter;
|
||||
|
||||
$this->pageMarginTop = $settings->labels_pmargin_top;
|
||||
$this->pageMarginBottom = $settings->labels_pmargin_bottom;
|
||||
$this->pageMarginLeft = $settings->labels_pmargin_left;
|
||||
$this->pageMarginRight = $settings->labels_pmargin_right;
|
||||
|
||||
$this->pageWidth = $settings->labels_pagewidth;
|
||||
$this->pageHeight = $settings->labels_pageheight;
|
||||
|
||||
$usableWidth = $this->pageWidth - $this->pageMarginLeft - $this->pageMarginRight;
|
||||
$usableHeight = $this->pageHeight - $this->pageMarginTop - $this->pageMarginBottom;
|
||||
|
||||
$this->columns = ($usableWidth + $this->labelSpacingH) / ($this->labelWidth + $this->labelSpacingH);
|
||||
$this->rows = ($usableHeight + $this->labelSpacingV) / ($this->labelHeight + $this->labelSpacingV);
|
||||
|
||||
// Make sure the columns and rows are never zero, since that scenario should never happen
|
||||
if ($this->columns == 0) {
|
||||
$this->columns = 1;
|
||||
}
|
||||
|
||||
if ($this->rows == 0) {
|
||||
$this->rows = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getUnit() { return 'in'; }
|
||||
|
||||
public function getPageWidth() { return $this->pageWidth; }
|
||||
public function getPageHeight() { return $this->pageHeight; }
|
||||
|
||||
public function getPageMarginTop() { return $this->pageMarginTop; }
|
||||
public function getPageMarginBottom() { return $this->pageMarginBottom; }
|
||||
public function getPageMarginLeft() { return $this->pageMarginLeft; }
|
||||
public function getPageMarginRight() { return $this->pageMarginRight; }
|
||||
|
||||
public function getColumns() { return $this->columns; }
|
||||
public function getRows() { return $this->rows; }
|
||||
public function getLabelBorder() { return 0; }
|
||||
|
||||
public function getLabelWidth() { return $this->labelWidth; }
|
||||
public function getLabelHeight() { return $this->labelHeight; }
|
||||
|
||||
public function getLabelMarginTop() { return 0; }
|
||||
public function getLabelMarginBottom() { return 0; }
|
||||
public function getLabelMarginLeft() { return 0; }
|
||||
public function getLabelMarginRight() { return 0; }
|
||||
|
||||
public function getLabelColumnSpacing() { return $this->labelSpacingH; }
|
||||
public function getLabelRowSpacing() { return $this->labelSpacingV; }
|
||||
|
||||
public function getSupportAssetTag() { return false; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 4; }
|
||||
public function getSupportTitle() { return true; }
|
||||
public function getSupportLogo() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
|
||||
$asset = $record->get('asset');
|
||||
$settings = Setting::getSettings();
|
||||
|
||||
$textY = 0;
|
||||
$textX1 = 0;
|
||||
$textX2 = $this->getLabelWidth();
|
||||
|
||||
// 1D Barcode
|
||||
if ($record->get('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
0.05, $this->getLabelHeight() - self::BARCODE1D_SIZE,
|
||||
$this->getLabelWidth() - 0.1, self::BARCODE1D_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
// 2D Barcode
|
||||
if ($record->get('barcode2d')) {
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
0, 0, self::BARCODE2D_SIZE, self::BARCODE2D_SIZE
|
||||
);
|
||||
$textX1 += self::BARCODE2D_SIZE + self::BARCODE2D_MARGIN;
|
||||
}
|
||||
|
||||
// Logo
|
||||
if ($record->get('logo')) {
|
||||
$logoSize = static::writeImage(
|
||||
$pdf, $record->get('logo'),
|
||||
$this->labelWidth - self::LOGO_SIZE[0], 0,
|
||||
self::LOGO_SIZE[0], self::LOGO_SIZE[1],
|
||||
'R', 'T', 300, true, false, 0
|
||||
);
|
||||
$textX2 -= ($logoSize[0] + self::LOGO_MARGIN);
|
||||
}
|
||||
|
||||
$textW = $textX2 - $textX1;
|
||||
|
||||
// Title
|
||||
if ($record->get('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$textX1, 0,
|
||||
'freesans', 'b', $this->textSize, 'L',
|
||||
$textW, $this->textSize,
|
||||
true, 0
|
||||
);
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
}
|
||||
|
||||
// Render the selected fields with their labels
|
||||
$fieldsDone = 0;
|
||||
if ($fieldsDone < $this->getSupportFields()) {
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
|
||||
// Actually write the selected fields and their matching values
|
||||
static::writeText(
|
||||
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
|
||||
$textX1, $textY,
|
||||
'freesans', '', $this->textSize, 'L',
|
||||
$textW, $this->textSize,
|
||||
true, 0
|
||||
);
|
||||
|
||||
$textY += $this->textSize + self::TEXT_MARGIN;
|
||||
$fieldsDone++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
41
SNIPE-IT/app/Models/Labels/Field.php
Normal file
41
SNIPE-IT/app/Models/Labels/Field.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels;
|
||||
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class Field {
|
||||
protected Collection $options;
|
||||
public function getOptions() { return $this->options; }
|
||||
public function setOptions($options) {
|
||||
$tempCollect = collect($options);
|
||||
if (!$tempCollect->contains(fn($o) => !is_subclass_of($o, FieldOption::class))) {
|
||||
$this->options = $options;
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(Asset $asset) { return Field::makeArray($this, $asset); }
|
||||
|
||||
/* Statics */
|
||||
|
||||
public static function makeArray(Field $field, Asset $asset) {
|
||||
return $field->getOptions()
|
||||
// filter out any FieldOptions that are accidentally null
|
||||
->filter()
|
||||
->map(fn($option) => $option->toArray($asset))
|
||||
->filter(fn($result) => $result['value'] != null);
|
||||
}
|
||||
|
||||
public static function makeString(Field $option) {
|
||||
return implode('|', $option->getOptions());
|
||||
}
|
||||
|
||||
public static function fromString(string $theString) {
|
||||
$field = new Field();
|
||||
$field->options = collect(explode('|', $theString))
|
||||
->filter(fn($optionString) => !empty($optionString))
|
||||
->map(fn($optionString) => FieldOption::fromString($optionString));
|
||||
return $field;
|
||||
}
|
||||
}
|
63
SNIPE-IT/app/Models/Labels/FieldOption.php
Normal file
63
SNIPE-IT/app/Models/Labels/FieldOption.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels;
|
||||
|
||||
use App\Models\Asset;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class FieldOption {
|
||||
protected string $label;
|
||||
public function getLabel() { return $this->label; }
|
||||
|
||||
protected string $dataSource;
|
||||
public function getDataSource() { return $this->dataSource; }
|
||||
|
||||
public function getValue(Asset $asset) {
|
||||
$dataPath = collect(explode('.', $this->dataSource));
|
||||
|
||||
// assignedTo directly on the asset is a special case where
|
||||
// we want to avoid returning the property directly
|
||||
// and instead return the entity's presented name.
|
||||
if ($dataPath[0] === 'assignedTo') {
|
||||
if ($asset->relationLoaded('assignedTo')) {
|
||||
// If the "assignedTo" relationship was eager loaded then the way to get the
|
||||
// relationship changes from $asset->assignedTo to $asset->assigned.
|
||||
return $asset->assigned ? $asset->assigned->present()->fullName() : null;
|
||||
}
|
||||
|
||||
return $asset->assignedTo ? $asset->assignedTo->present()->fullName() : null;
|
||||
}
|
||||
|
||||
return $dataPath->reduce(function ($myValue, $path) {
|
||||
try { return $myValue ? $myValue->{$path} : ${$myValue}; }
|
||||
catch (\Exception $e) { return $myValue; }
|
||||
}, $asset);
|
||||
}
|
||||
|
||||
public function toArray(Asset $asset=null) { return FieldOption::makeArray($this, $asset); }
|
||||
public function toString() { return FieldOption::makeString($this); }
|
||||
|
||||
/* Statics */
|
||||
|
||||
public static function makeArray(FieldOption $option, Asset $asset=null) {
|
||||
return [
|
||||
'label' => $option->getLabel(),
|
||||
'dataSource' => $option->getDataSource(),
|
||||
'value' => $asset ? $option->getValue($asset) : null
|
||||
];
|
||||
}
|
||||
|
||||
public static function makeString(FieldOption $option) {
|
||||
return $option->getLabel() . '=' . $option->getDataSource();
|
||||
}
|
||||
|
||||
public static function fromString(string $theString) {
|
||||
$parts = explode('=', $theString);
|
||||
if (count($parts) == 2) {
|
||||
$option = new FieldOption();
|
||||
$option->label = $parts[0];
|
||||
$option->dataSource = $parts[1];
|
||||
return $option;
|
||||
}
|
||||
}
|
||||
}
|
603
SNIPE-IT/app/Models/Labels/Label.php
Normal file
603
SNIPE-IT/app/Models/Labels/Label.php
Normal file
@ -0,0 +1,603 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use TCPDF;
|
||||
use TCPDF_STATIC;
|
||||
use TypeError;
|
||||
|
||||
/**
|
||||
* Model for Labels.
|
||||
*
|
||||
* @version v1.0
|
||||
*/
|
||||
abstract class Label
|
||||
{
|
||||
/**
|
||||
* Returns the unit of measure used
|
||||
* 'pt', 'mm', 'cm', 'in'
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public abstract function getUnit();
|
||||
|
||||
/**
|
||||
* Returns the label's width in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getWidth();
|
||||
|
||||
/**
|
||||
* Returns the label's height in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getHeight();
|
||||
|
||||
/**
|
||||
* Returns the label's top margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getMarginTop();
|
||||
|
||||
/**
|
||||
* Returns the label's bottom margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getMarginBottom();
|
||||
|
||||
/**
|
||||
* Returns the label's left margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getMarginLeft();
|
||||
|
||||
/**
|
||||
* Returns the label's right margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getMarginRight();
|
||||
|
||||
/**
|
||||
* Returns whether the template supports an asset tag.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public abstract function getSupportAssetTag();
|
||||
|
||||
/**
|
||||
* Returns whether the template supports a 1D barcode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public abstract function getSupport1DBarcode();
|
||||
|
||||
/**
|
||||
* Returns whether the template supports a 2D barcode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public abstract function getSupport2DBarcode();
|
||||
|
||||
/**
|
||||
* Returns the number of fields the template supports.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public abstract function getSupportFields();
|
||||
|
||||
/**
|
||||
* Returns whether the template supports a logo.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public abstract function getSupportLogo();
|
||||
|
||||
/**
|
||||
* Returns whether the template supports a title.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public abstract function getSupportTitle();
|
||||
|
||||
/**
|
||||
* Make changes to the PDF properties here. OPTIONAL.
|
||||
*
|
||||
* @param TCPDF $pdf The TCPDF instance
|
||||
*/
|
||||
public abstract function preparePDF(TCPDF $pdf);
|
||||
|
||||
/**
|
||||
* Write single data record as content here.
|
||||
*
|
||||
* @param TCPDF $pdf The TCPDF instance
|
||||
* @param Collection $record A data record
|
||||
*/
|
||||
public abstract function write(TCPDF $pdf, Collection $record);
|
||||
|
||||
/**
|
||||
* Handle the data here. Override for multiple-per-page handling
|
||||
*
|
||||
* @param TCPDF $pdf The TCPDF instance
|
||||
* @param Collection $data The data
|
||||
*/
|
||||
public function writeAll(TCPDF $pdf, Collection $data) {
|
||||
$data->each(function ($record, $index) use ($pdf) {
|
||||
$pdf->AddPage();
|
||||
$this->write($pdf, $record);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the qualified class name relative to the Label class's namespace.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public final function getName() {
|
||||
$refClass = new \ReflectionClass(Label::class);
|
||||
return str_replace($refClass->getNamespaceName() . '\\', '', get_class($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label's orientation as a string.
|
||||
* 'L' = Landscape
|
||||
* 'P' = Portrait
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public final function getOrientation() {
|
||||
return ($this->getWidth() >= $this->getHeight()) ? 'L' : 'P';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label's printable area as an object.
|
||||
*
|
||||
* @return object [ 'x1'=>0.00, 'y1'=>0.00, 'x2'=>0.00, 'y2'=>0.00, 'w'=>0.00, 'h'=>0.00 ]
|
||||
*/
|
||||
public final function getPrintableArea() {
|
||||
return (object)[
|
||||
'x1' => $this->getMarginLeft(),
|
||||
'y1' => $this->getMarginTop(),
|
||||
'x2' => $this->getWidth() - $this->getMarginRight(),
|
||||
'y2' => $this->getHeight() - $this->getMarginBottom(),
|
||||
'w' => $this->getWidth() - $this->getMarginLeft() - $this->getMarginRight(),
|
||||
'h' => $this->getHeight() - $this->getMarginTop() - $this->getMarginBottom(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a text cell.
|
||||
*
|
||||
* @param TCPDF $pdf The TCPDF instance
|
||||
* @param string $text The text to write. Supports 'some **bold** text'.
|
||||
* @param float $x X position of top-left
|
||||
* @param float $y Y position of top-left
|
||||
* @param string $font The font family
|
||||
* @param string $style The font style
|
||||
* @param int $size The font size in getUnit() units
|
||||
* @param string $align Align text in the box. 'L' left, 'R' right, 'C' center.
|
||||
* @param float $width Force text box width. NULL to auto-fit.
|
||||
* @param float $height Force text box height. NULL to auto-fit.
|
||||
* @param bool $squash Squash text if it's too big
|
||||
* @param int $border Thickness of border. Default = 0.
|
||||
* @param int $spacing Letter spacing. Default = 0.
|
||||
*/
|
||||
public final function writeText(TCPDF $pdf, $text, $x, $y, $font=null, $style=null, $size=null, $align='L', $width=null, $height=null, $squash=false, $border=0, $spacing=0) {
|
||||
$prevFamily = $pdf->getFontFamily();
|
||||
$prevStyle = $pdf->getFontStyle();
|
||||
$prevSizePt = $pdf->getFontSizePt();
|
||||
|
||||
$text = !empty($text) ? $text : '';
|
||||
|
||||
$fontFamily = !empty($font) ? $font : $prevFamily;
|
||||
$fontStyle = !empty($style) ? $style : $prevStyle;
|
||||
if ($size) $fontSizePt = Helper::convertUnit($size, $this->getUnit(), 'pt', true);
|
||||
else $fontSizePt = $prevSizePt;
|
||||
|
||||
$pdf->SetFontSpacing($spacing);
|
||||
|
||||
$parts = collect(explode('**', $text))
|
||||
->map(function ($part, $index) use ($pdf, $fontFamily, $fontStyle, $fontSizePt) {
|
||||
$modStyle = ($index % 2 == 1) ? 'B' : $fontStyle;
|
||||
$pdf->setFont($fontFamily, $modStyle, $fontSizePt);
|
||||
return [
|
||||
'text' => $part,
|
||||
'text_width' => $pdf->GetStringWidth($part),
|
||||
'font_family' => $fontFamily,
|
||||
'font_style' => $modStyle,
|
||||
'font_size' => $fontSizePt,
|
||||
];
|
||||
});
|
||||
|
||||
$textWidth = $parts->reduce(function ($carry, $part) { return $carry += $part['text_width']; });
|
||||
$cellWidth = !empty($width) ? $width : $textWidth;
|
||||
|
||||
if ($squash && ($textWidth > 0)) {
|
||||
$scaleFactor = min(1.0, $cellWidth / $textWidth);
|
||||
$parts = $parts->map(function ($part, $index) use ($scaleFactor) {
|
||||
$part['text_width'] = $part['text_width'] * $scaleFactor;
|
||||
return $part;
|
||||
});
|
||||
}
|
||||
$cellHeight = !empty($height) ? $height : Helper::convertUnit($fontSizePt, 'pt', $this->getUnit());
|
||||
|
||||
if ($border) {
|
||||
$prevLineWidth = $pdf->getLineWidth();
|
||||
$pdf->setLineWidth($border);
|
||||
$pdf->Rect($x, $y, $cellWidth, $cellHeight);
|
||||
$pdf->setLineWidth($prevLineWidth);
|
||||
}
|
||||
|
||||
switch($align) {
|
||||
case 'R': $startX = ($x + $cellWidth) - min($cellWidth, $textWidth); break;
|
||||
case 'C': $startX = ($x + ($cellWidth / 2)) - (min($cellWidth, $textWidth) / 2); break;
|
||||
case 'L':
|
||||
default: $startX = $x; break;
|
||||
}
|
||||
|
||||
$parts->reduce(function ($currentX, $part) use ($pdf, $y, $cellHeight) {
|
||||
$pdf->SetXY($currentX, $y);
|
||||
$pdf->setFont($part['font_family'], $part['font_style'], $part['font_size']);
|
||||
$pdf->Cell($part['text_width'], $cellHeight, $part['text'], 0, 0, '', false, '', 1, true);
|
||||
return $currentX += $part['text_width'];
|
||||
}, $startX);
|
||||
|
||||
$pdf->SetFont($prevFamily, $prevStyle, $prevSizePt);
|
||||
$pdf->SetFontSpacing(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an image.
|
||||
*
|
||||
* @param TCPDF $pdf The TCPDF instance
|
||||
* @param string $image The image to write
|
||||
* @param float $x X position of top-left
|
||||
* @param float $y Y position of top-left
|
||||
* @param float $width The container width
|
||||
* @param float $height The container height
|
||||
* @param string $halign Align text in the box. 'L' left, 'R' right, 'C' center. Default 'L'.
|
||||
* @param string $valign Align text in the box. 'T' top, 'B' bottom, 'C' center. Default 'T'.
|
||||
* @param int $dpi Pixels per inch
|
||||
* @param bool $resize Resize to fit container
|
||||
* @param bool $stretch Stretch (vs Scale) to fit container
|
||||
* @param int $border Thickness of border. Default = 0.
|
||||
*
|
||||
* @return array Returns the final calculated size [w,h]
|
||||
*/
|
||||
public final function writeImage(TCPDF $pdf, $image, $x, $y, $width=null, $height=null, $halign='L', $valign='L', $dpi=300, $resize=false, $stretch=false, $border=0) {
|
||||
|
||||
if (empty($image)) return [0,0];
|
||||
|
||||
$imageInfo = getimagesize($image);
|
||||
if (!$imageInfo) return [0,0]; // TODO: SVG or other
|
||||
|
||||
$imageWidthPx = $imageInfo[0];
|
||||
$imageHeightPx = $imageInfo[1];
|
||||
$imageType = image_type_to_extension($imageInfo[2], false);
|
||||
|
||||
$imageRatio = $imageWidthPx / $imageHeightPx;
|
||||
$dpu = Helper::convertUnit($dpi, $this->getUnit(), 'in');
|
||||
$imageWidth = $imageWidthPx / $dpu;
|
||||
$imageHeight = $imageHeightPx / $dpu;
|
||||
|
||||
$outputWidth = $imageWidth;
|
||||
$outputHeight = $imageHeight;
|
||||
|
||||
if ($resize) {
|
||||
// Assign specified parameters
|
||||
$limitWidth = $width;
|
||||
$limitHeight = $height;
|
||||
|
||||
// If not, try calculating from the other dimension
|
||||
$limitWidth = ($limitWidth > 0) ? $limitWidth : ($limitHeight / $imageRatio);
|
||||
$limitHeight = ($limitHeight > 0) ? $limitHeight : ($limitWidth * $imageRatio);
|
||||
|
||||
// If not, just use the image size
|
||||
$limitWidth = ($limitWidth > 0) ? $limitWidth : $imageWidth;
|
||||
$limitHeight = ($limitHeight > 0) ? $limitHeight : $imageHeight;
|
||||
|
||||
$scaleWidth = $limitWidth / $imageWidth;
|
||||
$scaleHeight = $limitHeight / $imageHeight;
|
||||
|
||||
// If non-stretch, make both scales factors equal
|
||||
if (!$stretch) {
|
||||
// Do we need to scale down at all? That's most important.
|
||||
if (($scaleWidth < 1.0) || ($scaleHeight < 1.0)) {
|
||||
// Choose largest scale-down
|
||||
$scaleWidth = min($scaleWidth, $scaleHeight);
|
||||
} else {
|
||||
// Choose smallest scale-up
|
||||
$scaleWidth = min(max($scaleWidth, 1.0), max($scaleHeight, 1.0));
|
||||
}
|
||||
$scaleHeight = $scaleWidth;
|
||||
}
|
||||
|
||||
$outputWidth = $imageWidth * $scaleWidth;
|
||||
$outputHeight = $imageHeight * $scaleHeight;
|
||||
}
|
||||
|
||||
// Container
|
||||
$containerWidth = !empty($width) ? $width : $outputWidth;
|
||||
$containerHeight = !empty($height) ? $height : $outputHeight;
|
||||
|
||||
// Horizontal Position
|
||||
switch ($halign) {
|
||||
case 'R': $originX = ($x + $containerWidth) - $outputWidth; break;
|
||||
case 'C': $originX = ($x + ($containerWidth / 2)) - ($outputWidth / 2); break;
|
||||
case 'L':
|
||||
default: $originX = $x; break;
|
||||
}
|
||||
|
||||
// Vertical Position
|
||||
switch ($valign) {
|
||||
case 'B': $originY = ($y + $containerHeight) - $outputHeight; break;
|
||||
case 'C': $originY = ($y + ($containerHeight / 2)) - ($outputHeight / 2); break;
|
||||
case 'T':
|
||||
default: $originY = $y; break;
|
||||
}
|
||||
|
||||
// Actual Image
|
||||
$pdf->Image($image, $originX, $originY, $outputWidth, $outputHeight, $imageType, '', '', true);
|
||||
|
||||
// Border
|
||||
if ($border) {
|
||||
$prevLineWidth = $pdf->getLineWidth();
|
||||
$pdf->setLineWidth($border);
|
||||
$pdf->Rect($x, $y, $containerWidth, $containerHeight);
|
||||
$pdf->setLineWidth($prevLineWidth);
|
||||
}
|
||||
|
||||
return [ $outputWidth, $outputHeight ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a 1D barcode.
|
||||
*
|
||||
* @param TCPDF $pdf The TCPDF instance
|
||||
* @param string $value The barcode content
|
||||
* @param string $type The barcode type
|
||||
* @param float $x X position of top-left
|
||||
* @param float $y Y position of top-left
|
||||
* @param float $width The container width
|
||||
* @param float $height The container height
|
||||
*/
|
||||
public final function write1DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) {
|
||||
if (empty($value)) return;
|
||||
try {
|
||||
$pdf->write1DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
|
||||
} catch (\Exception|TypeError $e) {
|
||||
\Log::debug('The 1D barcode ' . $value . ' is not compliant with the barcode type '. $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a 2D barcode.
|
||||
*
|
||||
* @param TCPDF $pdf The TCPDF instance
|
||||
* @param string $value The barcode content
|
||||
* @param string $type The barcode type
|
||||
* @param float $x X position of top-left
|
||||
* @param float $y Y position of top-left
|
||||
* @param float $width The container width
|
||||
* @param float $height The container height
|
||||
*/
|
||||
public final function write2DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) {
|
||||
if (empty($value)) return;
|
||||
$pdf->write2DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Checks the template is internally valid
|
||||
*/
|
||||
public final function validate() {
|
||||
$this->validateUnits();
|
||||
$this->validateSize();
|
||||
$this->validateMargins();
|
||||
$this->validateSupport();
|
||||
}
|
||||
|
||||
private function validateUnits() {
|
||||
$validUnits = [ 'pt', 'mm', 'cm', 'in' ];
|
||||
$unit = $this->getUnit();
|
||||
if (!in_array(strtolower($unit), $validUnits)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_value', [
|
||||
'name' => 'getUnit()',
|
||||
'expected' => '[ \''.implode('\', \'', $validUnits).'\' ]',
|
||||
'actual' => '\''.$unit.'\''
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSize() {
|
||||
$width = $this->getWidth();
|
||||
if (!is_numeric($width) || is_string($width)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getWidth()',
|
||||
'expected' => 'float',
|
||||
'actual' => gettype($width)
|
||||
]));
|
||||
}
|
||||
|
||||
$height = $this->getHeight();
|
||||
if (!is_numeric($height) || is_string($height)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getHeight()',
|
||||
'expected' => 'float',
|
||||
'actual' => gettype($height)
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
private function validateMargins() {
|
||||
$marginTop = $this->getMarginTop();
|
||||
if (!is_numeric($marginTop) || is_string($marginTop)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getMarginTop()',
|
||||
'expected' => 'float',
|
||||
'actual' => gettype($marginTop)
|
||||
]));
|
||||
}
|
||||
|
||||
$marginBottom = $this->getMarginBottom();
|
||||
if (!is_numeric($marginBottom) || is_string($marginBottom)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getMarginBottom()',
|
||||
'expected' => 'float',
|
||||
'actual' => gettype($marginBottom)
|
||||
]));
|
||||
}
|
||||
|
||||
$marginLeft = $this->getMarginLeft();
|
||||
if (!is_numeric($marginLeft) || is_string($marginLeft)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getMarginLeft()',
|
||||
'expected' => 'float',
|
||||
'actual' => gettype($marginLeft)
|
||||
]));
|
||||
}
|
||||
|
||||
$marginRight = $this->getMarginRight();
|
||||
if (!is_numeric($marginRight) || is_string($marginRight)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getMarginRight()',
|
||||
'expected' => 'float',
|
||||
'actual' => gettype($marginRight)
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSupport() {
|
||||
$support1D = $this->getSupport1DBarcode();
|
||||
if (!is_bool($support1D)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getSupport1DBarcode()',
|
||||
'expected' => 'boolean',
|
||||
'actual' => gettype($support1D)
|
||||
]));
|
||||
}
|
||||
|
||||
$support2D = $this->getSupport2DBarcode();
|
||||
if (!is_bool($support2D)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getSupport2DBarcode()',
|
||||
'expected' => 'boolean',
|
||||
'actual' => gettype($support2D)
|
||||
]));
|
||||
}
|
||||
|
||||
$supportFields = $this->getSupportFields();
|
||||
if (!is_int($supportFields)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getSupportFields()',
|
||||
'expected' => 'integer',
|
||||
'actual' => gettype($supportFields)
|
||||
]));
|
||||
}
|
||||
|
||||
$supportLogo = $this->getSupportLogo();
|
||||
if (!is_bool($supportLogo)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getSupportLogo()',
|
||||
'expected' => 'boolean',
|
||||
'actual' => gettype($supportLogo)
|
||||
]));
|
||||
}
|
||||
|
||||
$supportTitle = $this->getSupportTitle();
|
||||
if (!is_bool($supportTitle)) {
|
||||
throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
|
||||
'name' => 'getSupportTitle()',
|
||||
'expected' => 'boolean',
|
||||
'actual' => gettype($supportTitle)
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Public Static Functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Find size of a page by its format.
|
||||
*
|
||||
* @param string $format Format name (eg: 'A4', 'LETTER', etc.)
|
||||
* @param string $orientation 'L' for Landscape, 'P' for Portrait ('L' default)
|
||||
* @param string $unit Unit of measure to return in ('mm' default)
|
||||
*
|
||||
* @return object (object)[ 'width' => (float)123.4, 'height' => (float)123.4 ]
|
||||
*/
|
||||
public static function fromFormat($format, $orientation='L', $unit='mm', $round=false) {
|
||||
$size = collect(TCPDF_STATIC::getPageSizeFromFormat(strtoupper($format)))
|
||||
->sort()
|
||||
->map(function ($value) use ($unit) {
|
||||
return Helper::convertUnit($value, 'pt', $unit);
|
||||
})
|
||||
->toArray();
|
||||
$width = ($orientation == 'L') ? $size[1] : $size[0];
|
||||
$height = ($orientation == 'L') ? $size[0] : $size[1];
|
||||
return (object)[
|
||||
'width' => ($round !== false) ? round($width, $round) : $width,
|
||||
'height' => ($round !== false) ? round($height, $round) : $height,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a Label by its path (or just return them all).
|
||||
*
|
||||
* Unlike most Models, these are defined by their existence as non-
|
||||
* abstract classes stored in Models\Labels.
|
||||
*
|
||||
* @param string|Arrayable|array|null $path Label path[s]
|
||||
* @return Collection|Label|null
|
||||
*/
|
||||
public static function find($name=null) {
|
||||
// Find many
|
||||
if (is_array($name) || $name instanceof Arrayable) {
|
||||
$labels = collect($name)
|
||||
->map(function ($thisname) {
|
||||
return static::find($thisname);
|
||||
})
|
||||
->whereNotNull();
|
||||
return ($labels->count() > 0) ? $labels : null;
|
||||
}
|
||||
|
||||
// Find one
|
||||
if ($name !== null) {
|
||||
return static::find()
|
||||
->sole(function ($label) use ($name) {
|
||||
return $label->getName() == $name;
|
||||
});
|
||||
}
|
||||
|
||||
// Find all
|
||||
return collect(File::allFiles(__DIR__))
|
||||
->map(function ($file) {
|
||||
preg_match_all('/\/*(.+?)(?:\/|\.)/', $file->getRelativePathName(), $matches);
|
||||
return __NAMESPACE__ . '\\' . implode('\\', $matches[1]);
|
||||
})
|
||||
->filter(function ($name) {
|
||||
if (!class_exists($name)) return false;
|
||||
$refClass = new \ReflectionClass($name);
|
||||
if ($refClass->isAbstract()) return false;
|
||||
return $refClass->isSubclassOf(Label::class);
|
||||
})
|
||||
->map(function ($name) {
|
||||
return new $name();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
48
SNIPE-IT/app/Models/Labels/RectangleSheet.php
Normal file
48
SNIPE-IT/app/Models/Labels/RectangleSheet.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels;
|
||||
|
||||
abstract class RectangleSheet extends Sheet
|
||||
{
|
||||
/**
|
||||
* Returns the number of columns per sheet
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public abstract function getColumns();
|
||||
|
||||
/**
|
||||
* Returns the number of rows per sheet
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public abstract function getRows();
|
||||
|
||||
/**
|
||||
* Returns the spacing between columns
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public abstract function getLabelColumnSpacing();
|
||||
|
||||
/**
|
||||
* Returns the spacing between rows
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public abstract function getLabelRowSpacing();
|
||||
|
||||
|
||||
public function getLabelsPerPage() { return $this->getColumns() * $this->getRows(); }
|
||||
|
||||
public function getLabelPosition($index) {
|
||||
$printIndex = $index + $this->getLabelIndexOffset();
|
||||
$row = (int)($printIndex / $this->getColumns());
|
||||
$col = $printIndex - ($row * $this->getColumns());
|
||||
$x = $this->getPageMarginLeft() + (($this->getLabelWidth() + $this->getLabelColumnSpacing()) * $col);
|
||||
$y = $this->getPageMarginTop() + (($this->getLabelHeight() + $this->getLabelRowSpacing()) * $row);
|
||||
return [ $x, $y ];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
209
SNIPE-IT/app/Models/Labels/Sheet.php
Normal file
209
SNIPE-IT/app/Models/Labels/Sheet.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels;
|
||||
|
||||
abstract class Sheet extends Label
|
||||
{
|
||||
protected int $indexOffset = 0;
|
||||
|
||||
public function getWidth() { return $this->getPageWidth(); }
|
||||
public function getHeight() { return $this->getPageHeight(); }
|
||||
public function getMarginTop() { return $this->getPageMarginTop(); }
|
||||
public function getMarginBottom() { return $this->getPageMarginBottom(); }
|
||||
public function getMarginLeft() { return $this->getPageMarginLeft(); }
|
||||
public function getMarginRight() { return $this->getPageMarginRight(); }
|
||||
|
||||
/**
|
||||
* Returns the page width in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getPageWidth();
|
||||
|
||||
/**
|
||||
* Returns the page height in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getPageHeight();
|
||||
|
||||
/**
|
||||
* Returns the page top margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getPageMarginTop();
|
||||
|
||||
/**
|
||||
* Returns the page bottom margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getPageMarginBottom();
|
||||
|
||||
/**
|
||||
* Returns the page left margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getPageMarginLeft();
|
||||
|
||||
/**
|
||||
* Returns the page right margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getPageMarginRight();
|
||||
|
||||
/**
|
||||
* Returns the page width in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getLabelWidth();
|
||||
|
||||
/**
|
||||
* Returns each label's height in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getLabelHeight();
|
||||
|
||||
/**
|
||||
* Returns each label's top margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getLabelMarginTop();
|
||||
|
||||
/**
|
||||
* Returns each label's bottom margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getLabelMarginBottom();
|
||||
|
||||
/**
|
||||
* Returns each label's left margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getLabelMarginLeft();
|
||||
|
||||
/**
|
||||
* Returns each label's right margin in getUnit() units
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public abstract function getLabelMarginRight();
|
||||
|
||||
/**
|
||||
* Returns the number of labels each page supports
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public abstract function getLabelsPerPage();
|
||||
|
||||
/**
|
||||
* Returns label position based on its index
|
||||
*
|
||||
* @param int $index
|
||||
*
|
||||
* @return array [x,y]
|
||||
*/
|
||||
public abstract function getLabelPosition(int $index);
|
||||
|
||||
/**
|
||||
* Returns the border to draw around labels
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public abstract function getLabelBorder();
|
||||
|
||||
/**
|
||||
* Handle the data here. Override for multiple-per-page handling
|
||||
*
|
||||
* @param TCPDF $pdf The TCPDF instance
|
||||
* @param Collection $data The data
|
||||
*/
|
||||
public function writeAll($pdf, $data) {
|
||||
$prevPageNumber = -1;
|
||||
|
||||
foreach ($data->toArray() as $recordIndex => $record) {
|
||||
|
||||
$pageNumber = (int)($recordIndex / $this->getLabelsPerPage());
|
||||
if ($pageNumber != $prevPageNumber) {
|
||||
$pdf->AddPage();
|
||||
$prevPageNumber = $pageNumber;
|
||||
}
|
||||
|
||||
$pageIndex = $recordIndex - ($this->getLabelsPerPage() * $pageNumber);
|
||||
$position = $this->getLabelPosition($pageIndex);
|
||||
|
||||
$pdf->StartTemplate();
|
||||
$this->write($pdf, $data->get($recordIndex));
|
||||
$template = $pdf->EndTemplate();
|
||||
|
||||
$pdf->printTemplate($template, $position[0], $position[1]);
|
||||
|
||||
if ($this->getLabelBorder()) {
|
||||
$prevLineWidth = $pdf->GetLineWidth();
|
||||
|
||||
$borderThickness = $this->getLabelBorder();
|
||||
$borderOffset = $borderThickness / 2;
|
||||
$borderX = $position[0]- $borderOffset;
|
||||
$borderY = $position[1] - $borderOffset;
|
||||
$borderW = $this->getLabelWidth() + $borderThickness;
|
||||
$borderH = $this->getLabelHeight() + $borderThickness;
|
||||
|
||||
$pdf->setLineWidth($borderThickness);
|
||||
$pdf->Rect($borderX, $borderY, $borderW, $borderH);
|
||||
$pdf->setLineWidth($prevLineWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns each label's orientation as a string.
|
||||
* 'L' = Landscape
|
||||
* 'P' = Portrait
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public final function getLabelOrientation() {
|
||||
return ($this->getLabelWidth() >= $this->getLabelHeight()) ? 'L' : 'P';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns each label's printable area as an object.
|
||||
*
|
||||
* @return object [ 'x1'=>0.00, 'y1'=>0.00, 'x2'=>0.00, 'y2'=>0.00, 'w'=>0.00, 'h'=>0.00 ]
|
||||
*/
|
||||
public final function getLabelPrintableArea() {
|
||||
return (object)[
|
||||
'x1' => $this->getLabelMarginLeft(),
|
||||
'y1' => $this->getLabelMarginTop(),
|
||||
'x2' => $this->getLabelWidth() - $this->getLabelMarginRight(),
|
||||
'y2' => $this->getLabelHeight() - $this->getLabelMarginBottom(),
|
||||
'w' => $this->getLabelWidth() - $this->getLabelMarginLeft() - $this->getLabelMarginRight(),
|
||||
'h' => $this->getLabelHeight() - $this->getLabelMarginTop() - $this->getLabelMarginBottom(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns label index offset (skip positions)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLabelIndexOffset() { return $this->indexOffset; }
|
||||
|
||||
/**
|
||||
* Sets label index offset (skip positions)
|
||||
*
|
||||
* @param int $offset
|
||||
*
|
||||
*/
|
||||
public function setLabelIndexOffset(int $offset) { $this->indexOffset = $offset; }
|
||||
}
|
||||
|
||||
?>
|
71
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7162.php
Normal file
71
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7162.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Labels\RectangleSheet;
|
||||
|
||||
abstract class L7162 extends RectangleSheet
|
||||
{
|
||||
|
||||
private const PAPER_FORMAT = 'A4';
|
||||
private const PAPER_ORIENTATION = 'P';
|
||||
|
||||
/* Data in pt from Word Template */
|
||||
private const COLUMN1_X = 13.25;
|
||||
private const COLUMN2_X = 301.25;
|
||||
private const ROW1_Y = 37.00;
|
||||
private const ROW2_Y = 133.00;
|
||||
private const LABEL_W = 280.80;
|
||||
private const LABEL_H = 96.00;
|
||||
|
||||
|
||||
private float $pageWidth;
|
||||
private float $pageHeight;
|
||||
private float $pageMarginLeft;
|
||||
private float $pageMarginTop;
|
||||
|
||||
private float $columnSpacing;
|
||||
private float $rowSpacing;
|
||||
|
||||
private float $labelWidth;
|
||||
private float $labelHeight;
|
||||
|
||||
public function __construct() {
|
||||
$paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 0);
|
||||
$this->pageWidth = $paperSize->width;
|
||||
$this->pageHeight = $paperSize->height;
|
||||
|
||||
$this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
|
||||
$this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
|
||||
|
||||
$columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
|
||||
$this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
|
||||
$rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
|
||||
$this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
|
||||
|
||||
$this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
|
||||
$this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
|
||||
}
|
||||
|
||||
public function getPageWidth() { return $this->pageWidth; }
|
||||
public function getPageHeight() { return $this->pageHeight; }
|
||||
|
||||
public function getPageMarginTop() { return $this->pageMarginTop; }
|
||||
public function getPageMarginBottom() { return $this->pageMarginTop; }
|
||||
public function getPageMarginLeft() { return $this->pageMarginLeft; }
|
||||
public function getPageMarginRight() { return $this->pageMarginLeft; }
|
||||
|
||||
public function getColumns() { return 2; }
|
||||
public function getRows() { return 8; }
|
||||
|
||||
public function getLabelColumnSpacing() { return $this->columnSpacing; }
|
||||
public function getLabelRowSpacing() { return $this->rowSpacing; }
|
||||
|
||||
public function getLabelWidth() { return $this->labelWidth; }
|
||||
public function getLabelHeight() { return $this->labelHeight; }
|
||||
|
||||
public function getLabelBorder() { return 0; }
|
||||
}
|
||||
|
||||
?>
|
100
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7162_A.php
Normal file
100
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7162_A.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
|
||||
class L7162_A extends L7162
|
||||
{
|
||||
private const BARCODE_MARGIN = 1.60;
|
||||
private const TAG_SIZE = 4.60;
|
||||
private const TITLE_SIZE = 4.20;
|
||||
private const TITLE_MARGIN = 1.40;
|
||||
private const LABEL_SIZE = 2.20;
|
||||
private const LABEL_MARGIN = - 0.50;
|
||||
private const FIELD_SIZE = 4.60;
|
||||
private const FIELD_MARGIN = 0.30;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
|
||||
public function getLabelMarginTop() { return 1.0; }
|
||||
public function getLabelMarginBottom() { return 1.0; }
|
||||
public function getLabelMarginLeft() { return 1.0; }
|
||||
public function getLabelMarginRight() { return 1.0; }
|
||||
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return false; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 4; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getLabelPrintableArea();
|
||||
|
||||
$usableWidth = $pa->w;
|
||||
$usableHeight = $pa->h;
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
$titleShiftX = 0;
|
||||
|
||||
$barcodeSize = $pa->h - self::TAG_SIZE;
|
||||
|
||||
if ($record->has('barcode2d')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freemono', 'b', self::TAG_SIZE, 'C',
|
||||
$barcodeSize, self::TAG_SIZE, true, 0
|
||||
);
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$pa->x1, $pa->y1,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
} else {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y1,
|
||||
'freemono', 'b', self::TITLE_SIZE, 'L',
|
||||
$barcodeSize, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$titleShiftX = $barcodeSize;
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX + $titleShiftX, $currentY,
|
||||
'freesans', '', self::TITLE_SIZE, 'L',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, $field['label'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::LABEL_SIZE, 'L',
|
||||
$usableWidth, self::LABEL_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
|
||||
|
||||
static::writeText(
|
||||
$pdf, $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freemono', 'B', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
103
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7162_B.php
Normal file
103
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7162_B.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
|
||||
class L7162_B extends L7162
|
||||
{
|
||||
private const BARCODE_SIZE = 6.00;
|
||||
private const BARCODE_MARGIN = 1.40;
|
||||
private const TAG_SIZE = 3.20;
|
||||
private const LOGO_MAX_WIDTH = 25.00;
|
||||
private const LOGO_MARGIN = 2.20;
|
||||
private const TITLE_SIZE = 4.20;
|
||||
private const TITLE_MARGIN = 1.20;
|
||||
private const LABEL_SIZE = 2.20;
|
||||
private const LABEL_MARGIN = - 0.50;
|
||||
private const FIELD_SIZE = 4.20;
|
||||
private const FIELD_MARGIN = 0.30;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
|
||||
public function getLabelMarginTop() { return 1.0; }
|
||||
public function getLabelMarginBottom() { return 0; }
|
||||
public function getLabelMarginLeft() { return 1.0; }
|
||||
public function getLabelMarginRight() { return 1.0; }
|
||||
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return false; }
|
||||
public function getSupportFields() { return 3; }
|
||||
public function getSupportLogo() { return true; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getLabelPrintableArea();
|
||||
|
||||
$usableWidth = $pa->w;
|
||||
$usableHeight = $pa->h;
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$pa->x1, $pa->y2 - self::BARCODE_SIZE,
|
||||
$usableWidth, self::BARCODE_SIZE
|
||||
);
|
||||
$usableHeight -= self::BARCODE_SIZE + self::BARCODE_MARGIN;
|
||||
}
|
||||
|
||||
if ($record->has('logo')) {
|
||||
$logoSize = static::writeImage(
|
||||
$pdf, $record->get('logo'),
|
||||
$pa->x1, $pa->y1,
|
||||
self::LOGO_MAX_WIDTH, $usableHeight,
|
||||
'L', 'T', 300, true, false, 0.1
|
||||
);
|
||||
$currentX += $logoSize[0] + self::LOGO_MARGIN;
|
||||
$usableWidth -= $logoSize[0] + self::LOGO_MARGIN;
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::TITLE_SIZE, 'L',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, $field['label'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::LABEL_SIZE, 'L',
|
||||
$usableWidth, self::LABEL_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
|
||||
|
||||
static::writeText(
|
||||
$pdf, $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freemono', 'B', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$currentX, $pa->y2 - self::BARCODE_SIZE - self::BARCODE_MARGIN - self::TAG_SIZE,
|
||||
'freemono', 'b', self::TAG_SIZE, 'R',
|
||||
$usableWidth, self::TAG_SIZE, true, 0, 0.3
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
71
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7163.php
Normal file
71
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7163.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Labels\RectangleSheet;
|
||||
|
||||
abstract class L7163 extends RectangleSheet
|
||||
{
|
||||
|
||||
private const PAPER_FORMAT = 'A4';
|
||||
private const PAPER_ORIENTATION = 'P';
|
||||
|
||||
/* Data in pt from Word Template */
|
||||
private const COLUMN1_X = 13.25;
|
||||
private const COLUMN2_X = 301.25;
|
||||
private const ROW1_Y = 43.05;
|
||||
private const ROW2_Y = 151.05;
|
||||
private const LABEL_W = 280.80;
|
||||
private const LABEL_H = 108.00;
|
||||
|
||||
|
||||
private float $pageWidth;
|
||||
private float $pageHeight;
|
||||
private float $pageMarginLeft;
|
||||
private float $pageMarginTop;
|
||||
|
||||
private float $columnSpacing;
|
||||
private float $rowSpacing;
|
||||
|
||||
private float $labelWidth;
|
||||
private float $labelHeight;
|
||||
|
||||
public function __construct() {
|
||||
$paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 0);
|
||||
$this->pageWidth = $paperSize->width;
|
||||
$this->pageHeight = $paperSize->height;
|
||||
|
||||
$this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
|
||||
$this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
|
||||
|
||||
$columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
|
||||
$this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
|
||||
$rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
|
||||
$this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
|
||||
|
||||
$this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
|
||||
$this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
|
||||
}
|
||||
|
||||
public function getPageWidth() { return $this->pageWidth; }
|
||||
public function getPageHeight() { return $this->pageHeight; }
|
||||
|
||||
public function getPageMarginTop() { return $this->pageMarginTop; }
|
||||
public function getPageMarginBottom() { return $this->pageMarginTop; }
|
||||
public function getPageMarginLeft() { return $this->pageMarginLeft; }
|
||||
public function getPageMarginRight() { return $this->pageMarginLeft; }
|
||||
|
||||
public function getColumns() { return 2; }
|
||||
public function getRows() { return 7; }
|
||||
|
||||
public function getLabelColumnSpacing() { return $this->columnSpacing; }
|
||||
public function getLabelRowSpacing() { return $this->rowSpacing; }
|
||||
|
||||
public function getLabelWidth() { return $this->labelWidth; }
|
||||
public function getLabelHeight() { return $this->labelHeight; }
|
||||
|
||||
public function getLabelBorder() { return 0; }
|
||||
}
|
||||
|
||||
?>
|
98
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7163_A.php
Normal file
98
SNIPE-IT/app/Models/Labels/Sheets/Avery/L7163_A.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
|
||||
class L7163_A extends L7163
|
||||
{
|
||||
private const BARCODE_MARGIN = 1.80;
|
||||
private const TAG_SIZE = 4.80;
|
||||
private const TITLE_SIZE = 5.00;
|
||||
private const TITLE_MARGIN = 1.80;
|
||||
private const LABEL_SIZE = 2.35;
|
||||
private const LABEL_MARGIN = - 0.30;
|
||||
private const FIELD_SIZE = 4.80;
|
||||
private const FIELD_MARGIN = 0.30;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
|
||||
public function getLabelMarginTop() { return 1.0; }
|
||||
public function getLabelMarginBottom() { return 1.0; }
|
||||
public function getLabelMarginLeft() { return 1.0; }
|
||||
public function getLabelMarginRight() { return 1.0; }
|
||||
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return false; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 4; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getLabelPrintableArea();
|
||||
|
||||
$usableWidth = $pa->w;
|
||||
$usableHeight = $pa->h;
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::TITLE_SIZE, 'C',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
$barcodeSize = $pa->h - self::TITLE_SIZE - self::TITLE_MARGIN - self::TAG_SIZE;
|
||||
|
||||
if ($record->has('barcode2d')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freemono', 'b', self::TAG_SIZE, 'C',
|
||||
$barcodeSize, self::TAG_SIZE, true, 0
|
||||
);
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$pa->x1, $currentY,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
} else {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freemono', 'b', self::TAG_SIZE, 'R',
|
||||
$usableWidth, self::TAG_SIZE, true, 0
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, $field['label'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::LABEL_SIZE, 'L',
|
||||
$usableWidth, self::LABEL_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
|
||||
|
||||
static::writeText(
|
||||
$pdf, $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freemono', 'B', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.5
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
71
SNIPE-IT/app/Models/Labels/Sheets/Avery/_5267.php
Normal file
71
SNIPE-IT/app/Models/Labels/Sheets/Avery/_5267.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Labels\RectangleSheet;
|
||||
|
||||
abstract class _5267 extends RectangleSheet
|
||||
{
|
||||
|
||||
private const PAPER_FORMAT = 'LETTER';
|
||||
private const PAPER_ORIENTATION = 'P';
|
||||
|
||||
/* Data in pt from Word Template */
|
||||
private const COLUMN1_X = 21.60;
|
||||
private const COLUMN2_X = 169.20;
|
||||
private const ROW1_Y = 36.10;
|
||||
private const ROW2_Y = 72.10;
|
||||
private const LABEL_W = 126.00;
|
||||
private const LABEL_H = 36.00;
|
||||
|
||||
|
||||
private float $pageWidth;
|
||||
private float $pageHeight;
|
||||
private float $pageMarginLeft;
|
||||
private float $pageMarginTop;
|
||||
|
||||
private float $columnSpacing;
|
||||
private float $rowSpacing;
|
||||
|
||||
private float $labelWidth;
|
||||
private float $labelHeight;
|
||||
|
||||
public function __construct() {
|
||||
$paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 2);
|
||||
$this->pageWidth = $paperSize->width;
|
||||
$this->pageHeight = $paperSize->height;
|
||||
|
||||
$this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
|
||||
$this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
|
||||
|
||||
$columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
|
||||
$this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
|
||||
$rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
|
||||
$this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
|
||||
|
||||
$this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
|
||||
$this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
|
||||
}
|
||||
|
||||
public function getPageWidth() { return $this->pageWidth; }
|
||||
public function getPageHeight() { return $this->pageHeight; }
|
||||
|
||||
public function getPageMarginTop() { return $this->pageMarginTop; }
|
||||
public function getPageMarginBottom() { return $this->pageMarginTop; }
|
||||
public function getPageMarginLeft() { return $this->pageMarginLeft; }
|
||||
public function getPageMarginRight() { return $this->pageMarginLeft; }
|
||||
|
||||
public function getColumns() { return 4; }
|
||||
public function getRows() { return 20; }
|
||||
|
||||
public function getLabelColumnSpacing() { return $this->columnSpacing; }
|
||||
public function getLabelRowSpacing() { return $this->rowSpacing; }
|
||||
|
||||
public function getLabelWidth() { return $this->labelWidth; }
|
||||
public function getLabelHeight() { return $this->labelHeight; }
|
||||
|
||||
public function getLabelBorder() { return 0; }
|
||||
}
|
||||
|
||||
?>
|
68
SNIPE-IT/app/Models/Labels/Sheets/Avery/_5267_A.php
Normal file
68
SNIPE-IT/app/Models/Labels/Sheets/Avery/_5267_A.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
|
||||
class _5267_A extends _5267
|
||||
{
|
||||
private const BARCODE_SIZE = 0.175;
|
||||
private const BARCODE_MARGIN = 0.000;
|
||||
private const TAG_SIZE = 0.125;
|
||||
private const TITLE_SIZE = 0.140;
|
||||
private const FIELD_SIZE = 0.150;
|
||||
private const FIELD_MARGIN = 0.012;
|
||||
|
||||
public function getUnit() { return 'in'; }
|
||||
|
||||
public function getLabelMarginTop() { return 0.02; }
|
||||
public function getLabelMarginBottom() { return 0.00; }
|
||||
public function getLabelMarginLeft() { return 0.04; }
|
||||
public function getLabelMarginRight() { return 0.04; }
|
||||
|
||||
public function getSupportAssetTag() { return false; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return false; }
|
||||
public function getSupportFields() { return 1; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getLabelPrintableArea();
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$pa->x1, $pa->y2 - self::BARCODE_SIZE,
|
||||
$pa->w, self::BARCODE_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$pa->x1, $pa->y1,
|
||||
'freesans', '', self::TITLE_SIZE, 'L',
|
||||
$pa->w, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
}
|
||||
|
||||
$fieldY = $pa->y2 - self::BARCODE_SIZE - self::BARCODE_MARGIN - self::FIELD_SIZE;
|
||||
if ($record->has('fields')) {
|
||||
if ($record->get('fields')->count() >= 1) {
|
||||
$field = $record->get('fields')->first();
|
||||
static::writeText(
|
||||
$pdf, $field['value'],
|
||||
$pa->x1, $fieldY,
|
||||
'freemono', 'B', self::FIELD_SIZE, 'C',
|
||||
$pa->w, self::FIELD_SIZE, true, 0, 0.01
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
71
SNIPE-IT/app/Models/Labels/Sheets/Avery/_5520.php
Normal file
71
SNIPE-IT/app/Models/Labels/Sheets/Avery/_5520.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Labels\RectangleSheet;
|
||||
|
||||
abstract class _5520 extends RectangleSheet
|
||||
{
|
||||
|
||||
private const PAPER_FORMAT = 'LETTER';
|
||||
private const PAPER_ORIENTATION = 'P';
|
||||
|
||||
/* Data in pt from Word Template */
|
||||
private const COLUMN1_X = 13.55;
|
||||
private const COLUMN2_X = 211.55;
|
||||
private const ROW1_Y = 36.10;
|
||||
private const ROW2_Y = 108.10;
|
||||
private const LABEL_W = 189.35;
|
||||
private const LABEL_H = 72.00;
|
||||
|
||||
|
||||
private float $pageWidth;
|
||||
private float $pageHeight;
|
||||
private float $pageMarginLeft;
|
||||
private float $pageMarginTop;
|
||||
|
||||
private float $columnSpacing;
|
||||
private float $rowSpacing;
|
||||
|
||||
private float $labelWidth;
|
||||
private float $labelHeight;
|
||||
|
||||
public function __construct() {
|
||||
$paperSize = static::fromFormat(self::PAPER_FORMAT, self::PAPER_ORIENTATION, $this->getUnit(), 2);
|
||||
$this->pageWidth = $paperSize->width;
|
||||
$this->pageHeight = $paperSize->height;
|
||||
|
||||
$this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
|
||||
$this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
|
||||
|
||||
$columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
|
||||
$this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
|
||||
$rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
|
||||
$this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
|
||||
|
||||
$this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
|
||||
$this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
|
||||
}
|
||||
|
||||
public function getPageWidth() { return $this->pageWidth; }
|
||||
public function getPageHeight() { return $this->pageHeight; }
|
||||
|
||||
public function getPageMarginTop() { return $this->pageMarginTop; }
|
||||
public function getPageMarginBottom() { return $this->pageMarginTop; }
|
||||
public function getPageMarginLeft() { return $this->pageMarginLeft; }
|
||||
public function getPageMarginRight() { return $this->pageMarginLeft; }
|
||||
|
||||
public function getColumns() { return 3; }
|
||||
public function getRows() { return 10; }
|
||||
|
||||
public function getLabelColumnSpacing() { return $this->columnSpacing; }
|
||||
public function getLabelRowSpacing() { return $this->rowSpacing; }
|
||||
|
||||
public function getLabelWidth() { return $this->labelWidth; }
|
||||
public function getLabelHeight() { return $this->labelHeight; }
|
||||
|
||||
public function getLabelBorder() { return 0; }
|
||||
}
|
||||
|
||||
?>
|
85
SNIPE-IT/app/Models/Labels/Sheets/Avery/_5520_A.php
Normal file
85
SNIPE-IT/app/Models/Labels/Sheets/Avery/_5520_A.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Sheets\Avery;
|
||||
|
||||
|
||||
class _5520_A extends _5520
|
||||
{
|
||||
private const BARCODE_MARGIN = 0.075;
|
||||
private const TAG_SIZE = 0.125;
|
||||
private const TITLE_SIZE = 0.140;
|
||||
private const TITLE_MARGIN = 0.040;
|
||||
private const LABEL_SIZE = 0.090;
|
||||
private const LABEL_MARGIN = -0.015;
|
||||
private const FIELD_SIZE = 0.150;
|
||||
private const FIELD_MARGIN = 0.012;
|
||||
|
||||
public function getUnit() { return 'in'; }
|
||||
|
||||
public function getLabelMarginTop() { return 0.06; }
|
||||
public function getLabelMarginBottom() { return 0.06; }
|
||||
public function getLabelMarginLeft() { return 0.06; }
|
||||
public function getLabelMarginRight() { return 0.06; }
|
||||
|
||||
public function getSupportAssetTag() { return false; }
|
||||
public function getSupport1DBarcode() { return false; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 3; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getLabelPrintableArea();
|
||||
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
$usableWidth = $pa->w;
|
||||
$usableHeight = $pa->h;
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$pa->x1, $pa->y1,
|
||||
'freesans', '', self::TITLE_SIZE, 'C',
|
||||
$pa->w, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
$usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
$barcodeSize = $usableHeight;
|
||||
if ($record->has('barcode2d')) {
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$currentX, $currentY,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, $field['label'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::LABEL_SIZE, 'L',
|
||||
$usableWidth, self::LABEL_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
|
||||
|
||||
static::writeText(
|
||||
$pdf, $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freemono', 'B', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.01
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
19
SNIPE-IT/app/Models/Labels/Tapes/Brother/TZe_12mm.php
Normal file
19
SNIPE-IT/app/Models/Labels/Tapes/Brother/TZe_12mm.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Brother;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Labels\Label;
|
||||
|
||||
abstract class TZe_12mm extends Label
|
||||
{
|
||||
private const HEIGHT = 12.00;
|
||||
private const MARGIN_SIDES = 3.20;
|
||||
private const MARGIN_ENDS = 3.20;
|
||||
|
||||
public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); }
|
||||
public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); }
|
||||
public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());}
|
||||
public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
|
||||
public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
|
||||
}
|
56
SNIPE-IT/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php
Normal file
56
SNIPE-IT/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Brother;
|
||||
|
||||
class TZe_12mm_A extends TZe_12mm
|
||||
{
|
||||
private const BARCODE_SIZE = 3.20;
|
||||
private const BARCODE_MARGIN = 0.30;
|
||||
private const TEXT_SIZE_MOD = 1.00;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
public function getWidth() { return 50.0; }
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return false; }
|
||||
public function getSupportFields() { return 1; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return false; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getPrintableArea();
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$pa->x1, $pa->y1, $pa->w, self::BARCODE_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
$currentY = $pa->y1 + self::BARCODE_SIZE + self::BARCODE_MARGIN;
|
||||
$usableHeight = $pa->h - self::BARCODE_SIZE - self::BARCODE_MARGIN;
|
||||
$fontSize = $usableHeight + self::TEXT_SIZE_MOD;
|
||||
|
||||
$tagWidth = $pa->w / 3;
|
||||
$fieldWidth = $pa->w / 3 * 2;
|
||||
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $currentY,
|
||||
'freemono', 'b', $fontSize, 'L',
|
||||
$tagWidth, $usableHeight, true, 0, 0
|
||||
);
|
||||
|
||||
if ($record->get('fields')->count() >= 1) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('fields')->values()->get(0)['value'],
|
||||
$pa->x1 + ($tagWidth), $currentY,
|
||||
'freemono', 'b', $fontSize, 'R',
|
||||
$fieldWidth, $usableHeight, true, 0, 0
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
19
SNIPE-IT/app/Models/Labels/Tapes/Brother/TZe_24mm.php
Normal file
19
SNIPE-IT/app/Models/Labels/Tapes/Brother/TZe_24mm.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Brother;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Labels\Label;
|
||||
|
||||
abstract class TZe_24mm extends Label
|
||||
{
|
||||
private const HEIGHT = 24.00;
|
||||
private const MARGIN_SIDES = 3.20;
|
||||
private const MARGIN_ENDS = 3.20;
|
||||
|
||||
public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'mm', $this->getUnit()); }
|
||||
public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); }
|
||||
public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());}
|
||||
public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
|
||||
public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
|
||||
}
|
87
SNIPE-IT/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php
Normal file
87
SNIPE-IT/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Brother;
|
||||
|
||||
class TZe_24mm_A extends TZe_24mm
|
||||
{
|
||||
private const BARCODE_MARGIN = 1.40;
|
||||
private const TAG_SIZE = 2.80;
|
||||
private const TITLE_SIZE = 2.80;
|
||||
private const TITLE_MARGIN = 0.50;
|
||||
private const LABEL_SIZE = 2.00;
|
||||
private const LABEL_MARGIN = - 0.35;
|
||||
private const FIELD_SIZE = 3.20;
|
||||
private const FIELD_MARGIN = 0.15;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
public function getWidth() { return 65.0; }
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return false; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 3; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getPrintableArea();
|
||||
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
$usableWidth = $pa->w;
|
||||
|
||||
$barcodeSize = $pa->h - self::TAG_SIZE;
|
||||
|
||||
if ($record->has('barcode2d')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freemono', 'b', self::TAG_SIZE, 'C',
|
||||
$barcodeSize, self::TAG_SIZE, true, 0
|
||||
);
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$currentX, $currentY,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
} else {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freemono', 'b', self::TAG_SIZE, 'R',
|
||||
$usableWidth, self::TAG_SIZE, true, 0
|
||||
);
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::TITLE_SIZE, 'L',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, $field['label'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::LABEL_SIZE, 'L',
|
||||
$usableWidth, self::LABEL_SIZE, true, 0, 0
|
||||
);
|
||||
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
|
||||
|
||||
static::writeText(
|
||||
$pdf, $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freemono', 'B', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
}
|
||||
}
|
19
SNIPE-IT/app/Models/Labels/Tapes/Dymo/LabelWriter.php
Normal file
19
SNIPE-IT/app/Models/Labels/Tapes/Dymo/LabelWriter.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Dymo;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Labels\Label;
|
||||
|
||||
abstract class LabelWriter extends Label
|
||||
{
|
||||
private const HEIGHT = 1.15;
|
||||
private const MARGIN_SIDES = 0.1;
|
||||
private const MARGIN_ENDS = 0.1;
|
||||
|
||||
public function getHeight() { return Helper::convertUnit(self::HEIGHT, 'in', $this->getUnit()); }
|
||||
public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit()); }
|
||||
public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'in', $this->getUnit());}
|
||||
public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); }
|
||||
public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'in', $this->getUnit()); }
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Dymo;
|
||||
|
||||
|
||||
class LabelWriter_1933081 extends LabelWriter
|
||||
{
|
||||
private const BARCODE_MARGIN = 1.80;
|
||||
private const TAG_SIZE = 2.80;
|
||||
private const TITLE_SIZE = 2.80;
|
||||
private const TITLE_MARGIN = 0.50;
|
||||
private const LABEL_SIZE = 2.80;
|
||||
private const LABEL_MARGIN = - 0.35;
|
||||
private const FIELD_SIZE = 2.80;
|
||||
private const FIELD_MARGIN = 0.15;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
public function getWidth() { return 89; }
|
||||
public function getHeight() { return 25; }
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 5; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getPrintableArea();
|
||||
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
$usableWidth = $pa->w;
|
||||
|
||||
$barcodeSize = $pa->h - self::TAG_SIZE;
|
||||
|
||||
if ($record->has('barcode2d')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freesans', 'b', self::TAG_SIZE, 'C',
|
||||
$barcodeSize, self::TAG_SIZE, true, 0
|
||||
);
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$currentX, $currentY,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
} else {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freesans', 'b', self::TAG_SIZE, 'R',
|
||||
$usableWidth, self::TAG_SIZE, true, 0
|
||||
);
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX, $currentY,
|
||||
'freesans', 'b', self::TITLE_SIZE, 'L',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Dymo;
|
||||
|
||||
|
||||
class LabelWriter_2112283 extends LabelWriter
|
||||
{
|
||||
private const BARCODE_MARGIN = 1.80;
|
||||
private const TAG_SIZE = 2.80;
|
||||
private const TITLE_SIZE = 2.80;
|
||||
private const TITLE_MARGIN = 0.50;
|
||||
private const LABEL_SIZE = 2.80;
|
||||
private const LABEL_MARGIN = - 0.35;
|
||||
private const FIELD_SIZE = 2.80;
|
||||
private const FIELD_MARGIN = 0.15;
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
public function getWidth() { return 54; }
|
||||
public function getHeight() { return 25; }
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 5; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getPrintableArea();
|
||||
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
$usableWidth = $pa->w;
|
||||
|
||||
$barcodeSize = $pa->h - self::TAG_SIZE;
|
||||
|
||||
if ($record->has('barcode2d')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freesans', 'b', self::TAG_SIZE, 'C',
|
||||
$barcodeSize, self::TAG_SIZE, true, 0
|
||||
);
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$currentX, $currentY,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
} else {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freesans', 'b', self::TAG_SIZE, 'R',
|
||||
$usableWidth, self::TAG_SIZE, true, 0
|
||||
);
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX, $currentY,
|
||||
'freesans', 'b', self::TITLE_SIZE, 'L',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, (($field['label']) ? $field['label'].' ' : '') . $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
98
SNIPE-IT/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php
Normal file
98
SNIPE-IT/app/Models/Labels/Tapes/Dymo/LabelWriter_30252.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Labels\Tapes\Dymo;
|
||||
|
||||
|
||||
class LabelWriter_30252 extends LabelWriter
|
||||
{
|
||||
private const BARCODE_MARGIN = 1.80;
|
||||
private const TAG_SIZE = 2.80;
|
||||
private const TITLE_SIZE = 2.80;
|
||||
private const TITLE_MARGIN = 0.50;
|
||||
private const LABEL_SIZE = 2.00;
|
||||
private const LABEL_MARGIN = - 0.35;
|
||||
private const FIELD_SIZE = 3.20;
|
||||
private const FIELD_MARGIN = 0.15;
|
||||
|
||||
|
||||
|
||||
public function getUnit() { return 'mm'; }
|
||||
public function getWidth() { return 96.52; }
|
||||
public function getSupportAssetTag() { return true; }
|
||||
public function getSupport1DBarcode() { return true; }
|
||||
public function getSupport2DBarcode() { return true; }
|
||||
public function getSupportFields() { return 3; }
|
||||
public function getSupportLogo() { return false; }
|
||||
public function getSupportTitle() { return true; }
|
||||
|
||||
public function preparePDF($pdf) {}
|
||||
|
||||
public function write($pdf, $record) {
|
||||
$pa = $this->getPrintableArea();
|
||||
|
||||
$currentX = $pa->x1;
|
||||
$currentY = $pa->y1;
|
||||
$usableWidth = $pa->w;
|
||||
|
||||
$barcodeSize = $pa->h - self::TAG_SIZE;
|
||||
|
||||
if ($record->has('barcode2d')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freemono', 'b', self::TAG_SIZE, 'C',
|
||||
$barcodeSize, self::TAG_SIZE, true, 0
|
||||
);
|
||||
static::write2DBarcode(
|
||||
$pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
|
||||
$currentX, $currentY,
|
||||
$barcodeSize, $barcodeSize
|
||||
);
|
||||
$currentX += $barcodeSize + self::BARCODE_MARGIN;
|
||||
$usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
|
||||
} else {
|
||||
static::writeText(
|
||||
$pdf, $record->get('tag'),
|
||||
$pa->x1, $pa->y2 - self::TAG_SIZE,
|
||||
'freemono', 'b', self::TAG_SIZE, 'R',
|
||||
$usableWidth, self::TAG_SIZE, true, 0
|
||||
);
|
||||
}
|
||||
|
||||
if ($record->has('title')) {
|
||||
static::writeText(
|
||||
$pdf, $record->get('title'),
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::TITLE_SIZE, 'L',
|
||||
$usableWidth, self::TITLE_SIZE, true, 0
|
||||
);
|
||||
$currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
|
||||
}
|
||||
|
||||
foreach ($record->get('fields') as $field) {
|
||||
static::writeText(
|
||||
$pdf, $field['label'],
|
||||
$currentX, $currentY,
|
||||
'freesans', '', self::LABEL_SIZE, 'L',
|
||||
$usableWidth, self::LABEL_SIZE, true, 0, 0
|
||||
);
|
||||
$currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
|
||||
|
||||
static::writeText(
|
||||
$pdf, $field['value'],
|
||||
$currentX, $currentY,
|
||||
'freemono', 'B', self::FIELD_SIZE, 'L',
|
||||
$usableWidth, self::FIELD_SIZE, true, 0, 0.3
|
||||
);
|
||||
$currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
|
||||
}
|
||||
|
||||
if ($record->has('barcode1d')) {
|
||||
static::write1DBarcode(
|
||||
$pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
|
||||
$currentX, $barcodeSize + self::BARCODE_MARGIN, $usableWidth - self::TAG_SIZE, self::TAG_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
361
SNIPE-IT/app/Models/Ldap.php
Normal file
361
SNIPE-IT/app/Models/Ldap.php
Normal file
@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Input;
|
||||
use Log;
|
||||
|
||||
/***********************************************
|
||||
* TODOS:
|
||||
*
|
||||
* First off, we should probably make it so that the main LDAP thing we're using is an *instance* of this class,
|
||||
* rather than the static methods we use here. We should probably load up that class with its settings, so we
|
||||
* don't have to explicitly refer to them so often.
|
||||
*
|
||||
* Then, we should probably look at embedding some of the logic we use elsewhere into here - the various methods
|
||||
* should either return a User or false, or other things like that. Don't make the consumers of this class reach
|
||||
* into its guts. While that conflates this model with the User model, I think having the appropriate logic for
|
||||
* turning LDAP people into Users ought to belong here, so it's easier on the consumer of this class.
|
||||
*
|
||||
* We're probably going to have to eventually make it so that Snipe-IT users can define multiple LDAP servers,
|
||||
* and having this as a more instance-oriented class will be a step in the right direction.
|
||||
***********************************************/
|
||||
|
||||
class Ldap extends Model
|
||||
{
|
||||
/**
|
||||
* Makes a connection to LDAP using the settings in Admin > Settings.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @return connection
|
||||
*/
|
||||
public static function connectToLdap()
|
||||
{
|
||||
$ldap_host = Setting::getSettings()->ldap_server;
|
||||
$ldap_version = Setting::getSettings()->ldap_version ?: 3;
|
||||
$ldap_server_cert_ignore = Setting::getSettings()->ldap_server_cert_ignore;
|
||||
$ldap_use_tls = Setting::getSettings()->ldap_tls;
|
||||
|
||||
// If we are ignoring the SSL cert we need to setup the environment variable
|
||||
// before we create the connection
|
||||
if ($ldap_server_cert_ignore == '1') {
|
||||
putenv('LDAPTLS_REQCERT=never');
|
||||
}
|
||||
|
||||
// If the user specifies where CA Certs are, make sure to use them
|
||||
if (env('LDAPTLS_CACERT')) {
|
||||
putenv('LDAPTLS_CACERT='.env('LDAPTLS_CACERT'));
|
||||
}
|
||||
|
||||
$connection = @ldap_connect($ldap_host);
|
||||
|
||||
if (! $connection) {
|
||||
throw new Exception('Could not connect to LDAP server at '.$ldap_host.'. Please check your LDAP server name and port number in your settings.');
|
||||
}
|
||||
|
||||
// Needed for AD
|
||||
ldap_set_option($connection, LDAP_OPT_REFERRALS, 0);
|
||||
ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, $ldap_version);
|
||||
ldap_set_option($connection, LDAP_OPT_NETWORK_TIMEOUT, 20);
|
||||
|
||||
if (Setting::getSettings()->ldap_client_tls_cert && Setting::getSettings()->ldap_client_tls_key) {
|
||||
ldap_set_option(null, LDAP_OPT_X_TLS_CERTFILE, Setting::get_client_side_cert_path());
|
||||
ldap_set_option(null, 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.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param $username
|
||||
* @param $password
|
||||
* @param bool|false $user
|
||||
* @return bool true if the username and/or password provided are valid
|
||||
* false if the username and/or password provided are invalid
|
||||
* array of ldap_attributes if $user is true
|
||||
*/
|
||||
public static function findAndBindUserLdap($username, $password)
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$connection = self::connectToLdap();
|
||||
$ldap_username_field = $settings->ldap_username_field;
|
||||
$baseDn = $settings->ldap_basedn;
|
||||
$userDn = $ldap_username_field.'='.$username.','.$settings->ldap_basedn;
|
||||
|
||||
if ($settings->is_ad == '1') {
|
||||
// Check if they are using the userprincipalname for the username field.
|
||||
// If they are, we can skip building the UPN to authenticate against AD
|
||||
if ($ldap_username_field == 'userprincipalname') {
|
||||
$userDn = $username;
|
||||
} else {
|
||||
// TODO - we no longer respect the "add AD Domain to username" checkbox, but it still exists in settings.
|
||||
// We should probably just eliminate that checkbox to avoid confusion.
|
||||
// We let it sit in the DB, unused, to facilitate people downgrading (if they decide to).
|
||||
// Hopefully, in a later release, we can remove it from the settings.
|
||||
// This logic instead just means that if we're using UPN, we don't append ad_domain, if we aren't, then we do.
|
||||
// Hopefully that should handle all of our use cases, but if not we can backport our old logic.
|
||||
$userDn = ($settings->ad_domain != '') ? $username.'@'.$settings->ad_domain : $username.'@'.$settings->email_domain;
|
||||
}
|
||||
}
|
||||
|
||||
$filterQuery = $settings->ldap_auth_filter_query.$username;
|
||||
$filter = Setting::getSettings()->ldap_filter; //FIXME - this *does* respect the ldap filter, but I believe that AdLdap2 did *not*.
|
||||
$filterQuery = "({$filter}({$filterQuery}))";
|
||||
|
||||
\Log::debug('Filter query: '.$filterQuery);
|
||||
|
||||
if (! $ldapbind = @ldap_bind($connection, $userDn, $password)) {
|
||||
\Log::debug("Status of binding user: $userDn to directory: (directly!) ".($ldapbind ? "success" : "FAILURE"));
|
||||
if (! $ldapbind = self::bindAdminToLdap($connection)) {
|
||||
/*
|
||||
* TODO PLEASE:
|
||||
*
|
||||
* this isn't very clear, so it's important to note: the $ldapbind value is never correctly returned - we never 'return true' from self::bindAdminToLdap() (the function
|
||||
* just "falls off the end" without ever explictly returning 'true')
|
||||
*
|
||||
* but it *does* have an interesting side-effect of checking for the LDAP password being incorrectly encrypted with the wrong APP_KEY, so I'm leaving it in for now.
|
||||
*
|
||||
* If it *did* correctly return 'true' on a succesful bind, it would _probably_ allow users to log in with an incorrect password. Which would be horrible!
|
||||
*
|
||||
* Let's definitely fix this at the next refactor!!!!
|
||||
*
|
||||
*/
|
||||
\Log::debug("Status of binding Admin user: $userDn to directory instead: ".($ldapbind ? "success" : "FAILURE"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $results = ldap_search($connection, $baseDn, $filterQuery)) {
|
||||
throw new Exception('Could not search LDAP: ');
|
||||
}
|
||||
|
||||
if (! $entry = ldap_first_entry($connection, $results)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $user = ldap_get_attributes($connection, $entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_change_key_case($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds/authenticates an admin to LDAP for LDAP searching/syncing.
|
||||
* Here we also return a better error if the app key is donked.
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param bool|false $user
|
||||
* @return bool true if the username and/or password provided are valid
|
||||
* false if the username and/or password provided are invalid
|
||||
*/
|
||||
public static function bindAdminToLdap($connection)
|
||||
{
|
||||
$ldap_username = Setting::getSettings()->ldap_uname;
|
||||
|
||||
if ( $ldap_username ) {
|
||||
// Lets return some nicer messages for users who donked their app key, and disable LDAP
|
||||
try {
|
||||
$ldap_pass = \Crypt::decrypt(Setting::getSettings()->ldap_pword);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('Your app key has changed! Could not decrypt LDAP password using your current app key, so LDAP authentication has been disabled. Login with a local account, update the LDAP password and re-enable it in Admin > Settings.');
|
||||
}
|
||||
|
||||
if (! $ldapbind = @ldap_bind($connection, $ldap_username, $ldap_pass)) {
|
||||
throw new Exception('Could not bind to LDAP: '.ldap_error($connection));
|
||||
}
|
||||
// TODO - this just "falls off the end" but the function states that it should return true or false
|
||||
// unfortunately, one of the use cases for this function is wrong and *needs* for that failure mode to fire
|
||||
// so I don't want to fix this right now.
|
||||
// this method MODIFIES STATE on the passed-in $connection and just returns true or false (or, in this case, undefined)
|
||||
// at the next refactor, this should be appropriately modified to be more consistent.
|
||||
} else {
|
||||
// LDAP should also work with anonymous bind (no dn, no password available)
|
||||
if (! $ldapbind = @ldap_bind($connection )) {
|
||||
throw new Exception('Could not bind to LDAP: '.ldap_error($connection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and map LDAP attributes based on settings
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
*
|
||||
* @param $ldapatttibutes
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function parseAndMapLdapAttributes($ldapattributes)
|
||||
{
|
||||
//Get LDAP attribute config
|
||||
$ldap_result_username = Setting::getSettings()->ldap_username_field;
|
||||
$ldap_result_emp_num = Setting::getSettings()->ldap_emp_num;
|
||||
$ldap_result_last_name = Setting::getSettings()->ldap_lname_field;
|
||||
$ldap_result_first_name = Setting::getSettings()->ldap_fname_field;
|
||||
$ldap_result_email = Setting::getSettings()->ldap_email;
|
||||
$ldap_result_phone = Setting::getSettings()->ldap_phone;
|
||||
$ldap_result_jobtitle = Setting::getSettings()->ldap_jobtitle;
|
||||
$ldap_result_country = Setting::getSettings()->ldap_country;
|
||||
$ldap_result_location = Setting::getSettings()->ldap_location;
|
||||
$ldap_result_dept = Setting::getSettings()->ldap_dept;
|
||||
$ldap_result_manager = Setting::getSettings()->ldap_manager;
|
||||
// Get LDAP user data
|
||||
$item = [];
|
||||
$item['username'] = $ldapattributes[$ldap_result_username][0] ?? '';
|
||||
$item['employee_number'] = $ldapattributes[$ldap_result_emp_num][0] ?? '';
|
||||
$item['lastname'] = $ldapattributes[$ldap_result_last_name][0] ?? '';
|
||||
$item['firstname'] = $ldapattributes[$ldap_result_first_name][0] ?? '';
|
||||
$item['email'] = $ldapattributes[$ldap_result_email][0] ?? '';
|
||||
$item['telephone'] = $ldapattributes[$ldap_result_phone][0] ?? '';
|
||||
$item['jobtitle'] = $ldapattributes[$ldap_result_jobtitle][0] ?? '';
|
||||
$item['country'] = $ldapattributes[$ldap_result_country][0] ?? '';
|
||||
$item['department'] = $ldapattributes[$ldap_result_dept][0] ?? '';
|
||||
$item['manager'] = $ldapattributes[$ldap_result_manager][0] ?? '';
|
||||
$item['location'] = $ldapattributes[$ldap_result_location][0] ?? '';
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create user from LDAP attributes
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param $ldapatttibutes
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function createUserFromLdap($ldapatttibutes, $password)
|
||||
{
|
||||
$item = self::parseAndMapLdapAttributes($ldapatttibutes);
|
||||
|
||||
// Create user from LDAP data
|
||||
if (! empty($item['username'])) {
|
||||
$user = new User;
|
||||
$user->first_name = $item['firstname'];
|
||||
$user->last_name = $item['lastname'];
|
||||
$user->username = $item['username'];
|
||||
$user->email = $item['email'];
|
||||
$user->password = $user->noPassword();
|
||||
|
||||
if (Setting::getSettings()->ldap_pw_sync == '1') {
|
||||
$user->password = bcrypt($password);
|
||||
}
|
||||
|
||||
$user->activated = 1;
|
||||
$user->ldap_import = 1;
|
||||
$user->notes = 'Imported on first login from LDAP';
|
||||
|
||||
if ($user->save()) {
|
||||
return $user;
|
||||
} else {
|
||||
\Log::debug('Could not create user.'.$user->getErrors());
|
||||
throw new Exception('Could not create user: '.$user->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches LDAP
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v3.0]
|
||||
* @param $base_dn
|
||||
* @param $count
|
||||
* @param $filter
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function findLdapUsers($base_dn = null, $count = -1, $filter = null)
|
||||
{
|
||||
$ldapconn = self::connectToLdap();
|
||||
self::bindAdminToLdap($ldapconn);
|
||||
// Default to global base DN if nothing else is provided.
|
||||
if (is_null($base_dn)) {
|
||||
$base_dn = Setting::getSettings()->ldap_basedn;
|
||||
}
|
||||
if($filter === null) {
|
||||
$filter = Setting::getSettings()->ldap_filter;
|
||||
}
|
||||
|
||||
// Set up LDAP pagination for very large databases
|
||||
$page_size = 500;
|
||||
$cookie = '';
|
||||
$result_set = [];
|
||||
$global_count = 0;
|
||||
|
||||
// Perform the search
|
||||
do {
|
||||
|
||||
if ($filter != '' && substr($filter, 0, 1) != '(') { // wrap parens around NON-EMPTY filters that DON'T have them, for back-compatibility with AdLdap2-based filters
|
||||
$filter = "($filter)";
|
||||
} elseif ($filter == '') {
|
||||
$filter = '(cn=*)';
|
||||
}
|
||||
|
||||
// HUGE thanks to this article: https://stackoverflow.com/questions/68275972/how-to-get-paged-ldap-queries-in-php-8-and-read-more-than-1000-entries
|
||||
// which helped me wrap my head around paged results!
|
||||
// if a $count is set and it's smaller than $page_size then use that as the page size
|
||||
$ldap_controls = [];
|
||||
//if($count == -1) { //count is -1 means we have to employ paging to query the entire directory
|
||||
$ldap_controls = [['oid' => LDAP_CONTROL_PAGEDRESULTS, 'iscritical' => false, 'value' => ['size'=> $count == -1||$count>$page_size ? $page_size : $count, 'cookie' => $cookie]]];
|
||||
//}
|
||||
$search_results = ldap_search($ldapconn, $base_dn, $filter, [], 0, /* $page_size */ -1, -1, LDAP_DEREF_NEVER, $ldap_controls); // TODO - I hate the @, and I hate that we get a full page even if we ask for 10 records. Can we use an ldap_control?
|
||||
\Log::debug("LDAP search executed successfully.");
|
||||
if (! $search_results) {
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_search').ldap_error($ldapconn)); // TODO this is never called in any routed context - only from the Artisan command. So this redirect will never work.
|
||||
}
|
||||
|
||||
$errcode = null;
|
||||
$matcheddn = null;
|
||||
$errmsg = null;
|
||||
$referrals = null;
|
||||
$controls = [];
|
||||
ldap_parse_result($ldapconn, $search_results, $errcode , $matcheddn , $errmsg , $referrals, $controls);
|
||||
if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
|
||||
// You need to pass the cookie from the last call to the next one
|
||||
$cookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
|
||||
\Log::debug("okay, at least one more page to go!!!");
|
||||
} else {
|
||||
\Log::debug("okay, we're out of pages - no cookie (or empty cookie) was passed");
|
||||
$cookie = '';
|
||||
}
|
||||
// Empty cookie means last page
|
||||
|
||||
// Get results from page
|
||||
$results = ldap_get_entries($ldapconn, $search_results);
|
||||
if (! $results) {
|
||||
return redirect()->route('users.index')->with('error', trans('admin/users/message.error.ldap_could_not_get_entries').ldap_error($ldapconn)); // TODO this is never called in any routed context - only from the Artisan command. So this redirect will never work.
|
||||
}
|
||||
|
||||
// Add results to result set
|
||||
$global_count += $results['count'];
|
||||
$result_set = array_merge($result_set, $results);
|
||||
\Log::debug("Total count is: $global_count");
|
||||
|
||||
} while ($cookie !== null && $cookie != '' && ($count == -1 || $global_count < $count)); // some servers don't even have pagination, and some will give you more results than you asked for, so just see if you have enough.
|
||||
|
||||
// Clean up after search
|
||||
$result_set['count'] = $global_count; // TODO: I would've figured you could just count the array instead?
|
||||
$results = $result_set;
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
721
SNIPE-IT/app/Models/License.php
Normal file
721
SNIPE-IT/app/Models/License.php
Normal file
@ -0,0 +1,721 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class License extends Depreciable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\LicensePresenter::class;
|
||||
|
||||
use SoftDeletes;
|
||||
use CompanyableTrait;
|
||||
use Loggable, Presentable;
|
||||
protected $injectUniqueIdentifier = true;
|
||||
use ValidatingTrait;
|
||||
|
||||
// We set these as protected dates so that they will be easily accessible via Carbon
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
protected $guarded = 'id';
|
||||
protected $table = 'licenses';
|
||||
|
||||
|
||||
protected $casts = [
|
||||
'purchase_date' => 'date',
|
||||
'expiration_date' => 'date',
|
||||
'termination_date' => 'date',
|
||||
'category_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string|min:3|max:255',
|
||||
'seats' => 'required|min:1|integer',
|
||||
'license_email' => 'email|nullable|max:120',
|
||||
'license_name' => 'string|nullable|max:100',
|
||||
'notes' => 'string|nullable',
|
||||
'category_id' => 'required|exists:categories,id',
|
||||
'company_id' => 'integer|nullable',
|
||||
'purchase_cost'=> 'numeric|nullable|gte:0',
|
||||
'purchase_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'expiration_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'termination_date' => 'date_format:Y-m-d|nullable|max:10',
|
||||
'min_amt' => 'numeric|nullable|gte:0',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'company_id',
|
||||
'depreciation_id',
|
||||
'expiration_date',
|
||||
'license_email',
|
||||
'license_name', //actually licensed_to
|
||||
'maintained',
|
||||
'manufacturer_id',
|
||||
'category_id',
|
||||
'name',
|
||||
'notes',
|
||||
'order_number',
|
||||
'purchase_cost',
|
||||
'purchase_date',
|
||||
'purchase_order',
|
||||
'reassignable',
|
||||
'seats',
|
||||
'serial',
|
||||
'supplier_id',
|
||||
'termination_date',
|
||||
'user_id',
|
||||
'min_amt',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = [
|
||||
'name',
|
||||
'serial',
|
||||
'notes',
|
||||
'order_number',
|
||||
'purchase_order',
|
||||
'purchase_cost',
|
||||
'purchase_date',
|
||||
'expiration_date',
|
||||
];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'manufacturer' => ['name'],
|
||||
'company' => ['name'],
|
||||
'category' => ['name'],
|
||||
'depreciation' => ['name'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Update seat counts when the license is updated
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
*/
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
// We need to listen for created for the initial setup so that we have a license ID.
|
||||
static::created(function ($license) {
|
||||
$newSeatCount = $license->getAttributes()['seats'];
|
||||
|
||||
return static::adjustSeatCount($license, 0, $newSeatCount);
|
||||
});
|
||||
// However, we listen for updating to be able to prevent the edit if we cannot delete enough seats.
|
||||
static::updating(function ($license) {
|
||||
$newSeatCount = $license->getAttributes()['seats'];
|
||||
//$oldSeatCount = isset($license->getOriginal()['seats']) ? $license->getOriginal()['seats'] : 0;
|
||||
/*
|
||||
That previous method *did* mostly work, but if you ever managed to get your $license->seats value out of whack
|
||||
with your actual count of license_seats *records*, you would never manage to get back 'into whack'.
|
||||
The below method actually grabs a count of existing license_seats records, so it will be more accurate.
|
||||
This means that if your license_seats are out of whack, you can change the quantity and hit 'save' and it
|
||||
will manage to 'true up' and make your counts line up correctly.
|
||||
*/
|
||||
$oldSeatCount = $license->license_seats_count;
|
||||
|
||||
return static::adjustSeatCount($license, $oldSeatCount, $newSeatCount);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Balance seat counts
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public static function adjustSeatCount($license, $oldSeats, $newSeats)
|
||||
{
|
||||
// If the seats haven't changed, continue on happily.
|
||||
if ($oldSeats == $newSeats) {
|
||||
return true;
|
||||
}
|
||||
// On Create, we just make one for each of the seats.
|
||||
$change = abs($oldSeats - $newSeats);
|
||||
if ($oldSeats > $newSeats) {
|
||||
$license->load('licenseseats.user');
|
||||
|
||||
// Need to delete seats... lets see if if we have enough.
|
||||
$seatsAvailableForDelete = $license->licenseseats->reject(function ($seat) {
|
||||
return ((bool) $seat->assigned_to) || ((bool) $seat->asset_id);
|
||||
});
|
||||
|
||||
if ($change > $seatsAvailableForDelete->count()) {
|
||||
Session::flash('error', trans('admin/licenses/message.assoc_users'));
|
||||
|
||||
return false;
|
||||
}
|
||||
for ($i = 1; $i <= $change; $i++) {
|
||||
$seatsAvailableForDelete->pop()->delete();
|
||||
}
|
||||
// Log Deletion of seats.
|
||||
$logAction = new Actionlog;
|
||||
$logAction->item_type = self::class;
|
||||
$logAction->item_id = $license->id;
|
||||
$logAction->user_id = Auth::id() ?: 1; // We don't have an id while running the importer from CLI.
|
||||
$logAction->note = "deleted ${change} seats";
|
||||
$logAction->target_id = null;
|
||||
$logAction->logaction('delete seats');
|
||||
|
||||
return true;
|
||||
}
|
||||
// Else we're adding seats.
|
||||
//Create enough seats for the change.
|
||||
$licenseInsert = [];
|
||||
for ($i = $oldSeats; $i < $newSeats; $i++) {
|
||||
$licenseInsert[] = [
|
||||
'user_id' => Auth::id(),
|
||||
'license_id' => $license->id,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now()
|
||||
];
|
||||
}
|
||||
//Chunk and use DB transactions to prevent timeouts.
|
||||
|
||||
collect($licenseInsert)->chunk(1000)->each(function ($chunk) {
|
||||
DB::transaction(function () use ($chunk) {
|
||||
LicenseSeat::insert($chunk->toArray());
|
||||
});
|
||||
});
|
||||
|
||||
// On initial create, we shouldn't log the addition of seats.
|
||||
if ($license->id) {
|
||||
//Log the addition of license to the log.
|
||||
$logAction = new Actionlog();
|
||||
$logAction->item_type = self::class;
|
||||
$logAction->item_id = $license->id;
|
||||
$logAction->user_id = Auth::id() ?: 1; // Importer.
|
||||
$logAction->note = "added ${change} seats";
|
||||
$logAction->target_id = null;
|
||||
$logAction->logaction('add seats');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the attribute for whether or not the license is maintained
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return mixed
|
||||
*/
|
||||
public function setMaintainedAttribute($value)
|
||||
{
|
||||
$this->attributes['maintained'] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reassignable attribute
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return mixed
|
||||
*/
|
||||
public function setReassignableAttribute($value)
|
||||
{
|
||||
$this->attributes['reassignable'] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets expiration date attribute
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return mixed
|
||||
*/
|
||||
public function setExpirationDateAttribute($value)
|
||||
{
|
||||
if ($value == '' || $value == '0000-00-00') {
|
||||
$value = null;
|
||||
} else {
|
||||
$value = (new Carbon($value))->toDateString();
|
||||
}
|
||||
$this->attributes['expiration_date'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets termination date attribute
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return mixed
|
||||
*/
|
||||
public function setTerminationDateAttribute($value)
|
||||
{
|
||||
if ($value == '' || $value == '0000-00-00') {
|
||||
$value = null;
|
||||
} else {
|
||||
$value = (new Carbon($value))->toDateString();
|
||||
}
|
||||
$this->attributes['termination_date'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> company relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Company::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> category relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Category::class, 'category_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> manufacturer relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function manufacturer()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Manufacturer::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user should be emailed on checkin/checkout
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function checkin_email()
|
||||
{
|
||||
if ($this->category) {
|
||||
return $this->category->checkin_email;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user should be required to accept the license
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function requireAcceptance()
|
||||
{
|
||||
if ($this->category) {
|
||||
return $this->category->require_acceptance;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for a category-specific EULA, and if that doesn't exist,
|
||||
* checks for a settings level EULA
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
* @return string | false
|
||||
*/
|
||||
public function getEula()
|
||||
{
|
||||
if ($this->category){
|
||||
if ($this->category->eula_text) {
|
||||
return Helper::parseEscapedMarkedown($this->category->eula_text);
|
||||
} elseif ($this->category->use_default_eula == '1') {
|
||||
return Helper::parseEscapedMarkedown(Setting::getSettings()->default_eula_text);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> assigned user relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assignedusers()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\User::class, 'license_seats', 'license_id', 'assigned_to');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> action logs relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assetlog()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> action logs -> uploads relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', '=', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the license -> admin user relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function adminuser()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of all license seats
|
||||
*
|
||||
* @todo this can probably be refactored at some point. We don't need counting methods.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return int
|
||||
*/
|
||||
public static function assetcount()
|
||||
{
|
||||
return LicenseSeat::whereNull('deleted_at')
|
||||
->count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the number of seats for this asset
|
||||
*
|
||||
* @todo this can also probably be refactored at some point.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function totalSeatsByLicenseID()
|
||||
{
|
||||
return LicenseSeat::where('license_id', '=', $this->id)
|
||||
->whereNull('deleted_at')
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> seat relationship
|
||||
*
|
||||
* We do this to eager load the "count" of seats from the controller.
|
||||
* Otherwise calling "count()" on each model results in n+1 sadness.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenseSeatsRelation()
|
||||
{
|
||||
return $this->hasMany(LicenseSeat::class)->whereNull('deleted_at')->selectRaw('license_id, count(*) as count')->groupBy('license_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the license seat count attribute
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return int
|
||||
*/
|
||||
public function getLicenseSeatsCountAttribute()
|
||||
{
|
||||
if ($this->licenseSeatsRelation->first()) {
|
||||
return $this->licenseSeatsRelation->first()->count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of total available seats across all licenses
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return int
|
||||
*/
|
||||
public static function availassetcount()
|
||||
{
|
||||
return LicenseSeat::whereNull('assigned_to')
|
||||
->whereNull('asset_id')
|
||||
->whereNull('deleted_at')
|
||||
->count();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of total available seats for this license
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function availCount()
|
||||
{
|
||||
return $this->licenseSeatsRelation()
|
||||
->whereNull('asset_id')
|
||||
->whereNull('assigned_to')
|
||||
->whereNull('deleted_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available seats attribute
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAvailSeatsCountAttribute()
|
||||
{
|
||||
if ($this->availCount->first()) {
|
||||
return $this->availCount->first()->count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retuns the number of assigned seats for this asset
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assignedCount()
|
||||
{
|
||||
return $this->licenseSeatsRelation()->where(function ($query) {
|
||||
$query->whereNotNull('assigned_to')
|
||||
->orWhereNotNull('asset_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the assigned seats attribute
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return int
|
||||
*/
|
||||
public function getAssignedSeatsCountAttribute()
|
||||
{
|
||||
if ($this->assignedCount->first()) {
|
||||
return $this->assignedCount->first()->count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of remaining seats
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return int
|
||||
*/
|
||||
public function remaincount()
|
||||
{
|
||||
$total = $this->licenseSeatsCount;
|
||||
$taken = $this->assigned_seats_count;
|
||||
$diff = ($total - $taken);
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of seats for this license
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return int
|
||||
*/
|
||||
public function totalcount()
|
||||
{
|
||||
$avail = $this->availSeatsCount;
|
||||
$taken = $this->assignedcount();
|
||||
$diff = ($avail + $taken);
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> seats relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenseseats()
|
||||
{
|
||||
return $this->hasMany(\App\Models\LicenseSeat::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the license -> supplier relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function supplier()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Supplier::class, 'supplier_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the next available free seat - used by
|
||||
* the API to populate next_seat
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return mixed
|
||||
*/
|
||||
public function freeSeat()
|
||||
{
|
||||
return $this->licenseseats()
|
||||
->whereNull('deleted_at')
|
||||
->where(function ($query) {
|
||||
$query->whereNull('assigned_to')
|
||||
->whereNull('asset_id');
|
||||
})
|
||||
->orderBy('id', 'asc')
|
||||
->first();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the license -> free seats relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function freeSeats()
|
||||
{
|
||||
return $this->hasMany(\App\Models\LicenseSeat::class)->whereNull('assigned_to')->whereNull('deleted_at')->whereNull('asset_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns expiring licenses
|
||||
*
|
||||
* @todo should refactor. I don't like get() in model methods
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public static function getExpiringLicenses($days = 60)
|
||||
{
|
||||
$days = (is_null($days)) ? 60 : $days;
|
||||
|
||||
return self::whereNotNull('expiration_date')
|
||||
->whereNull('deleted_at')
|
||||
->whereRaw(DB::raw('DATE_SUB(`expiration_date`,INTERVAL '.$days.' DAY) <= DATE(NOW()) '))
|
||||
->where('expiration_date', '>', date('Y-m-d'))
|
||||
->orderBy('expiration_date', 'ASC')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on manufacturer
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderManufacturer($query, $order)
|
||||
{
|
||||
return $query->leftJoin('manufacturers', 'licenses.manufacturer_id', '=', 'manufacturers.id')->select('licenses.*')
|
||||
->orderBy('manufacturers.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on supplier
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderSupplier($query, $order)
|
||||
{
|
||||
return $query->leftJoin('suppliers', 'licenses.supplier_id', '=', 'suppliers.id')->select('licenses.*')
|
||||
->orderBy('suppliers.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
|
||||
*/
|
||||
public function scopeOrderCompany($query, $order)
|
||||
{
|
||||
return $query->leftJoin('companies as companies', 'licenses.company_id', '=', 'companies.id')->select('licenses.*')
|
||||
->orderBy('companies.name', $order);
|
||||
}
|
||||
}
|
132
SNIPE-IT/app/Models/LicenseSeat.php
Normal file
132
SNIPE-IT/app/Models/LicenseSeat.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Acceptable;
|
||||
use App\Notifications\CheckinLicenseNotification;
|
||||
use App\Notifications\CheckoutLicenseNotification;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class LicenseSeat extends SnipeModel implements ICompanyableChild
|
||||
{
|
||||
use CompanyableChildTrait;
|
||||
use HasFactory;
|
||||
use Loggable;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $presenter = \App\Presenters\LicenseSeatPresenter::class;
|
||||
use Presentable;
|
||||
|
||||
protected $guarded = 'id';
|
||||
protected $table = 'license_seats';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'assigned_to',
|
||||
'asset_id',
|
||||
];
|
||||
|
||||
use Acceptable;
|
||||
|
||||
public function getCompanyableParents()
|
||||
{
|
||||
return ['asset', 'license'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user should be required to accept the license
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function requireAcceptance()
|
||||
{
|
||||
if ($this->license && $this->license->category) {
|
||||
return $this->license->category->require_acceptance;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getEula()
|
||||
{
|
||||
return $this->license->getEula();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the seat -> license relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function license()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\License::class, 'license_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the seat -> assignee relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'assigned_to')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the seat -> asset relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function asset()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Asset::class, 'asset_id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the assigned seat's location based on user
|
||||
* or asset its assigned to
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return string
|
||||
*/
|
||||
public function location()
|
||||
{
|
||||
if (($this->user) && ($this->user->location)) {
|
||||
return $this->user->location;
|
||||
} elseif (($this->asset) && ($this->asset->location)) {
|
||||
return $this->asset->location;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on department
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderDepartments($query, $order)
|
||||
{
|
||||
return $query->leftJoin('users as license_seat_users', 'license_seats.assigned_to', '=', 'license_seat_users.id')
|
||||
->leftJoin('departments as license_user_dept', 'license_user_dept.id', '=', 'license_seat_users.department_id')
|
||||
->whereNotNull('license_seats.assigned_to')
|
||||
->orderBy('license_user_dept.name', $order);
|
||||
}
|
||||
}
|
315
SNIPE-IT/app/Models/Location.php
Normal file
315
SNIPE-IT/app/Models/Location.php
Normal file
@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Asset;
|
||||
use App\Models\SnipeModel;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Models\User;
|
||||
use App\Presenters\Presentable;
|
||||
use DB;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class Location extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\LocationPresenter::class;
|
||||
use Presentable;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'locations';
|
||||
protected $rules = [
|
||||
'name' => 'required|min:2|max:255|unique_undeleted',
|
||||
'address' => 'max:191|nullable',
|
||||
'address2' => 'max:191|nullable',
|
||||
'city' => 'max:191|nullable',
|
||||
'state' => 'min:2|max:191|nullable',
|
||||
'country' => 'min:2|max:191|nullable',
|
||||
'zip' => 'max:10|nullable',
|
||||
'manager_id' => 'exists:users,id|nullable',
|
||||
'parent_id' => 'non_circular:locations,id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'parent_id' => 'integer',
|
||||
'manager_id' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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 UniqueUndeletedTrait;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'parent_id',
|
||||
'address',
|
||||
'address2',
|
||||
'city',
|
||||
'state',
|
||||
'country',
|
||||
'zip',
|
||||
'phone',
|
||||
'fax',
|
||||
'ldap_ou',
|
||||
'currency',
|
||||
'manager_id',
|
||||
'image',
|
||||
];
|
||||
protected $hidden = ['user_id'];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'address', 'city', 'state', 'zip', 'created_at', 'ldap_ou', 'phone', 'fax'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'parent' => ['name'],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Determine whether or not this location can be deleted.
|
||||
*
|
||||
* This method requires the eager loading of the relationships in order to determine whether
|
||||
* it can be deleted. It's tempting to load those here, but that increases the query load considerably.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets_count === 0)
|
||||
&& ($this->assigned_assets_count === 0)
|
||||
&& ($this->children_count === 0)
|
||||
&& ($this->users_count === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany(\App\Models\User::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find assets with this location as their location_id
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'location_id')
|
||||
->whereHas('assetstatus', function ($query) {
|
||||
$query->where('status_labels.deployable', '=', 1)
|
||||
->orWhere('status_labels.pending', '=', 1)
|
||||
->orWhere('status_labels.archived', '=', 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the asset -> rtd_location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function rtd_assets()
|
||||
{
|
||||
/* This used to have an ...->orHas() clause that referred to
|
||||
assignedAssets, and that was probably incorrect, as well as
|
||||
definitely was setting fire to the query-planner. So don't do that.
|
||||
|
||||
It is arguable that we should have a '...->whereNull('assigned_to')
|
||||
bit in there, but that isn't always correct either (in the case
|
||||
where a user has no location, for example).
|
||||
*/
|
||||
return $this->hasMany(\App\Models\Asset::class, 'rtd_location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the consumable -> location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Consumable::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function components()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Component::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the component -> accessory relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Accessory::class, 'location_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the parent of a location
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id')
|
||||
->with('parent');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the manager of a location
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function manager()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'manager_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find children of a location
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id')
|
||||
->with('children');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the asset -> location assignment relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assignedAssets()
|
||||
{
|
||||
return $this->morphMany(\App\Models\Asset::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed();
|
||||
}
|
||||
|
||||
public function setLdapOuAttribute($ldap_ou)
|
||||
{
|
||||
return $this->attributes['ldap_ou'] = empty($ldap_ou) ? null : $ldap_ou;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on parent
|
||||
*
|
||||
* @param Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public static function indenter($locations_with_children, $parent_id = null, $prefix = '')
|
||||
{
|
||||
$results = [];
|
||||
|
||||
if (! array_key_exists($parent_id, $locations_with_children)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($locations_with_children[$parent_id] as $location) {
|
||||
$location->use_text = $prefix.' '.$location->name;
|
||||
$location->use_image = ($location->image) ? config('app.url').'/uploads/locations/'.$location->image : null;
|
||||
$results[] = $location;
|
||||
//now append the children. (if we have any)
|
||||
if (array_key_exists($location->id, $locations_with_children)) {
|
||||
$results = array_merge($results, self::indenter($locations_with_children, $location->id, $prefix.'--'));
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on parent
|
||||
*
|
||||
* @param Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderParent($query, $order)
|
||||
{
|
||||
// Left join here, or it will only return results with parents
|
||||
return $query->leftJoin('locations as parent_loc', 'locations.parent_id', '=', 'parent_loc.id')->orderBy('parent_loc.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on manager name
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param text $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderManager($query, $order)
|
||||
{
|
||||
return $query->leftJoin('users as location_user', 'locations.manager_id', '=', 'location_user.id')->orderBy('location_user.first_name', $order)->orderBy('location_user.last_name', $order);
|
||||
}
|
||||
}
|
295
SNIPE-IT/app/Models/Loggable.php
Normal file
295
SNIPE-IT/app/Models/Loggable.php
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Notifications\AuditNotification;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
trait Loggable
|
||||
{
|
||||
// an attribute for setting whether or not the item was imported
|
||||
public ?bool $imported = false;
|
||||
|
||||
/**
|
||||
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
|
||||
* @since [v3.4]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function log()
|
||||
{
|
||||
return $this->morphMany(Actionlog::class, 'item');
|
||||
}
|
||||
|
||||
public function setImported(bool $bool): void
|
||||
{
|
||||
$this->imported = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
|
||||
* @since [v3.4]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logCheckout($note, $target, $action_date = null, $originalValues = [])
|
||||
{
|
||||
$log = new Actionlog;
|
||||
$log = $this->determineLogItemType($log);
|
||||
if (Auth::user()) {
|
||||
$log->user_id = Auth::user()->id;
|
||||
}
|
||||
|
||||
if (! isset($target)) {
|
||||
throw new \Exception('All checkout logs require a target.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! isset($target->id)) {
|
||||
throw new \Exception('That target seems invalid (no target ID available).');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$log->target_type = get_class($target);
|
||||
$log->target_id = $target->id;
|
||||
|
||||
// Figure out what the target is
|
||||
if ($log->target_type == Location::class) {
|
||||
$log->location_id = $target->id;
|
||||
} elseif ($log->target_type == Asset::class) {
|
||||
$log->location_id = $target->location_id;
|
||||
} else {
|
||||
$log->location_id = $target->location_id;
|
||||
}
|
||||
|
||||
$log->note = $note;
|
||||
$log->action_date = $action_date;
|
||||
|
||||
if (! $log->action_date) {
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$changed = [];
|
||||
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','expected_checkin']));
|
||||
|
||||
foreach ($originalValues as $key => $value) {
|
||||
if ($key == 'action_date' && $value != $action_date) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');
|
||||
} elseif ($value != $this->getAttributes()[$key]) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = $this->getAttributes()[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($changed)){
|
||||
$log->log_meta = json_encode($changed);
|
||||
}
|
||||
|
||||
$log->logaction('checkout');
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine the log item type
|
||||
*/
|
||||
private function determineLogItemType($log)
|
||||
{
|
||||
// We need to special case licenses because of license_seat vs license. So much for clean polymorphism :
|
||||
if (static::class == LicenseSeat::class) {
|
||||
$log->item_type = License::class;
|
||||
$log->item_id = $this->license_id;
|
||||
} else {
|
||||
$log->item_type = static::class;
|
||||
$log->item_id = $this->id;
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
|
||||
* @since [v3.4]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logCheckin($target, $note, $action_date = null, $originalValues = [])
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$log = new Actionlog;
|
||||
|
||||
if($target != null){
|
||||
$log->target_type = get_class($target);
|
||||
$log->target_id = $target->id;
|
||||
|
||||
}
|
||||
|
||||
if (static::class == LicenseSeat::class) {
|
||||
$log->item_type = License::class;
|
||||
$log->item_id = $this->license_id;
|
||||
} else {
|
||||
$log->item_type = static::class;
|
||||
$log->item_id = $this->id;
|
||||
|
||||
if (static::class == Asset::class) {
|
||||
if ($asset = Asset::find($log->item_id)) {
|
||||
$asset->increment('checkin_counter', 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log->location_id = null;
|
||||
$log->note = $note;
|
||||
$log->action_date = $action_date;
|
||||
|
||||
if (! $log->action_date) {
|
||||
$log->action_date = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
if (Auth::user()) {
|
||||
$log->user_id = Auth::user()->id;
|
||||
}
|
||||
|
||||
$changed = [];
|
||||
$originalValues = array_intersect_key($originalValues, array_flip(['action_date','name','status_id','location_id','rtd_location_id','expected_checkin']));
|
||||
|
||||
foreach ($originalValues as $key => $value) {
|
||||
if ($key == 'action_date' && $value != $action_date) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = is_string($action_date) ? $action_date : $action_date->format('Y-m-d H:i:s');
|
||||
} elseif ($value != $this->getAttributes()[$key]) {
|
||||
$changed[$key]['old'] = $value;
|
||||
$changed[$key]['new'] = $this->getAttributes()[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($changed)){
|
||||
$log->log_meta = json_encode($changed);
|
||||
}
|
||||
|
||||
$log->logaction('checkin from');
|
||||
|
||||
// $params = [
|
||||
// 'target' => $target,
|
||||
// 'item' => $log->item,
|
||||
// 'admin' => $log->user,
|
||||
// 'note' => $note,
|
||||
// 'target_type' => $log->target_type,
|
||||
// 'settings' => $settings,
|
||||
// ];
|
||||
//
|
||||
//
|
||||
// $checkinClass = null;
|
||||
//
|
||||
// if (method_exists($target, 'notify')) {
|
||||
// try {
|
||||
// $target->notify(new static::$checkinClass($params));
|
||||
// } catch (\Exception $e) {
|
||||
// \Log::debug($e);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// // Send to the admin, if settings dictate
|
||||
// $recipient = new \App\Models\Recipients\AdminRecipient();
|
||||
//
|
||||
// if (($settings->admin_cc_email!='') && (static::$checkinClass!='')) {
|
||||
// try {
|
||||
// $recipient->notify(new static::$checkinClass($params));
|
||||
// } catch (\Exception $e) {
|
||||
// \Log::debug($e);
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logAudit($note, $location_id, $filename = null)
|
||||
{
|
||||
$log = new Actionlog;
|
||||
$location = Location::find($location_id);
|
||||
if (static::class == LicenseSeat::class) {
|
||||
$log->item_type = License::class;
|
||||
$log->item_id = $this->license_id;
|
||||
} else {
|
||||
$log->item_type = static::class;
|
||||
$log->item_id = $this->id;
|
||||
}
|
||||
$log->location_id = ($location_id) ? $location_id : null;
|
||||
$log->note = $note;
|
||||
$log->user_id = Auth::user()->id;
|
||||
$log->filename = $filename;
|
||||
$log->logaction('audit');
|
||||
|
||||
$params = [
|
||||
'item' => $log->item,
|
||||
'filename' => $log->filename,
|
||||
'admin' => $log->admin,
|
||||
'location' => ($location) ? $location->name : '',
|
||||
'note' => $note,
|
||||
];
|
||||
Setting::getSettings()->notify(new AuditNotification($params));
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
|
||||
* @since [v3.5]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logCreate($note = null)
|
||||
{
|
||||
$user_id = -1;
|
||||
if (Auth::user()) {
|
||||
$user_id = Auth::user()->id;
|
||||
}
|
||||
$log = new Actionlog;
|
||||
if (static::class == LicenseSeat::class) {
|
||||
$log->item_type = License::class;
|
||||
$log->item_id = $this->license_id;
|
||||
} else {
|
||||
$log->item_type = static::class;
|
||||
$log->item_id = $this->id;
|
||||
}
|
||||
$log->location_id = null;
|
||||
$log->note = $note;
|
||||
$log->user_id = $user_id;
|
||||
$log->logaction('create');
|
||||
$log->save();
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Daniel Meltzer <dmeltzer.devel@gmail.com>
|
||||
* @since [v3.4]
|
||||
* @return \App\Models\Actionlog
|
||||
*/
|
||||
public function logUpload($filename, $note)
|
||||
{
|
||||
$log = new Actionlog;
|
||||
if (static::class == LicenseSeat::class) {
|
||||
$log->item_type = License::class;
|
||||
$log->item_id = $this->license_id;
|
||||
} else {
|
||||
$log->item_type = static::class;
|
||||
$log->item_id = $this->id;
|
||||
}
|
||||
$log->user_id = Auth::user()->id;
|
||||
$log->note = $note;
|
||||
$log->target_id = null;
|
||||
$log->created_at = date('Y-m-d H:i:s');
|
||||
$log->filename = $filename;
|
||||
$log->logaction('uploaded');
|
||||
|
||||
return $log;
|
||||
}
|
||||
}
|
108
SNIPE-IT/app/Models/Manufacturer.php
Normal file
108
SNIPE-IT/app/Models/Manufacturer.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class Manufacturer extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\ManufacturerPresenter::class;
|
||||
use Presentable;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'manufacturers';
|
||||
|
||||
// Declare the rules for the form validation
|
||||
protected $rules = [
|
||||
'name' => 'required|min:2|max:255|unique:manufacturers,name,NULL,id,deleted_at,NULL',
|
||||
'url' => 'url|nullable',
|
||||
'support_email' => 'email|nullable',
|
||||
'support_url' => 'nullable|url',
|
||||
'warranty_lookup_url' => 'nullable|starts_with:http://,https://,afp://,facetime://,file://,irc://'
|
||||
];
|
||||
|
||||
protected $hidden = ['user_id'];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'image',
|
||||
'support_email',
|
||||
'support_phone',
|
||||
'support_url',
|
||||
'url',
|
||||
'warranty_lookup_url',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'created_at'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
public function isDeletable()
|
||||
{
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets()->count() === 0)
|
||||
&& ($this->licenses()->count() === 0)
|
||||
&& ($this->consumables()->count() === 0)
|
||||
&& ($this->accessories()->count() === 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasManyThrough(\App\Models\Asset::class, \App\Models\AssetModel::class, 'manufacturer_id', 'model_id');
|
||||
}
|
||||
|
||||
public function models()
|
||||
{
|
||||
return $this->hasMany(\App\Models\AssetModel::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
public function licenses()
|
||||
{
|
||||
return $this->hasMany(\App\Models\License::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
public function accessories()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Accessory::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
public function consumables()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Consumable::class, 'manufacturer_id');
|
||||
}
|
||||
}
|
182
SNIPE-IT/app/Models/PredefinedKit.php
Normal file
182
SNIPE-IT/app/Models/PredefinedKit.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
/**
|
||||
* Model for predefined kits.
|
||||
*
|
||||
* @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>]
|
||||
* @version v1.0
|
||||
*/
|
||||
class PredefinedKit extends SnipeModel
|
||||
{
|
||||
protected $presenter = \App\Presenters\PredefinedKitPresenter::class;
|
||||
use Presentable;
|
||||
protected $table = 'kits';
|
||||
|
||||
/**
|
||||
* Category validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'name' => 'required|min:1|max:255|unique',
|
||||
];
|
||||
|
||||
use ValidatingTrait;
|
||||
|
||||
public $modelRules = [
|
||||
'model_id' => 'required|exists:models,id',
|
||||
'quantity' => 'required|integer|min:1',
|
||||
'pivot_id' => 'integer|exists:kits_models,id',
|
||||
];
|
||||
|
||||
/**
|
||||
* this rules use in edit an attached asset model form
|
||||
* see PredefinedKit::_makeRuleHelper function for details
|
||||
* @param int $model_id
|
||||
* @param bool $new = true if append a new element to kit
|
||||
*/
|
||||
public function makeModelRules($model_id, $new = false)
|
||||
{
|
||||
return $this->_makeRuleHelper('models', 'kits_models', 'model_id', $model_id, $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* this rules use in edit an attached license form
|
||||
* see PredefinedKit::_makeRuleHelper function for details
|
||||
* @param int $license_id
|
||||
* @param bool $new = true if append a new element to kit
|
||||
*/
|
||||
public function makeLicenseRules($license_id, $new = false)
|
||||
{
|
||||
return $this->_makeRuleHelper('licenses', 'kits_licenses', 'license_id', $license_id, $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* this rules use in edit an attached accessory form
|
||||
* see PredefinedKit::_makeRuleHelper function for details
|
||||
* @param int $accessoriy_id
|
||||
* @param bool $new = true if append a new element to kit
|
||||
*/
|
||||
public function makeAccessoryRules($accessory_id, $new = false)
|
||||
{
|
||||
return $this->_makeRuleHelper('accessories', 'kits_accessories', 'accessory_id', $accessory_id, $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* this rules use in edit an attached consumable form
|
||||
* see PredefinedKit::_makeRuleHelper function for details
|
||||
* @param int $consumable_id
|
||||
* @param bool $new = true if append a new element to kit
|
||||
*/
|
||||
public function makeConsumableRules($consumable_id, $new = false)
|
||||
{
|
||||
return $this->_makeRuleHelper('consumables', 'kits_consumables', 'consumable_id', $consumable_id, $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make rules for validation kit attached elements via Illuminate\Validation\Rule
|
||||
* checks:
|
||||
* uniqueness of the record in table for this kit
|
||||
* existence of record in table
|
||||
* and simple types check
|
||||
* @param string $table element table name
|
||||
* @param string $pivot_table kit+element table name
|
||||
* @param string $pivot_elem_key element key name inside pivot table
|
||||
* @param int $element_id
|
||||
* @param bool $new = true if append a new element to kit
|
||||
* @return array
|
||||
*/
|
||||
protected function _makeRuleHelper($table, $pivot_table, $pivot_elem_key, $element_id, $new)
|
||||
{
|
||||
$rule = [
|
||||
$pivot_elem_key => [
|
||||
'required',
|
||||
"exists:$table,id",
|
||||
Rule::unique($pivot_table)->whereNot($pivot_elem_key, $element_id)->where('kit_id', $this->id),
|
||||
],
|
||||
'quantity' => 'required|integer|min:1',
|
||||
];
|
||||
if (! $new) {
|
||||
$rule['pivot_id'] = "integer|exists:$pivot_table,id";
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the kit.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the kit.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
/**
|
||||
* Establishes the kits -> models relationship
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function models()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\AssetModel::class, 'kits_models', 'kit_id', 'model_id')->withPivot('id', 'quantity');
|
||||
}
|
||||
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasManyThrough(\App\Models\Asset::class, \App\Models\AssetModel::class, 'country_id', 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the kits -> licenses relationship
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenses()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\License::class, 'kits_licenses', 'kit_id', 'license_id')->withPivot('id', 'quantity');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the kits -> licenses relationship
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Consumable::class, 'kits_consumables', 'kit_id', 'consumable_id')->withPivot('id', 'quantity');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the kits -> licenses relationship
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Accessory::class, 'kits_accessories', 'kit_id', 'accessory_id')->withPivot('id', 'quantity');
|
||||
}
|
||||
|
||||
/**
|
||||
* -----------------------------------------------
|
||||
* BEGIN QUERY SCOPES
|
||||
* -----------------------------------------------
|
||||
**/
|
||||
}
|
14
SNIPE-IT/app/Models/Recipients/AdminRecipient.php
Normal file
14
SNIPE-IT/app/Models/Recipients/AdminRecipient.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Recipients;
|
||||
|
||||
use App\Models\Setting;
|
||||
|
||||
class AdminRecipient extends Recipient
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$settings = Setting::getSettings();
|
||||
$this->email = trim($settings->admin_cc_email);
|
||||
}
|
||||
}
|
11
SNIPE-IT/app/Models/Recipients/AlertRecipient.php
Normal file
11
SNIPE-IT/app/Models/Recipients/AlertRecipient.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Recipients;
|
||||
|
||||
class AlertRecipient extends Recipient
|
||||
{
|
||||
public function __construct(string $email)
|
||||
{
|
||||
$this->email = trim($email);
|
||||
}
|
||||
}
|
12
SNIPE-IT/app/Models/Recipients/Recipient.php
Normal file
12
SNIPE-IT/app/Models/Recipients/Recipient.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Recipients;
|
||||
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
abstract class Recipient
|
||||
{
|
||||
use Notifiable;
|
||||
|
||||
protected $email;
|
||||
}
|
49
SNIPE-IT/app/Models/Requestable.php
Normal file
49
SNIPE-IT/app/Models/Requestable.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
// $asset->requests
|
||||
// $asset->isRequestedBy($user)
|
||||
// $asset->whereRequestedBy($user)
|
||||
trait Requestable
|
||||
{
|
||||
public function requests()
|
||||
{
|
||||
return $this->morphMany(CheckoutRequest::class, 'requestable');
|
||||
}
|
||||
|
||||
public function isRequestedBy(User $user)
|
||||
{
|
||||
return $this->requests->where('canceled_at', null)->where('user_id', $user->id)->first();
|
||||
}
|
||||
|
||||
public function scopeRequestedBy($query, User $user)
|
||||
{
|
||||
return $query->whereHas('requests', function ($query) use ($user) {
|
||||
$query->where('user_id', $user->id);
|
||||
});
|
||||
}
|
||||
|
||||
public function request($qty = 1)
|
||||
{
|
||||
$this->requests()->save(
|
||||
new CheckoutRequest(['user_id' => Auth::id(), 'qty' => $qty])
|
||||
);
|
||||
}
|
||||
|
||||
public function deleteRequest()
|
||||
{
|
||||
$this->requests()->where('user_id', Auth::id())->delete();
|
||||
}
|
||||
|
||||
public function cancelRequest($user_id = null)
|
||||
{
|
||||
if (!$user_id){
|
||||
$user_id = Auth::id();
|
||||
}
|
||||
|
||||
$this->requests()->where('user_id', $user_id)->update(['canceled_at' => \Carbon\Carbon::now()]);
|
||||
}
|
||||
}
|
15
SNIPE-IT/app/Models/SCIMUser.php
Normal file
15
SNIPE-IT/app/Models/SCIMUser.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class SCIMUser extends User
|
||||
{
|
||||
protected $table = 'users';
|
||||
|
||||
protected $throwValidationExceptions = true; // we want model-level validation to fully THROW, not just return false
|
||||
|
||||
public function __construct(array $attributes = []) {
|
||||
$attributes['password'] = $this->noPassword();
|
||||
parent::__construct($attributes);
|
||||
}
|
||||
}
|
15
SNIPE-IT/app/Models/SamlNonce.php
Normal file
15
SNIPE-IT/app/Models/SamlNonce.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SamlNonce extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['nonce','not_on_or_after'];
|
||||
|
||||
public $timestamps = false;
|
||||
}
|
414
SNIPE-IT/app/Models/Setting.php
Normal file
414
SNIPE-IT/app/Models/Setting.php
Normal file
@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Helpers\Helper;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
|
||||
/**
|
||||
* Settings model.
|
||||
*/
|
||||
class Setting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use Notifiable, ValidatingTrait;
|
||||
|
||||
/**
|
||||
* The cache property so that multiple invocations of this will only load the Settings record from disk only once
|
||||
* @var self
|
||||
*/
|
||||
public static ?self $_cache = null;
|
||||
|
||||
/**
|
||||
* The setup check cache key name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const SETUP_CHECK_KEY = 'snipeit_setup_check';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Model rules.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rules = [
|
||||
'brand' => 'required|min:1|numeric',
|
||||
'qr_text' => 'max:31|nullable',
|
||||
'alert_email' => 'email_array|nullable',
|
||||
'admin_cc_email' => 'email|nullable',
|
||||
'default_currency' => 'required',
|
||||
'locale' => 'required',
|
||||
'labels_per_page' => 'numeric',
|
||||
'labels_width' => 'numeric',
|
||||
'labels_height' => 'numeric',
|
||||
'labels_pmargin_left' => 'numeric|nullable',
|
||||
'labels_pmargin_right' => 'numeric|nullable',
|
||||
'labels_pmargin_top' => 'numeric|nullable',
|
||||
'labels_pmargin_bottom' => 'numeric|nullable',
|
||||
'labels_display_bgutter' => 'numeric|nullable',
|
||||
'labels_display_sgutter' => 'numeric|nullable',
|
||||
'labels_fontsize' => 'numeric|min:5',
|
||||
'labels_pagewidth' => 'numeric|nullable',
|
||||
'labels_pageheight' => 'numeric|nullable',
|
||||
'login_remote_user_enabled' => 'numeric|nullable',
|
||||
'login_common_disabled' => 'numeric|nullable',
|
||||
'login_remote_user_custom_logout_url' => 'string|nullable',
|
||||
'login_remote_user_header_name' => 'string|nullable',
|
||||
'thumbnail_max_h' => 'numeric|max:500|min:25',
|
||||
'pwd_secure_min' => 'numeric|required|min:8',
|
||||
'audit_warning_days' => 'numeric|nullable',
|
||||
'audit_interval' => 'numeric|nullable',
|
||||
'custom_forgot_pass_url' => 'url|nullable',
|
||||
'privacy_policy_link' => 'nullable|url',
|
||||
'google_client_id' => 'nullable|ends_with:apps.googleusercontent.com'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'site_name',
|
||||
'email_domain',
|
||||
'email_format',
|
||||
'username_format',
|
||||
'webhook_endpoint',
|
||||
'webhook_channel',
|
||||
'webhook_botname',
|
||||
'google_login',
|
||||
'google_client_id',
|
||||
'google_client_secret',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'label2_asset_logo' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the app settings.
|
||||
* Cache is expired on Setting model saved in EventServiceProvider.
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return \App\Models\Setting|null
|
||||
*/
|
||||
public static function getSettings(): ?self
|
||||
{
|
||||
if (!self::$_cache) {
|
||||
// Need for setup as no tables exist
|
||||
try {
|
||||
self::$_cache = self::first();
|
||||
} catch (\Throwable $th) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return self::$_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if setup process is complete.
|
||||
* Cache is expired on Setting model saved in EventServiceProvider.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function setupCompleted(): bool
|
||||
{
|
||||
try {
|
||||
$usercount = User::withTrashed()->count();
|
||||
$settingsCount = self::count();
|
||||
|
||||
return $usercount > 0 && $settingsCount > 0;
|
||||
} catch (\Throwable $th) {
|
||||
\Log::debug('User table and settings table DO NOT exist or DO NOT have records');
|
||||
// Catch the error if the tables dont exit
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Laravel version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function lar_ver(): string
|
||||
{
|
||||
$app = App::getFacadeApplication();
|
||||
return $app::VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default EULA text.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getDefaultEula(): ?string
|
||||
{
|
||||
if (self::getSettings()->default_eula_text) {
|
||||
return Helper::parseEscapedMarkedown(self::getSettings()->default_eula_text);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether to show in model dropdowns.
|
||||
*
|
||||
* @param string $element
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function modellistCheckedValue($element): bool
|
||||
{
|
||||
$settings = self::getSettings();
|
||||
// If the value is blank for some reason
|
||||
if ($settings->modellist_displays == '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = explode(',', $settings->modellist_displays);
|
||||
|
||||
foreach ($values as $value) {
|
||||
if ($value == $element) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the custom CSS, and then un-escapes the greater-than symbol
|
||||
* so it can work with direct descendant characters for bootstrap
|
||||
* menu overrides like:.
|
||||
*
|
||||
* .skin-blue .sidebar-menu>li.active>a, .skin-blue .sidebar-menu>li:hover>a
|
||||
*
|
||||
* Important: Do not remove the e() escaping here, as we output raw in the blade.
|
||||
*
|
||||
* @return string escaped CSS
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
*/
|
||||
public function show_custom_css(): string
|
||||
{
|
||||
$custom_css = self::getSettings()->custom_css;
|
||||
$custom_css = e($custom_css);
|
||||
// Needed for modifying the bootstrap nav :(
|
||||
$custom_css = str_ireplace('script', 'SCRIPTS-NOT-ALLOWED-HERE', $custom_css);
|
||||
$custom_css = str_replace('>', '>', $custom_css);
|
||||
// Allow String output (needs quotes)
|
||||
$custom_css = str_replace('"', '"', $custom_css);
|
||||
|
||||
return $custom_css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes into human readable file size.
|
||||
*
|
||||
* @param string $bytes
|
||||
*
|
||||
* @return string human readable file size (2,87 Мб)
|
||||
*
|
||||
* @author Mogilev Arseny
|
||||
*/
|
||||
public static function fileSizeConvert($bytes): string
|
||||
{
|
||||
$result = 0;
|
||||
$bytes = floatval($bytes);
|
||||
$arBytes = [
|
||||
0 => [
|
||||
'UNIT' => 'TB',
|
||||
'VALUE' => pow(1024, 4),
|
||||
],
|
||||
1 => [
|
||||
'UNIT' => 'GB',
|
||||
'VALUE' => pow(1024, 3),
|
||||
],
|
||||
2 => [
|
||||
'UNIT' => 'MB',
|
||||
'VALUE' => pow(1024, 2),
|
||||
],
|
||||
3 => [
|
||||
'UNIT' => 'KB',
|
||||
'VALUE' => 1024,
|
||||
],
|
||||
4 => [
|
||||
'UNIT' => 'B',
|
||||
'VALUE' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($arBytes as $arItem) {
|
||||
if ($bytes >= $arItem['VALUE']) {
|
||||
$result = $bytes / $arItem['VALUE'];
|
||||
$result = round($result, 2).$arItem['UNIT'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The url for slack notifications.
|
||||
* Used by Notifiable trait.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function routeNotificationForSlack(): string
|
||||
{
|
||||
// At this point the endpoint is the same for everything.
|
||||
// In the future this may want to be adapted for individual notifications.
|
||||
return self::getSettings()->webhook_endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail reply to address from configuration.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function routeNotificationForMail(): string
|
||||
{
|
||||
// At this point the endpoint is the same for everything.
|
||||
// In the future this may want to be adapted for individual notifications.
|
||||
return config('mail.reply_to.address');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the password complexity rule.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function passwordComplexityRulesSaving($action = 'update'): string
|
||||
{
|
||||
$security_rules = '';
|
||||
$settings = self::getSettings();
|
||||
|
||||
// Check if they have uncommon password enforcement selected in settings
|
||||
if ($settings->pwd_secure_uncommon == 1) {
|
||||
$security_rules .= '|dumbpwd';
|
||||
}
|
||||
|
||||
// Check for any secure password complexity rules that may have been selected
|
||||
if ($settings->pwd_secure_complexity != '') {
|
||||
$security_rules .= '|'.$settings->pwd_secure_complexity;
|
||||
}
|
||||
|
||||
if ($action == 'update') {
|
||||
return 'nullable|min:'.$settings->pwd_secure_min.$security_rules;
|
||||
}
|
||||
|
||||
return 'required|min:'.$settings->pwd_secure_min.$security_rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specific LDAP settings
|
||||
*
|
||||
* @author Wes Hulette <jwhulette@gmail.com>
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public static function getLdapSettings(): Collection
|
||||
{
|
||||
$ldapSettings = self::select([
|
||||
'ldap_enabled',
|
||||
'ldap_server',
|
||||
'ldap_uname',
|
||||
'ldap_pword',
|
||||
'ldap_basedn',
|
||||
'ldap_filter',
|
||||
'ldap_username_field',
|
||||
'ldap_lname_field',
|
||||
'ldap_fname_field',
|
||||
'ldap_auth_filter_query',
|
||||
'ldap_version',
|
||||
'ldap_active_flag',
|
||||
'ldap_emp_num',
|
||||
'ldap_email',
|
||||
'ldap_server_cert_ignore',
|
||||
'ldap_port',
|
||||
'ldap_tls',
|
||||
'ldap_pw_sync',
|
||||
'is_ad',
|
||||
'ad_domain',
|
||||
'ad_append_domain',
|
||||
'ldap_client_tls_key',
|
||||
'ldap_client_tls_cert',
|
||||
'ldap_default_group',
|
||||
'ldap_dept',
|
||||
'ldap_phone_field',
|
||||
'ldap_jobtitle',
|
||||
'ldap_manager',
|
||||
'ldap_country',
|
||||
'ldap_location',
|
||||
])->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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
158
SNIPE-IT/app/Models/SnipeModel.php
Normal file
158
SNIPE-IT/app/Models/SnipeModel.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SnipeModel extends Model
|
||||
{
|
||||
// Setters that are appropriate across multiple models.
|
||||
public function setPurchaseDateAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['purchase_date'] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*/
|
||||
public function setPurchaseCostAttribute($value)
|
||||
{
|
||||
$value = Helper::ParseCurrency($value);
|
||||
|
||||
if ($value == 0) {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['purchase_cost'] = $value;
|
||||
}
|
||||
|
||||
public function setLocationIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['location_id'] = $value;
|
||||
}
|
||||
|
||||
public function setCategoryIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['category_id'] = $value;
|
||||
// dd($this->attributes);
|
||||
}
|
||||
|
||||
public function setSupplierIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['supplier_id'] = $value;
|
||||
}
|
||||
|
||||
public function setDepreciationIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['depreciation_id'] = $value;
|
||||
}
|
||||
|
||||
public function setManufacturerIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['manufacturer_id'] = $value;
|
||||
}
|
||||
|
||||
public function setMinAmtAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['min_amt'] = $value;
|
||||
}
|
||||
|
||||
public function setParentIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['parent_id'] = $value;
|
||||
}
|
||||
|
||||
public function setFieldSetIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['fieldset_id'] = $value;
|
||||
}
|
||||
|
||||
public function setCompanyIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['company_id'] = $value;
|
||||
}
|
||||
|
||||
public function setWarrantyMonthsAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['warranty_months'] = $value;
|
||||
}
|
||||
|
||||
public function setRtdLocationIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['rtd_location_id'] = $value;
|
||||
}
|
||||
|
||||
public function setDepartmentIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['department_id'] = $value;
|
||||
}
|
||||
|
||||
public function setManagerIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['manager_id'] = $value;
|
||||
}
|
||||
|
||||
public function setModelIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['model_id'] = $value;
|
||||
}
|
||||
|
||||
public function setStatusIdAttribute($value)
|
||||
{
|
||||
if ($value == '') {
|
||||
$value = null;
|
||||
}
|
||||
$this->attributes['status_id'] = $value;
|
||||
}
|
||||
|
||||
//
|
||||
public function getDisplayNameAttribute()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
250
SNIPE-IT/app/Models/SnipeSCIMConfig.php
Normal file
250
SNIPE-IT/app/Models/SnipeSCIMConfig.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Helper;
|
||||
use ArieTimmerman\Laravel\SCIMServer\SCIM\Schema;
|
||||
use ArieTimmerman\Laravel\SCIMServer\Attribute\AttributeMapping;
|
||||
|
||||
|
||||
class SnipeSCIMConfig extends \ArieTimmerman\Laravel\SCIMServer\SCIMConfig
|
||||
{
|
||||
public function getUserConfig()
|
||||
{
|
||||
// Much of this is copied verbatim from the library, then adjusted for our needs
|
||||
|
||||
/*
|
||||
more snipe-it attributes I'd like to check out (to map to 'enterprise' maybe?):
|
||||
- website
|
||||
- notes?
|
||||
- remote???
|
||||
- location_id ?
|
||||
- company_id to "organization?"
|
||||
*/
|
||||
|
||||
|
||||
$user_prefix = 'urn:ietf:params:scim:schemas:core:2.0:User:';
|
||||
$enterprise_prefix = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:';
|
||||
|
||||
return [
|
||||
|
||||
// Set to 'null' to make use of auth.providers.users.model (App\User::class)
|
||||
'class' => SCIMUser::class,
|
||||
|
||||
'validations' => [
|
||||
$user_prefix . 'userName' => 'required',
|
||||
$user_prefix . 'name.givenName' => 'required',
|
||||
$user_prefix . 'name.familyName' => 'nullable|string',
|
||||
$user_prefix . 'externalId' => 'nullable|string',
|
||||
$user_prefix . 'emails' => 'nullable|array',
|
||||
$user_prefix . 'emails.*.value' => 'nullable|email',
|
||||
$user_prefix . 'active' => 'boolean',
|
||||
$user_prefix . 'phoneNumbers' => 'nullable|array',
|
||||
$user_prefix . 'phoneNumbers.*.value' => 'nullable|string',
|
||||
$user_prefix . 'addresses' => 'nullable|array',
|
||||
$user_prefix . 'addresses.*.streetAddress' => 'nullable|string',
|
||||
$user_prefix . 'addresses.*.locality' => 'nullable|string',
|
||||
$user_prefix . 'addresses.*.region' => 'nullable|string',
|
||||
$user_prefix . 'addresses.*.postalCode' => 'nullable|string',
|
||||
$user_prefix . 'addresses.*.country' => 'nullable|string',
|
||||
$user_prefix . 'title' => 'nullable|string',
|
||||
$user_prefix . 'preferredLanguage' => 'nullable|string',
|
||||
|
||||
// Enterprise validations:
|
||||
$enterprise_prefix . 'employeeNumber' => 'nullable|string',
|
||||
$enterprise_prefix . 'department' => 'nullable|string',
|
||||
$enterprise_prefix . 'manager' => 'nullable',
|
||||
$enterprise_prefix . 'manager.value' => 'nullable|string'
|
||||
],
|
||||
|
||||
'singular' => 'User',
|
||||
'schema' => [Schema::SCHEMA_USER],
|
||||
|
||||
//eager loading
|
||||
'withRelations' => [],
|
||||
'map_unmapped' => false,
|
||||
// 'unmapped_namespace' => 'urn:ietf:params:scim:schemas:laravel:unmapped',
|
||||
'description' => 'User Account',
|
||||
|
||||
// Map a SCIM attribute to an attribute of the object.
|
||||
'mapping' => [
|
||||
|
||||
'id' => (new AttributeMapping())->setRead(
|
||||
function (&$object) {
|
||||
return (string)$object->id;
|
||||
}
|
||||
)->disableWrite(),
|
||||
|
||||
'externalId' => AttributeMapping::eloquent('scim_externalid'), // FIXME - I have a PR that changes a lot of this.
|
||||
|
||||
'meta' => [
|
||||
'created' => AttributeMapping::eloquent("created_at")->disableWrite(),
|
||||
'lastModified' => AttributeMapping::eloquent("updated_at")->disableWrite(),
|
||||
|
||||
'location' => (new AttributeMapping())->setRead(
|
||||
function ($object) {
|
||||
return route(
|
||||
'scim.resource',
|
||||
[
|
||||
'resourceType' => 'Users',
|
||||
'resourceObject' => $object->id
|
||||
]
|
||||
);
|
||||
}
|
||||
)->disableWrite(),
|
||||
|
||||
'resourceType' => AttributeMapping::constant("User")
|
||||
],
|
||||
|
||||
'schemas' => AttributeMapping::constant(
|
||||
[
|
||||
'urn:ietf:params:scim:schemas:core:2.0:User',
|
||||
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
||||
]
|
||||
)->ignoreWrite(),
|
||||
|
||||
'urn:ietf:params:scim:schemas:core:2.0:User' => [
|
||||
|
||||
'userName' => AttributeMapping::eloquent("username"),
|
||||
|
||||
'name' => [
|
||||
'formatted' => (new AttributeMapping())->ignoreWrite()->setRead(
|
||||
function (&$object) {
|
||||
return $object->getFullNameAttribute();
|
||||
}
|
||||
),
|
||||
'familyName' => AttributeMapping::eloquent("last_name"),
|
||||
'givenName' => AttributeMapping::eloquent("first_name"),
|
||||
'middleName' => null,
|
||||
'honorificPrefix' => null,
|
||||
'honorificSuffix' => null
|
||||
],
|
||||
|
||||
'displayName' => null,
|
||||
'nickName' => null,
|
||||
'profileUrl' => null,
|
||||
'title' => AttributeMapping::eloquent('jobtitle'),
|
||||
'userType' => null,
|
||||
'preferredLanguage' => AttributeMapping::eloquent('locale'), // Section 5.3.5 of [RFC7231]
|
||||
'locale' => null, // see RFC5646
|
||||
'timezone' => null, // see RFC6557
|
||||
'active' => (new AttributeMapping())->setAdd(
|
||||
function ($value, &$object) {
|
||||
$object->activated = $value;
|
||||
}
|
||||
)->setReplace(
|
||||
function ($value, &$object) {
|
||||
$object->activated = $value;
|
||||
}
|
||||
)->setRead(
|
||||
// this works as specified.
|
||||
function (&$object) {
|
||||
return (bool)$object->activated;
|
||||
}
|
||||
),
|
||||
'password' => AttributeMapping::eloquent('password')->disableRead(),
|
||||
|
||||
// Multi-Valued Attributes
|
||||
'emails' => [[
|
||||
"value" => AttributeMapping::eloquent("email"),
|
||||
"display" => null,
|
||||
"type" => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
"primary" => AttributeMapping::constant(true)->ignoreWrite()
|
||||
]],
|
||||
|
||||
'phoneNumbers' => [[
|
||||
"value" => AttributeMapping::eloquent("phone"),
|
||||
"display" => null,
|
||||
"type" => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
"primary" => AttributeMapping::constant(true)->ignoreWrite()
|
||||
]],
|
||||
|
||||
'ims' => [[
|
||||
"value" => null,
|
||||
"display" => null,
|
||||
"type" => null,
|
||||
"primary" => null
|
||||
]], // Instant messaging addresses for the User
|
||||
|
||||
'photos' => [[
|
||||
"value" => null,
|
||||
"display" => null,
|
||||
"type" => null,
|
||||
"primary" => null
|
||||
]],
|
||||
|
||||
'addresses' => [[
|
||||
'type' => AttributeMapping::constant("work")->ignoreWrite(),
|
||||
'formatted' => AttributeMapping::constant("n/a")->ignoreWrite(), // TODO - is this right? This doesn't look right.
|
||||
'streetAddress' => AttributeMapping::eloquent("address"),
|
||||
'locality' => AttributeMapping::eloquent("city"),
|
||||
'region' => AttributeMapping::eloquent("state"),
|
||||
'postalCode' => AttributeMapping::eloquent("zip"),
|
||||
'country' => AttributeMapping::eloquent("country"),
|
||||
'primary' => AttributeMapping::constant(true)->ignoreWrite() //this isn't in the example?
|
||||
]],
|
||||
|
||||
'groups' => [[
|
||||
'value' => null,
|
||||
'$ref' => null,
|
||||
'display' => null,
|
||||
'type' => null,
|
||||
]],
|
||||
|
||||
'entitlements' => null,
|
||||
'roles' => null,
|
||||
'x509Certificates' => null
|
||||
],
|
||||
|
||||
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => [
|
||||
'employeeNumber' => AttributeMapping::eloquent('employee_num'),
|
||||
'department' => (new AttributeMapping())->setAdd( // FIXME parent?
|
||||
function ($value, &$object) {
|
||||
$department = Department::where("name", $value)->first();
|
||||
if ($department) {
|
||||
$object->department_id = $department->id;
|
||||
}
|
||||
}
|
||||
)->setReplace(
|
||||
function ($value, &$object) {
|
||||
$department = Department::where("name", $value)->first();
|
||||
if ($department) {
|
||||
$object->department_id = $department->id;
|
||||
}
|
||||
}
|
||||
)->setRead(
|
||||
function (&$object) {
|
||||
return $object->department ? $object->department->name : null;
|
||||
}
|
||||
),
|
||||
'manager' => [
|
||||
// FIXME - manager writes are disabled. This kinda works but it leaks errors all over the place. Not cool.
|
||||
// '$ref' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
|
||||
// 'displayName' => (new AttributeMapping())->ignoreWrite()->ignoreRead(),
|
||||
// NOTE: you could probably do a 'plain' Eloquent mapping here, but we don't for future-proofing
|
||||
'value' => (new AttributeMapping())->setAdd(
|
||||
function ($value, &$object) {
|
||||
$manager = User::find($value);
|
||||
if ($manager) {
|
||||
$object->manager_id = $manager->id;
|
||||
}
|
||||
}
|
||||
)->setReplace(
|
||||
function ($value, &$object) {
|
||||
$manager = User::find($value);
|
||||
if ($manager) {
|
||||
$object->manager_id = $manager->id;
|
||||
}
|
||||
}
|
||||
)->setRead(
|
||||
function (&$object) {
|
||||
return $object->manager_id;
|
||||
}
|
||||
),
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
164
SNIPE-IT/app/Models/Statuslabel.php
Normal file
164
SNIPE-IT/app/Models/Statuslabel.php
Normal file
@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Traits\Searchable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class Statuslabel extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
use ValidatingTrait;
|
||||
use UniqueUndeletedTrait;
|
||||
|
||||
protected $injectUniqueIdentifier = true;
|
||||
|
||||
protected $table = 'status_labels';
|
||||
protected $hidden = ['user_id', 'deleted_at'];
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string|unique_undeleted',
|
||||
'notes' => 'string|nullable',
|
||||
'deployable' => 'required',
|
||||
'pending' => 'required',
|
||||
'archived' => 'required',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'archived',
|
||||
'deployable',
|
||||
'name',
|
||||
'notes',
|
||||
'pending',
|
||||
];
|
||||
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name', 'notes'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
/**
|
||||
* Establishes the status label -> assets relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'status_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status label type
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return string
|
||||
*/
|
||||
public function getStatuslabelType()
|
||||
{
|
||||
if (($this->pending == '1') && ($this->archived == '0') && ($this->deployable == '0')) {
|
||||
return 'pending';
|
||||
} elseif (($this->pending == '0') && ($this->archived == '1') && ($this->deployable == '0')) {
|
||||
return 'archived';
|
||||
} elseif (($this->pending == '0') && ($this->archived == '0') && ($this->deployable == '0')) {
|
||||
return 'undeployable';
|
||||
}
|
||||
|
||||
return 'deployable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to for pending status types
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopePending()
|
||||
{
|
||||
return $this->where('pending', '=', 1)
|
||||
->where('archived', '=', 0)
|
||||
->where('deployable', '=', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope for archived status types
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeArchived()
|
||||
{
|
||||
return $this->where('pending', '=', 0)
|
||||
->where('archived', '=', 1)
|
||||
->where('deployable', '=', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope for deployable status types
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeDeployable()
|
||||
{
|
||||
return $this->where('pending', '=', 0)
|
||||
->where('archived', '=', 0)
|
||||
->where('deployable', '=', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope for undeployable status types
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeUndeployable()
|
||||
{
|
||||
return $this->where('pending', '=', 0)
|
||||
->where('archived', '=', 0)
|
||||
->where('deployable', '=', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine type attributes
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return string
|
||||
*/
|
||||
public static function getStatuslabelTypesForDB($type)
|
||||
{
|
||||
$statustype['pending'] = 0;
|
||||
$statustype['deployable'] = 0;
|
||||
$statustype['archived'] = 0;
|
||||
|
||||
if ($type == 'pending') {
|
||||
$statustype['pending'] = 1;
|
||||
$statustype['deployable'] = 0;
|
||||
$statustype['archived'] = 0;
|
||||
} elseif ($type == 'deployable') {
|
||||
$statustype['pending'] = 0;
|
||||
$statustype['deployable'] = 1;
|
||||
$statustype['archived'] = 0;
|
||||
} elseif ($type == 'archived') {
|
||||
$statustype['pending'] = 0;
|
||||
$statustype['deployable'] = 0;
|
||||
$statustype['archived'] = 1;
|
||||
}
|
||||
|
||||
return $statustype;
|
||||
}
|
||||
}
|
200
SNIPE-IT/app/Models/Supplier.php
Normal file
200
SNIPE-IT/app/Models/Supplier.php
Normal file
@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Traits\Searchable;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class Supplier extends SnipeModel
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'suppliers';
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|min:1|max:255|unique_undeleted',
|
||||
'fax' => 'min:7|max:35|nullable',
|
||||
'phone' => 'min:7|max:35|nullable',
|
||||
'contact' => 'max:100|nullable',
|
||||
'notes' => 'max:191|nullable', // Default string length is 191 characters..
|
||||
'email' => 'email|max:150|nullable',
|
||||
'address' => 'max:250|nullable',
|
||||
'address2' => 'max:250|nullable',
|
||||
'city' => 'max:191|nullable',
|
||||
'state' => 'min:2|max:191|nullable',
|
||||
'country' => 'min:2|max:191|nullable',
|
||||
'zip' => 'max:10|nullable',
|
||||
'url' => 'sometimes|nullable|string|max:250',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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 UniqueUndeletedTrait;
|
||||
use Searchable;
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = ['name'];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['name', 'address', 'address2', 'city', 'state', 'country', 'zip', 'phone', 'fax', 'email', 'contact', 'url', 'notes'];
|
||||
|
||||
/**
|
||||
* Eager load counts
|
||||
*
|
||||
* We do this to eager load the "count" of seats from the controller.
|
||||
* Otherwise calling "count()" on each model results in n+1.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assetsRelation()
|
||||
{
|
||||
return $this->hasMany(Asset::class)->whereNull('deleted_at')->selectRaw('supplier_id, count(*) as count')->groupBy('supplier_id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the supplier -> assets relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'supplier_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the supplier -> accessories relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Accessory::class, 'supplier_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the supplier -> component relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.1]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function components()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Component::class, 'supplier_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the supplier -> component relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.1.1]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Consumable::class, 'supplier_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the supplier -> asset maintenances relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function asset_maintenances()
|
||||
{
|
||||
return $this->hasMany(\App\Models\AssetMaintenance::class, 'supplier_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of assets by supplier
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return int
|
||||
*/
|
||||
public function num_assets()
|
||||
{
|
||||
if ($this->assetsRelation->first()) {
|
||||
return $this->assetsRelation->first()->count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the supplier -> license relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenses()
|
||||
{
|
||||
return $this->hasMany(\App\Models\License::class, 'supplier_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of licenses by supplier
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return int
|
||||
*/
|
||||
public function num_licenses()
|
||||
{
|
||||
return $this->licenses()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add http to the url in suppliers if the user didn't give one
|
||||
*
|
||||
* @todo this should be handled via validation, no?
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function addhttp($url)
|
||||
{
|
||||
if (($url!='') && (! preg_match('~^(?:f|ht)tps?://~i', $url))) {
|
||||
$url = 'http://'.$url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
34
SNIPE-IT/app/Models/Traits/Acceptable.php
Normal file
34
SNIPE-IT/app/Models/Traits/Acceptable.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Traits;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* This trait allows models to have a callback after their checkout gets accepted or declined.
|
||||
*
|
||||
* @author Till Deeke <kontakt@tilldeeke.de>
|
||||
*/
|
||||
trait Acceptable
|
||||
{
|
||||
/**
|
||||
* Run after the checkout acceptance was accepted by the user
|
||||
*
|
||||
* @param User $acceptedBy
|
||||
* @param string $signature
|
||||
*/
|
||||
public function acceptedCheckout(User $acceptedBy, $signature, $filename = null)
|
||||
{
|
||||
\Log::debug('acceptedCheckout in Acceptable trait fired, tho it doesn\'t do anything?');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run after the checkout acceptance was declined by the user
|
||||
*
|
||||
* @param User $acceptedBy
|
||||
* @param string $signature
|
||||
*/
|
||||
public function declinedCheckout(User $declinedBy, $signature)
|
||||
{
|
||||
}
|
||||
}
|
300
SNIPE-IT/app/Models/Traits/Searchable.php
Normal file
300
SNIPE-IT/app/Models/Traits/Searchable.php
Normal file
@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Traits;
|
||||
|
||||
use App\Models\Asset;
|
||||
use App\Models\CustomField;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* This trait allows for cleaner searching of models,
|
||||
* moving from complex queries to an easier declarative syntax.
|
||||
*
|
||||
* @author Till Deeke <kontakt@tilldeeke.de>
|
||||
*/
|
||||
trait Searchable
|
||||
{
|
||||
/**
|
||||
* Performs a search on the model, using the provided search terms
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query The query to start the search on
|
||||
* @param string $search
|
||||
* @return \Illuminate\Database\Eloquent\Builder A query with added "where" clauses
|
||||
*/
|
||||
public function scopeTextSearch($query, $search)
|
||||
{
|
||||
$terms = $this->prepeareSearchTerms($search);
|
||||
|
||||
/**
|
||||
* Search the attributes of this model
|
||||
*/
|
||||
$query = $this->searchAttributes($query, $terms);
|
||||
|
||||
/**
|
||||
* Search through the custom fields of the model
|
||||
*/
|
||||
$query = $this->searchCustomFields($query, $terms);
|
||||
|
||||
/**
|
||||
* Search through the relations of the model
|
||||
*/
|
||||
$query = $this->searchRelations($query, $terms);
|
||||
|
||||
/**
|
||||
* Search for additional attributes defined by the model
|
||||
*/
|
||||
$query = $this->advancedTextSearch($query, $terms);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the search term, splitting and cleaning it up
|
||||
* @param string $search The search term
|
||||
* @return array An array of search terms
|
||||
*/
|
||||
private function prepeareSearchTerms($search)
|
||||
{
|
||||
return explode(' OR ', $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the models attributes for the search terms
|
||||
*
|
||||
* @param Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $terms
|
||||
* @return Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
private function searchAttributes(Builder $query, array $terms)
|
||||
{
|
||||
$table = $this->getTable();
|
||||
|
||||
$firstConditionAdded = false;
|
||||
|
||||
foreach ($this->getSearchableAttributes() as $column) {
|
||||
foreach ($terms as $term) {
|
||||
/**
|
||||
* Making sure to only search in date columns if the search term consists of characters that can make up a MySQL timestamp!
|
||||
*
|
||||
* @see https://github.com/snipe/snipe-it/issues/4590
|
||||
*/
|
||||
if (! preg_match('/^[0-9 :-]++$/', $term) && in_array($column, $this->getDates())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to form the query properly, starting with a "where",
|
||||
* otherwise the generated select is wrong.
|
||||
*
|
||||
* @todo This does the job, but is inelegant and fragile
|
||||
*/
|
||||
if (! $firstConditionAdded) {
|
||||
$query = $query->where($table.'.'.$column, 'LIKE', '%'.$term.'%');
|
||||
|
||||
$firstConditionAdded = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$query = $query->orWhere($table.'.'.$column, 'LIKE', '%'.$term.'%');
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the models custom fields for the search terms
|
||||
*
|
||||
* @param Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $terms
|
||||
* @return Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
private function searchCustomFields(Builder $query, array $terms)
|
||||
{
|
||||
|
||||
/**
|
||||
* If we are searching on something other that an asset, skip custom fields.
|
||||
*/
|
||||
if (! $this instanceof Asset) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
$customFields = CustomField::all();
|
||||
|
||||
foreach ($customFields as $field) {
|
||||
foreach ($terms as $term) {
|
||||
$query->orWhere($this->getTable().'.'.$field->db_column_name(), 'LIKE', '%'.$term.'%');
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the models relations for the search terms
|
||||
*
|
||||
* @param Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $terms
|
||||
* @return Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
private function searchRelations(Builder $query, array $terms)
|
||||
{
|
||||
foreach ($this->getSearchableRelations() as $relation => $columns) {
|
||||
$query = $query->orWhereHas($relation, function ($query) use ($relation, $columns, $terms) {
|
||||
$table = $this->getRelationTable($relation);
|
||||
|
||||
/**
|
||||
* We need to form the query properly, starting with a "where",
|
||||
* otherwise the generated nested select is wrong.
|
||||
*
|
||||
* @todo This does the job, but is inelegant and fragile
|
||||
*/
|
||||
$firstConditionAdded = false;
|
||||
|
||||
foreach ($columns as $column) {
|
||||
foreach ($terms as $term) {
|
||||
if (! $firstConditionAdded) {
|
||||
$query->where($table.'.'.$column, 'LIKE', '%'.$term.'%');
|
||||
$firstConditionAdded = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$query->orWhere($table.'.'.$column, 'LIKE', '%'.$term.'%');
|
||||
}
|
||||
}
|
||||
// I put this here because I only want to add the concat one time in the end of the user relation search
|
||||
if($relation == 'user') {
|
||||
$query->orWhereRaw(
|
||||
$this->buildMultipleColumnSearch([
|
||||
'users.first_name',
|
||||
'users.last_name',
|
||||
]),
|
||||
["%{$term}%"]
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run additional, advanced searches that can't be done using the attributes or relations.
|
||||
*
|
||||
* This is a noop in this trait, but can be overridden in the implementing model, to allow more advanced searches
|
||||
*
|
||||
* @param Illuminate\Database\Eloquent\Builder $query
|
||||
* @param array $terms The search terms
|
||||
* @return Illuminate\Database\Eloquent\Builder
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
public function advancedTextSearch(Builder $query, array $terms)
|
||||
{
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the searchable attributes, if defined. Otherwise it returns an empty array
|
||||
*
|
||||
* @return array The attributes to search in
|
||||
*/
|
||||
private function getSearchableAttributes()
|
||||
{
|
||||
return $this->searchableAttributes ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the searchable relations, if defined. Otherwise it returns an empty array
|
||||
*
|
||||
* @return array The relations to search in
|
||||
*/
|
||||
private function getSearchableRelations()
|
||||
{
|
||||
return $this->searchableRelations ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the table name of a relation.
|
||||
*
|
||||
* This method loops over a relation name,
|
||||
* getting the table name of the last relation in the series.
|
||||
* So "category" would get the table name for the Category model,
|
||||
* "model.manufacturer" would get the tablename for the Manufacturer model.
|
||||
*
|
||||
* @param string $relation
|
||||
* @return string The table name
|
||||
*/
|
||||
private function getRelationTable($relation)
|
||||
{
|
||||
$related = $this;
|
||||
|
||||
foreach (explode('.', $relation) as $relationName) {
|
||||
$related = $related->{$relationName}()->getRelated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we referencing the model that called?
|
||||
* Then get the internal join-tablename, since laravel
|
||||
* has trouble selecting the correct one in this type of
|
||||
* parent-child self-join.
|
||||
*
|
||||
* @todo Does this work with deeply nested resources? Like "category.assets.model.category" or something like that?
|
||||
*/
|
||||
if ($this instanceof $related) {
|
||||
|
||||
/**
|
||||
* Since laravel increases the counter on the hash on retrieval, we have to count it down again.
|
||||
*
|
||||
* This causes side effects! Every time we access this method, laravel increases the counter!
|
||||
*
|
||||
* Format: laravel_reserved_XXX
|
||||
*/
|
||||
$relationCountHash = $this->{$relationName}()->getRelationCountHash();
|
||||
|
||||
$parts = collect(explode('_', $relationCountHash));
|
||||
|
||||
$counter = $parts->pop();
|
||||
|
||||
$parts->push($counter - 1);
|
||||
|
||||
return implode('_', $parts->toArray());
|
||||
}
|
||||
|
||||
return $related->getTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a search string for either MySQL or sqlite by separating the provided columns with a space.
|
||||
*
|
||||
* @param array $columns Columns to include in search string.
|
||||
* @return string
|
||||
*/
|
||||
private function buildMultipleColumnSearch(array $columns): string
|
||||
{
|
||||
$mappedColumns = collect($columns)->map(fn($column) => DB::getTablePrefix() . $column)->toArray();
|
||||
|
||||
$driver = config('database.connections.' . config('database.default') . '.driver');
|
||||
|
||||
if ($driver === 'sqlite') {
|
||||
return implode("||' '||", $mappedColumns) . ' LIKE ?';
|
||||
}
|
||||
|
||||
// Default to MySQL's concatenation method
|
||||
return 'CONCAT(' . implode('," ",', $mappedColumns) . ') LIKE ?';
|
||||
}
|
||||
|
||||
/**
|
||||
* Search a string across multiple columns separated with a space.
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param array $columns - Columns to include in search string.
|
||||
* @param $term
|
||||
* @return Builder
|
||||
*/
|
||||
public function scopeOrWhereMultipleColumns($query, array $columns, $term)
|
||||
{
|
||||
return $query->orWhereRaw($this->buildMultipleColumnSearch($columns), ["%{$term}%"]);
|
||||
}
|
||||
}
|
811
SNIPE-IT/app/Models/User.php
Normal file
811
SNIPE-IT/app/Models/User.php
Normal file
@ -0,0 +1,811 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Traits\UniqueUndeletedTrait;
|
||||
use App\Models\Traits\Searchable;
|
||||
use App\Presenters\Presentable;
|
||||
use DB;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Auth\Passwords\CanResetPassword;
|
||||
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
||||
use Illuminate\Contracts\Translation\HasLocalePreference;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\Access\Authorizable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Laravel\Passport\HasApiTokens;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
class User extends SnipeModel implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, HasLocalePreference
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $presenter = \App\Presenters\UserPresenter::class;
|
||||
use SoftDeletes, ValidatingTrait;
|
||||
use Authenticatable, Authorizable, CanResetPassword, HasApiTokens;
|
||||
use UniqueUndeletedTrait;
|
||||
use Notifiable;
|
||||
use Presentable;
|
||||
use Searchable;
|
||||
|
||||
protected $hidden = ['password', 'remember_token', 'permissions', 'reset_password_code', 'persist_code'];
|
||||
protected $table = 'users';
|
||||
protected $injectUniqueIdentifier = true;
|
||||
|
||||
protected $fillable = [
|
||||
'activated',
|
||||
'address',
|
||||
'city',
|
||||
'company_id',
|
||||
'country',
|
||||
'department_id',
|
||||
'email',
|
||||
'employee_num',
|
||||
'first_name',
|
||||
'jobtitle',
|
||||
'last_name',
|
||||
'ldap_import',
|
||||
'locale',
|
||||
'location_id',
|
||||
'manager_id',
|
||||
'password',
|
||||
'phone',
|
||||
'notes',
|
||||
'state',
|
||||
'username',
|
||||
'zip',
|
||||
'remote',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'scim_externalid',
|
||||
'avatar',
|
||||
'gravatar',
|
||||
'vip',
|
||||
'autoassign_licenses',
|
||||
'website',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'manager_id' => 'integer',
|
||||
'location_id' => 'integer',
|
||||
'company_id' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Model validation rules
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
|
||||
protected $rules = [
|
||||
'first_name' => 'required|string|min:1|max:191',
|
||||
'username' => 'required|string|min:1|unique_undeleted|max:191',
|
||||
'email' => 'email|nullable|max:191',
|
||||
'password' => 'required|min:8',
|
||||
'locale' => 'max:10|nullable',
|
||||
'website' => 'url|nullable|max:191',
|
||||
'manager_id' => 'nullable|exists:users,id|cant_manage_self',
|
||||
'location_id' => 'exists:locations,id|nullable',
|
||||
'start_date' => 'nullable|date_format:Y-m-d',
|
||||
'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date',
|
||||
'autoassign_licenses' => 'boolean',
|
||||
'address' => 'max:191|nullable',
|
||||
'city' => 'max:191|nullable',
|
||||
'state' => 'min:2|max:191|nullable',
|
||||
'country' => 'min:2|max:191|nullable',
|
||||
'zip' => 'max:10|nullable',
|
||||
'vip' => 'boolean',
|
||||
'remote' => 'boolean',
|
||||
'activated' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableAttributes = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'username',
|
||||
'notes',
|
||||
'phone',
|
||||
'jobtitle',
|
||||
'employee_num',
|
||||
'website',
|
||||
];
|
||||
|
||||
/**
|
||||
* The relations and their attributes that should be included when searching the model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $searchableRelations = [
|
||||
'userloc' => ['name'],
|
||||
'department' => ['name'],
|
||||
'groups' => ['name'],
|
||||
'company' => ['name'],
|
||||
'manager' => ['first_name', 'last_name', 'username'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Internally check the user permission for the given section
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkPermissionSection($section)
|
||||
{
|
||||
$user_groups = $this->groups;
|
||||
if (($this->permissions == '') && (count($user_groups) == 0)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_permissions = json_decode($this->permissions, true);
|
||||
|
||||
$is_user_section_permissions_set = ($user_permissions != '') && array_key_exists($section, $user_permissions);
|
||||
//If the user is explicitly granted, return true
|
||||
if ($is_user_section_permissions_set && ($user_permissions[$section] == '1')) {
|
||||
return true;
|
||||
}
|
||||
// If the user is explicitly denied, return false
|
||||
if ($is_user_section_permissions_set && ($user_permissions[$section] == '-1')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loop through the groups to see if any of them grant this permission
|
||||
foreach ($user_groups as $user_group) {
|
||||
$group_permissions = (array) json_decode($user_group->permissions, true);
|
||||
if (((array_key_exists($section, $group_permissions)) && ($group_permissions[$section] == '1'))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user permissions
|
||||
*
|
||||
* Parses the user and group permission masks to see if the user
|
||||
* is authorized to do the thing
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAccess($section)
|
||||
{
|
||||
if ($this->isSuperUser()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->checkPermissionSection($section);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is a SuperUser
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function isSuperUser()
|
||||
{
|
||||
return $this->checkPermissionSection('superuser');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is deletable
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.3.4]
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletable()
|
||||
{
|
||||
return Gate::allows('delete', $this)
|
||||
&& ($this->assets()->count() === 0)
|
||||
&& ($this->licenses()->count() === 0)
|
||||
&& ($this->consumables()->count() === 0)
|
||||
&& ($this->accessories()->count() === 0)
|
||||
&& ($this->deleted_at == '');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the user -> company relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Company::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> department relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function department()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Department::class, 'department_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks activated status
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return bool
|
||||
*/
|
||||
public function isActivated()
|
||||
{
|
||||
return $this->activated == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full name attribute
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return string
|
||||
*/
|
||||
public function getFullNameAttribute()
|
||||
{
|
||||
$setting = Setting::getSettings();
|
||||
|
||||
if ($setting->name_display_format=='last_first') {
|
||||
return ($this->last_name) ? $this->last_name.' '.$this->first_name : $this->first_name;
|
||||
}
|
||||
return $this->last_name ? $this->first_name.' '.$this->last_name : $this->first_name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Establishes the user -> assets relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assets()
|
||||
{
|
||||
return $this->morphMany(\App\Models\Asset::class, 'assigned', 'assigned_type', 'assigned_to')->withTrashed()->orderBy('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> maintenances relationship
|
||||
*
|
||||
* This would only be used to return maintenances that this user
|
||||
* created.
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assetmaintenances()
|
||||
{
|
||||
return $this->hasMany(\App\Models\AssetMaintenance::class, 'user_id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> accessories relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function accessories()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Accessory::class, 'accessories_users', 'assigned_to', 'accessory_id')
|
||||
->withPivot('id', 'created_at', 'note')->withTrashed()->orderBy('accessory_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> consumables relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function consumables()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Consumable::class, 'consumables_users', 'assigned_to', 'consumable_id')->withPivot('id','created_at','note')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> license seats relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function licenses()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id', 'created_at', 'updated_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a count of all items assigned
|
||||
*
|
||||
* @author J. Vinsmoke
|
||||
* @since [v6.1]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
Public function allAssignedCount() {
|
||||
$assetsCount = $this->assets()->count();
|
||||
$licensesCount = $this->licenses()->count();
|
||||
$accessoriesCount = $this->accessories()->count();
|
||||
$consumablesCount = $this->consumables()->count();
|
||||
|
||||
$totalCount = $assetsCount + $licensesCount + $accessoriesCount + $consumablesCount;
|
||||
|
||||
return (int) $totalCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> actionlogs relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function userlog()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'target_id')->where('target_type', '=', self::class)->orderBy('created_at', 'DESC')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> location relationship
|
||||
*
|
||||
* Get the asset's location based on the assigned user
|
||||
*
|
||||
* @todo - this should be removed once we're sure we've switched it to location()
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function userloc()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Location::class, 'location_id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> location relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function location()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Location::class, 'location_id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> manager relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function manager()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'manager_id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> managed locations relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function managedLocations()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Location::class, 'manager_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> groups relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v1.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function groups()
|
||||
{
|
||||
return $this->belongsToMany(\App\Models\Group::class, 'users_groups');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> assets relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v4.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function assetlog()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Asset::class, 'id')->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> uploads relationship
|
||||
*
|
||||
* @todo I don't think we use this?
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v3.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function uploads()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Actionlog::class, 'item_id')
|
||||
->where('item_type', self::class)
|
||||
->where('action_type', '=', 'uploaded')
|
||||
->whereNotNull('filename')
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes the user -> requested assets relationship
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function checkoutRequests()
|
||||
{
|
||||
return $this->belongsToMany(Asset::class, 'checkout_requests', 'user_id', 'requestable_id')->whereNull('canceled_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a common string when the user has been imported/synced from:
|
||||
*
|
||||
* - LDAP without password syncing
|
||||
* - SCIM
|
||||
* - CSV import where no password was provided
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v6.2.0]
|
||||
* @return string
|
||||
*/
|
||||
public function noPassword()
|
||||
{
|
||||
return "*** NO PASSWORD ***";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope to return NOT-deleted users
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
*
|
||||
* @param string $query
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function scopeGetNotDeleted($query)
|
||||
{
|
||||
return $query->whereNull('deleted_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to return users by email or username
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
*
|
||||
* @param string $query
|
||||
* @param string $user_username
|
||||
* @param string $user_email
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function scopeMatchEmailOrUsername($query, $user_username, $user_email)
|
||||
{
|
||||
return $query->where('email', '=', $user_email)
|
||||
->orWhere('username', '=', $user_username)
|
||||
->orWhere('username', '=', $user_email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate email from full name
|
||||
*
|
||||
* @author A. Gianotto <snipe@snipe.net>
|
||||
* @since [v2.0]
|
||||
*
|
||||
* @param string $query
|
||||
* @return string
|
||||
*/
|
||||
public static function generateEmailFromFullName($name)
|
||||
{
|
||||
$username = self::generateFormattedNameFromFullName($name, Setting::getSettings()->email_format);
|
||||
|
||||
return $username['username'].'@'.Setting::getSettings()->email_domain;
|
||||
}
|
||||
|
||||
public static function generateFormattedNameFromFullName($users_name, $format = 'filastname')
|
||||
{
|
||||
|
||||
// If there was only one name given
|
||||
if (strpos($users_name, ' ') === false) {
|
||||
$first_name = $users_name;
|
||||
$last_name = '';
|
||||
$username = $users_name;
|
||||
} else {
|
||||
|
||||
list($first_name, $last_name) = explode(' ', $users_name, 2);
|
||||
|
||||
// Assume filastname by default
|
||||
$username = str_slug(substr($first_name, 0, 1).$last_name);
|
||||
|
||||
if ($format=='firstname.lastname') {
|
||||
$username = str_slug($first_name) . '.' . str_slug($last_name);
|
||||
|
||||
} elseif ($format == 'lastnamefirstinitial') {
|
||||
$username = str_slug($last_name.substr($first_name, 0, 1));
|
||||
} elseif ($format == 'firstintial.lastname') {
|
||||
$username = substr($first_name, 0, 1).'.'.str_slug($last_name);
|
||||
} elseif ($format == 'firstname_lastname') {
|
||||
$username = str_slug($first_name).'_'.str_slug($last_name);
|
||||
} elseif ($format == 'firstname') {
|
||||
$username = str_slug($first_name);
|
||||
} elseif ($format == 'firstinitial.lastname') {
|
||||
$username = str_slug(substr($first_name, 0, 1).'.'.str_slug($last_name));
|
||||
} elseif ($format == 'lastname_firstinitial') {
|
||||
$username = str_slug($last_name).'_'.str_slug(substr($first_name, 0, 1));
|
||||
} elseif ($format == 'firstnamelastname') {
|
||||
$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;
|
||||
$user['last_name'] = $last_name;
|
||||
$user['username'] = strtolower($username);
|
||||
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether two-factor authorization is requiredfor this user
|
||||
*
|
||||
* 0 = 2FA disabled
|
||||
* 1 = 2FA optional
|
||||
* 2 = 2FA universally required
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.0]
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function two_factor_active()
|
||||
{
|
||||
|
||||
// If the 2FA is optional and the user has opted in
|
||||
if ((Setting::getSettings()->two_factor_enabled == '1') && ($this->two_factor_optin == '1')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the 2FA is required for everyone so is implicitly active
|
||||
elseif (Setting::getSettings()->two_factor_enabled == '2') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether two-factor authorization is required and the user has activated it
|
||||
* and enrolled a device
|
||||
*
|
||||
* 0 = 2FA disabled
|
||||
* 1 = 2FA optional
|
||||
* 2 = 2FA universally required
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v4.6.14]
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function two_factor_active_and_enrolled()
|
||||
{
|
||||
|
||||
// If the 2FA is optional and the user has opted in and is enrolled
|
||||
if ((Setting::getSettings()->two_factor_enabled == '1') && ($this->two_factor_optin == '1') && ($this->two_factor_enrolled == '1')) {
|
||||
return true;
|
||||
}
|
||||
// If the 2FA is required for everyone and the user has enrolled
|
||||
elseif ((Setting::getSettings()->two_factor_enabled == '2') && ($this->two_factor_enrolled)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the admin user who created this user
|
||||
*
|
||||
* @author [A. Gianotto] [<snipe@snipe.net>]
|
||||
* @since [v6.0.5]
|
||||
* @return \Illuminate\Database\Eloquent\Relations\Relation
|
||||
*/
|
||||
public function createdBy()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\User::class, 'created_by')->withTrashed();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function decodePermissions()
|
||||
{
|
||||
return json_decode($this->permissions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to search user by name with spaces in it.
|
||||
* We don't use the advancedTextSearch() scope because that searches
|
||||
* all of the relations as well, which is more than what we need.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param array $terms The search terms
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function scopeSimpleNameSearch($query, $search)
|
||||
{
|
||||
return $query->where('first_name', 'LIKE', '%' . $search . '%')
|
||||
->orWhere('last_name', 'LIKE', '%' . $search . '%')
|
||||
->orWhereMultipleColumns([
|
||||
'users.first_name',
|
||||
'users.last_name',
|
||||
], $search);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run additional, advanced searches.
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param array $terms The search terms
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function advancedTextSearch(Builder $query, array $terms) {
|
||||
foreach($terms as $term) {
|
||||
$query->orWhereMultipleColumns([
|
||||
'users.first_name',
|
||||
'users.last_name',
|
||||
], $term);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to return users by group
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param int $id
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function scopeByGroup($query, $id)
|
||||
{
|
||||
return $query->whereHas('groups', function ($query) use ($id) {
|
||||
$query->where('permission_groups.id', '=', $id);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Query builder scope to order on manager
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderManager($query, $order)
|
||||
{
|
||||
// Left join here, or it will only return results with parents
|
||||
return $query->leftJoin('users as users_manager', 'users.manager_id', '=', 'users_manager.id')->orderBy('users_manager.first_name', $order)->orderBy('users_manager.last_name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on company
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderLocation($query, $order)
|
||||
{
|
||||
return $query->leftJoin('locations as locations_users', 'users.location_id', '=', 'locations_users.id')->orderBy('locations_users.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on department
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderDepartment($query, $order)
|
||||
{
|
||||
return $query->leftJoin('departments as departments_users', 'users.department_id', '=', 'departments_users.id')->orderBy('departments_users.name', $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query builder scope to order on admin user
|
||||
*
|
||||
* @param \Illuminate\Database\Query\Builder $query Query builder instance
|
||||
* @param string $order Order
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder Modified query builder
|
||||
*/
|
||||
public function scopeOrderByCreatedBy($query, $order)
|
||||
{
|
||||
// Left join here, or it will only return results with parents
|
||||
return $query->leftJoin('users as admin_user', 'users.created_by', '=', 'admin_user.id')
|
||||
->orderBy('admin_user.first_name', $order)
|
||||
->orderBy('admin_user.last_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
|
||||
*/
|
||||
public function scopeOrderCompany($query, $order)
|
||||
{
|
||||
return $query->leftJoin('companies as companies_user', 'users.company_id', '=', 'companies_user.id')->orderBy('companies_user.name', $order);
|
||||
}
|
||||
|
||||
public function preferredLocale()
|
||||
{
|
||||
return $this->locale;
|
||||
}
|
||||
public function getUserTotalCost(){
|
||||
$asset_cost= 0;
|
||||
$license_cost= 0;
|
||||
$accessory_cost= 0;
|
||||
foreach ($this->assets as $asset){
|
||||
$asset_cost += $asset->purchase_cost;
|
||||
$this->asset_cost = $asset_cost;
|
||||
}
|
||||
foreach ($this->licenses as $license){
|
||||
$license_cost += $license->purchase_cost;
|
||||
$this->license_cost = $license_cost;
|
||||
}
|
||||
foreach ($this->accessories as $accessory){
|
||||
$accessory_cost += $accessory->purchase_cost;
|
||||
$this->accessory_cost = $accessory_cost;
|
||||
}
|
||||
|
||||
$this->total_user_cost = ($asset_cost + $accessory_cost + $license_cost);
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user