mirror of
https://github.com/snipe/snipe-it.git
synced 2024-12-25 21:54:14 -08:00
fea11ec7f1
Bulk Editing Custom Fields
398 lines
12 KiB
PHP
398 lines
12 KiB
PHP
<?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',
|
|
];
|
|
|
|
/**
|
|
* 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',
|
|
|
|
];
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @todo figure out if this is still needed? I don't know WTF it's for.
|
|
*
|
|
* @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;
|
|
}
|
|
}
|