├── .github
└── workflows
│ ├── dependabot-auto-merge.yml
│ ├── fix-php-code-style-issues.yml
│ ├── phpstan.yml
│ ├── run-tests.yml
│ └── update-changelog.yml
├── .gitignore
├── CHANGELOG.md
├── README.md
├── composer.json
├── config
└── filament-auditing.php
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── resources
├── lang
│ ├── ar
│ │ └── filament-auditing.php
│ ├── de
│ │ └── filament-auditing.php
│ ├── en
│ │ └── filament-auditing.php
│ ├── es
│ │ └── filament-auditing.php
│ ├── fr
│ │ └── filament-auditing.php
│ ├── ko
│ │ └── filament-auditing.php
│ └── pt_BR
│ │ └── filament-auditing.php
└── views
│ └── tables
│ ├── columns
│ └── key-value.blade.php
│ └── custom-audit-content.blade.php
├── src
├── FilamentAuditingServiceProvider.php
└── RelationManagers
│ └── AuditsRelationManager.php
└── tests
├── ArchTest.php
├── ExampleTest.php
├── Pest.php
└── TestCase.php
/.github/workflows/dependabot-auto-merge.yml:
--------------------------------------------------------------------------------
1 | name: dependabot-auto-merge
2 | on: pull_request_target
3 |
4 | permissions:
5 | pull-requests: write
6 | contents: write
7 |
8 | jobs:
9 | dependabot:
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 5
12 | if: ${{ github.actor == 'dependabot[bot]' }}
13 | steps:
14 |
15 | - name: Dependabot metadata
16 | id: metadata
17 | uses: dependabot/fetch-metadata@v2.3.0
18 | with:
19 | github-token: "${{ secrets.GITHUB_TOKEN }}"
20 |
21 | - name: Auto-merge Dependabot PRs for semver-minor updates
22 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
23 | run: gh pr merge --auto --merge "$PR_URL"
24 | env:
25 | PR_URL: ${{github.event.pull_request.html_url}}
26 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
27 |
28 | - name: Auto-merge Dependabot PRs for semver-patch updates
29 | if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
30 | run: gh pr merge --auto --merge "$PR_URL"
31 | env:
32 | PR_URL: ${{github.event.pull_request.html_url}}
33 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
34 |
--------------------------------------------------------------------------------
/.github/workflows/fix-php-code-style-issues.yml:
--------------------------------------------------------------------------------
1 | name: Fix PHP code style issues
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**.php'
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | php-code-styling:
13 | runs-on: ubuntu-latest
14 | timeout-minutes: 5
15 |
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v4
19 | with:
20 | ref: ${{ github.head_ref }}
21 |
22 | - name: Fix PHP code style issues
23 | uses: aglipanci/laravel-pint-action@2.5
24 |
25 | - name: Commit changes
26 | uses: stefanzweifel/git-auto-commit-action@v5
27 | with:
28 | commit_message: Fix styling
29 |
--------------------------------------------------------------------------------
/.github/workflows/phpstan.yml:
--------------------------------------------------------------------------------
1 | name: PHPStan
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**.php'
7 | - 'phpstan.neon.dist'
8 | - '.github/workflows/phpstan.yml'
9 |
10 | jobs:
11 | phpstan:
12 | name: phpstan
13 | runs-on: ubuntu-latest
14 | timeout-minutes: 5
15 | steps:
16 | - uses: actions/checkout@v4
17 |
18 | - name: Setup PHP
19 | uses: shivammathur/setup-php@v2
20 | with:
21 | php-version: '8.3'
22 | coverage: none
23 |
24 | - name: Install composer dependencies
25 | uses: ramsey/composer-install@v3
26 |
27 | - name: Run PHPStan
28 | run: ./vendor/bin/phpstan --error-format=github
29 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: run-tests
2 |
3 | on:
4 | push:
5 | paths:
6 | - '**.php'
7 | - '.github/workflows/run-tests.yml'
8 | - 'phpunit.xml.dist'
9 | - 'composer.json'
10 | - 'composer.lock'
11 |
12 | jobs:
13 | test:
14 | runs-on: ${{ matrix.os }}
15 | timeout-minutes: 5
16 | strategy:
17 | fail-fast: true
18 | matrix:
19 | os: [ubuntu-latest]
20 | php: [8.4, 8.3, 8.2]
21 | laravel: [12.*, 11.*,10.*]
22 | stability: [prefer-lowest, prefer-stable]
23 | include:
24 | - laravel: 12.*
25 | testbench: 10.*
26 | - laravel: 11.*
27 | testbench: 9.*
28 | - laravel: 10.*
29 | testbench: 8.*
30 |
31 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
32 |
33 | steps:
34 | - name: Checkout code
35 | uses: actions/checkout@v4
36 |
37 | - name: Setup PHP
38 | uses: shivammathur/setup-php@v2
39 | with:
40 | php-version: ${{ matrix.php }}
41 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
42 | coverage: none
43 |
44 | - name: Setup problem matchers
45 | run: |
46 | echo "::add-matcher::${{ runner.tool_cache }}/php.json"
47 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
48 |
49 | - name: Install dependencies
50 | run: |
51 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
52 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction
53 |
54 | - name: List Installed Dependencies
55 | run: composer show -D
56 |
57 | - name: Execute tests
58 | run: vendor/bin/pest --ci
59 |
--------------------------------------------------------------------------------
/.github/workflows/update-changelog.yml:
--------------------------------------------------------------------------------
1 | name: "Update Changelog"
2 |
3 | on:
4 | release:
5 | types: [released]
6 |
7 | permissions:
8 | contents: write
9 |
10 | jobs:
11 | update:
12 | runs-on: ubuntu-latest
13 | timeout-minutes: 5
14 |
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v4
18 | with:
19 | ref: main
20 |
21 | - name: Update Changelog
22 | uses: stefanzweifel/changelog-updater-action@v1
23 | with:
24 | latest-version: ${{ github.event.release.name }}
25 | release-notes: ${{ github.event.release.body }}
26 |
27 | - name: Commit updated CHANGELOG
28 | uses: stefanzweifel/git-auto-commit-action@v5
29 | with:
30 | branch: main
31 | commit_message: Update CHANGELOG
32 | file_pattern: CHANGELOG.md
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /vendor
3 | .phpunit.result.cache
4 | Homestead.json
5 | Homestead.yaml
6 | auth.json
7 | npm-debug.log
8 | yarn-error.log
9 | /.idea
10 | /.vscode
11 | /build
12 | /coverage
13 | .DS_Store
14 | composer.phar
15 | phpunit.xml
16 | phpstan.neon
17 | composer.lock
18 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to the "Filament Laravel Auditing" will be documented in this file.
4 |
5 | ## v3.1.1 - 2025-06-04
6 |
7 | ### What's Changed
8 |
9 | * feat: add support to pt_BR by @samuelterra22 in https://github.com/TappNetwork/filament-auditing/pull/42
10 |
11 | ### New Contributors
12 |
13 | * @samuelterra22 made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/42
14 |
15 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.1.0...v3.1.1
16 |
17 | ## v3.1.0 - 2025-03-15
18 |
19 | ### What's Changed
20 |
21 | * Laravel 12 Support by @swilla in https://github.com/TappNetwork/filament-auditing/pull/39
22 |
23 | ### New Contributors
24 |
25 | * @swilla made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/39
26 |
27 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.9...v3.1.0
28 |
29 | ## v3.0.9 - 2025-03-08
30 |
31 | ### What's Changed
32 |
33 | * add empty state heading by @erossdev in https://github.com/TappNetwork/filament-auditing/pull/38
34 |
35 | ### New Contributors
36 |
37 | * @erossdev made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/38
38 |
39 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.8...v3.0.9
40 |
41 | ## v3.0.8 - 2024-12-12
42 |
43 | ### What's Changed
44 |
45 | * Allow multiple parameter methods when extending columns by @intrepidws in https://github.com/TappNetwork/filament-auditing/pull/34
46 |
47 | ### New Contributors
48 |
49 | * @intrepidws made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/34
50 |
51 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.7...v3.0.8
52 |
53 | ## v3.0.7 - 2024-08-15
54 |
55 | ### What's Changed
56 |
57 | * Add the ability to customize the presentation by @andreia in https://github.com/TappNetwork/filament-auditing/pull/26
58 |
59 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.6...v3.0.7
60 |
61 | ## v3.0.6 - 2024-05-31
62 |
63 | ### What's Changed
64 |
65 | * German translation add by @DGINXREAL in https://github.com/TappNetwork/filament-auditing/pull/28
66 |
67 | ### New Contributors
68 |
69 | * @DGINXREAL made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/28
70 |
71 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.5...v3.0.6
72 |
73 | ## v3.0.5 - 2024-03-24
74 |
75 | ### What's Changed
76 |
77 | * Korean translations add by @corean in https://github.com/TappNetwork/filament-auditing/pull/27
78 |
79 | ### New Contributors
80 |
81 | * @corean made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/27
82 |
83 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.4...v3.0.5
84 |
85 | ## v3.0.4 - 2024-02-05
86 |
87 | ### What's Changed
88 |
89 | * use Filament::auth() instead of auth() by @eelco2k in https://github.com/TappNetwork/filament-auditing/pull/23
90 | * Add is_lazy configuration by @andreia in https://github.com/TappNetwork/filament-auditing/pull/24
91 |
92 | ### New Contributors
93 |
94 | * @eelco2k made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/23
95 |
96 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.3...v3.0.4
97 |
98 | ## v3.0.3 - 2024-01-10
99 |
100 | ### What's Changed
101 |
102 | * Added fr_FR translation file for 3.x by @loanbesson in https://github.com/TappNetwork/filament-auditing/pull/19
103 |
104 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.2...v3.0.3
105 |
106 | ## v3.0.2 - 2023-09-24
107 |
108 | ### What's Changed
109 |
110 | - Add updateAuditsRelationManager event listener by @andreia in https://github.com/TappNetwork/filament-auditing/pull/17
111 |
112 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.1...v3.0.2
113 |
114 | ## v3.0.1 - 2023-09-24
115 |
116 | ### What's Changed
117 |
118 | - Ensure it does not break with null values. by @haringsrob in https://github.com/TappNetwork/filament-auditing/pull/16
119 |
120 | ### New Contributors
121 |
122 | - @haringsrob made their first contribution in https://github.com/TappNetwork/filament-auditing/pull/16
123 |
124 | **Full Changelog**: https://github.com/TappNetwork/filament-auditing/compare/v3.0.0...v3.0.1
125 |
126 | ## 1.0
127 |
128 | - Initial release
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Filament Laravel Auditing
2 |
3 | [](https://packagist.org/packages/tapp/filament-auditing)
4 | 
5 | [](https://packagist.org/packages/tapp/filament-auditing)
6 |
7 | A Filament plugin for [Laravel Auditing](https://laravel-auditing.com/) package.
8 | This plugin contains a relation manager for audits that you can add to your Filament resources.
9 |
10 | This package provides a Filament resource manager that shows a table with all audits on view and edit pages and allows
11 | restore audits.
12 |
13 | ## Installation
14 |
15 | > **Note**
16 | > This plugin uses the [Laravel Auditing](https://laravel-auditing.com/) package. First install and configure this
17 | > package.
18 |
19 | You can install the plugin via composer:
20 |
21 | ```bash
22 | composer require tapp/filament-auditing:"^3.0"
23 | ```
24 |
25 | > **Note**
26 | > For **Filament 2.x** check the **[2.x](https://github.com//TappNetwork/filament-auditing/tree/2.x)** branch
27 |
28 | You can publish the view files with:
29 |
30 | ```bash
31 | php artisan vendor:publish --tag="filament-auditing-views"
32 | ```
33 | You can publish the translation files with:
34 |
35 | ```bash
36 | php artisan vendor:publish --tag="filament-auditing-translations"
37 | ```
38 |
39 | You can publish the config file with:
40 |
41 | ```bash
42 | php artisan vendor:publish --tag="filament-auditing-config"
43 | ```
44 |
45 | This is the content of the published config file:
46 |
47 | ```php
48 | [
53 | 'column' => 'created_at',
54 | 'direction' => 'desc',
55 | ],
56 |
57 | 'is_lazy' => true,
58 |
59 | 'audits_extend' => [
60 | // 'url' => [
61 | // 'class' => \Filament\Tables\Columns\TextColumn::class,
62 | // 'methods' => [
63 | // 'sortable',
64 | // 'searchable' => true,
65 | // 'default' => 'N/A'
66 | // ]
67 | // ],
68 | ]
69 |
70 | 'custom_audits_view' => false,
71 |
72 | 'custom_view_parameters' => [
73 | ],
74 |
75 | 'mapping' => [
76 | ],
77 | ];
78 | ```
79 |
80 | The `audits_sort` can be used to change the default sort on the audits table.
81 |
82 | ## Usage
83 |
84 | To show the audits table in your Filament resource, just add `AuditsRelationManager::class` on your
85 | resource's `getRelations` method:
86 |
87 | ```php
88 | use Tapp\FilamentAuditing\RelationManagers\AuditsRelationManager;
89 |
90 | public static function getRelations(): array
91 | {
92 | return [
93 | // ...
94 | AuditsRelationManager::class,
95 | ];
96 | }
97 | ```
98 |
99 | That's it, you're all set!
100 |
101 | If you access your resource, and edit some data, you will now see the audits table on edit and view pages.
102 |
103 | ### Extending Columns
104 |
105 | In case you need to add a column to the AuditsRelationManager that does
106 | not already exist in the table, you can add it in the config using the format denoted in the example below, and it will be
107 | prepended to the table builder. The name of the column to be added is the key of an associative array that contains other information about the class, as shown in the example below. The class instance of the column must be added, but the methods can be left out if not required, or added wherever necessary.
108 |
109 | ```php
110 | [
115 | 'url' => [
116 | 'class' => \Filament\Tables\Columns\TextColumn::class, // required
117 | 'methods' => [
118 | 'sortable',
119 | 'default' => 'NIL',
120 | ],
121 | ],
122 | ]
123 |
124 | ];
125 | ```
126 |
127 | After adding this information in the config, please run this command for changes to take place.
128 |
129 | ```bash
130 | php artisan optimize
131 | ```
132 |
133 | Methods with two or more parameters can be specified with an array like so:
134 |
135 | ```php
136 | [
141 | 'created_at' => [
142 | 'class' => \Filament\Tables\Columns\TextColumn::class, // required
143 | 'methods' => [
144 | 'sortable',
145 | 'date' => ['Y-m-d H:i:s', 'America/New_York'],
146 | ],
147 | ],
148 | ]
149 |
150 | ];
151 | ```
152 |
153 | ### Custom View Data Formatting
154 |
155 | If you want to modify the content of the audit to display the old and new values in a specific way, such as showing the value of a specific column instead of the ID for relationships, or even customize the entire view to display data in a different way than the default table, you can use one of these methods described below (first, make sure the plugin views are published):
156 |
157 | #### Show a related column instead of foreign id
158 |
159 | To use another field to be displayed for relationships instead of the foreign id, in old and new values, you can add on the `mapping` array, in `filament-auditing.php` config file, the label and field that should be displayed, as well as the related model, using the foreign key as the array key. For example, on an `user` relationship with the `user_id` foreing key, this config will display the user `name` along with the `User` label:
160 |
161 | ```bash
162 | 'mapping' => [
163 | 'user_id' => [
164 | 'model' => App\Models\User::class,
165 | 'field' => 'name',
166 | 'label' => 'User',
167 | ],
168 | ],
169 | ```
170 |
171 | And you'd like to customize the view, you can do it in the published view `views/vendor/filament-auditing/tables/columns/key-value.blade.php` file.
172 |
173 | #### Customizing the Old and New Values
174 |
175 | If you need to customize the presentation for other old and new values, besides the related fields, you can add a `formatAuditFieldsForPresentation($field, $record)` method on the model that is auditable, with two parameters:
176 | - the first parameter contains the name of the field (`old_values` or `new_values`).
177 | - the second parameter contains de current audit record
178 |
179 | This method must return the formatted audit fields.
180 |
181 | For example, let's say you have an `Article` model that is auditable and contains a related user, and you added a `formatAuditFieldsForPresentation($field, $record)` method that returns the related user name instead of the id, and the data formatted with some HTML code:
182 |
183 | ```php
184 | {$field});
204 |
205 | $formattedResult = '
';
206 |
207 | foreach ($fields as $key => $value) {
208 | $formattedResult .= '- ';
209 | $formattedResult .= match ($key) {
210 | 'user_id' => 'User: '.User::find($record->{$field}['user_id'])?->name.'
',
211 | 'title' => 'Title: '.(string) str($record->{$field}['title'])->title().'
',
212 | 'order' => 'Order: '.$record->{$field}['order'].'
',
213 | 'content' => 'Content: '.$record->{$field}['content'].'
',
214 | default => ' - ',
215 | };
216 | $formattedResult .= ' ';
217 | }
218 |
219 | $formattedResult .= '
';
220 |
221 | return new HtmlString($formattedResult);
222 | }
223 |
224 | public function user(): BelongsTo
225 | {
226 | return $this->belongsTo(User::class);
227 | }
228 | }
229 | ```
230 |
231 | #### Customizing the Entire View Content
232 |
233 | If you'd like to customize the entire view content, you may set the `custom_audits_view` config value to `true` on `config/filament-auditing.php` file:
234 |
235 | ```php
236 | 'custom_audits_view' => true,
237 | ```
238 |
239 | This modification will allow you to take full control of the display and tailor it to your specific requirements. You can now add your custom content on `resources/views/vendor/filament-auditing/tables/custom-audit-content.blade.php` file.
240 | For example:
241 |
242 | ```php
243 | @php
244 | $type = (string) str(class_basename($owner))->lower();
245 | @endphp
246 |
247 | @if(isset($records))
248 |
249 |
250 | @foreach($headers as $header)
251 |
252 | {{$header}}
253 |
254 | @endforeach
255 |
256 | @foreach($records as $audit)
257 |
258 | @foreach ($audit->getModified() as $attribute => $modified)
259 |
260 | @lang($type.'.metadata', $audit->getMetadata())
261 |
262 | @php
263 | $current = $type.'.'.$audit->event.'.modified.'.$attribute;
264 |
265 | $modified['new'] = $owner->formatFieldForPresentation($attribute, $modified['new']);
266 |
267 | if (isset($modified['old'])) {
268 | $modified['old'] = $owner->formatFieldForPresentation($attribute, $modified['old']);
269 | }
270 | @endphp
271 |
272 | @lang($current, $modified)
273 |
274 | @endforeach
275 |
276 | @endforeach
277 |
278 | @else
279 |
280 | @lang($type.'.unavailable_audits')
281 |
282 | @endif
283 | ```
284 |
285 | The owner record is available to this view via `$owner` variable. To pass some additional parameters to the view, you may use the `custom_view_parameters` config:
286 |
287 | ```php
288 | 'custom_view_parameters' => [
289 | 'headers' => [
290 | 'Audit',
291 | ],
292 | ],
293 | ```
294 |
295 | To format a field, you may also add a `formatFieldForPresentation` method on the owner model, with the field name and value as parameters, like in the example above. This method must return a formatted field.
296 |
297 | For example, in an `Article` model, to return the name of the related user:
298 |
299 | ```php
300 | public function formatFieldForPresentation($field, $value)
301 | {
302 | return match($field) {
303 | 'user_id' => $value ? optional(User::find($value))->name : $value,
304 | default => $value,
305 | };
306 | }
307 | ```
308 |
309 | An example of the `article.php` lang file content used in the `custom-audit-content.blade.php` view code above:
310 |
311 | ```php
312 | 'No article audits available',
316 |
317 | 'metadata' => 'On :audit_created_at, :user_name [:audit_ip_address] :audit_event this record via :audit_url',
318 |
319 | 'updated' => [
320 | 'modified' => [
321 | 'order' => 'The Order has been modified from :old to :new',
322 | 'title' => 'The Title has been modified from :old to :new',
323 | 'content' => 'The Content has been modified from :old to :new',
324 | 'user_id' => 'The User has been modified from :old to :new',
325 | ],
326 | ],
327 | ];
328 | ```
329 |
330 | ### Permissions
331 |
332 | Two permissions are registered by default, allowing access to:
333 |
334 | - `audit`: view audits
335 | - `restoreAudit`: restore audits
336 |
337 | You can override these permissions by adding a policy with `audit` and `restoreAudit`.
338 |
339 | ### Event emitted
340 |
341 | The `auditRestored` event is emitted when an audit is restored, so you could register a listener using the $listeners property to execute some extra code after the audit is restored.
342 |
343 | E.g.: on Edit page of your resource:
344 |
345 | ```php
346 | protected $listeners = [
347 | 'auditRestored',
348 | ];
349 |
350 | public function auditRestored()
351 | {
352 | // your code
353 | }
354 | ```
355 |
356 | ### Event listener
357 |
358 | The audits relation manager listen to the `updateAuditsRelationManager` event to refresh the audits table.
359 |
360 | So you can dispatch this event in the Edit page of your resource (e.g.: in a edit page of a `PostResource` -> `app/Filament/Resources/PostResource/Pages/EditPost.php`) when the form is updated:
361 |
362 | ```php
363 | protected function afterSave(): void
364 | {
365 | $this->dispatch('updateAuditsRelationManager');
366 | }
367 | ```
368 |
369 | > [!WARNING]
370 | > When dispaching this event, set the [is_lazy](https://filamentphp.com/docs/3.x/panels/resources/relation-managers#disabling-lazy-loading) configuration to `false`, on `filament-auditing.php`
371 | > config file, to avoid this exception: "Typed property Filament\Resources\RelationManagers\RelationManager::$table
372 | > must not be accessed before initialization"
373 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tapp/filament-auditing",
3 | "description": "Filament Laravel Auditing plugin.",
4 | "keywords": [
5 | "tapp network",
6 | "filament",
7 | "laravel",
8 | "auditing",
9 | "audit"
10 | ],
11 | "license": "MIT",
12 | "authors": [
13 | {
14 | "name": "Tapp Network",
15 | "email": "steve@tappnetwork.com",
16 | "role": "Developer"
17 | },
18 | {
19 | "name": "Tapp Network",
20 | "email": "andreia.bohner@tappnetwork.com",
21 | "role": "Developer"
22 | }
23 | ],
24 | "homepage": "https://github.com/TappNetwork/filament-auditing",
25 | "support": {
26 | "issues": "https://github.com/TappNetwork/filament-auditing/issues",
27 | "source": "https://github.com/TappNetwork/filament-auditing"
28 | },
29 | "require": {
30 | "php": "^8.2",
31 | "filament/filament": "^3.0-stable",
32 | "owen-it/laravel-auditing": "^14.0||^13.0",
33 | "spatie/laravel-package-tools": "^1.16"
34 | },
35 | "require-dev": {
36 | "larastan/larastan": "^2.9||^3.0",
37 | "laravel/pint": "^1.14",
38 | "nunomaduro/collision": "^8.1.1||^7.10.0",
39 | "orchestra/testbench": "^10.0.0||^9.0.0||^8.22.0",
40 | "pestphp/pest": "^3.0||^2.34",
41 | "pestphp/pest-plugin-arch": "^3.0||^2.7",
42 | "pestphp/pest-plugin-laravel": "^3.0||^2.3",
43 | "phpstan/extension-installer": "^1.3||^2.0",
44 | "phpstan/phpstan-deprecation-rules": "^1.1||^2.0",
45 | "phpstan/phpstan-phpunit": "^1.3||^2.0",
46 | "spatie/laravel-ray": "^1.35"
47 | },
48 | "autoload": {
49 | "psr-4": {
50 | "Tapp\\FilamentAuditing\\": "src"
51 | }
52 | },
53 | "autoload-dev": {
54 | "psr-4": {
55 | "Tapp\\FilamentAuditing\\Tests\\": "tests/",
56 | "Workbench\\App\\": "workbench/app/"
57 | }
58 | },
59 | "config": {
60 | "sort-packages": true,
61 | "allow-plugins": {
62 | "pestphp/pest-plugin": true,
63 | "phpstan/extension-installer": true
64 | }
65 | },
66 | "extra": {
67 | "laravel": {
68 | "providers": [
69 | "Tapp\\FilamentAuditing\\FilamentAuditingServiceProvider"
70 | ]
71 | }
72 | },
73 | "minimum-stability": "dev",
74 | "prefer-stable": true
75 | }
76 |
--------------------------------------------------------------------------------
/config/filament-auditing.php:
--------------------------------------------------------------------------------
1 | [
6 | 'column' => 'created_at',
7 | 'direction' => 'desc',
8 | ],
9 |
10 | 'is_lazy' => true,
11 |
12 | /**
13 | * Extending Columns
14 | * --------------------------------------------------------------------------
15 | * In case you need to add a column to the AuditsRelationManager that does
16 | * not already exist in the table, you can add it here, and it will be
17 | * prepended to the table builder.
18 | */
19 | 'audits_extend' => [
20 | // 'url' => [
21 | // 'class' => \Filament\Tables\Columns\TextColumn::class,
22 | // 'methods' => [
23 | // 'sortable',
24 | // 'searchable' => true,
25 | // 'default' => 'N/A'
26 | // ]
27 | // ],
28 | ],
29 |
30 | 'custom_audits_view' => false,
31 |
32 | 'custom_view_parameters' => [
33 | ],
34 |
35 | 'mapping' => [
36 | ],
37 |
38 | ];
39 |
--------------------------------------------------------------------------------
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | ignoreErrors:
3 | -
4 | message: '#^Method Spatie\\LaravelPackageTools\\Package\:\:hasTranslations\(\) invoked with 1 parameter, 0 required\.$#'
5 | identifier: arguments.count
6 | count: 1
7 | path: src/FilamentAuditingServiceProvider.php
8 |
9 | -
10 | message: '#^Access to an undefined property OwenIt\\Auditing\\Contracts\\Audit\:\:\$event\.$#'
11 | identifier: property.notFound
12 | count: 1
13 | path: src/RelationManagers/AuditsRelationManager.php
14 |
15 | -
16 | message: '#^Call to function is_array\(\) with array will always evaluate to true\.$#'
17 | identifier: function.alreadyNarrowedType
18 | count: 1
19 | path: src/RelationManagers/AuditsRelationManager.php
20 |
21 | -
22 | message: '#^Call to function method_exists\(\) with Illuminate\\Database\\Eloquent\\Model and ''formatAuditFieldsFo…'' will always evaluate to true\.$#'
23 | identifier: function.alreadyNarrowedType
24 | count: 2
25 | path: src/RelationManagers/AuditsRelationManager.php
26 |
27 | -
28 | message: '#^Unreachable statement \- code above always terminates\.$#'
29 | identifier: deadCode.unreachable
30 | count: 1
31 | path: src/RelationManagers/AuditsRelationManager.php
32 |
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan-baseline.neon
3 |
4 | parameters:
5 | level: 4
6 | paths:
7 | - src
8 | - config
9 | tmpDir: build/phpstan
10 | checkOctaneCompatibility: true
11 | checkModelProperties: true
12 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 | tests
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ./src
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/resources/lang/ar/filament-auditing.php:
--------------------------------------------------------------------------------
1 | 'سجل التعديلات',
12 | 'table.empty_state_heading' => 'لا توجد عمليات تدقيق',
13 |
14 | /*
15 | |--------------------------------------------------------------------------
16 | | Table Columns
17 | |--------------------------------------------------------------------------
18 | */
19 |
20 | 'column.user_name' => 'المستخدم',
21 | 'column.event' => 'العملية',
22 | 'column.created_at' => 'تاريخ التعديل',
23 | 'column.old_values' => 'القيم السابقة',
24 | 'column.new_values' => 'القيم الجديدة',
25 | 'column.new_values' => 'القيم الجديدة',
26 |
27 | /*
28 | |--------------------------------------------------------------------------
29 | | Table Actions
30 | |--------------------------------------------------------------------------
31 | */
32 |
33 | 'action.restore' => 'استعادة',
34 |
35 | /*
36 | |--------------------------------------------------------------------------
37 | | Notifications
38 | |--------------------------------------------------------------------------
39 | */
40 |
41 | 'notification.restored' => 'تمت الاستعادة',
42 | 'notification.unchanged' => 'لا توجد تعديلات',
43 |
44 | ];
45 |
--------------------------------------------------------------------------------
/resources/lang/de/filament-auditing.php:
--------------------------------------------------------------------------------
1 | 'Audits',
12 | 'table.empty_state_heading' => 'Keine Audits',
13 |
14 | /*
15 | |--------------------------------------------------------------------------
16 | | Table Columns
17 | |--------------------------------------------------------------------------
18 | */
19 |
20 | 'column.user_name' => 'Benutzer',
21 | 'column.event' => 'Event',
22 | 'column.created_at' => 'Erstellt am',
23 | 'column.old_values' => 'Alter Wert',
24 | 'column.new_values' => 'Neuer Wert',
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Table Actions
29 | |--------------------------------------------------------------------------
30 | */
31 |
32 | 'action.restore' => 'Wiederherstellen',
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Notifications
37 | |--------------------------------------------------------------------------
38 | */
39 |
40 | 'notification.restored' => 'Audit wiederhergestellt',
41 | 'notification.unchanged' => 'Es gibt nichts zu ändern',
42 |
43 | ];
44 |
--------------------------------------------------------------------------------
/resources/lang/en/filament-auditing.php:
--------------------------------------------------------------------------------
1 | 'Audits',
12 | 'table.empty_state_heading' => 'No audits',
13 |
14 | /*
15 | |--------------------------------------------------------------------------
16 | | Table Columns
17 | |--------------------------------------------------------------------------
18 | */
19 |
20 | 'column.user_name' => 'User',
21 | 'column.event' => 'Event',
22 | 'column.created_at' => 'Created',
23 | 'column.old_values' => 'Old Values',
24 | 'column.new_values' => 'New Values',
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Table Actions
29 | |--------------------------------------------------------------------------
30 | */
31 |
32 | 'action.restore' => 'Restore',
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Notifications
37 | |--------------------------------------------------------------------------
38 | */
39 |
40 | 'notification.restored' => 'Audit restored',
41 | 'notification.unchanged' => 'Nothing to change',
42 |
43 | ];
44 |
--------------------------------------------------------------------------------
/resources/lang/es/filament-auditing.php:
--------------------------------------------------------------------------------
1 | 'Auditorías',
12 | 'table.empty_state_heading' => 'Sin auditorías',
13 |
14 | /*
15 | |--------------------------------------------------------------------------
16 | | Table Columns
17 | |--------------------------------------------------------------------------
18 | */
19 |
20 | 'column.user_name' => 'Usuario',
21 | 'column.event' => 'Evento',
22 | 'column.created_at' => 'Creado en',
23 | 'column.old_values' => 'Valores antiguos',
24 | 'column.new_values' => 'Nuevos valores',
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Table Actions
29 | |--------------------------------------------------------------------------
30 | */
31 |
32 | 'action.restore' => 'Restaurar',
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Notifications
37 | |--------------------------------------------------------------------------
38 | */
39 |
40 | 'notification.restored' => 'Auditoría restaurada',
41 | 'notification.unchanged' => 'Nada que cambiar',
42 |
43 | ];
44 |
--------------------------------------------------------------------------------
/resources/lang/fr/filament-auditing.php:
--------------------------------------------------------------------------------
1 | 'Audits',
12 | 'table.empty_state_heading' => "Pas d'audits",
13 |
14 | /*
15 | |--------------------------------------------------------------------------
16 | | Table Columns
17 | |--------------------------------------------------------------------------
18 | */
19 |
20 | 'column.user_name' => 'Utilisateur',
21 | 'column.event' => 'Événement',
22 | 'column.created_at' => 'Créé',
23 | 'column.old_values' => 'Anciennes valeurs',
24 | 'column.new_values' => 'Nouvelles valeurs',
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Table Actions
29 | |--------------------------------------------------------------------------
30 | */
31 |
32 | 'action.restore' => 'Restaurer',
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Notifications
37 | |--------------------------------------------------------------------------
38 | */
39 |
40 | 'notification.restored' => 'Audit restauré',
41 | 'notification.unchanged' => 'Rien à modifier',
42 |
43 | ];
44 |
--------------------------------------------------------------------------------
/resources/lang/ko/filament-auditing.php:
--------------------------------------------------------------------------------
1 | '변경이력',
12 | 'table.empty_state_heading' => '감사 없음',
13 |
14 | /*
15 | |--------------------------------------------------------------------------
16 | | Table Columns
17 | |--------------------------------------------------------------------------
18 | */
19 |
20 | 'column.user_name' => '사용자',
21 | 'column.event' => '이벤트',
22 | 'column.created_at' => '등록됨',
23 | 'column.old_values' => '기존 내용',
24 | 'column.new_values' => '신규 내용',
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Table Actions
29 | |--------------------------------------------------------------------------
30 | */
31 |
32 | 'action.restore' => '복원',
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Notifications
37 | |--------------------------------------------------------------------------
38 | */
39 |
40 | 'notification.restored' => '변경이력 복원',
41 | 'notification.unchanged' => '변경할 사항이 없습니다.',
42 |
43 | ];
44 |
--------------------------------------------------------------------------------
/resources/lang/pt_BR/filament-auditing.php:
--------------------------------------------------------------------------------
1 | 'Auditorias',
12 | 'table.empty_state_heading' => 'Nenhuma auditoria',
13 |
14 | /*
15 | |--------------------------------------------------------------------------
16 | | Table Columns
17 | |--------------------------------------------------------------------------
18 | */
19 |
20 | 'column.user_name' => 'Usuário',
21 | 'column.event' => 'Evento',
22 | 'column.created_at' => 'Criado em',
23 | 'column.old_values' => 'Valores Antigos',
24 | 'column.new_values' => 'Novos Valores',
25 |
26 | /*
27 | |--------------------------------------------------------------------------
28 | | Table Actions
29 | |--------------------------------------------------------------------------
30 | */
31 |
32 | 'action.restore' => 'Restaurar',
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | Notifications
37 | |--------------------------------------------------------------------------
38 | */
39 |
40 | 'notification.restored' => 'Auditoria restaurada',
41 | 'notification.unchanged' => 'Nada a alterar',
42 |
43 | ];
44 |
--------------------------------------------------------------------------------
/resources/views/tables/columns/key-value.blade.php:
--------------------------------------------------------------------------------
1 | @php
2 | $data = isset($state) ? $state : $getState()
3 | @endphp
4 |
5 |
6 |
7 | @foreach($data ?? [] as $key => $value)
8 | -
9 |
10 | {{ Str::title($key) }}:
11 |
12 |
13 | @unless(is_array($value))
14 | {{ $value }}
15 | @else
16 |
17 | @foreach ($value as $nestedValue)
18 | {{$nestedValue['id']}}
19 | @endforeach
20 |
21 | @endunless
22 |
23 |
24 | @endforeach
25 |
26 |
27 |
--------------------------------------------------------------------------------
/resources/views/tables/custom-audit-content.blade.php:
--------------------------------------------------------------------------------
1 | {{-- add your custom code here --}}
2 |
--------------------------------------------------------------------------------
/src/FilamentAuditingServiceProvider.php:
--------------------------------------------------------------------------------
1 | name('filament-auditing')
16 | ->hasConfigFile('filament-auditing')
17 | ->hasTranslations('filament-auditing')
18 | ->hasViews('filament-auditing');
19 | }
20 |
21 | public function packageBooted(): void
22 | {
23 | parent::packageBooted();
24 |
25 | // Default values for view and restore audits. Add a policy to override these values
26 | Gate::define('audit', function ($user, $resource) {
27 | return true;
28 | });
29 |
30 | Gate::define('restoreAudit', function ($user, $resource) {
31 | return true;
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/RelationManagers/AuditsRelationManager.php:
--------------------------------------------------------------------------------
1 | '$refresh'];
25 |
26 | public static function isLazy(): bool
27 | {
28 | return config('filament-auditing.is_lazy');
29 | }
30 |
31 | public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
32 | {
33 | return Filament::auth()->user()->can('audit', $ownerRecord);
34 | }
35 |
36 | public static function getTitle(Model $ownerRecord, string $pageClass): string
37 | {
38 | return trans('filament-auditing::filament-auditing.table.heading');
39 | }
40 |
41 | public function table(Table $table): Table
42 | {
43 | $oldValuesColumn =
44 | method_exists($this->getOwnerRecord(), 'formatAuditFieldsForPresentation')
45 | ?
46 | Tables\Columns\TextColumn::make('old_values')
47 | ->formatStateUsing(fn (Column $column, $record, $state) => method_exists($this->getOwnerRecord(), 'formatAuditFieldsForPresentation') ? $this->getOwnerRecord()->formatAuditFieldsForPresentation($column->getName(), $record) : $state)
48 | ->label(trans('filament-auditing::filament-auditing.column.old_values'))
49 | :
50 | Tables\Columns\TextColumn::make('old_values')
51 | ->formatStateUsing(fn (Column $column, $record, $state): View => view('filament-auditing::tables.columns.key-value', ['state' => $this->mapRelatedColumns($column->getState(), $record)]))
52 | ->label(trans('filament-auditing::filament-auditing.column.old_values'));
53 |
54 | $newValuesColumn =
55 | method_exists($this->getOwnerRecord(), 'formatAuditFieldsForPresentation')
56 | ?
57 | Tables\Columns\TextColumn::make('new_values')
58 | ->formatStateUsing(fn (Column $column, $record, $state) => method_exists($this->getOwnerRecord(), 'formatAuditFieldsForPresentation') ? $this->getOwnerRecord()->formatAuditFieldsForPresentation($column->getName(), $record) : $state)
59 | ->label(trans('filament-auditing::filament-auditing.column.new_values'))
60 | :
61 | Tables\Columns\TextColumn::make('new_values')
62 | ->formatStateUsing(fn (Column $column, $record, $state): View => view('filament-auditing::tables.columns.key-value', ['state' => $this->mapRelatedColumns($column->getState(), $record)]))
63 | ->label(trans('filament-auditing::filament-auditing.column.new_values'));
64 |
65 | return $table
66 | ->modifyQueryUsing(fn (Builder $query) => $query->with('user')->orderBy(config('filament-auditing.audits_sort.column'), config('filament-auditing.audits_sort.direction')))
67 | ->content(fn (): ?View => config('filament-auditing.custom_audits_view') ? view('filament-auditing::tables.custom-audit-content', Arr::add(self::customViewParameters(), 'owner', $this->getOwnerRecord())) : null)
68 | ->emptyStateHeading(trans('filament-auditing::filament-auditing.table.empty_state_heading'))
69 | ->columns(Arr::flatten([
70 | Tables\Columns\TextColumn::make('user.name')
71 | ->label(trans('filament-auditing::filament-auditing.column.user_name')),
72 | Tables\Columns\TextColumn::make('event')
73 | ->label(trans('filament-auditing::filament-auditing.column.event')),
74 | Tables\Columns\TextColumn::make('created_at')
75 | ->since()
76 | ->label(trans('filament-auditing::filament-auditing.column.created_at')),
77 | $oldValuesColumn,
78 | $newValuesColumn,
79 | self::extraColumns(),
80 | ]))
81 | ->filters([
82 | //
83 | ])
84 | ->headerActions([
85 | //
86 | ])
87 | ->actions([
88 | Tables\Actions\Action::make('restore')
89 | ->label(trans('filament-auditing::filament-auditing.action.restore'))
90 | ->action(fn (Audit $record) => static::restoreAuditSelected($record))
91 | ->icon('heroicon-o-arrow-path')
92 | ->requiresConfirmation()
93 | ->visible(fn (Audit $record, RelationManager $livewire): bool => Filament::auth()->user()->can('restoreAudit', $livewire->ownerRecord) && $record->event === 'updated')
94 | ->after(function ($livewire) {
95 | $livewire->dispatch('auditRestored');
96 | }),
97 | ])
98 | ->bulkActions([
99 | //
100 | ]);
101 | }
102 |
103 | protected static function customViewParameters(): array
104 | {
105 | return config('filament-auditing.custom_view_parameters');
106 | }
107 |
108 | protected function mapRelatedColumns($state, $record)
109 | {
110 | $relationshipsToUpdate = Arr::wrap(config('filament-auditing.mapping'));
111 |
112 | if (count($relationshipsToUpdate) !== 0) {
113 | foreach ($relationshipsToUpdate as $key => $relationship) {
114 | if (array_key_exists($key, $state)) {
115 | $state[$relationship['label']] = $relationship['model']::find($state[$key])?->{$relationship['field']};
116 | unset($state[$key]);
117 | }
118 | }
119 | }
120 |
121 | return $state;
122 | }
123 |
124 | protected static function extraColumns()
125 | {
126 | return Arr::map(config('filament-auditing.audits_extend'), function ($buildParameters, $columnName) {
127 | return collect($buildParameters)->pipeThrough([
128 | function ($collection) use ($columnName) {
129 | $columnClass = (string) $collection->get('class');
130 |
131 | if (! is_null($collection->get('methods'))) {
132 | $columnClass = $columnClass::make($columnName);
133 |
134 | collect($collection->get('methods'))->transform(function ($value, $key) use ($columnClass) {
135 | if (is_numeric($key)) {
136 | return $columnClass->$value();
137 | }
138 |
139 | return $columnClass->$key(...Arr::wrap($value));
140 | });
141 |
142 | return $columnClass;
143 | }
144 |
145 | return $columnClass::make($columnName);
146 | },
147 | ]);
148 | });
149 | }
150 |
151 | protected static function restoreAuditSelected($audit)
152 | {
153 | $morphClass = Relation::getMorphedModel($audit->auditable_type) ?? $audit->auditable_type;
154 |
155 | $record = $morphClass::find($audit->auditable_id);
156 |
157 | if (! $record) {
158 | self::unchangedAuditNotification();
159 |
160 | return;
161 | }
162 |
163 | if ($audit->event !== 'updated') {
164 | self::unchangedAuditNotification();
165 |
166 | return;
167 | }
168 |
169 | $restore = $audit->old_values;
170 |
171 | Arr::pull($restore, 'id');
172 |
173 | if (is_array($restore)) {
174 |
175 | foreach ($restore as $key => $item) {
176 | $decode = json_decode($item);
177 |
178 | if (json_last_error() === JSON_ERROR_NONE) {
179 | $restore[$key] = $decode;
180 | }
181 | }
182 |
183 | $record->fill($restore);
184 | $record->save();
185 |
186 | self::restoredAuditNotification();
187 |
188 | return;
189 | }
190 |
191 | self::unchangedAuditNotification();
192 | }
193 |
194 | protected static function restoredAuditNotification()
195 | {
196 | Notification::make()
197 | ->title(trans('filament-auditing::filament-auditing.notification.restored'))
198 | ->success()
199 | ->send();
200 | }
201 |
202 | protected static function unchangedAuditNotification()
203 | {
204 | Notification::make()
205 | ->title(trans('filament-auditing::filament-auditing.notification.unchanged'))
206 | ->warning()
207 | ->send();
208 | }
209 |
210 | protected function canCreate(): bool
211 | {
212 | return false;
213 | }
214 |
215 | protected function canEdit(Model $record): bool
216 | {
217 | return false;
218 | }
219 |
220 | protected function canDelete(Model $record): bool
221 | {
222 | return false;
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/tests/ArchTest.php:
--------------------------------------------------------------------------------
1 | expect(['dd', 'dump', 'ray'])
5 | ->each->not->toBeUsed();
6 |
--------------------------------------------------------------------------------
/tests/ExampleTest.php:
--------------------------------------------------------------------------------
1 | toBeTrue();
5 | });
6 |
--------------------------------------------------------------------------------
/tests/Pest.php:
--------------------------------------------------------------------------------
1 | in(__DIR__);
6 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | set('database.default', 'testing');
25 |
26 | /*
27 | $migration = include __DIR__.'/../database/migrations/create_filament-maillog_table.php.stub';
28 | $migration->up();
29 | */
30 | }
31 | }
32 |
--------------------------------------------------------------------------------