diff --git a/config/filament-activitylog.php b/config/filament-activitylog.php index ad68b21..112e1d3 100755 --- a/config/filament-activitylog.php +++ b/config/filament-activitylog.php @@ -17,6 +17,6 @@ 'navigation_count_badge' => false, 'resource' => \Rmsramos\Activitylog\Resources\ActivitylogResource::class, ], - 'date_format' => 'd/m/Y', - 'datetime_format' => 'd/m/Y H:i:s', + 'date_format' => 'd/m/Y', + 'datetime_format' => 'd/m/Y H:i:s', ]; diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php index ecda00a..5a203fa 100755 --- a/resources/lang/en/forms.php +++ b/resources/lang/en/forms.php @@ -2,7 +2,7 @@ return [ 'changes' => 'Changes', - 'fields' => [ + 'fields' => [ 'log_name' => [ 'label' => 'Type', ], diff --git a/resources/lang/en/infolists.php b/resources/lang/en/infolists.php index cedaf25..271d6cb 100644 --- a/resources/lang/en/infolists.php +++ b/resources/lang/en/infolists.php @@ -2,10 +2,10 @@ return [ 'components' => [ - "created_by_at" => "The :subject was :event by :causer.
Updated at: :update_at", - "updater_updated" => ":causer :event the following:
:changes", - "from_oldvalue_to_newvalue" => "- :key from :old_value to :new_value", - "to_newvalue" => "- :key :new_value", - "unknown" => "Unknown" + 'created_by_at' => 'The :subject was :event by :causer.
Updated at: :update_at', + 'updater_updated' => ':causer :event the following:
:changes', + 'from_oldvalue_to_newvalue' => '- :key from :old_value to :new_value', + 'to_newvalue' => '- :key :new_value', + 'unknown' => 'Unknown', ], ]; diff --git a/resources/lang/en/notifications.php b/resources/lang/en/notifications.php index da098fe..6dbd4e7 100644 --- a/resources/lang/en/notifications.php +++ b/resources/lang/en/notifications.php @@ -1,8 +1,9 @@ 'No properties to restore.' - 'activity_restored' => 'Activity restored.' - 'activity_restored_successfully' => 'Activity restored successfully.' - 'failed_to_restore_activity' => 'Failed to restore activity : :error.' + 'no_properties_to_restore' => 'No properties to restore.', + 'activity_restored' => 'Activity restored.', + 'activity_restored_successfully' => 'Activity restored successfully.', + 'failed_to_restore_activity' => 'Failed to restore activity : :error.', + 'subject_not_found' => 'Subject not found.', ]; diff --git a/resources/lang/en/tables.php b/resources/lang/en/tables.php index 9bb8e8c..ecc8cf6 100755 --- a/resources/lang/en/tables.php +++ b/resources/lang/en/tables.php @@ -23,11 +23,11 @@ ], 'filters' => [ 'created_at' => [ - 'label' => 'Logged at', - 'created_from' => 'Created from ', + 'label' => 'Logged at', + 'created_from' => 'Created from ', 'created_from_indicator' => 'Created from : :created_from', - 'created_until' => 'Created until ', - 'created_until_indicator' => 'Created until : :created_from', + 'created_until' => 'Created until ', + 'created_until_indicator' => 'Created until : :created_until', ], 'event' => [ 'label' => 'Event', diff --git a/resources/lang/fa/action.php b/resources/lang/fa/action.php index 461b811..491557a 100644 --- a/resources/lang/fa/action.php +++ b/resources/lang/fa/action.php @@ -14,5 +14,5 @@ ], 'view' => 'نمایش', 'edit' => 'ویرایش', - 'restore' => 'بازیابی' + 'restore' => 'بازیابی', ]; diff --git a/resources/lang/fa/forms.php b/resources/lang/fa/forms.php index 76cc146..00f3d6f 100644 --- a/resources/lang/fa/forms.php +++ b/resources/lang/fa/forms.php @@ -2,7 +2,7 @@ return [ 'changes' => 'تغییرات', - 'fields' => [ + 'fields' => [ 'log_name' => [ 'label' => 'نوع', ], diff --git a/resources/lang/fa/infolists.php b/resources/lang/fa/infolists.php index 774bd81..104ccdb 100644 --- a/resources/lang/fa/infolists.php +++ b/resources/lang/fa/infolists.php @@ -2,10 +2,10 @@ return [ 'components' => [ - "created_by_at" => ":subject توسط :causer در تاریخ :update_at :event.", - "updater_updated" => "ویژگی های زیر توسط :causer :event :
:changes ", - "from_oldvalue_to_newvalue" => "مقدار ویژگی :key از :old_value به :new_value تغییر کرد.", - "to_newvalue" => "ویژگی :key به :new_value مقدار دهی شد.", - "unknown" => "ناشناس" + 'created_by_at' => ':subject توسط :causer در تاریخ :update_at :event.', + 'updater_updated' => 'ویژگی های زیر توسط :causer :event :
:changes ', + 'from_oldvalue_to_newvalue' => 'مقدار ویژگی :key از :old_value به :new_value تغییر کرد.', + 'to_newvalue' => 'ویژگی :key به :new_value مقدار دهی شد.', + 'unknown' => 'ناشناس', ], ]; diff --git a/resources/lang/fa/notifications.php b/resources/lang/fa/notifications.php index 3fcbda7..789c22e 100644 --- a/resources/lang/fa/notifications.php +++ b/resources/lang/fa/notifications.php @@ -1,8 +1,8 @@ 'مقادیری برای بازگردانی وجود ندارد.', - 'activity_restored' => 'فعالیت بازگردانی شد.', + 'no_properties_to_restore' => 'مقادیری برای بازگردانی وجود ندارد.', + 'activity_restored' => 'فعالیت بازگردانی شد.', 'activity_restored_successfully' => 'فعالیت با موفقیت بازگردانی شد.', - 'failed_to_restore_activity' => 'بازگردانی فعالیت ناموفق بود. خطا : :error', + 'failed_to_restore_activity' => 'بازگردانی فعالیت ناموفق بود. خطا : :error', ]; diff --git a/resources/lang/fa/tables.php b/resources/lang/fa/tables.php index a77a900..efed7c8 100644 --- a/resources/lang/fa/tables.php +++ b/resources/lang/fa/tables.php @@ -23,10 +23,10 @@ ], 'filters' => [ 'created_at' => [ - 'label' => 'ورود به سیستم در', - 'created_from' => 'ایجاد شده از ', + 'label' => 'ورود به سیستم در', + 'created_from' => 'ایجاد شده از ', 'created_from_indicator' => 'ایجاد شده از تاریخ : :created_from', - 'created_until' => 'ایجاد شده تا ', + 'created_until' => 'ایجاد شده تا ', 'created_until_indicator' => 'ایجاد شده تا تاریخ : :created_until', ], 'event' => [ diff --git a/resources/lang/pt_BR/action.php b/resources/lang/pt_BR/action.php index 60627fd..20db56f 100644 --- a/resources/lang/pt_BR/action.php +++ b/resources/lang/pt_BR/action.php @@ -6,4 +6,13 @@ 'description' => 'Acompanhe todas as atividades do usuário', 'tooltip' => 'Atividades do usuário', ], + 'event' => [ + 'created' => 'criado', + 'deleted' => 'excluído', + 'updated' => 'atualizado', + 'restored' => 'restaurado', + ], + 'view' => 'Visualizar', + 'edit' => 'Editar', + 'restore' => 'Restaurar', ]; diff --git a/resources/lang/pt_BR/forms.php b/resources/lang/pt_BR/forms.php index c4e2c2f..a18a6be 100755 --- a/resources/lang/pt_BR/forms.php +++ b/resources/lang/pt_BR/forms.php @@ -1,7 +1,8 @@ [ + 'changes' => 'Mudanças', + 'fields' => [ 'log_name' => [ 'label' => 'Tipo', ], diff --git a/resources/lang/pt_BR/infolists.php b/resources/lang/pt_BR/infolists.php new file mode 100644 index 0000000..33d87e9 --- /dev/null +++ b/resources/lang/pt_BR/infolists.php @@ -0,0 +1,11 @@ + [ + 'created_by_at' => 'O :subject foi :event por :causer.
Atualizado em: :update_at', + 'updater_updated' => ':causer :event para:
: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 new file mode 100644 index 0000000..4179a6e --- /dev/null +++ b/resources/lang/pt_BR/notifications.php @@ -0,0 +1,9 @@ + 'Nenhuma propriedade para restaurar.', + 'activity_restored' => 'Atividade restaurada.', + 'activity_restored_successfully' => 'Atividade restaurada com sucesso.', + 'failed_to_restore_activity' => 'Falha ao restaurar a atividade: :error.', + 'subject_not_found' => 'Registro não encontrado.', +]; diff --git a/resources/lang/pt_BR/tables.php b/resources/lang/pt_BR/tables.php index fc956e8..5d2eb5d 100755 --- a/resources/lang/pt_BR/tables.php +++ b/resources/lang/pt_BR/tables.php @@ -23,9 +23,11 @@ ], 'filters' => [ 'created_at' => [ - 'label' => 'Criado em', - 'created_from' => 'Criado a partir de ', - 'created_until' => 'Criado até ', + 'label' => 'Criado em', + 'created_from' => 'Criado a partir de ', + 'created_from_indicator' => 'Criado a partir de : :created_from', + 'created_until' => 'Criado até ', + 'created_until_indicator' => 'Criado até : :created_until', ], 'event' => [ 'label' => 'Eventos', diff --git a/src/Actions/Concerns/ActionContent.php b/src/Actions/Concerns/ActionContent.php index 5bce4de..163dcda 100644 --- a/src/Actions/Concerns/ActionContent.php +++ b/src/Actions/Concerns/ActionContent.php @@ -2,8 +2,6 @@ namespace Rmsramos\Activitylog\Actions\Concerns; -use Carbon\Carbon; -use Carbon\CarbonImmutable; use Carbon\Exceptions\InvalidFormatException; use Closure; use Filament\Actions\StaticAction; @@ -12,30 +10,30 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +use Rmsramos\Activitylog\ActivitylogPlugin; use Rmsramos\Activitylog\Infolists\Components\TimeLineIconEntry; use Rmsramos\Activitylog\Infolists\Components\TimeLinePropertiesEntry; use Rmsramos\Activitylog\Infolists\Components\TimeLineRepeatableEntry; use Rmsramos\Activitylog\Infolists\Components\TimeLineTitleEntry; use Spatie\Activitylog\Models\Activity; -use Rmsramos\Activitylog\ActivitylogPlugin; trait ActionContent { private ?array $withRelations = null; private ?array $timelineIcons = [ - 'created' => 'heroicon-m-plus', - 'updated' => 'heroicon-m-pencil-square', - 'deleted' => 'heroicon-m-trash', - 'restored' => 'heroicon-m-arrow-uturn-left', - ]; + 'created' => 'heroicon-m-plus', + 'updated' => 'heroicon-m-pencil-square', + 'deleted' => 'heroicon-m-trash', + 'restored' => 'heroicon-m-arrow-uturn-left', + ]; private ?array $timelineIconColors = [ - 'created' => 'success', - 'updated' => 'warning', - 'deleted' => 'danger', - 'restored' => 'info', - ]; + 'created' => 'success', + 'updated' => 'warning', + 'deleted' => 'danger', + 'restored' => 'info', + ]; private ?int $limit = 10; @@ -66,32 +64,57 @@ protected function setUp(): void $this->modifyQueryUsing = fn ($builder) => $builder; $this->modalHeading = __('activitylog::action.modal.heading'); $this->modalDescription = __('activitylog::action.modal.description'); - $this->query = function (?Model $record) { + + $this->query = function (?Model $record) { + if (! $record) { + return Activity::query()->whereRaw('1 = 0'); + } + return Activity::query() ->with(['subject', 'causer']) ->where(function (Builder $query) use ($record) { - $query->where(function (Builder $q) use ($record) { - $q->where('subject_type', $record->getMorphClass()) - ->where('subject_id', $record->getKey()); - })->when($this->getWithRelations(), function (Builder $query, array $relations) use ($record) { + $query->where('subject_type', $record->getMorphClass()) + ->where('subject_id', $record->getKey()); + + if ($relations = $this->getWithRelations()) { foreach ($relations as $relation) { - $model = get_class($record->{$relation}()->getRelated()); - $query->orWhere(function (Builder $q) use ($record, $model, $relation) { - $q->where('subject_type', (new $model)->getMorphClass()) - ->whereIn('subject_id', $record->{$relation}()->pluck('id')); - }); + if (method_exists($record, $relation)) { + try { + $relationInstance = $record->{$relation}(); + $relatedModel = $relationInstance->getRelated(); + $relatedIds = $relationInstance->pluck('id')->toArray(); + + if (! empty($relatedIds)) { + $query->orWhere(function (Builder $q) use ($relatedModel, $relatedIds) { + $q->where('subject_type', $relatedModel->getMorphClass()) + ->whereIn('subject_id', $relatedIds); + }); + } + } catch (\Exception $e) { + } + } } - }); + } }); - }; } private function configureInfolist(): void { $this->infolist(function (?Model $record, Infolist $infolist) { + $activities = $this->getActivityLogRecord($record, $this->getWithRelations()); + + $formattedActivities = $activities->map(function ($activity) { + return [ + 'id' => $activity->id, + 'log_name' => $activity->log_name, + 'updated_at' => $activity->updated_at, + 'activityData' => $activity->activityData, + ]; + })->toArray(); + return $infolist - ->state(['activities' => $this->getActivityLogRecord($record, $this->getWithRelations())]) + ->state(['activities' => $formattedActivities]) ->schema($this->getSchema()); }); } @@ -139,11 +162,6 @@ public function withRelations(?array $relations = null): ?StaticAction return $this; } - public function getWithRelations(): ?array - { - return $this->evaluate($this->withRelations); - } - public function timelineIcons(?array $timelineIcons = null): ?StaticAction { $this->timelineIcons = $timelineIcons; @@ -151,11 +169,6 @@ public function timelineIcons(?array $timelineIcons = null): ?StaticAction return $this; } - public function getTimelineIcons(): ?array - { - return $this->evaluate($this->timelineIcons); - } - public function timelineIconColors(?array $timelineIconColors = null): ?StaticAction { $this->timelineIconColors = $timelineIconColors; @@ -163,11 +176,6 @@ public function timelineIconColors(?array $timelineIconColors = null): ?StaticAc return $this; } - public function getTimelineIconColors(): ?array - { - return $this->evaluate($this->timelineIconColors); - } - public function limit(?int $limit = 10): ?StaticAction { $this->limit = $limit; @@ -175,11 +183,6 @@ public function limit(?int $limit = 10): ?StaticAction return $this; } - public function getLimit(): ?int - { - return $this->evaluate($this->limit); - } - public function query(Closure|Builder|null $query): static { $this->query = $query; @@ -187,11 +190,6 @@ public function query(Closure|Builder|null $query): static return $this; } - public function getQuery(): ?Builder - { - return $this->evaluate($this->query); - } - public function modifyQueryUsing(Closure $closure): static { $this->modifyQueryUsing = $closure; @@ -199,11 +197,6 @@ public function modifyQueryUsing(Closure $closure): static return $this; } - public function getModifyQueryUsing(Builder $builder): Builder - { - return $this->evaluate($this->modifyQueryUsing, ['builder' => $builder]); - } - public function modifyTitleUsing(Closure $closure): static { $this->modifyTitleUsing = $closure; @@ -225,6 +218,36 @@ public function activitiesUsing(Closure $closure): static return $this; } + public function getWithRelations(): ?array + { + return $this->evaluate($this->withRelations); + } + + public function getTimelineIcons(): ?array + { + return $this->evaluate($this->timelineIcons); + } + + public function getTimelineIconColors(): ?array + { + return $this->evaluate($this->timelineIconColors); + } + + public function getLimit(): ?int + { + return $this->evaluate($this->limit); + } + + public function getQuery(): ?Builder + { + return $this->evaluate($this->query); + } + + public function getModifyQueryUsing(Builder $builder): Builder + { + return $this->evaluate($this->modifyQueryUsing, ['builder' => $builder]); + } + public function getActivitiesUsing(): ?Collection { return $this->evaluate($this->activitiesUsing); @@ -234,14 +257,17 @@ protected function getActivities(?Model $record, ?array $relations = null): Coll { if ($activities = $this->getActivitiesUsing()) { return $activities; - } else { - $builder = $this->getQuery() - ->latest() - ->limit($this->getLimit()); + } - return $this->getModifyQueryUsing($builder) - ->get(); + if (! $record) { + return collect(); } + + $builder = $this->getQuery() + ->latest() + ->limit($this->getLimit()); + + return $this->getModifyQueryUsing($builder)->get(); } protected function getActivityLogRecord(?Model $record, ?array $relations = null): Collection @@ -257,13 +283,25 @@ protected function getActivityLogRecord(?Model $record, ?array $relations = null protected function formatActivityData($activity): array { + $properties = []; + + if ($activity->properties) { + if (is_string($activity->properties)) { + $properties = json_decode($activity->properties, true) ?? []; + } elseif (is_array($activity->properties)) { + $properties = $activity->properties; + } elseif (is_object($activity->properties) && method_exists($activity->properties, 'toArray')) { + $properties = $activity->properties->toArray(); + } + } + return [ 'log_name' => $activity->log_name, 'description' => $activity->description, 'subject' => $activity->subject, 'event' => $activity->event, 'causer' => $activity->causer, - 'properties' => $this->formatDateValues(json_decode($activity->properties, true)), + 'properties' => $this->formatDateValues($properties), 'batch_uuid' => $activity->batch_uuid, 'update' => $activity->updated_at, ]; @@ -290,41 +328,11 @@ private static function formatDateValues(array|string|null $value): array|string try { $parser = ActivitylogPlugin::get()->getDateParser(); - return $parser($value) - ->format(ActivitylogPlugin::get()->getDatetimeFormat()); + return $parser($value)->format(ActivitylogPlugin::get()->getDatetimeFormat()); } catch (InvalidFormatException $e) { return $value; + } catch (\Exception $e) { + return $value; } - - return $value; - } - - private static function isValidDate(string $dateString, string $dateFormat = 'Y-m-d', string $dateTimeFormat = 'Y-m-d H:i:s'): bool|string - { - try { - - $dateTime = CarbonImmutable::createFromFormat($dateFormat, $dateString); - - if ($dateTime && $dateTime->format($dateFormat) === $dateString) { - return true; - } - - } catch (InvalidFormatException $e) { - - } - - try { - - $dateTime = CarbonImmutable::createFromFormat($dateTimeFormat, $dateString); - - if ($dateTime && $dateTime->format($dateTimeFormat) === $dateString) { - return true; - } - - } catch (InvalidFormatException $e) { - - } - - return false; } } diff --git a/src/ActivitylogPlugin.php b/src/ActivitylogPlugin.php index 4e38a18..704ecd4 100755 --- a/src/ActivitylogPlugin.php +++ b/src/ActivitylogPlugin.php @@ -6,7 +6,6 @@ use Filament\Contracts\Plugin; use Filament\Panel; use Filament\Support\Concerns\EvaluatesClosures; -use DateTimeInterface; use Illuminate\Support\Carbon; class ActivitylogPlugin implements Plugin @@ -37,7 +36,7 @@ class ActivitylogPlugin implements Plugin protected ?Closure $datePickerCallback = null; - protected Closure|null $translateSubject = null; + protected ?Closure $translateSubject = null; protected ?string $navigationIcon = null; @@ -139,16 +138,18 @@ public function getDatePickerCallback(): ?Closure public function getTranslateSubject($label): ?string { - if(is_null($this->translateSubject)) + if (is_null($this->translateSubject)) { return $label; + } $callable = $this->translateSubject; + return $callable($label); } public function getDateParser(): ?Closure - { - return $this->dateParser ?? fn($date) => Carbon::parse($date); + { + return $this->dateParser ?? fn ($date) => Carbon::parse($date); } public function getNavigationIcon(): ?string @@ -222,7 +223,7 @@ public function navigationGroup(string|Closure|null $group = null): static return $this; } - public function dateParser(Closure|null $parser = null): static + public function dateParser(?Closure $parser = null): static { $this->dateParser = $parser; @@ -246,12 +247,14 @@ public function datetimeFormat(string|Closure|null $format = null): static public function customizeDatetimeColumn(Closure $callable): self { $this->datetimeColumnCallback = $callable; + return $this; } public function customizeDatePicker(Closure $callable): self { $this->datePickerCallback = $callable; + return $this; } diff --git a/src/Helpers/ActivityLogHelper.php b/src/Helpers/ActivityLogHelper.php index 00257e2..1c5ce57 100644 --- a/src/Helpers/ActivityLogHelper.php +++ b/src/Helpers/ActivityLogHelper.php @@ -9,13 +9,13 @@ class ActivityLogHelper /** * Checks if a class uses a specific trait. * - * @param mixed $class The class or object instance to check. - * @param string $trait The fully qualified name of the trait to look for. - * @return bool + * @param mixed $class The class or object instance to check. + * @param string $trait The fully qualified name of the trait to look for. */ public static function classUsesTrait($class, $trait): bool { $traits = class_uses_recursive($class); + return in_array($trait, $traits); } diff --git a/src/Infolists/Components/TimeLinePropertiesEntry.php b/src/Infolists/Components/TimeLinePropertiesEntry.php index 293da53..1060db7 100644 --- a/src/Infolists/Components/TimeLinePropertiesEntry.php +++ b/src/Infolists/Components/TimeLinePropertiesEntry.php @@ -34,10 +34,10 @@ private function modifiedProperties($state): ?HtmlString $changes = $this->getPropertyChanges($properties); $causerName = $this->getCauserName($state['causer']); - return new HtmlString(trans("activitylog::infolists.components.updater_updated",[ - "causer" => $causerName, - "event" => __("activitylog::action.event.".$state['event']), - "changes" => implode('
', $changes) + return new HtmlString(trans('activitylog::infolists.components.updater_updated', [ + 'causer' => $causerName, + 'event' => __('activitylog::action.event.' . $state['event']), + 'changes' => implode('
', $changes), ])); } @@ -66,17 +66,17 @@ private function compareOldAndNewValues(array $oldValues, array $newValues): arr $newValue = $this->formatNewValue($newValue); if (isset($oldValues[$key]) && $oldValues[$key] != $newValue) { - $changes[] = trans("activitylog::infolists.components.from_oldvalue_to_newvalue", + $changes[] = trans('activitylog::infolists.components.from_oldvalue_to_newvalue', [ - "key" => $key, - "old_value" => htmlspecialchars($oldValue), - "new_value" => htmlspecialchars($newValue) + 'key' => $key, + 'old_value' => htmlspecialchars($oldValue), + 'new_value' => htmlspecialchars($newValue), ]); } else { - $changes[] = trans("activitylog::infolists.components.to_newvalue", + $changes[] = trans('activitylog::infolists.components.to_newvalue', [ - "key" => $key, - "new_value" => htmlspecialchars($newValue) + 'key' => $key, + 'new_value' => htmlspecialchars($newValue), ]); } } diff --git a/src/Infolists/Components/TimeLineTitleEntry.php b/src/Infolists/Components/TimeLineTitleEntry.php index 2aa14ed..5124e02 100644 --- a/src/Infolists/Components/TimeLineTitleEntry.php +++ b/src/Infolists/Components/TimeLineTitleEntry.php @@ -2,15 +2,14 @@ namespace Rmsramos\Activitylog\Infolists\Components; -use Carbon\Carbon; use Closure; use Filament\Forms\Components\Concerns\CanAllowHtml; use Filament\Infolists\Components\Entry; use Filament\Support\Concerns\HasExtraAttributes; use Illuminate\Support\HtmlString; use Illuminate\Support\Str; -use Rmsramos\Activitylog\Infolists\Concerns\HasModifyState; use Rmsramos\Activitylog\ActivitylogPlugin; +use Rmsramos\Activitylog\Infolists\Concerns\HasModifyState; class TimeLineTitleEntry extends Entry { @@ -61,16 +60,16 @@ private function modifiedTitle($state): string|HtmlString|Closure $className = Str::lower(Str::snake(class_basename($state['subject']), ' ')); $causerName = $this->getCauserName($state['causer']); - $parser = ActivitylogPlugin::get()->getDateParser(); + $parser = ActivitylogPlugin::get()->getDateParser(); $update_at = $parser($state['update'])->format(ActivitylogPlugin::get()->getDatetimeFormat()); return new HtmlString( - __("activitylog::infolists.components.created_by_at", + __('activitylog::infolists.components.created_by_at', [ - "subject" => ActivitylogPlugin::get()->getTranslateSubject($className), - "event"=>__("activitylog::action.event.".$state['event']), - "causer"=>$causerName, - "update_at"=>$update_at + 'subject' => ActivitylogPlugin::get()->getTranslateSubject($className), + 'event' => __('activitylog::action.event.' . $state['event']), + 'causer' => $causerName, + 'update_at' => $update_at, ]), ); } diff --git a/src/Infolists/Concerns/HasModifyState.php b/src/Infolists/Concerns/HasModifyState.php index 219f453..596ab57 100644 --- a/src/Infolists/Concerns/HasModifyState.php +++ b/src/Infolists/Concerns/HasModifyState.php @@ -23,6 +23,6 @@ public function getModifiedState(): null|string|HtmlString private function getCauserName($causer): string { - return $causer->name ?? $causer->first_name ?? $causer->last_name ?? $causer->username ?? trans("activitylog::infolists.components.unknown"); + return $causer->name ?? $causer->first_name ?? $causer->last_name ?? $causer->username ?? trans('activitylog::infolists.components.unknown'); } } diff --git a/src/Resources/ActivitylogResource.php b/src/Resources/ActivitylogResource.php index 771861a..63babd1 100755 --- a/src/Resources/ActivitylogResource.php +++ b/src/Resources/ActivitylogResource.php @@ -2,7 +2,9 @@ namespace Rmsramos\Activitylog\Resources; +use Exception; use Filament\Facades\Filament; +use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\KeyValue; use Filament\Forms\Components\Placeholder; @@ -11,6 +13,7 @@ use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; +use Filament\Notifications\Notification; use Filament\Resources\Resource; use Filament\Tables\Columns\Column; use Filament\Tables\Columns\TextColumn; @@ -20,22 +23,19 @@ use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Carbon; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Gate; use Illuminate\Support\HtmlString; use Illuminate\Support\Str; use Livewire\Component as Livewire; use Rmsramos\Activitylog\Actions\Concerns\ActionContent; use Rmsramos\Activitylog\ActivitylogPlugin; +use Rmsramos\Activitylog\Helpers\ActivityLogHelper; use Rmsramos\Activitylog\RelationManagers\ActivitylogRelationManager; use Rmsramos\Activitylog\Resources\ActivitylogResource\Pages\ListActivitylog; use Rmsramos\Activitylog\Resources\ActivitylogResource\Pages\ViewActivitylog; -use Spatie\Activitylog\Models\Activity; -use Filament\Forms\Components\Actions\Action; -use Filament\Notifications\Notification; -use Rmsramos\Activitylog\Helpers\ActivityLogHelper; use Rmsramos\Activitylog\Traits\HasCustomActivityResource; - +use Spatie\Activitylog\Models\Activity; class ActivitylogResource extends Resource { @@ -88,80 +88,123 @@ public static function restoreActivity(int|string $key): void { $activity = Activity::find($key); - $oldProperties = data_get($activity, 'properties.old'); + 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")) + ->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 - activity() - ->performedOn($record) - ->causedBy(auth()->user()) - ->withProperties(["attributes"=>$oldProperties, "old" => $newProperties]) - ->tap(function ($log) { - $log->event = 'restored'; - }) - ->log('restored'); + $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")) + ->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()])) + ->title(__('activitylog::notifications.failed_to_restore_activity', ['error' => $e->getMessage()])) ->danger() ->send(); } } - private static function getResourceUrl($record) + private static function getResourceUrl(Activity $record): string { $panelID = Filament::getCurrentPanel()->getId(); if ($record->subject_type && $record->subject_id) { - $model = app($record->subject_type); - if (ActivityLogHelper::classUsesTrait($model, HasCustomActivityResource::class)) { - $resourceModel = $model->getFilamentActualResourceModel($record); - $resourcePluralName = ActivityLogHelper::getResourcePluralName($resourceModel); - return route('filament.'.$panelID.'.resources.' . $resourcePluralName . '.edit', ['record' => $resourceModel->id]); - } - - // Fallback to a standard resource mapping - $resourcePluralName = ActivityLogHelper::getResourcePluralName($record->subject_type); - return route('filament.'.$panelID.'.resources.' . $resourcePluralName . '.edit', ['record' => $record->subject_id]); + try { + $model = app($record->subject_type); + + if (ActivityLogHelper::classUsesTrait($model, HasCustomActivityResource::class)) { + $resourceModel = $model->getFilamentActualResourceModel($record); + $resourcePluralName = ActivityLogHelper::getResourcePluralName($resourceModel); + + return route('filament.' . $panelID . '.resources.' . $resourcePluralName . '.edit', ['record' => $resourceModel->id]); + } + + // Fallback to a standard resource mapping + $resourcePluralName = ActivityLogHelper::getResourcePluralName($record->subject_type); + + return route('filament.' . $panelID . '.resources.' . $resourcePluralName . '.edit', ['record' => $record->subject_id]); + } catch (Exception $e) { + // If there's any error generating the URL, return placeholder + return '#'; + } } return '#'; } - private static function canViewResource($record) + private static function canViewResource(Activity $record): bool { - return true; if ($record->subject_type && $record->subject_id) { - $model = app($record->subject_type); - if (ActivityLogHelper::classUsesTrait($model, HasCustomActivityResource::class)) { - $resourceModel = $model->getFilamentActualResourceModel($record); - return auth()->user()->can('update', $resourceModel); - } - - // Fallback to check if the user can edit the model using a generic policy - return auth()->user()->can('update', $record->subject); + 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; @@ -176,14 +219,14 @@ public static function form(Form $form): Form TextInput::make('causer_id') ->afterStateHydrated(function ($component, ?Model $record) { /** @phpstan-ignore-next-line */ - return $component->state($record->causer?->name); + return $component->state($record?->causer?->name ?? '-'); }) ->label(__('activitylog::forms.fields.causer.label')), TextInput::make('subject_type') ->afterStateHydrated(function ($component, ?Model $record, $state) { - /** @var Activity&ActivityModel $record */ - return $state ? $component->state(Str::of($state)->afterLast('\\')->headline() . ' # ' . $record->subject_id) : '-'; + /** @var Activity $record */ + return $state ? $component->state(Str::of($state)->afterLast('\\')->headline() . ' # ' . $record->subject_id) : $component->state('-'); }) ->label(__('activitylog::forms.fields.subject_type.label')), @@ -195,75 +238,89 @@ public static function form(Form $form): Form Section::make([ Placeholder::make('log_name') ->content(function (?Model $record): string { - /** @var Activity&ActivityModel $record */ - return $record->log_name ? ucwords($record->log_name) : '-'; + /** @var Activity $record */ + return $record?->log_name ? ucwords($record->log_name) : '-'; }) ->label(__('activitylog::forms.fields.log_name.label')), Placeholder::make('event') ->content(function (?Model $record): string { - /** @phpstan-ignore-next-line */ - return $record?->event ? ucwords(__('activitylog::action.event.'.$record?->event)) : '-'; + /** @var Activity $record */ + return $record?->event ? ucwords(__('activitylog::action.event.' . $record->event)) : '-'; }) ->label(__('activitylog::forms.fields.event.label')), Placeholder::make('created_at') ->label(__('activitylog::forms.fields.created_at.label')) ->content(function (?Model $record): string { - /** @var Activity&ActivityModel $record */ + /** @var Activity $record */ + if (! $record?->created_at) { + return '-'; + } $parser = ActivitylogPlugin::get()->getDateParser(); - return $record->created_at ? - $parser($record->created_at) - ->format(ActivitylogPlugin::get()->getDatetimeFormat()) - : '-'; + return $parser($record->created_at) + ->format(ActivitylogPlugin::get()->getDatetimeFormat()); }), ])->grow(false), ])->from('md'), Section::make(__('activitylog::forms.changes')) ->headerActions([ - Action::make(__('activitylog::action.restore')) - ->icon('heroicon-o-eye') + Action::make('restore') + ->label(__('activitylog::action.restore')) + ->icon('heroicon-o-arrow-uturn-left') ->color('primary') ->action(fn (Activity $record) => self::restoreActivity($record->id)) - ->visible(fn () => !ActivitylogPlugin::get()->getIsRestoreActionHidden() ?? true) - ->authorize(fn () => auth()->user()->can("restore_activitylog")) + ->visible(fn () => ! ActivitylogPlugin::get()->getIsRestoreActionHidden()) + ->authorize(fn () => auth()->user()?->can('restore_activitylog') ?? false) ->requiresConfirmation(), - Action::make(__('activitylog::action.edit')) - ->label(fn () => ActivitylogPlugin::get()->getResourceActionLabel() ?? __('activitylog::action.edit')) + Action::make('edit') + ->label(__('activitylog::action.edit')) ->icon('heroicon-o-eye') ->color('info') - ->url(fn ($record) => self::getResourceUrl($record)) - ->visible(fn () => !ActivitylogPlugin::get()->getIsResourceActionHidden() ?? true) - ->authorize(fn ($record) => self::canViewResource($record)) - , + ->url(fn (Activity $record) => self::getResourceUrl($record)) + ->visible(fn () => ! ActivitylogPlugin::get()->getIsResourceActionHidden()) + ->authorize(fn (Activity $record) => self::canViewResource($record)), ]) ->columns() - ->visible(fn ($record) => $record->properties?->count() > 0) + ->visible(fn (?Model $record) => $record?->properties?->count() > 0) ->schema(function (?Model $record) { - /** @var Activity&ActivityModel $record */ - $properties = $record->properties->except(['attributes', 'old']); + /** @var Activity $record */ + if (! $record?->properties) { + return []; + } - $schema = []; + $properties = $record->properties->except(['attributes', 'old']); + $schema = []; if ($properties->count()) { $schema[] = KeyValue::make('properties') + ->afterStateHydrated(function (KeyValue $component) use ($properties) { + $component->state($properties->toArray()); + }) ->label(__('activitylog::forms.fields.properties.label')) - ->columnSpan('full'); + ->columnSpan('full') + ->disabled(); } if ($old = $record->properties->get('old')) { $schema[] = KeyValue::make('old') - ->formatStateUsing(fn () => self::formatDateValues($old)) - ->label(__('activitylog::forms.fields.old.label')); + ->afterStateHydrated(function (KeyValue $component) use ($old) { + $component->state(is_array($old) ? $old : []); + }) + ->label(__('activitylog::forms.fields.old.label')) + ->disabled(); } if ($attributes = $record->properties->get('attributes')) { $schema[] = KeyValue::make('attributes') - ->formatStateUsing(fn () => self::formatDateValues($attributes)) - ->label(__('activitylog::forms.fields.attributes.label')); + ->afterStateHydrated(function (KeyValue $component) use ($attributes) { + $component->state(is_array($attributes) ? $attributes : []); + }) + ->label(__('activitylog::forms.fields.attributes.label')) + ->disabled(); } return $schema; @@ -282,7 +339,10 @@ public static function table(Table $table): Table static::getPropertiesColumnComponent(), static::getCreatedAtColumnComponent(), ]) - ->defaultSort(config('filament-activitylog.resources.default_sort_column', 'created_at'), config('filament-activitylog.resources.default_sort_direction', 'asc')) + ->defaultSort( + config('filament-activitylog.resources.default_sort_column', 'created_at'), + config('filament-activitylog.resources.default_sort_direction', 'desc') + ) ->filters([ static::getDateFilterComponent(), static::getEventFilterComponent(), @@ -293,7 +353,7 @@ public static function getLogNameColumnComponent(): Column { return TextColumn::make('log_name') ->label(__('activitylog::tables.columns.log_name.label')) - ->formatStateUsing(fn ($state) => ucwords($state)) + ->formatStateUsing(fn ($state) => $state ? ucwords($state) : '-') ->searchable() ->sortable() ->badge(); @@ -303,15 +363,15 @@ public static function getEventColumnComponent(): Column { return TextColumn::make('event') ->label(__('activitylog::tables.columns.event.label')) - ->formatStateUsing(fn ($state) => ucwords(__("activitylog::action.event.".$state))) + ->formatStateUsing(fn ($state) => $state ? ucwords(__('activitylog::action.event.' . $state)) : '-') ->badge() - ->color(fn (string $state): string => match ($state) { - 'draft' => 'gray', - 'updated' => 'warning', - 'created' => 'success', - 'deleted' => 'danger', - 'restored'=> 'info', - default => 'primary', + ->color(fn (?string $state): string => match ($state) { + 'draft' => 'gray', + 'updated' => 'warning', + 'created' => 'success', + 'deleted' => 'danger', + 'restored' => 'info', + default => 'primary', }) ->searchable() ->sortable(); @@ -322,7 +382,7 @@ public static function getSubjectTypeColumnComponent(): Column return TextColumn::make('subject_type') ->label(__('activitylog::tables.columns.subject_type.label')) ->formatStateUsing(function ($state, Model $record) { - /** @var Activity&ActivityModel $record */ + /** @var Activity $record */ if (! $state) { return '-'; } @@ -338,13 +398,12 @@ public static function getCauserNameColumnComponent(): Column return TextColumn::make('causer.name') ->label(__('activitylog::tables.columns.causer.label')) ->getStateUsing(function (Model $record) { - // Check if causer is null or causer_id is null - if ($record->causer_id == null || $record->causer == null) { + /** @var Activity $record */ + if ($record->causer_id === null || $record->causer === null) { return new HtmlString('—'); } - // Return the causer's name if causer exists - return $record->causer->name; + return $record->causer->name ?? new HtmlString('—'); }) ->searchable(); } @@ -368,6 +427,7 @@ public static function getCreatedAtColumnComponent(): Column // Apply the custom callback if set $callback = ActivitylogPlugin::get()->getDatetimeColumnCallback(); + if ($callback) { $column = $callback($column); } @@ -381,22 +441,20 @@ public static function getDateFilterComponent(): Filter ->label(__('activitylog::tables.filters.created_at.label')) ->indicateUsing(function (array $data): array { $indicators = []; - $parser = ActivitylogPlugin::get()->getDateParser(); + $parser = ActivitylogPlugin::get()->getDateParser(); if ($data['created_from'] ?? null) { - $indicators['created_from'] = __('activitylog::tables.filters.created_at.created_from_indicator', - [ - "created_from" => $parser($data['created_from']) - ->format(ActivitylogPlugin::get()->getDateFormat()) - ]); + $indicators['created_from'] = __('activitylog::tables.filters.created_at.created_from_indicator', [ + 'created_from' => $parser($data['created_from']) + ->format(ActivitylogPlugin::get()->getDateFormat()), + ]); } if ($data['created_until'] ?? null) { - $indicators['created_until'] = __('activitylog::tables.filters.created_at.created_until_indicator', - [ - "created_until" => $parser($data['created_until']) - ->format(ActivitylogPlugin::get()->getDateFormat()) - ]); + $indicators['created_until'] = __('activitylog::tables.filters.created_at.created_until_indicator', [ + 'created_until' => $parser($data['created_until']) + ->format(ActivitylogPlugin::get()->getDateFormat()), + ]); } return $indicators; @@ -408,24 +466,25 @@ public static function getDateFilterComponent(): Filter ->query(function (Builder $query, array $data): Builder { return $query ->when( - $data['created_from'], + $data['created_from'] ?? null, fn (Builder $query, $date): Builder => $query->whereDate('created_at', '>=', $date), ) ->when( - $data['created_until'], + $data['created_until'] ?? null, fn (Builder $query, $date): Builder => $query->whereDate('created_at', '<=', $date), ); }); } - public static function getDatePickerCompoment($label): DatePicker + public static function getDatePickerCompoment(string $label): DatePicker { $field = DatePicker::make($label) - ->format(ActivitylogPlugin::get()->getDateFormat()) - ->label(__('activitylog::tables.filters.created_at.'.$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); } @@ -433,7 +492,7 @@ public static function getDatePickerCompoment($label): DatePicker return $field; } - public static function getEventFilterCompoment(): SelectFilter + public static function getEventFilterComponent(): SelectFilter { return SelectFilter::make('event') ->label(__('activitylog::tables.filters.event.label')) @@ -443,7 +502,6 @@ public static function getEventFilterCompoment(): SelectFilter ); } - public static function getPages(): array { return [ @@ -456,7 +514,7 @@ public static function shouldRegisterNavigation(): bool { $plugin = Filament::getCurrentPanel()?->getPlugin('rmsramos/activitylog'); - return $plugin->getNavigationItem(); + return $plugin?->getNavigationItem() ?? false; } public static function canAccess(): bool @@ -465,8 +523,8 @@ public static function canAccess(): bool if ($policy && method_exists($policy, 'viewAny')) { return static::canViewAny(); - } else { - return ActivitylogPlugin::get()->isAuthorized(); } + + return ActivitylogPlugin::get()->isAuthorized(); } } diff --git a/src/Traits/HasCustomActivityResource.php b/src/Traits/HasCustomActivityResource.php index 2e6004a..fd54362 100644 --- a/src/Traits/HasCustomActivityResource.php +++ b/src/Traits/HasCustomActivityResource.php @@ -3,7 +3,6 @@ namespace Rmsramos\Activitylog\Traits; use Illuminate\Database\Eloquent\Model; -use \Illuminate\Support\Str; trait HasCustomActivityResource { @@ -11,9 +10,8 @@ trait HasCustomActivityResource * Retrieve the associated Filament resource model. * * @param Model $record The activity log record providing context. - * @return Model */ - public function getFilamentActualResourceModel($record) : Model + public function getFilamentActualResourceModel($record): Model { return $record; }