diff --git a/composer.json b/composer.json
index d2826b1..a12508a 100755
--- a/composer.json
+++ b/composer.json
@@ -22,7 +22,7 @@
"spatie/laravel-package-tools": "^1.16"
},
"require-dev": {
- "filament/filament": "^3.2",
+ "filament/filament": "^3.3",
"larastan/larastan": "^2.9",
"laravel/pint": "^1.16",
"nunomaduro/collision": "^8.1.1||^7.10.0",
diff --git a/config/filament-activitylog.php b/config/filament-activitylog.php
index 112e1d3..49eb3db 100755
--- a/config/filament-activitylog.php
+++ b/config/filament-activitylog.php
@@ -2,20 +2,21 @@
return [
'resources' => [
- 'label' => 'Activity Log',
- 'plural_label' => 'Activity Logs',
- 'hide_restore_action' => false,
- 'restore_action_label' => 'Restore',
- 'hide_resource_action' => false,
- 'resource_action_label' => 'View',
- 'navigation_item' => true,
- 'navigation_group' => null,
- 'navigation_icon' => 'heroicon-o-shield-check',
- 'navigation_sort' => null,
- 'default_sort_column' => 'id',
- 'default_sort_direction' => 'desc',
- 'navigation_count_badge' => false,
- 'resource' => \Rmsramos\Activitylog\Resources\ActivitylogResource::class,
+ 'label' => 'Activity Log',
+ 'plural_label' => 'Activity Logs',
+ 'hide_restore_action' => false,
+ 'restore_action_label' => 'Restore',
+ 'hide_resource_action' => false,
+ 'hide_restore_model_action' => true,
+ 'resource_action_label' => 'View',
+ 'navigation_item' => true,
+ 'navigation_group' => null,
+ 'navigation_icon' => 'heroicon-o-shield-check',
+ 'navigation_sort' => null,
+ 'default_sort_column' => 'id',
+ 'default_sort_direction' => 'desc',
+ 'navigation_count_badge' => false,
+ 'resource' => \Rmsramos\Activitylog\Resources\ActivitylogResource::class,
],
'date_format' => 'd/m/Y',
'datetime_format' => 'd/m/Y H:i:s',
diff --git a/resources/lang/en/action.php b/resources/lang/en/action.php
index b9e05b7..e8819f9 100644
--- a/resources/lang/en/action.php
+++ b/resources/lang/en/action.php
@@ -12,7 +12,12 @@
'updated' => 'updated',
'restored' => 'restored',
],
- 'view' => 'View',
- 'edit' => 'Edit',
- 'restore' => 'Restore',
+ 'view' => 'View',
+ 'edit' => 'Edit',
+ 'restore' => 'Restore',
+ 'restore_soft_delete' => [
+ 'label' => 'Restore Model',
+ 'modal_heading' => 'Restore Deleted Model',
+ 'modal_description' => 'This will restore the model that was deleted (soft delete).',
+ ],
];
diff --git a/resources/lang/en/notifications.php b/resources/lang/en/notifications.php
index 6dbd4e7..a6e8141 100644
--- a/resources/lang/en/notifications.php
+++ b/resources/lang/en/notifications.php
@@ -6,4 +6,7 @@
'activity_restored_successfully' => 'Activity restored successfully.',
'failed_to_restore_activity' => 'Failed to restore activity : :error.',
'subject_not_found' => 'Subject not found.',
+ 'unable_to_restore_this_model' => 'Unable to restore this model',
+ 'model_successfully_restored' => 'Model successfully restored',
+ 'error_restoring_model' => 'Erro ao restaurar modelo',
];
diff --git a/resources/lang/en/tables.php b/resources/lang/en/tables.php
index ecc8cf6..4812552 100755
--- a/resources/lang/en/tables.php
+++ b/resources/lang/en/tables.php
@@ -9,7 +9,9 @@
'label' => 'Event',
],
'subject_type' => [
- 'label' => 'Subject',
+ 'label' => 'Subject',
+ 'soft_deleted' => ' (Soft Deleted)',
+ 'deleted' => ' (Deleted)',
],
'causer' => [
'label' => 'User',
diff --git a/resources/lang/pt_BR/action.php b/resources/lang/pt_BR/action.php
index 20db56f..210d444 100644
--- a/resources/lang/pt_BR/action.php
+++ b/resources/lang/pt_BR/action.php
@@ -12,7 +12,12 @@
'updated' => 'atualizado',
'restored' => 'restaurado',
],
- 'view' => 'Visualizar',
- 'edit' => 'Editar',
- 'restore' => 'Restaurar',
+ 'view' => 'Visualizar',
+ 'edit' => 'Editar',
+ 'restore' => 'Restaurar',
+ 'restore_soft_delete' => [
+ 'label' => 'Restaurar Modelo',
+ 'modal_heading' => 'Restaurar Modelo Excluído',
+ 'modal_description' => 'Isso irá restaurar o modelo que foi excluído (soft delete).',
+ ],
];
diff --git a/resources/lang/pt_BR/infolists.php b/resources/lang/pt_BR/infolists.php
index 33d87e9..95f39f3 100644
--- a/resources/lang/pt_BR/infolists.php
+++ b/resources/lang/pt_BR/infolists.php
@@ -3,7 +3,7 @@
return [
'components' => [
'created_by_at' => 'O :subject foi :event por :causer.
Atualizado em: :update_at',
- 'updater_updated' => ':causer :event para:
:changes',
+ 'updater_updated' => ':causer :event o seguinte:
:changes',
'from_oldvalue_to_newvalue' => '- :key de :old_value para :new_value',
'to_newvalue' => '- :key :new_value',
'unknown' => 'Desconhecido',
diff --git a/resources/lang/pt_BR/notifications.php b/resources/lang/pt_BR/notifications.php
index 4179a6e..0da53d7 100644
--- a/resources/lang/pt_BR/notifications.php
+++ b/resources/lang/pt_BR/notifications.php
@@ -6,4 +6,7 @@
'activity_restored_successfully' => 'Atividade restaurada com sucesso.',
'failed_to_restore_activity' => 'Falha ao restaurar a atividade: :error.',
'subject_not_found' => 'Registro não encontrado.',
+ 'unable_to_restore_this_model' => 'Não é possível restaurar este modelo',
+ 'model_successfully_restored' => 'Modelo restaurado com sucesso',
+ 'error_restoring_model' => 'Erro ao restaurar modelo',
];
diff --git a/resources/lang/pt_BR/tables.php b/resources/lang/pt_BR/tables.php
index 5d2eb5d..53e9a7a 100755
--- a/resources/lang/pt_BR/tables.php
+++ b/resources/lang/pt_BR/tables.php
@@ -9,7 +9,9 @@
'label' => 'Evento',
],
'subject_type' => [
- 'label' => 'Assunto',
+ 'label' => 'Assunto',
+ 'soft_deleted' => ' (Delete suave)',
+ 'deleted' => ' (Deletado)',
],
'causer' => [
'label' => 'Usuário',
diff --git a/src/Actions/Concerns/ActionContent.php b/src/Actions/Concerns/ActionContent.php
index 163dcda..0ad4bf1 100644
--- a/src/Actions/Concerns/ActionContent.php
+++ b/src/Actions/Concerns/ActionContent.php
@@ -71,7 +71,14 @@ protected function setUp(): void
}
return Activity::query()
- ->with(['subject', 'causer'])
+ ->with([
+ 'subject' => function ($query) {
+ if (method_exists($query, 'withTrashed')) {
+ $query->withTrashed();
+ }
+ },
+ 'causer',
+ ])
->where(function (Builder $query) use ($record) {
$query->where('subject_type', $record->getMorphClass())
->where('subject_id', $record->getKey());
@@ -91,6 +98,7 @@ protected function setUp(): void
});
}
} catch (\Exception $e) {
+ // Ignore errors
}
}
}
@@ -98,7 +106,6 @@ protected function setUp(): void
});
};
}
-
private function configureInfolist(): void
{
$this->infolist(function (?Model $record, Infolist $infolist) {
@@ -295,6 +302,12 @@ protected function formatActivityData($activity): array
}
}
+ if ($activity->event === 'restored') {
+ if (empty($properties) && $activity->description !== 'restored') {
+ $properties['description'] = $activity->description;
+ }
+ }
+
return [
'log_name' => $activity->log_name,
'description' => $activity->description,
diff --git a/src/ActivitylogPlugin.php b/src/ActivitylogPlugin.php
index 704ecd4..56f513b 100755
--- a/src/ActivitylogPlugin.php
+++ b/src/ActivitylogPlugin.php
@@ -22,6 +22,8 @@ class ActivitylogPlugin implements Plugin
protected bool|Closure|null $isRestoreActionHidden = null;
+ protected bool|Closure|null $isRestoreModelActionHidden = null;
+
protected Closure|bool $navigationItem = true;
protected string|Closure|null $navigationGroup = null;
@@ -101,6 +103,11 @@ public function getIsRestoreActionHidden(): bool
return $this->evaluate($this->isRestoreActionHidden) ?? config('filament-activitylog.resources.hide_restore_action');
}
+ public function getIsRestoreModelActionHidden(): bool
+ {
+ return $this->evaluate($this->isRestoreModelActionHidden) ?? config('filament-activitylog.resources.hide_restore_model_action');
+ }
+
public function getPluralLabel(): string
{
return $this->evaluate($this->pluralLabel) ?? config('filament-activitylog.resources.plural_label');
@@ -202,6 +209,13 @@ public function isRestoreActionHidden(bool|Closure $isHidden): static
return $this;
}
+ public function isRestoreModelActionHidden(bool|Closure $isHidden): static
+ {
+ $this->isRestoreModelActionHidden = $isHidden;
+
+ return $this;
+ }
+
public function pluralLabel(string|Closure $label): static
{
$this->pluralLabel = $label;
diff --git a/src/Infolists/Components/TimeLinePropertiesEntry.php b/src/Infolists/Components/TimeLinePropertiesEntry.php
index 1060db7..ad1b958 100644
--- a/src/Infolists/Components/TimeLinePropertiesEntry.php
+++ b/src/Infolists/Components/TimeLinePropertiesEntry.php
@@ -52,6 +52,8 @@ private function getPropertyChanges(array $properties): array
$changes = $this->compareOldAndNewValues($properties['old'], $properties['attributes']);
} elseif (isset($properties['attributes'])) {
$changes = $this->getNewValues($properties['attributes']);
+ } elseif (isset($properties['old'])) {
+ $changes = $this->getNewValues($properties['old']);
}
return $changes;
diff --git a/src/Infolists/Components/TimeLineTitleEntry.php b/src/Infolists/Components/TimeLineTitleEntry.php
index a093ad8..31b56ad 100644
--- a/src/Infolists/Components/TimeLineTitleEntry.php
+++ b/src/Infolists/Components/TimeLineTitleEntry.php
@@ -56,7 +56,7 @@ private function modifiedTitle($state): string|HtmlString|Closure
if ($this->configureTitleUsing !== null && $this->shouldConfigureTitleUsing !== null && $this->evaluate($this->shouldConfigureTitleUsing)) {
return $this->evaluate($this->configureTitleUsing, ['state' => $state]);
} else {
- if ($state['description'] == $state['event']) {
+ if ($state['description'] == $state['event'] || $state['event'] === 'restored') {
$className = property_exists($state['subject'], 'activityTitleName') && ! empty($state['subject']::$activityTitleName)
? $state['subject']::$activityTitleName
: Str::lower(Str::snake(class_basename($state['subject']), ' '));
diff --git a/src/Resources/ActivitylogResource.php b/src/Resources/ActivitylogResource.php
index 75af8aa..8f2671c 100755
--- a/src/Resources/ActivitylogResource.php
+++ b/src/Resources/ActivitylogResource.php
@@ -24,6 +24,7 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
@@ -35,6 +36,7 @@
use Rmsramos\Activitylog\Resources\ActivitylogResource\Pages\ListActivitylog;
use Rmsramos\Activitylog\Resources\ActivitylogResource\Pages\ViewActivitylog;
use Rmsramos\Activitylog\Traits\HasCustomActivityResource;
+
use Spatie\Activitylog\Models\Activity;
class ActivitylogResource extends Resource
@@ -84,79 +86,6 @@ public static function getNavigationBadge(): ?string
number_format(static::getModel()::count()) : null;
}
- public static function restoreActivity(int|string $key): void
- {
- $activity = Activity::find($key);
-
- if (! $activity) {
- Notification::make()
- ->title(__('activitylog::notifications.activity_not_found'))
- ->danger()
- ->send();
-
- return;
- }
-
- $oldProperties = data_get($activity, 'properties.old');
- $newProperties = data_get($activity, 'properties.attributes');
-
- if ($oldProperties === null) {
- Notification::make()
- ->title(__('activitylog::notifications.no_properties_to_restore'))
- ->danger()
- ->send();
-
- return;
- }
-
- try {
- $record = $activity->subject;
-
- if (! $record) {
- Notification::make()
- ->title(__('activitylog::notifications.subject_not_found'))
- ->danger()
- ->send();
-
- return;
- }
-
- // Temporarily disable activity logging to prevent updated log
- activity()->withoutLogs(function () use ($record, $oldProperties) {
- $record->update($oldProperties);
- });
-
- // Log the restore event
- $user = auth()->user();
-
- if ($user) {
- activity()
- ->performedOn($record)
- ->causedBy(auth()->user())
- ->withProperties(['attributes' => $oldProperties, 'old' => $newProperties])
- ->tap(function ($log) {
- $log->event = 'restored';
- })
- ->log('restored');
- }
-
- Notification::make()
- ->title(__('activitylog::notifications.activity_restored_successfully'))
- ->success()
- ->send();
- } catch (ModelNotFoundException $e) {
- Notification::make()
- ->title(__('activitylog::notifications.record_not_found'))
- ->danger()
- ->send();
- } catch (Exception $e) {
- Notification::make()
- ->title(__('activitylog::notifications.failed_to_restore_activity', ['error' => $e->getMessage()]))
- ->danger()
- ->send();
- }
- }
-
private static function getResourceUrl(Activity $record): string
{
$panelID = Filament::getCurrentPanel()->getId();
@@ -185,31 +114,6 @@ private static function getResourceUrl(Activity $record): string
return '#';
}
- private static function canViewResource(Activity $record): bool
- {
- if ($record->subject_type && $record->subject_id) {
- try {
- $model = app($record->subject_type);
-
- if (ActivityLogHelper::classUsesTrait($model, HasCustomActivityResource::class)) {
- $resourceModel = $model->getFilamentActualResourceModel($record);
- $user = auth()->user();
-
- return $user && $user->can('update', $resourceModel);
- }
-
- // Fallback to check if the user can edit the model using a generic policy
- $user = auth()->user();
-
- return $user && $record->subject && $user->can('update', $record->subject);
- } catch (Exception $e) {
- return false;
- }
- }
-
- return false;
- }
-
public static function form(Form $form): Form
{
return $form
@@ -218,7 +122,6 @@ public static function form(Form $form): Form
Section::make([
TextInput::make('causer_id')
->afterStateHydrated(function ($component, ?Model $record) {
- /** @phpstan-ignore-next-line */
return $component->state($record?->causer?->name ?? '-');
})
->label(__('activitylog::forms.fields.causer.label')),
@@ -273,9 +176,14 @@ public static function form(Form $form): Form
->icon('heroicon-o-arrow-uturn-left')
->color('primary')
->action(fn (Activity $record) => self::restoreActivity($record->id))
- ->visible(fn () => ! ActivitylogPlugin::get()->getIsRestoreActionHidden())
+ ->visible(function (Activity $record): bool {
+ return ! ActivitylogPlugin::get()->getIsRestoreActionHidden() && $record->properties &&
+ data_get($record->properties, 'old') !== null &&
+ $record->subject !== null && $record->event !== 'deleted';
+ })
->authorize(fn () => auth()->user()?->can('restore_activitylog') ?? false)
->requiresConfirmation(),
+
Action::make('edit')
->label(__('activitylog::action.edit'))
->icon('heroicon-o-eye')
@@ -283,6 +191,21 @@ public static function form(Form $form): Form
->url(fn (Activity $record) => self::getResourceUrl($record))
->visible(fn () => ! ActivitylogPlugin::get()->getIsResourceActionHidden())
->authorize(fn (Activity $record) => self::canViewResource($record)),
+
+ Action::make('restore_soft_delete')
+ ->label(__('activitylog::action.restore_soft_delete.label'))
+ ->icon('heroicon-o-arrow-uturn-left')
+ ->color('warning')
+ ->visible(function (Activity $record): bool {
+ return static::canRestoreSubjectFromSoftDelete($record);
+ })
+ ->action(function (Activity $record) {
+ static::restoreSubjectFromSoftDelete($record);
+ })
+ ->authorize(fn () => auth()->user()?->can('restore_activitylog') ?? false)
+ ->requiresConfirmation()
+ ->modalHeading(__('activitylog::action.restore_soft_delete.modal_heading'))
+ ->modalDescription(__('activitylog::action.restore_soft_delete.modal_description')),
])
->columns()
->visible(fn (?Model $record) => $record?->properties?->count() > 0)
@@ -298,7 +221,7 @@ public static function form(Form $form): Form
if ($properties->count()) {
$schema[] = KeyValue::make('properties')
->afterStateHydrated(function (KeyValue $component) use ($properties) {
- $component->state($properties->toArray());
+ $component->state(static::flattenArrayForKeyValue($properties->toArray()));
})
->label(__('activitylog::forms.fields.properties.label'))
->columnSpan('full')
@@ -308,7 +231,8 @@ public static function form(Form $form): Form
if ($old = $record->properties->get('old')) {
$schema[] = KeyValue::make('old')
->afterStateHydrated(function (KeyValue $component) use ($old) {
- $component->state(is_array($old) ? $old : []);
+ $oldArray = is_array($old) ? $old : [];
+ $component->state(static::flattenArrayForKeyValue($oldArray));
})
->label(__('activitylog::forms.fields.old.label'))
->disabled();
@@ -317,7 +241,8 @@ public static function form(Form $form): Form
if ($attributes = $record->properties->get('attributes')) {
$schema[] = KeyValue::make('attributes')
->afterStateHydrated(function (KeyValue $component) use ($attributes) {
- $component->state(is_array($attributes) ? $attributes : []);
+ $attributesArray = is_array($attributes) ? $attributes : [];
+ $component->state(static::flattenArrayForKeyValue($attributesArray));
})
->label(__('activitylog::forms.fields.attributes.label'))
->disabled();
@@ -328,6 +253,21 @@ public static function form(Form $form): Form
])->columns(1);
}
+ private static function flattenArrayForKeyValue(array $data): array
+ {
+ $flattened = [];
+
+ foreach ($data as $key => $value) {
+ if (is_array($value) || is_object($value)) {
+ $flattened[$key] = json_encode($value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
+ } else {
+ $flattened[$key] = $value;
+ }
+ }
+
+ return $flattened;
+ }
+
public static function table(Table $table): Table
{
return $table
@@ -349,6 +289,19 @@ public static function table(Table $table): Table
]);
}
+ public static function getEloquentQuery(): Builder
+ {
+ return parent::getEloquentQuery()
+ ->with([
+ 'subject' => function ($query) {
+ if (method_exists($query, 'withTrashed')) {
+ $query->withTrashed();
+ }
+ },
+ 'causer',
+ ]);
+ }
+
public static function getLogNameColumnComponent(): Column
{
return TextColumn::make('log_name')
@@ -387,7 +340,17 @@ public static function getSubjectTypeColumnComponent(): Column
return '-';
}
- return Str::of($state)->afterLast('\\')->headline() . ' # ' . $record->subject_id;
+ $subjectInfo = Str::of($state)->afterLast('\\')->headline() . ' # ' . $record->subject_id;
+
+ if ($record->subject) {
+ if (method_exists($record->subject, 'trashed') && $record->subject->trashed()) {
+ $subjectInfo .= __('activitylog::tables.columns.subject_type.soft_deleted');
+ }
+ } else {
+ $subjectInfo .= __('activitylog::tables.columns.subject_type.deleted');
+ }
+
+ return $subjectInfo;
})
->searchable()
->hidden(fn (Livewire $livewire) => $livewire instanceof ActivitylogRelationManager);
@@ -435,6 +398,22 @@ public static function getCreatedAtColumnComponent(): Column
return $column;
}
+ public static function getDatePickerCompoment(string $label): DatePicker
+ {
+ $field = DatePicker::make($label)
+ ->format(ActivitylogPlugin::get()->getDateFormat())
+ ->label(__('activitylog::tables.filters.created_at.' . $label));
+
+ // Apply the custom callback if set
+ $callback = ActivitylogPlugin::get()->getDatePickerCallback();
+
+ if ($callback) {
+ $field = $callback($field);
+ }
+
+ return $field;
+ }
+
public static function getDateFilterComponent(): Filter
{
return Filter::make('created_at')
@@ -476,22 +455,6 @@ public static function getDateFilterComponent(): Filter
});
}
- public static function getDatePickerCompoment(string $label): DatePicker
- {
- $field = DatePicker::make($label)
- ->format(ActivitylogPlugin::get()->getDateFormat())
- ->label(__('activitylog::tables.filters.created_at.' . $label));
-
- // Apply the custom callback if set
- $callback = ActivitylogPlugin::get()->getDatePickerCallback();
-
- if ($callback) {
- $field = $callback($field);
- }
-
- return $field;
- }
-
public static function getEventFilterComponent(): SelectFilter
{
return SelectFilter::make('event')
@@ -527,4 +490,200 @@ public static function canAccess(): bool
return ActivitylogPlugin::get()->isAuthorized();
}
+
+ private static function canViewResource(Activity $record): bool
+ {
+ if ($record->subject_type && $record->subject_id) {
+ try {
+ $model = app($record->subject_type);
+
+ if (ActivityLogHelper::classUsesTrait($model, HasCustomActivityResource::class)) {
+ $resourceModel = $model->getFilamentActualResourceModel($record);
+ $user = auth()->user();
+
+ return $user && $user->can('update', $resourceModel);
+ }
+
+ // Fallback to check if the user can edit the model using a generic policy
+ $user = auth()->user();
+
+ return $user && $record->subject && $user->can('update', $record->subject);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ public static function restoreActivity(int|string $key): void
+ {
+ $activity = Activity::find($key);
+
+ if (! $activity) {
+ Notification::make()
+ ->title(__('activitylog::notifications.activity_not_found'))
+ ->danger()
+ ->send();
+
+ return;
+ }
+
+ $oldProperties = data_get($activity, 'properties.old');
+ $newProperties = data_get($activity, 'properties.attributes');
+
+ if ($oldProperties === null) {
+ Notification::make()
+ ->title(__('activitylog::notifications.no_properties_to_restore'))
+ ->danger()
+ ->send();
+
+ return;
+ }
+
+ try {
+ $record = $activity->subject;
+
+ if (! $record) {
+ Notification::make()
+ ->title(__('activitylog::notifications.subject_not_found'))
+ ->danger()
+ ->send();
+
+ return;
+ }
+
+ // Temporarily disable activity logging to prevent updated log
+ activity()->withoutLogs(function () use ($record, $oldProperties) {
+ $record->update($oldProperties);
+ });
+
+ if (auth()->user()) {
+ activity()
+ ->performedOn($record)
+ ->causedBy(auth()->user())
+ ->withProperties([
+ 'attributes' => $oldProperties,
+ 'old' => $newProperties,
+ ])
+ ->tap(function ($log) {
+ $log->event = 'restored';
+ })
+ ->log('restored');
+ }
+
+ Notification::make()
+ ->title(__('activitylog::notifications.activity_restored_successfully'))
+ ->success()
+ ->send();
+ } catch (ModelNotFoundException $e) {
+ Notification::make()
+ ->title(__('activitylog::notifications.record_not_found'))
+ ->danger()
+ ->send();
+ } catch (Exception $e) {
+ Notification::make()
+ ->title(__('activitylog::notifications.failed_to_restore_activity', ['error' => $e->getMessage()]))
+ ->danger()
+ ->send();
+ }
+ }
+
+ public static function canRestoreSubjectFromSoftDelete(Activity $record): bool
+ {
+ if (ActivitylogPlugin::get()->getIsRestoreModelActionHidden()) {
+ return false;
+ }
+
+ if ($record->event !== 'deleted') {
+ return false;
+ }
+
+ if (! $record->subject) {
+ return false;
+ }
+
+ if (! method_exists($record->subject, 'trashed') ||
+ ! method_exists($record->subject, 'restore')) {
+ return false;
+ }
+
+ if (! $record->subject->trashed()) {
+ return false;
+ }
+
+ $user = auth()->user();
+
+ if ($user && method_exists($record->subject, 'exists')) {
+ try {
+ return $user->can('restore', $record->subject);
+ } catch (\Exception $e) {
+ return true;
+ }
+ }
+
+ return true;
+ }
+
+ public static function restoreSubjectFromSoftDelete(Activity $record): void
+ {
+ if (! static::canRestoreSubjectFromSoftDelete($record)) {
+ Notification::make()
+ ->title(__('activitylog::notifications.unable_to_restore_this_model'))
+ ->danger()
+ ->send();
+
+ return;
+ }
+
+ try {
+ DB::beginTransaction();
+
+ $subject = $record->subject;
+
+ $beforeRestore = $subject->toArray();
+
+ activity()->withoutLogs(function () use ($subject) {
+ $subject->restore();
+ });
+
+ $subject->refresh();
+ $afterRestore = $subject->toArray();
+
+ if (auth()->user()) {
+ activity()
+ ->performedOn($subject)
+ ->causedBy(auth()->user())
+ ->withProperties([
+ 'attributes' => $afterRestore,
+ 'old' => $beforeRestore,
+ 'restore_metadata' => [
+ 'restored_from_soft_delete' => true,
+ 'original_activity_id' => $record->id,
+ 'restore_type' => 'soft_delete',
+ ],
+ ])
+ ->tap(function ($log) {
+ $log->event = 'restored';
+ })
+ ->log('restored');
+ }
+
+ DB::commit();
+
+ Notification::make()
+ ->title(__('activitylog::notifications.model_successfully_restored'))
+ ->success()
+ ->send();
+
+ } catch (Exception $e) {
+ DB::rollBack();
+
+ Notification::make()
+ ->title(__('activitylog::notifications.error_restoring_model'))
+ ->body('Erro: ' . $e->getMessage())
+ ->danger()
+ ->send();
+ }
+ }
}