Applied pint to Livewire directory
This commit is contained in:
@@ -43,6 +43,6 @@ class CategoryEditForm extends Component
|
||||
#[Computed]
|
||||
public function eulaTextDisabled()
|
||||
{
|
||||
return (bool)$this->useDefaultEula;
|
||||
return (bool) $this->useDefaultEula;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\AssetModel;
|
||||
use App\Models\CustomField;
|
||||
use App\Models\CustomFieldset;
|
||||
use Livewire\Attributes\Computed;
|
||||
use Livewire\Component;
|
||||
|
||||
use App\Models\CustomFieldset;
|
||||
use App\Models\AssetModel;
|
||||
|
||||
class CustomFieldSetDefaultValuesForModel extends Component
|
||||
{
|
||||
public $add_default_values;
|
||||
|
||||
public $fieldset_id;
|
||||
|
||||
public $model_id;
|
||||
|
||||
public array $selectedValues = [];
|
||||
@@ -24,14 +24,13 @@ class CustomFieldSetDefaultValuesForModel extends Component
|
||||
$this->fieldset_id = $this->model?->fieldset_id;
|
||||
$this->add_default_values = ($this->model?->defaultValues->count() > 0);
|
||||
|
||||
|
||||
$this->initializeSelectedValuesArray();
|
||||
if (session()->has('errors')) {
|
||||
$errors = session('errors')->keys();
|
||||
$selectedValuesKeys = array_keys($this->selectedValues);
|
||||
if (count(array_intersect($selectedValuesKeys, $errors)) > 0) {
|
||||
$this->add_default_values = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
$this->populatedSelectedValuesArray();
|
||||
}
|
||||
@@ -65,8 +64,6 @@ class CustomFieldSetDefaultValuesForModel extends Component
|
||||
* dynamically added (this is especially true for checkboxes).
|
||||
*
|
||||
* Let's go ahead and initialize selectedValues with all the potential keys (custom field db_columns).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function initializeSelectedValuesArray(): void
|
||||
{
|
||||
@@ -82,8 +79,6 @@ class CustomFieldSetDefaultValuesForModel extends Component
|
||||
/**
|
||||
* Populate the selectedValues array with the
|
||||
* default values or old input for each field.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function populatedSelectedValuesArray(): void
|
||||
{
|
||||
@@ -107,7 +102,7 @@ class CustomFieldSetDefaultValuesForModel extends Component
|
||||
// back with the old input.
|
||||
// Let's use what they had previously set.
|
||||
if (old('default_values')) {
|
||||
$defaultValue = old('default_values.' . $field->id);
|
||||
$defaultValue = old('default_values.'.$field->id);
|
||||
}
|
||||
|
||||
// on first load the default value for checkboxes will be
|
||||
|
||||
+217
-220
@@ -10,39 +10,64 @@ use Livewire\Component;
|
||||
|
||||
class Importer extends Component
|
||||
{
|
||||
public $progress = -1; //upload progress - '-1' means don't show
|
||||
public $progress = -1; // upload progress - '-1' means don't show
|
||||
|
||||
public $progress_message;
|
||||
|
||||
public $progress_bar_class = 'progress-bar-warning';
|
||||
|
||||
public $message; //status/error message?
|
||||
public $message_type; //success/error?
|
||||
public $message; // status/error message?
|
||||
|
||||
//originally from ImporterFile
|
||||
public $message_type; // success/error?
|
||||
|
||||
// originally from ImporterFile
|
||||
public $import_errors; //
|
||||
|
||||
public $activeFileId;
|
||||
|
||||
public $headerRow = [];
|
||||
|
||||
public $typeOfImport;
|
||||
|
||||
public $importTypes;
|
||||
|
||||
public $columnOptions;
|
||||
|
||||
public $statusType;
|
||||
|
||||
public $statusText;
|
||||
|
||||
public $update;
|
||||
|
||||
public $send_welcome;
|
||||
|
||||
public $run_backup;
|
||||
|
||||
public $field_map; // we need a separate variable for the field-mapping, because the keys in the normal array are too complicated for Livewire to understand
|
||||
|
||||
// Make these variables public - we set the properties in the constructor so we can localize them (versus the old static arrays)
|
||||
public $accessories_fields;
|
||||
|
||||
public $assets_fields;
|
||||
|
||||
public $users_fields;
|
||||
|
||||
public $assetmodels_fields;
|
||||
|
||||
public $suppliers_fields;
|
||||
|
||||
public $licenses_fields;
|
||||
|
||||
public $locations_fields;
|
||||
|
||||
public $consumables_fields;
|
||||
|
||||
public $components_fields;
|
||||
|
||||
public $manufacturers_fields;
|
||||
|
||||
public $categories_fields;
|
||||
|
||||
public $aliases_fields;
|
||||
|
||||
protected $rules = [
|
||||
@@ -51,7 +76,7 @@ class Importer extends Component
|
||||
'files.*.filesize' => 'required|integer',
|
||||
'headerRow' => 'array',
|
||||
'typeOfImport' => 'string',
|
||||
'field_map' => 'array'
|
||||
'field_map' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -62,11 +87,12 @@ class Importer extends Component
|
||||
*/
|
||||
public function generate_field_map()
|
||||
{
|
||||
$tmp = array();
|
||||
$tmp = [];
|
||||
if ($this->activeFile) {
|
||||
$tmp = array_combine($this->headerRow, $this->field_map);
|
||||
$tmp = array_filter($tmp);
|
||||
}
|
||||
|
||||
return json_encode($tmp);
|
||||
|
||||
}
|
||||
@@ -112,13 +138,14 @@ class Importer extends Component
|
||||
}
|
||||
asort($results, SORT_FLAG_CASE | SORT_STRING);
|
||||
|
||||
if ($type == "asset") {
|
||||
if ($type == 'asset') {
|
||||
// add Custom Fields after a horizontal line
|
||||
$results['-'] = "———" . trans('admin/custom_fields/general.custom_fields') . "———’";
|
||||
$results['-'] = '———'.trans('admin/custom_fields/general.custom_fields').'———’';
|
||||
foreach (CustomField::orderBy('name')->get() as $field) {
|
||||
$results[$field->db_column_name()] = $field->name;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
@@ -132,10 +159,10 @@ class Importer extends Component
|
||||
// yes, we do. Is it valid for this type of import?
|
||||
// (e.g. the import type might have been changed...?)
|
||||
if (array_key_exists($this->field_map[$i], $this->columnOptions[$type])) {
|
||||
//yes, this key *is* valid. Continue on to the next field.
|
||||
// yes, this key *is* valid. Continue on to the next field.
|
||||
continue;
|
||||
} else {
|
||||
//no, this key is *INVALID* for this import type. Better set it to null,
|
||||
// no, this key is *INVALID* for this import type. Better set it to null,
|
||||
// and we'll hope that the $aliases_fields or something else picks it up.
|
||||
$this->field_map[$i] = null; // fingers crossed! But it's not likely, tbh.
|
||||
} // TODO - strictly speaking, this isn't necessary here I don't think.
|
||||
@@ -144,7 +171,8 @@ class Importer extends Component
|
||||
foreach ($this->columnOptions[$type] as $v => $text) {
|
||||
if (strcasecmp($text, $header) === 0) { // case-INSENSITIVe on purpose!
|
||||
$this->field_map[$i] = $v;
|
||||
continue 2; //don't bother with the alias check, go to the next header
|
||||
|
||||
continue 2; // don't bother with the alias check, go to the next header
|
||||
}
|
||||
}
|
||||
// if you got here, we didn't find a match. Try the $aliases_fields
|
||||
@@ -161,6 +189,7 @@ class Importer extends Component
|
||||
|
||||
if (array_key_exists($key, $this->columnOptions[$type])) {
|
||||
$this->field_map[$i] = $key;
|
||||
|
||||
continue 3; // bust out of both of these loops and the surrounding one - e.g. move on to the next header
|
||||
}
|
||||
}
|
||||
@@ -175,17 +204,17 @@ class Importer extends Component
|
||||
{
|
||||
$this->authorize('import');
|
||||
$this->importTypes = [
|
||||
'accessory' => trans('general.accessories'),
|
||||
'asset' => trans('general.assets'),
|
||||
'assetModel' => trans('general.asset_models'),
|
||||
'component' => trans('general.components'),
|
||||
'consumable' => trans('general.consumables'),
|
||||
'license' => trans('general.licenses'),
|
||||
'location' => trans('general.locations'),
|
||||
'user' => trans('general.users'),
|
||||
'supplier' => trans('general.suppliers'),
|
||||
'manufacturer' => trans('general.manufacturers'),
|
||||
'category' => trans('general.categories'),
|
||||
'accessory' => trans('general.accessories'),
|
||||
'asset' => trans('general.assets'),
|
||||
'assetModel' => trans('general.asset_models'),
|
||||
'component' => trans('general.components'),
|
||||
'consumable' => trans('general.consumables'),
|
||||
'license' => trans('general.licenses'),
|
||||
'location' => trans('general.locations'),
|
||||
'user' => trans('general.users'),
|
||||
'supplier' => trans('general.suppliers'),
|
||||
'manufacturer' => trans('general.manufacturers'),
|
||||
'category' => trans('general.categories'),
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -391,11 +420,11 @@ class Importer extends Component
|
||||
'id' => trans('general.id'),
|
||||
'name' => trans('general.name'),
|
||||
'notes' => trans('general.notes'),
|
||||
'support_phone' => trans('admin/manufacturers/table.support_phone'),
|
||||
'support_url' => trans('admin/manufacturers/table.support_url'),
|
||||
'support_email' => trans('admin/manufacturers/table.support_email'),
|
||||
'warranty_lookup_url' => trans('admin/manufacturers/table.warranty_lookup_url'),
|
||||
'url' => trans('general.url'),
|
||||
'support_phone' => trans('admin/manufacturers/table.support_phone'),
|
||||
'support_url' => trans('admin/manufacturers/table.support_url'),
|
||||
'support_email' => trans('admin/manufacturers/table.support_email'),
|
||||
'warranty_lookup_url' => trans('admin/manufacturers/table.warranty_lookup_url'),
|
||||
'url' => trans('general.url'),
|
||||
'tag_color' => trans('general.tag_color'),
|
||||
];
|
||||
|
||||
@@ -412,9 +441,7 @@ class Importer extends Component
|
||||
'tag_color' => trans('general.tag_color'),
|
||||
];
|
||||
|
||||
|
||||
|
||||
$this->assetmodels_fields = [
|
||||
$this->assetmodels_fields = [
|
||||
'id' => trans('general.id'),
|
||||
'category' => trans('general.category'),
|
||||
'eol' => trans('general.eol'),
|
||||
@@ -437,22 +464,20 @@ class Importer extends Component
|
||||
* This just makes the user's experience a little better when they're using
|
||||
* their own CSV template.
|
||||
*/
|
||||
|
||||
$this->aliases_fields = [
|
||||
'item_name' =>
|
||||
[
|
||||
'item name',
|
||||
'asset name',
|
||||
'model name',
|
||||
'asset model name',
|
||||
'accessory name',
|
||||
'user name',
|
||||
'consumable name',
|
||||
'component name',
|
||||
'name',
|
||||
'supplier name',
|
||||
'location name',
|
||||
],
|
||||
'item_name' => [
|
||||
'item name',
|
||||
'asset name',
|
||||
'model name',
|
||||
'asset model name',
|
||||
'accessory name',
|
||||
'user name',
|
||||
'consumable name',
|
||||
'component name',
|
||||
'name',
|
||||
'supplier name',
|
||||
'location name',
|
||||
],
|
||||
'item_no' => [
|
||||
'item number',
|
||||
'item no.',
|
||||
@@ -472,180 +497,151 @@ class Importer extends Component
|
||||
'checkin_email' => [
|
||||
'checkin email',
|
||||
],
|
||||
'asset_model' =>
|
||||
[
|
||||
'model name',
|
||||
'model',
|
||||
],
|
||||
'eol_date' =>
|
||||
[
|
||||
'eol',
|
||||
'eol date',
|
||||
'asset eol date',
|
||||
],
|
||||
'eol' =>
|
||||
[
|
||||
'eol',
|
||||
'EOL',
|
||||
'eol months',
|
||||
],
|
||||
'gravatar' =>
|
||||
[
|
||||
'gravatar',
|
||||
],
|
||||
'currency' =>
|
||||
[
|
||||
'$',
|
||||
],
|
||||
'jobtitle' =>
|
||||
[
|
||||
'job title for user',
|
||||
'job title',
|
||||
],
|
||||
'full_name' =>
|
||||
[
|
||||
'full name',
|
||||
'fullname',
|
||||
trans('general.importer.checked_out_to_fullname')
|
||||
],
|
||||
'username' =>
|
||||
[
|
||||
'user name',
|
||||
'username',
|
||||
trans('general.importer.checked_out_to_username'),
|
||||
],
|
||||
'display_name' =>
|
||||
[
|
||||
'display name',
|
||||
'displayName',
|
||||
'display',
|
||||
trans('admin/users/table.display_name'),
|
||||
],
|
||||
'first_name' =>
|
||||
[
|
||||
'first name',
|
||||
trans('general.importer.checked_out_to_first_name'),
|
||||
],
|
||||
'last_name' =>
|
||||
[
|
||||
'last name',
|
||||
'lastname',
|
||||
trans('general.importer.checked_out_to_last_name'),
|
||||
],
|
||||
'email' =>
|
||||
[
|
||||
'email',
|
||||
'e-mail',
|
||||
trans('general.importer.checked_out_to_email'),
|
||||
],
|
||||
'phone_number' =>
|
||||
[
|
||||
'phone',
|
||||
'phone number',
|
||||
'phone num',
|
||||
'telephone number',
|
||||
'telephone',
|
||||
'tel.',
|
||||
],
|
||||
'mobile_number' =>
|
||||
[
|
||||
'mobile',
|
||||
'mobile number',
|
||||
'cell',
|
||||
'cellphone',
|
||||
],
|
||||
'asset_model' => [
|
||||
'model name',
|
||||
'model',
|
||||
],
|
||||
'eol_date' => [
|
||||
'eol',
|
||||
'eol date',
|
||||
'asset eol date',
|
||||
],
|
||||
'eol' => [
|
||||
'eol',
|
||||
'EOL',
|
||||
'eol months',
|
||||
],
|
||||
'gravatar' => [
|
||||
'gravatar',
|
||||
],
|
||||
'currency' => [
|
||||
'$',
|
||||
],
|
||||
'jobtitle' => [
|
||||
'job title for user',
|
||||
'job title',
|
||||
],
|
||||
'full_name' => [
|
||||
'full name',
|
||||
'fullname',
|
||||
trans('general.importer.checked_out_to_fullname'),
|
||||
],
|
||||
'username' => [
|
||||
'user name',
|
||||
'username',
|
||||
trans('general.importer.checked_out_to_username'),
|
||||
],
|
||||
'display_name' => [
|
||||
'display name',
|
||||
'displayName',
|
||||
'display',
|
||||
trans('admin/users/table.display_name'),
|
||||
],
|
||||
'first_name' => [
|
||||
'first name',
|
||||
trans('general.importer.checked_out_to_first_name'),
|
||||
],
|
||||
'last_name' => [
|
||||
'last name',
|
||||
'lastname',
|
||||
trans('general.importer.checked_out_to_last_name'),
|
||||
],
|
||||
'email' => [
|
||||
'email',
|
||||
'e-mail',
|
||||
trans('general.importer.checked_out_to_email'),
|
||||
],
|
||||
'phone_number' => [
|
||||
'phone',
|
||||
'phone number',
|
||||
'phone num',
|
||||
'telephone number',
|
||||
'telephone',
|
||||
'tel.',
|
||||
],
|
||||
'mobile_number' => [
|
||||
'mobile',
|
||||
'mobile number',
|
||||
'cell',
|
||||
'cellphone',
|
||||
],
|
||||
|
||||
'serial' =>
|
||||
[
|
||||
'serial number',
|
||||
'serial no.',
|
||||
'serial no',
|
||||
'product key',
|
||||
'key',
|
||||
],
|
||||
'require_serial' =>
|
||||
[
|
||||
trans('admin/models/general.importer.require_serial'),
|
||||
trans('admin/models/general.importer.serial_reqiured'),
|
||||
],
|
||||
'model_number' =>
|
||||
[
|
||||
'model',
|
||||
'model no',
|
||||
'model no.',
|
||||
'model number',
|
||||
'model num',
|
||||
'model num.'
|
||||
],
|
||||
'warranty_months' =>
|
||||
[
|
||||
'Warranty',
|
||||
'Warranty Months'
|
||||
],
|
||||
'qty' =>
|
||||
[
|
||||
'QTY',
|
||||
'Quantity'
|
||||
],
|
||||
'zip' =>
|
||||
[
|
||||
'Postal Code',
|
||||
'Post Code',
|
||||
'Zip Code'
|
||||
],
|
||||
'min_amt' =>
|
||||
[
|
||||
'Min Amount',
|
||||
'Minimum Amount',
|
||||
'Min Quantity',
|
||||
'Minimum Quantity',
|
||||
],
|
||||
'next_audit_date' =>
|
||||
[
|
||||
'Next Audit',
|
||||
],
|
||||
'last_checkout' =>
|
||||
[
|
||||
'Last Checkout',
|
||||
'Last Checkout Date',
|
||||
'Checkout Date',
|
||||
],
|
||||
'address2' =>
|
||||
[
|
||||
'Address 2',
|
||||
'Address2',
|
||||
],
|
||||
'ldap_ou' =>
|
||||
[
|
||||
'LDAP OU',
|
||||
'OU',
|
||||
],
|
||||
'parent_location' =>
|
||||
[
|
||||
'Parent',
|
||||
'Parent Location',
|
||||
],
|
||||
'manager' =>
|
||||
[
|
||||
'Managed By',
|
||||
'Manager Name',
|
||||
'Manager Full Name',
|
||||
],
|
||||
'manager_username' =>
|
||||
[
|
||||
'Manager Username',
|
||||
],
|
||||
'tag_color' =>
|
||||
[
|
||||
'color',
|
||||
'tag color',
|
||||
'label color',
|
||||
'color code',
|
||||
trans('general.tag_color'),
|
||||
],
|
||||
'serial' => [
|
||||
'serial number',
|
||||
'serial no.',
|
||||
'serial no',
|
||||
'product key',
|
||||
'key',
|
||||
],
|
||||
'require_serial' => [
|
||||
trans('admin/models/general.importer.require_serial'),
|
||||
trans('admin/models/general.importer.serial_reqiured'),
|
||||
],
|
||||
'model_number' => [
|
||||
'model',
|
||||
'model no',
|
||||
'model no.',
|
||||
'model number',
|
||||
'model num',
|
||||
'model num.',
|
||||
],
|
||||
'warranty_months' => [
|
||||
'Warranty',
|
||||
'Warranty Months',
|
||||
],
|
||||
'qty' => [
|
||||
'QTY',
|
||||
'Quantity',
|
||||
],
|
||||
'zip' => [
|
||||
'Postal Code',
|
||||
'Post Code',
|
||||
'Zip Code',
|
||||
],
|
||||
'min_amt' => [
|
||||
'Min Amount',
|
||||
'Minimum Amount',
|
||||
'Min Quantity',
|
||||
'Minimum Quantity',
|
||||
],
|
||||
'next_audit_date' => [
|
||||
'Next Audit',
|
||||
],
|
||||
'last_checkout' => [
|
||||
'Last Checkout',
|
||||
'Last Checkout Date',
|
||||
'Checkout Date',
|
||||
],
|
||||
'address2' => [
|
||||
'Address 2',
|
||||
'Address2',
|
||||
],
|
||||
'ldap_ou' => [
|
||||
'LDAP OU',
|
||||
'OU',
|
||||
],
|
||||
'parent_location' => [
|
||||
'Parent',
|
||||
'Parent Location',
|
||||
],
|
||||
'manager' => [
|
||||
'Managed By',
|
||||
'Manager Name',
|
||||
'Manager Full Name',
|
||||
],
|
||||
'manager_username' => [
|
||||
'Manager Username',
|
||||
],
|
||||
'tag_color' => [
|
||||
'color',
|
||||
'tag color',
|
||||
'label color',
|
||||
'color code',
|
||||
trans('general.tag_color'),
|
||||
],
|
||||
];
|
||||
|
||||
$this->columnOptions[''] = $this->getColumns(''); //blank mode? I don't know what this is supposed to mean
|
||||
$this->columnOptions[''] = $this->getColumns(''); // blank mode? I don't know what this is supposed to mean
|
||||
foreach ($this->importTypes as $type => $name) {
|
||||
$this->columnOptions[$type] = $this->getColumns($type);
|
||||
}
|
||||
@@ -657,9 +653,10 @@ class Importer extends Component
|
||||
|
||||
$this->activeFileId = $id;
|
||||
|
||||
if (!$this->activeFile) {
|
||||
if (! $this->activeFile) {
|
||||
$this->message = trans('admin/hardware/message.import.file_missing');
|
||||
$this->message_type = 'danger';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -691,21 +688,21 @@ class Importer extends Component
|
||||
// @todo: next up...handle the file being missing for other interactions...
|
||||
// for example having an import open in two tabs, deleting it, and then changing
|
||||
// the import type in the other tab. The error message below wouldn't display in that case.
|
||||
if (!$import) {
|
||||
if (! $import) {
|
||||
$this->message = trans('admin/hardware/message.import.file_already_deleted');
|
||||
$this->message_type = 'danger';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((auth()->user()->id != $import->created_by) && (!auth()->user()->isSuperUser())) {
|
||||
if ((auth()->user()->id != $import->created_by) && (! auth()->user()->isSuperUser())) {
|
||||
$this->message = trans('general.generic_model_not_found', ['model' => trans('general.import')]);
|
||||
$this->message_type = 'danger';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Storage::delete('private_uploads/imports/' . $import->file_path)) {
|
||||
if (Storage::delete('private_uploads/imports/'.$import->file_path)) {
|
||||
$import->delete();
|
||||
$this->message = trans('admin/hardware/message.import.file_delete_success');
|
||||
$this->message_type = 'success';
|
||||
|
||||
@@ -9,7 +9,9 @@ use Livewire\Component;
|
||||
class LocationScopeCheck extends Component
|
||||
{
|
||||
public $mismatched = [];
|
||||
|
||||
public $setting;
|
||||
|
||||
public $is_tested = false;
|
||||
|
||||
public function check_locations()
|
||||
@@ -18,7 +20,8 @@ class LocationScopeCheck extends Component
|
||||
$this->is_tested = true;
|
||||
}
|
||||
|
||||
public function mount() {
|
||||
public function mount()
|
||||
{
|
||||
$this->setting = Setting::getSettings();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Laravel\Passport\Client;
|
||||
use Laravel\Passport\ClientRepository;
|
||||
@@ -12,9 +11,13 @@ use Livewire\Component;
|
||||
class OauthClients extends Component
|
||||
{
|
||||
public $name;
|
||||
|
||||
public $redirect;
|
||||
|
||||
public $editClientId;
|
||||
|
||||
public $editName;
|
||||
|
||||
public $editRedirect;
|
||||
|
||||
public $authorizationError;
|
||||
@@ -50,7 +53,7 @@ class OauthClients extends Component
|
||||
if ($clientId->user_id == auth()->id()) {
|
||||
app(ClientRepository::class)->delete($clientId);
|
||||
} else {
|
||||
Log::warning('User ' . auth()->id() . ' attempted to delete client ' . $clientId->id . ' which belongs to user ' . $clientId->created_by);
|
||||
Log::warning('User '.auth()->id().' attempted to delete client '.$clientId->id.' which belongs to user '.$clientId->created_by);
|
||||
$this->authorizationError = 'You are not authorized to delete this client.';
|
||||
}
|
||||
}
|
||||
@@ -61,7 +64,7 @@ class OauthClients extends Component
|
||||
if ($token->created_by == auth()->id()) {
|
||||
app(TokenRepository::class)->revokeAccessToken($tokenId);
|
||||
} else {
|
||||
Log::warning('User ' . auth()->id() . ' attempted to delete token ' . $tokenId . ' which belongs to user ' . $token->created_by);
|
||||
Log::warning('User '.auth()->id().' attempted to delete token '.$tokenId.' which belongs to user '.$token->created_by);
|
||||
$this->authorizationError = 'You are not authorized to delete this token.';
|
||||
}
|
||||
}
|
||||
@@ -89,7 +92,7 @@ class OauthClients extends Component
|
||||
$client->redirect = $this->editRedirect;
|
||||
$client->save();
|
||||
} else {
|
||||
Log::warning('User ' . auth()->id() . ' attempted to edit client ' . $editClientId->id . ' which belongs to user ' . $client->created_by);
|
||||
Log::warning('User '.auth()->id().' attempted to edit client '.$editClientId->id.' which belongs to user '.$client->created_by);
|
||||
$this->authorizationError = 'You are not authorized to edit this client.';
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
namespace App\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class PersonalAccessTokens extends Component
|
||||
{
|
||||
public $name;
|
||||
|
||||
public $newTokenString;
|
||||
|
||||
protected $listeners = ['openModal' => 'autoFocusModalEvent'];
|
||||
|
||||
//this is just an annoying thing to make the modal input autofocus
|
||||
// this is just an annoying thing to make the modal input autofocus
|
||||
public function autoFocusModalEvent(): void
|
||||
{
|
||||
$this->dispatch('autoFocusModal');
|
||||
@@ -36,19 +35,19 @@ class PersonalAccessTokens extends Component
|
||||
|
||||
public function createToken(): void
|
||||
{
|
||||
$this->validate();
|
||||
$this->validate();
|
||||
|
||||
$newToken = auth()->user()->createToken($this->name);
|
||||
$newToken = auth()->user()->createToken($this->name);
|
||||
|
||||
$this->newTokenString = $newToken->accessToken;
|
||||
$this->newTokenString = $newToken->accessToken;
|
||||
|
||||
$this->dispatch('tokenCreated', token: $newToken->accessToken);
|
||||
}
|
||||
|
||||
public function deleteToken($tokenId): void
|
||||
{
|
||||
//this needs safety (though the scope of auth::user might kind of do it...)
|
||||
//seems like it does, test more
|
||||
// this needs safety (though the scope of auth::user might kind of do it...)
|
||||
// seems like it does, test more
|
||||
auth()->user()->tokens()->find($tokenId)?->delete();
|
||||
}
|
||||
}
|
||||
|
||||
+144
-119
@@ -2,25 +2,36 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Helpers\Helper;
|
||||
use App\Models\Setting;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use App\Models\Setting;
|
||||
use App\Helpers\Helper;
|
||||
use Osama\LaravelTeamsNotification\TeamsNotification;
|
||||
|
||||
class SlackSettingsForm extends Component
|
||||
{
|
||||
public $webhook_endpoint;
|
||||
|
||||
public $webhook_channel;
|
||||
|
||||
public $webhook_botname;
|
||||
public $isDisabled ='disabled' ;
|
||||
|
||||
public $isDisabled = 'disabled';
|
||||
|
||||
public $webhook_name;
|
||||
|
||||
public $webhook_link;
|
||||
|
||||
public $webhook_placeholder;
|
||||
|
||||
public $webhook_icon;
|
||||
|
||||
public $webhook_selected;
|
||||
|
||||
public $teams_webhook_deprecated;
|
||||
|
||||
public array $webhook_text;
|
||||
|
||||
public Setting $setting;
|
||||
@@ -30,105 +41,110 @@ class SlackSettingsForm extends Component
|
||||
public $webhook_test;
|
||||
|
||||
public $webhook_endpoint_rules;
|
||||
|
||||
protected $rules = [
|
||||
'webhook_endpoint' => 'required_with:webhook_channel|starts_with:http://,https://,ftp://,irc://,https://hooks.slack.com/services/|url|nullable',
|
||||
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
|
||||
'webhook_botname' => 'string|nullable',
|
||||
'webhook_endpoint' => 'required_with:webhook_channel|starts_with:http://,https://,ftp://,irc://,https://hooks.slack.com/services/|url|nullable',
|
||||
'webhook_channel' => 'required_with:webhook_endpoint|starts_with:#|nullable',
|
||||
'webhook_botname' => 'string|nullable',
|
||||
];
|
||||
|
||||
|
||||
public function mount() {
|
||||
$this->webhook_text= [
|
||||
"slack" => array(
|
||||
"name" => trans('admin/settings/general.slack') ,
|
||||
"icon" => 'fab fa-slack',
|
||||
"placeholder" => "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX",
|
||||
"link" => 'https://api.slack.com/messaging/webhooks',
|
||||
"test" => "testWebhook"
|
||||
),
|
||||
"general"=> array(
|
||||
"name" => trans('admin/settings/general.general_webhook'),
|
||||
"icon" => "fab fa-hashtag",
|
||||
"placeholder" => trans('general.url'),
|
||||
"link" => "",
|
||||
"test" => "testWebhook"
|
||||
),
|
||||
"google" => array(
|
||||
"name" => trans('admin/settings/general.google_workspaces'),
|
||||
"icon" => "fa-brands fa-google",
|
||||
"placeholder" => "https://chat.googleapis.com/v1/spaces/xxxxxxxx/messages?key=xxxxxx",
|
||||
"link" => "https://developers.google.com/chat/how-tos/webhooks#register_the_incoming_webhook",
|
||||
"test" => "googleWebhookTest"
|
||||
),
|
||||
"microsoft" => array(
|
||||
"name" => trans('admin/settings/general.ms_teams'),
|
||||
"icon" => "fa-brands fa-microsoft",
|
||||
"placeholder" => "https://abcd.webhook.office.com/webhookb2/XXXXXXX",
|
||||
"link" => "https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498",
|
||||
"test" => "msTeamTestWebhook"
|
||||
),
|
||||
public function mount()
|
||||
{
|
||||
$this->webhook_text = [
|
||||
'slack' => [
|
||||
'name' => trans('admin/settings/general.slack'),
|
||||
'icon' => 'fab fa-slack',
|
||||
'placeholder' => 'https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXX',
|
||||
'link' => 'https://api.slack.com/messaging/webhooks',
|
||||
'test' => 'testWebhook',
|
||||
],
|
||||
'general' => [
|
||||
'name' => trans('admin/settings/general.general_webhook'),
|
||||
'icon' => 'fab fa-hashtag',
|
||||
'placeholder' => trans('general.url'),
|
||||
'link' => '',
|
||||
'test' => 'testWebhook',
|
||||
],
|
||||
'google' => [
|
||||
'name' => trans('admin/settings/general.google_workspaces'),
|
||||
'icon' => 'fa-brands fa-google',
|
||||
'placeholder' => 'https://chat.googleapis.com/v1/spaces/xxxxxxxx/messages?key=xxxxxx',
|
||||
'link' => 'https://developers.google.com/chat/how-tos/webhooks#register_the_incoming_webhook',
|
||||
'test' => 'googleWebhookTest',
|
||||
],
|
||||
'microsoft' => [
|
||||
'name' => trans('admin/settings/general.ms_teams'),
|
||||
'icon' => 'fa-brands fa-microsoft',
|
||||
'placeholder' => 'https://abcd.webhook.office.com/webhookb2/XXXXXXX',
|
||||
'link' => 'https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498',
|
||||
'test' => 'msTeamTestWebhook',
|
||||
],
|
||||
];
|
||||
|
||||
$this->setting = Setting::getSettings();
|
||||
$this->save_button = trans('general.save');
|
||||
$this->webhook_selected = ($this->setting->webhook_selected !== '') ? $this->setting->webhook_selected : 'slack';
|
||||
$this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]["name"] ?? $this->webhook_text['slack']["name"];
|
||||
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]["icon"] ?? $this->webhook_text['slack']["icon"];
|
||||
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]["placeholder"] ?? $this->webhook_text['slack']["placeholder"];
|
||||
$this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]["link"] ?? $this->webhook_text['slack']["link"];
|
||||
$this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]["test"] ?? $this->webhook_text['slack']["test"];
|
||||
$this->webhook_name = $this->webhook_text[$this->setting->webhook_selected]['name'] ?? $this->webhook_text['slack']['name'];
|
||||
$this->webhook_icon = $this->webhook_text[$this->setting->webhook_selected]['icon'] ?? $this->webhook_text['slack']['icon'];
|
||||
$this->webhook_placeholder = $this->webhook_text[$this->setting->webhook_selected]['placeholder'] ?? $this->webhook_text['slack']['placeholder'];
|
||||
$this->webhook_link = $this->webhook_text[$this->setting->webhook_selected]['link'] ?? $this->webhook_text['slack']['link'];
|
||||
$this->webhook_test = $this->webhook_text[$this->setting->webhook_selected]['test'] ?? $this->webhook_text['slack']['test'];
|
||||
$this->webhook_endpoint = $this->setting->webhook_endpoint;
|
||||
$this->webhook_channel = $this->setting->webhook_channel;
|
||||
$this->webhook_botname = $this->setting->webhook_botname;
|
||||
$this->webhook_options = $this->setting->webhook_selected;
|
||||
$this->teams_webhook_deprecated = !Str::contains($this->webhook_endpoint, 'workflows');
|
||||
if($this->webhook_selected === 'microsoft' || $this->webhook_selected === 'google'){
|
||||
$this->teams_webhook_deprecated = ! Str::contains($this->webhook_endpoint, 'workflows');
|
||||
if ($this->webhook_selected === 'microsoft' || $this->webhook_selected === 'google') {
|
||||
$this->webhook_channel = '#NA';
|
||||
}
|
||||
|
||||
if($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null){
|
||||
$this->isDisabled= '';
|
||||
if ($this->setting->webhook_endpoint != null && $this->setting->webhook_channel != null) {
|
||||
$this->isDisabled = '';
|
||||
}
|
||||
if($this->webhook_selected === 'microsoft' && $this->teams_webhook_deprecated) {
|
||||
if ($this->webhook_selected === 'microsoft' && $this->teams_webhook_deprecated) {
|
||||
session()->flash('warning', trans('admin/settings/message.webhook.ms_teams_deprecation'));
|
||||
}
|
||||
}
|
||||
public function updated($field) {
|
||||
|
||||
$this->validateOnly($field, $this->rules);
|
||||
public function updated($field)
|
||||
{
|
||||
|
||||
$this->validateOnly($field, $this->rules);
|
||||
|
||||
}
|
||||
|
||||
public function updatedWebhookSelected() {
|
||||
public function updatedWebhookSelected()
|
||||
{
|
||||
$this->webhook_name = $this->webhook_text[$this->webhook_selected]['name'];
|
||||
$this->webhook_icon = $this->webhook_text[$this->webhook_selected]["icon"]; ;
|
||||
$this->webhook_placeholder = $this->webhook_text[$this->webhook_selected]["placeholder"];
|
||||
$this->webhook_icon = $this->webhook_text[$this->webhook_selected]['icon'];
|
||||
$this->webhook_placeholder = $this->webhook_text[$this->webhook_selected]['placeholder'];
|
||||
$this->webhook_endpoint = null;
|
||||
$this->webhook_link = $this->webhook_text[$this->webhook_selected]["link"];
|
||||
$this->webhook_test = $this->webhook_text[$this->webhook_selected]["test"];
|
||||
if($this->webhook_selected != 'slack'){
|
||||
$this->isDisabled= '';
|
||||
$this->webhook_link = $this->webhook_text[$this->webhook_selected]['link'];
|
||||
$this->webhook_test = $this->webhook_text[$this->webhook_selected]['test'];
|
||||
if ($this->webhook_selected != 'slack') {
|
||||
$this->isDisabled = '';
|
||||
$this->save_button = trans('general.save');
|
||||
}
|
||||
if($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google'){
|
||||
if ($this->webhook_selected == 'microsoft' || $this->webhook_selected == 'google') {
|
||||
$this->webhook_channel = '#NA';
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedwebhookEndpoint()
|
||||
{
|
||||
$this->teams_webhook_deprecated = !Str::contains($this->webhook_endpoint, 'workflows');
|
||||
$this->teams_webhook_deprecated = ! Str::contains($this->webhook_endpoint, 'workflows');
|
||||
}
|
||||
|
||||
private function isButtonDisabled() {
|
||||
if (empty($this->webhook_endpoint)) {
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
}
|
||||
if (empty($this->webhook_channel)) {
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
}
|
||||
private function isButtonDisabled()
|
||||
{
|
||||
if (empty($this->webhook_endpoint)) {
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
}
|
||||
if (empty($this->webhook_channel)) {
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
@@ -139,7 +155,8 @@ class SlackSettingsForm extends Component
|
||||
|
||||
}
|
||||
|
||||
public function testWebhook(){
|
||||
public function testWebhook()
|
||||
{
|
||||
|
||||
$webhook = new Client([
|
||||
'base_url' => e($this->webhook_endpoint),
|
||||
@@ -151,9 +168,9 @@ class SlackSettingsForm extends Component
|
||||
|
||||
$payload = json_encode(
|
||||
[
|
||||
'channel' => e($this->webhook_channel),
|
||||
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||
'username' => e($this->webhook_botname),
|
||||
'channel' => e($this->webhook_channel),
|
||||
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||
'username' => e($this->webhook_botname),
|
||||
'icon_emoji' => ':heart:',
|
||||
|
||||
]);
|
||||
@@ -161,29 +178,31 @@ class SlackSettingsForm extends Component
|
||||
try {
|
||||
$test = $webhook->post($this->webhook_endpoint, ['body' => $payload, 'headers' => ['Content-Type' => 'application/json']]);
|
||||
|
||||
if(($test->getStatusCode() == 302)||($test->getStatusCode() == 301)){
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
||||
if (($test->getStatusCode() == 302) || ($test->getStatusCode() == 301)) {
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
||||
}
|
||||
$this->isDisabled='';
|
||||
$this->isDisabled = '';
|
||||
$this->save_button = trans('general.save');
|
||||
return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
|
||||
|
||||
return session()->flash('success', trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$this->isDisabled='disabled';
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||
}
|
||||
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error_misc'));
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error_misc'));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function clearSettings(){
|
||||
public function clearSettings()
|
||||
{
|
||||
|
||||
if (Helper::isDemoMode()) {
|
||||
session()->flash('error',trans('general.feature_disabled'));
|
||||
session()->flash('error', trans('general.feature_disabled'));
|
||||
} else {
|
||||
$this->webhook_endpoint = '';
|
||||
$this->webhook_channel = '';
|
||||
@@ -201,7 +220,7 @@ class SlackSettingsForm extends Component
|
||||
public function submit()
|
||||
{
|
||||
if (Helper::isDemoMode()) {
|
||||
session()->flash('error',trans('general.feature_disabled'));
|
||||
session()->flash('error', trans('general.feature_disabled'));
|
||||
} else {
|
||||
$this->validate($this->rules);
|
||||
|
||||
@@ -212,14 +231,16 @@ class SlackSettingsForm extends Component
|
||||
|
||||
$this->setting->save();
|
||||
|
||||
session()->flash('success',trans('admin/settings/message.update.success'));
|
||||
session()->flash('success', trans('admin/settings/message.update.success'));
|
||||
}
|
||||
|
||||
}
|
||||
public function googleWebhookTest(){
|
||||
|
||||
public function googleWebhookTest()
|
||||
{
|
||||
|
||||
$payload = [
|
||||
"text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||
];
|
||||
|
||||
try {
|
||||
@@ -228,65 +249,69 @@ class SlackSettingsForm extends Component
|
||||
])->post($this->webhook_endpoint,
|
||||
$payload)->throw();
|
||||
|
||||
|
||||
if (($response->getStatusCode() == 302) || ($response->getStatusCode() == 301)) {
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
||||
}
|
||||
|
||||
$this->isDisabled='';
|
||||
$this->isDisabled = '';
|
||||
$this->save_button = trans('general.save');
|
||||
return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
|
||||
|
||||
return session()->flash('success', trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$this->isDisabled='disabled';
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||
}
|
||||
}
|
||||
public function msTeamTestWebhook(){
|
||||
|
||||
public function msTeamTestWebhook()
|
||||
{
|
||||
|
||||
try {
|
||||
|
||||
if($this->teams_webhook_deprecated){
|
||||
//will use the deprecated webhook format
|
||||
if ($this->teams_webhook_deprecated) {
|
||||
// will use the deprecated webhook format
|
||||
$payload =
|
||||
[
|
||||
"@type" => "MessageCard",
|
||||
"@context" => "http://schema.org/extensions",
|
||||
"summary" => trans('mail.snipe_webhook_summary'),
|
||||
"title" => trans('mail.snipe_webhook_test'),
|
||||
"text" => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||
'@type' => 'MessageCard',
|
||||
'@context' => 'http://schema.org/extensions',
|
||||
'summary' => trans('mail.snipe_webhook_summary'),
|
||||
'title' => trans('mail.snipe_webhook_test'),
|
||||
'text' => trans('general.webhook_test_msg', ['app' => $this->webhook_name]),
|
||||
];
|
||||
$response = Http::withHeaders([
|
||||
'content-type' => 'application/json',
|
||||
])->post($this->webhook_endpoint,
|
||||
$payload)->throw();
|
||||
} else {
|
||||
$notification = new TeamsNotification($this->webhook_endpoint);
|
||||
$message = trans('general.webhook_test_msg', ['app' => $this->webhook_name]);
|
||||
$notification->success()->sendMessage($message);
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'content-type' => 'application/json',
|
||||
])->post($this->webhook_endpoint);
|
||||
}
|
||||
else {
|
||||
$notification = new TeamsNotification($this->webhook_endpoint);
|
||||
$message = trans('general.webhook_test_msg', ['app' => $this->webhook_name]);
|
||||
$notification->success()->sendMessage($message);
|
||||
|
||||
$response = Http::withHeaders([
|
||||
'content-type' => 'application/json',
|
||||
])->post($this->webhook_endpoint);
|
||||
}
|
||||
if (($response->getStatusCode() == 302) || ($response->getStatusCode() == 301)) {
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
||||
}
|
||||
$this->isDisabled = '';
|
||||
$this->save_button = trans('general.save');
|
||||
|
||||
if(($response->getStatusCode() == 302)||($response->getStatusCode() == 301)){
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error_redirect', ['endpoint' => $this->webhook_endpoint]));
|
||||
}
|
||||
$this->isDisabled='';
|
||||
$this->save_button = trans('general.save');
|
||||
return session()->flash('success' , trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
|
||||
return session()->flash('success', trans('admin/settings/message.webhook.success', ['webhook_name' => $this->webhook_name]));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
|
||||
$this->isDisabled='disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||
}
|
||||
$this->isDisabled = 'disabled';
|
||||
$this->save_button = trans('admin/settings/general.webhook_presave');
|
||||
|
||||
return session()->flash('error' , trans('admin/settings/message.webhook.error_misc'));
|
||||
}
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error', ['error_message' => $e->getMessage(), 'app' => $this->webhook_name]));
|
||||
}
|
||||
|
||||
return session()->flash('error', trans('admin/settings/message.webhook.error_misc'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user