🆙 Add cms i using 🆙

This commit is contained in:
Remco
2025-11-25 22:42:56 +01:00
parent 94704e0925
commit d44196149e
35591 changed files with 3601123 additions and 0 deletions
@@ -0,0 +1,2 @@
github: spatie
custom: https://spatie.be/open-source/support-us
@@ -0,0 +1,36 @@
---
name: "🐛 Bug Report"
about: Create a bug report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Things needed to reproduce the error.
eg: Configuration, Migration, Model, Controller/Command
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Versions (please complete the following information)**
- PHP:
- Database:
- Laravel:
- Package:
**Additional context**
Add any other context about the problem here.
**Exception**
The thrown exception message.
**Stack Trace**
The full stack trace of the thrown exception.
@@ -0,0 +1,15 @@
blank_issues_enabled: false
contact_links:
- name: 🚀 Feature Request
url: https://github.com/spatie/laravel-activitylog/discussions/new?category_id=4507850
about: Share ideas for new features
- name: 🙏 Ask a Question
url: https://github.com/spatie/laravel-activitylog/discussions/new?category_id=4507849
about: Ask the community for help
- name: 💖 Share some love
url: https://github.com/spatie/laravel-activitylog/discussions/new?category_id=4507852
about: Share gratitude with the team
- name: 🚨 Report Security Vulnerability
url: https://github.com/spatie/laravel-activitylog#security
about: Please report security vulnerabilities by email to freek@spatie.be
@@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
reviewers:
- "Gummibeer"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
reviewers:
- "Gummibeer"
@@ -0,0 +1,23 @@
name: normalize composer.json
on:
push:
paths:
- "composer.json"
jobs:
normalize:
runs-on: ubuntu-latest
steps:
- name: Git checkout
uses: actions/checkout@v3
- name: normalize composer.json
run: |
composer global config --no-plugins allow-plugins.ergebnis/composer-normalize true
composer global require ergebnis/composer-normalize
composer normalize
- uses: stefanzweifel/git-auto-commit-action@v5.2.0
with:
commit_message: normalize composer.json
@@ -0,0 +1,32 @@
name: dependabot-auto-merge
on: pull_request_target
permissions:
pull-requests: write
contents: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2.4.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Auto-merge Dependabot PRs for semver-minor updates
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor'}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
- name: Auto-merge Dependabot PRs for semver-patch updates
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
run: gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
@@ -0,0 +1,23 @@
name: Check & fix styling
on: [push]
jobs:
php-cs-fixer:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}
- name: Run PHP CS Fixer
uses: docker://oskarstark/php-cs-fixer-ga:2.19.0
with:
args: --config=.php_cs.dist --allow-risky=yes
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5.2.0
with:
commit_message: Fix styling
@@ -0,0 +1,43 @@
name: run-tests
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
test:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: [8.3, 8.2, 8.1]
laravel: ["^12.0", "^11.0", "^10.0", "^9.0"]
dependency-version: [prefer-lowest, prefer-stable]
exclude:
- laravel: "^12.0"
php: 8.1
- laravel: "^11.0"
php: 8.1
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, mysql, mysqli, pdo_mysql, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
coverage: none
- name: Install dependencies
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "nesbot/carbon:>=2.72" --dev --no-interaction --no-update
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
- name: Execute tests
run: vendor/bin/pest
@@ -0,0 +1,17 @@
name: "Close stale issues"
on:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue is stale because it has been open 21 days with no activity. Remove stale label or comment or this will be closed in 7 days"
stale-issue-label: "stale"
exempt-issue-labels: "bug,enhancement,help wanted,next release,revisit for next major version"
days-before-stale: 21
days-before-close: 7
@@ -0,0 +1,28 @@
name: "Update Changelog"
on:
release:
types: [released]
jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
ref: main
- name: Update Changelog
uses: stefanzweifel/changelog-updater-action@v1
with:
latest-version: ${{ github.event.release.name }}
release-notes: ${{ github.event.release.body }}
- name: Commit updated CHANGELOG
uses: stefanzweifel/git-auto-commit-action@v5.2.0
with:
branch: main
commit_message: Update CHANGELOG
file_pattern: CHANGELOG.md
@@ -0,0 +1,30 @@
<?php
$finder = \PhpCsFixer\Finder::create()
->notPath('bootstrap/*')
->notPath('storage/*')
->notPath('resources/view/mail/*')
->in([
__DIR__.'/src',
__DIR__.'/tests',
])
->name('*.php')
->notName('*.blade.php')
->ignoreDotFiles(true)
->ignoreVCS(true);
return \PhpCsFixer\Config::create()
->setRules(array_merge(require '.php_cs.laravel', [
'@PSR2' => true,
'@PSR12' => true,
'no_unused_imports' => true,
'phpdoc_to_comment' => false,
'phpdoc_order' => true,
'phpdoc_separation' => true,
'simplified_null_return' => false,
]))
->setLineEnding("\n")
->setIndent(str_repeat(' ', 4))
->setUsingCache(false)
->setRiskyAllowed(true)
->setFinder($finder);
@@ -0,0 +1,126 @@
<?php
/**
* @link https://gist.github.com/laravel-shift/cab527923ed2a109dda047b97d53c200
*/
return [
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => [
'default' => 'single_space',
'operators' => ['=>' => null]
],
'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => [
'statements' => ['return']
],
'braces' => true,
'cast_spaces' => true,
'class_attributes_separation' => [
'elements' => ['method']
],
'class_definition' => true,
'concat_space' => [
'spacing' => 'none'
],
'declare_equal_normalize' => true,
'elseif' => true,
'encoding' => true,
'full_opening_tag' => true,
'fully_qualified_strict_types' => true, // added by Shift
'function_declaration' => true,
'function_typehint_space' => true,
'heredoc_to_nowdoc' => true,
'include' => true,
'increment_style' => ['style' => 'post'],
'indentation_type' => true,
'linebreak_after_opening_tag' => true,
'line_ending' => true,
'lowercase_cast' => true,
'lowercase_constants' => true,
'lowercase_keywords' => true,
'lowercase_static_reference' => true, // added from Symfony
'magic_method_casing' => true, // added from Symfony
'magic_constant_casing' => true,
'method_argument_space' => true,
'native_function_casing' => true,
'no_alias_functions' => true,
'no_extra_blank_lines' => [
'tokens' => [
'extra',
'throw',
'use',
'use_trait',
]
],
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_closing_tag' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => [
'use' => 'echo'
],
'no_multiline_whitespace_around_double_arrow' => true,
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line'
],
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => true,
'no_spaces_inside_parenthesis' => true,
'no_trailing_comma_in_list_call' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_trailing_whitespace' => true,
'no_trailing_whitespace_in_comment' => true,
'no_unneeded_control_parentheses' => true,
'no_unreachable_default_argument_value' => true,
'no_useless_return' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'normalize_index_brace' => true,
'not_operator_with_successor_space' => true,
'object_operator_without_whitespace' => true,
'ordered_imports' => ['sortAlgorithm' => 'alpha'],
'phpdoc_indent' => true,
'phpdoc_inline_tag' => true,
'phpdoc_no_access' => true,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_scalar' => true,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => true,
'phpdoc_to_comment' => true,
'phpdoc_trim' => true,
'phpdoc_types' => true,
'phpdoc_var_without_name' => true,
'psr4' => true,
'self_accessor' => true,
'short_scalar_cast' => true,
'simplified_null_return' => true,
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => true,
'single_import_per_statement' => true,
'single_line_after_imports' => true,
'single_line_comment_style' => [
'comment_types' => ['hash']
],
'single_quote' => true,
'space_after_semicolon' => true,
'standardize_not_equals' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline_array' => true,
'trim_array_spaces' => true,
'unary_operator_spaces' => true,
'visibility_required' => [
'elements' => ['method', 'property']
],
'whitespace_after_comma_in_array' => true,
];
@@ -0,0 +1,673 @@
# Changelog
All notable changes to `spatie/laravel-activitylog` will be documented in this file
## 4.10.1 - 2025-02-10
### What's Changed
* Laravel 12.x Support by @erikn69 in https://github.com/spatie/laravel-activitylog/pull/1370
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.10.0...4.10.1
## 4.10.0 - 2025-02-10
### What's Changed
* Bump stefanzweifel/git-auto-commit-action from 5.0.1 to 5.1.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1365
* Bump actions/stale from 9.0.0 to 9.1.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1367
* Bump dependabot/fetch-metadata from 2.2.0 to 2.3.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1368
* Activity Facade by @stevebauman in https://github.com/spatie/laravel-activitylog/pull/1372
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.9.1...4.10.0
## 4.9.1 - 2024-11-18
### What's Changed
* Update using-multiple-logs.md by @tobischulz in https://github.com/spatie/laravel-activitylog/pull/1345
* fix: php 8.4 deprecation warnings by @ashleyshenton in https://github.com/spatie/laravel-activitylog/pull/1351
### New Contributors
* @tobischulz made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1345
* @ashleyshenton made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1351
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.9.0...4.9.1
## 4.9.0 - 2024-10-18
### What's Changed
* Bump dependabot/fetch-metadata from 1.6.0 to 2.1.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1298
* Bump stefanzweifel/git-auto-commit-action from 4.15.4 to 5.0.1 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1293
* Corrected the link to the event logging example by @makaronnik in https://github.com/spatie/laravel-activitylog/pull/1300
* Bump dependabot/fetch-metadata from 2.1.0 to 2.2.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1315
* Update batch-logs.md by @jesseschutt in https://github.com/spatie/laravel-activitylog/pull/1320
* Adjust PHPDoc type for causer and subject by @gtg-bantonio in https://github.com/spatie/laravel-activitylog/pull/1321
* Add missing return type by @dwightwatson in https://github.com/spatie/laravel-activitylog/pull/1330
* Possibility to define table name in environment file by @edwinvdpol in https://github.com/spatie/laravel-activitylog/pull/1334
### New Contributors
* @makaronnik made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1300
* @jesseschutt made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1320
* @gtg-bantonio made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1321
* @edwinvdpol made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1334
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.8.0...4.9.0
## 4.8.0 - 2024-03-08
### What's Changed
* Bump dependabot/fetch-metadata from 1.3.6 to 1.4.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1183
* Bump dependabot/fetch-metadata from 1.4.0 to 1.5.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1195
* Bump dependabot/fetch-metadata from 1.5.0 to 1.5.1 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1196
* Bump dependabot/fetch-metadata from 1.5.1 to 1.6.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1205
* Add a missing apostrophe by @dwightwatson in https://github.com/spatie/laravel-activitylog/pull/1249
* Bump actions/stale from 6.0.1 to 9.0.0 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1250
* Bump actions/cache from 3 to 4 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1263
* Add more detail to manipulating changes by @tonypartridge in https://github.com/spatie/laravel-activitylog/pull/1268
* feat!: add laravel 11 support by @StevePorter92 in https://github.com/spatie/laravel-activitylog/pull/1276
### New Contributors
* @dwightwatson made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1249
* @tonypartridge made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1268
* @StevePorter92 made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1276
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.7.3...4.8.0
## 4.7.3 - 2023-01-25
### What's Changed
- Add Dependabot Automation by @patinthehat in https://github.com/spatie/laravel-activitylog/pull/1128
- Add PHP 8.2 Support by @patinthehat in https://github.com/spatie/laravel-activitylog/pull/1129
- Bump dependabot/fetch-metadata from 1.3.5 to 1.3.6 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1156
- Fix tests badge by @erikn69 in https://github.com/spatie/laravel-activitylog/pull/1153
- Laravel 10.x support by @erikn69 in https://github.com/spatie/laravel-activitylog/pull/1152
- Fix for replacePlaceholders with ending dot by @Stefan-Dressler in https://github.com/spatie/laravel-activitylog/pull/1154
### New Contributors
- @patinthehat made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1128
- @erikn69 made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1153
- @Stefan-Dressler made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1154
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.7.2...4.7.3
## v4.7.2 - 2022-11-14
### What's Changed
- Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1117
- Bump actions/stale from 2.0.0 to 6.0.1 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1118
- Bump stefanzweifel/git-auto-commit-action from 4.0.0 to 4.15.4 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1119
- Add missing properties to Activity model by @AndreasHerss in https://github.com/spatie/laravel-activitylog/pull/1101
- Bump actions/cache from 2 to 3 by @dependabot in https://github.com/spatie/laravel-activitylog/pull/1120
- Fix enum casting by @Gummibeer in https://github.com/spatie/laravel-activitylog/pull/1121
### New Contributors
- @dependabot made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1117
- @AndreasHerss made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1101
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.7.1...4.7.2
## v4.7.1 - 2022-11-11
### What's Changed
- Fix nullable custom properties in PHP 8.0 by @stevebauman in https://github.com/spatie/laravel-activitylog/pull/1115
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.7.0...4.7.1
## v4.7.0 - 2022-11-10
### What's Changed
- Fix indentation by @mouadziani in https://github.com/spatie/laravel-activitylog/pull/1092
- Support non backed enum & php 8.1 by @pemudakoding in https://github.com/spatie/laravel-activitylog/pull/1110
### New Contributors
- @mouadziani made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1092
- @pemudakoding made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1110
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.6.0...4.7.0
## v4.6.0 - 2022-09-22
### What's Changed
- Add a default value to `getExtraProperty()` by @grantholle in https://github.com/spatie/laravel-activitylog/pull/1090
### New Contributors
- @grantholle made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1090
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.5.3...4.6.0
## 4.5.3 - 2022-05-31
- Fix default auth guard for causer - [#1053](https://github.com/spatie/laravel-activitylog/pull/1053)
## 4.5.2 - 2022-04-21
- Fix placeholder resolving - [#1038](https://github.com/spatie/laravel-activitylog/pull/1038)
## 4.5.1 - 2022-04-07
- [Use scoped instances of instead of singletons (Octane support)](https://github.com/spatie/laravel-activitylog/commit/0d0075b9c56ed0c282f59037e71cdaa6a052d336)
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.5.0...4.5.1
## 4.5.0 - 2022-04-07
## What's Changed
- Add `Conditionable` trait to `ActivityLogger` by @usernotnull in https://github.com/spatie/laravel-activitylog/pull/997
## New Contributors
- @usernotnull made their first contribution in https://github.com/spatie/laravel-activitylog/pull/997
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.4.3...4.5.0
## 4.4.3 - 2022-04-07
## What's Changed
- Fix model serialization when using `LogsActivity` with `setDescriptionForEvent()` by @stevebauman in https://github.com/spatie/laravel-activitylog/pull/977
- Fix activirt logging on model restore (#895) by @kryptamine in https://github.com/spatie/laravel-activitylog/pull/1000
- Fix nullable log names by @stevebauman in https://github.com/spatie/laravel-activitylog/pull/1029
- Fix `tapActivity` when manually creating activity logs by @FrancisMawn in https://github.com/spatie/laravel-activitylog/pull/1031
## New Contributors
- @stevebauman made their first contribution in https://github.com/spatie/laravel-activitylog/pull/977
- @kryptamine made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1000
- @FrancisMawn made their first contribution in https://github.com/spatie/laravel-activitylog/pull/1031
**Full Changelog**: https://github.com/spatie/laravel-activitylog/compare/4.4.2...4.4.3
## 4.4.2 - 2022-03-07
- [#1018](https://github.com/spatie/laravel-activitylog/pull/1018)
## 4.4.1 - 2022-03-04
- https://github.com/spatie/laravel-activitylog/pull/956
## 4.4.0 - 2022-01-12
- allow Laravel 9
## 4.3.1 - 2021-10-20
- Fix hardcoded `deleted_at` column usage - [#965](https://github.com/spatie/laravel-activitylog/issues/965)
## 4.3.0 - 2021-10-20
- Add `\Spatie\Activitylog\LogOptions::useAttributeRawValues()` to log uncasted attributes - [#972](https://github.com/spatie/laravel-activitylog/issues/972)
## 4.2.0 - 2021-10-06
- Add `immutable_date` cast support - [#969](https://github.com/spatie/laravel-activitylog/issues/969)
## 4.1.1 - 2021-07-23
- Fix migration stub names - [#914](https://github.com/spatie/laravel-activitylog/issues/914)
## 4.1.0 - 2021-07-23
- Add `\Spatie\Activitylog\LogBatch::setBatch(string $uuid)` method to keep batches across requests or multiple jobs - [#918](https://github.com/spatie/laravel-activitylog/issues/918)
## 4.0.0 - 2021-05-04
PR: [#787](https://github.com/spatie/laravel-activitylog/pull/787)
Special thanks to [Ahmed Nagi](https://github.com/nagi1).
- Drop Laravel 6 and 7 support.
- Drop PHP 7.x support.
- Add `LogOptions` configuration object to replace all configuration properties.
- Add ability to batch activity logs [#560](https://github.com/spatie/laravel-activitylog/issues/560)
- Add Pipeline to customize logged changes data.
- - Deep diff array/JSON sub-keys and respect for only-dirty, no-empty ... [#692](https://github.com/spatie/laravel-activitylog/issues/692) using new pipeline. See implementation in the tests.
-
-
-
-
-
-
-
-
-
-
-
- Implement a `CauserResolver` to define causer for current runtime [#582](https://github.com/spatie/laravel-activitylog/issues/582).
## 3.17.0 - 2021-03-02
- drop PHP 7.2 support - [#855](https://github.com/spatie/laravel-activitylog/pull/855)
## 3.16.1 - 2020-11-03
- add PHP 8.0 support - [#806](https://github.com/spatie/laravel-activitylog/pull/806)
## 3.16.0 - 2020-09-16
- use `nullableMorphs()` in default migration - [#707](https://github.com/spatie/laravel-activitylog/pull/707)
- add support for snake and camel cased related model attribute logging - [#721](https://github.com/spatie/laravel-activitylog/pull/721)
## 3.15.0 - 2020-09-14
- Add multiple/chained relation attribute logging support - [#784](https://github.com/spatie/laravel-activitylog/pull/784)
## 3.14.3 - 2020-09-09
- Add support for Laravel 8
## 3.14.2 - 2020-05-19
- fix `retrieved` event logging
## 3.14.1 - 2020-03-23
- revert breaking changes in `v3.14.0`
## 3.14.0 - 2020-03-23 - BC
Please use `v3.14.1` instead - this release is breaking because of the new column. There is also a `v4.0.0-rc.1` release that equals to this one.
- add `\Spatie\Activitylog\ActivityLogger::event()` method and column [#702](https://github.com/spatie/laravel-activitylog/pull/702)
## 3.13.0 - 2020-03-13
- add `\Spatie\Activitylog\ActivityLogger::withoutLogs()` method [#695](https://github.com/spatie/laravel-activitylog/pull/695)
## 3.12.0 - 2020-03-13
- respect custom date casts [#627](https://github.com/spatie/laravel-activitylog/pull/627)
## 3.11.4 - 2020-03-11
- remove `spatie/string` dependency [#690](https://github.com/spatie/laravel-activitylog/pull/690)
## 3.11.3 - 2020-03-10
- fix performance issue around global vs model log disabling [#682](https://github.com/spatie/laravel-activitylog/pull/682)
## 3.11.2 - 2020-03-10
- fix Laravel 7 array/json casted attributes [#680](https://github.com/spatie/laravel-activitylog/pull/680)
## 3.11.1 - 2020-03-02
- fix requirements
## 3.11.0 - 2020-03-02
- add support for Laravel 7
## 3.10.0 - 2020-02-22
- add ability to manually set created at date - [#622](https://github.com/spatie/laravel-activitylog/pull/622)
## 3.9.2 - 2020-02-04
- drop support for Laravel 5
## 3.9.1 - 2019-10-15
- fix default database connection - [#616](https://github.com/spatie/laravel-activitylog/pull/616)
## 3.9.0 - 2019-10-06
- add anonymous causer with `null` value - [#605](https://github.com/spatie/laravel-activitylog/pull/605)
- fix relationships to allow snake case keys - [#602](https://github.com/spatie/laravel-activitylog/pull/602)
- add JOSN sub-key attribute logging - [#601](https://github.com/spatie/laravel-activitylog/pull/601)
## 3.8.0 - 2019-09-04
- add support for Laravel 6
- change fields with value `null` to be strictly compared when logging dirty fields [#453](https://github.com/spatie/laravel-activitylog/pull/453)
- add composite indexes for subject and causer to migration
## 3.7.2 - 2019-08-28
- do not export docs folder
## 3.7.1 - 2019-07-24
- fix default database connection env var
## 3.7.0 - 2019-07-23
- add database connection to configuration `activitylog.database_connection` and `ACTIVITY_LOGGER_DB_CONNECTION` env var [#568](https://github.com/spatie/laravel-activitylog/pull/568)
## 3.6.3 - 2019-07-23
- fix deprecated `array_` helper [#569](https://github.com/spatie/laravel-activitylog/pull/569)
## 3.6.2 - 2019-07-16
- fix existing description [#563](https://github.com/spatie/laravel-activitylog/pull/563)
## 3.6.1 - 2019-05-29
- fix nullable date attributes [#546](https://github.com/spatie/laravel-activitylog/pull/546)
## 3.6.0 - 2019-05-28
- update `properties` column type from `text` to `json` [#525](https://github.com/spatie/laravel-activitylog/pull/525)
- update `subject_id` and `causer_id` column type from `integer` to `big_integer` and `unsigned` [#527](https://github.com/spatie/laravel-activitylog/pull/527)
- fix attribute getter support in `DetectsChanges` trait [#534](https://github.com/spatie/laravel-activitylog/pull/534)
- fix old attributes retrieval in `DetectsChanges` trait [#537](https://github.com/spatie/laravel-activitylog/pull/537)
- clean up old attributes in `DetectsChanges` trait [#538](https://github.com/spatie/laravel-activitylog/pull/538)
## 3.5.0 - 2019-04-15
- add days option to clean command [#497](https://github.com/spatie/laravel-activitylog/pull/497)
- add `LogsActivity::$submitEmptyLogs` [#514](https://github.com/spatie/laravel-activitylog/pull/514)
## 3.4.0 - 2019-04-09
- use `Illuminate\Contracts\Config\Repository` instead of `Illuminate\Config\Repository` [#505](https://github.com/spatie/laravel-activitylog/pull/505)
- fix `logChanges()` [#512](https://github.com/spatie/laravel-activitylog/pull/512)
## 3.3.0 - 2019-04-08
- drop support for Laravel 5.7 and lower
- drop support for PHP 7.1 and lower
## 3.2.2 - 2019-02-27
- add support for Laravel 5.8
- fix logging hidden attributes
- fix logging for a causer model without a provider
- add code coverage reporting for repository
## 3.2.1 - 2019-02-01
- use Str:: and Arr:: instead of helper methods
## 3.2.0 - 2019-01-29
- add `ActivityLogger::tap()` method
- add `LogsActivity::tapActivity()` method
- the `ActivityLogger` will work on an activity model instance instead of cache variables
## 3.1.2 - 2018-10-18
- add `shouldLogUnguarded()` method
- fix typo in methodname `shouldLogOnlyDirty()`
## 3.1.1 - 2018-10-17
- fix `$logUnguarded`
## 3.1.0 - 2018-10-17
- add `$logUnguarded`
## 3.0.0 - 2018-10-16
- the preferred way to get changes on an `Activity` model is through the `changes` property instead of the `changes()` function
- the `activity` relation of the `CausesActivity` trait has been renamed to `actions`
- the `activity` relation of the `LogsActivity` trait has been renamed to `activities`
- the deprecated `loggedActivity` relation has been removed
- the `HasActivity` trait has been removed.
- fix for setting a custom table name for the `Activity` model via the `$table` property
- support for PHP 7.0 has been dropped
## 2.8.4. - 2018-09-23
- improve migration
## 2.8.3 - 2018-09-01
- add support for L5.7
## 2.8.2 - 2018-07-28
- allow `null` to be passed to `causedBy`
## 2.8.1 - 2018-07-28
- make sure a fresh instance of `ActivityLogger` is used
## 2.8.0 - 2018-07-21
- add `enableLogging()` and `disableLogging()`
## 2.7.0 - 2018-06-18
- add ability to ignore changes to attributes specified in `$logAttributesToIgnore`
## 2.6.0 - 2018-04-03
- add `table_name` config option
## 2.5.1 - 2018-02-11
- improve support for soft deletes
## 2.5.0 - 2018-02-09
- allow model to override the default log name
## 2.4.2 - 2018-02-08
- add compatibility with L5.6
## 2.4.1 - 2018-01-20
- use a `text` column for `description`
## 2.4.0 - 2018-01-20
- add `HasActivity`
## 2.3.2 - 2017-12-13
- fix bugs concerning `attributesToBeLogged`
## 2.3.1 - 2017-11-13
- allow nullable relation when using `logChanges`
## 2.3.0 - 2017-11-07
- add a `log` argument to `activitylog:clean`
## 2.2.0 - 2017-10-16
- add support for logging all changed attributes using `*`
## 2.1.2 - 2017-09-28
- fix for logging changes attributes when deleting soft deletable models
## 2.1.1 - 2017-09-12
- make sure `properties` always is a collection
## 2.1.0 - 2017-09-19
- added support for logging fillable attributes
## 2.0.0 - 2017-08-30
- added support for Laravel 5.5, dropped support for older laravel versions
- renamed config file from `laravel-activitylog` to `activitylog`
- rename `getChangesAttribute` function to `changes` so it doesn't conflict with Laravel's native functionality
## 1.16.0 - 2017-06-28
- added `enableLogging` and `disableLogging`
## 1.15.5 - 2017-08-08
- fix model scope
## 1.15.4 - 2017-08-05
- fix detecting `SoftDeletes`
## 1.15.3 - 2017-06-23
- fix for when there is no 'web' guard
## 1.15.2 - 2017-06-15
- fixes errors in `DetectsChanges`
## 1.15.1 - 2017-04-28
- fixes error in `DetectsChanges`
## 1.15.0 - 2017-04-28
- add compatibility with L5.1 and L5.2
## 1.14.0 - 2017-04-16
- add support array/collection casted attributes when using `logDirtyOnly`
## 1.13.0 - 2017-04-16
- add `logDirtyOnly`
## 1.12.2 - 2017-03-22
- fix a bug where changes to a related model would not be logged
## 1.12.1 - 2017-02-12
- avoid PHP error when dealing with placeholders that cannot be filled
## 1.12.0 - 2017-02-04
- drop support for L5.2 and lower
- add ability to log attributes of related models
## 1.11.0 - 2017-01-23
- add support for L5.4
## 1.10.4 - 2017-01-20
- `Activity` now extends from `Model` instead of `Eloquent`
## 1.10.2 - 2016-11-26
- fix compatibilty for Laravel 5.1
## 1.10.1 - 2016-10-11
- fix `scopeCausedBy` and `scopeForSubject`
## 1.10.0 - 2016-10-10
- add support for `restored` event
## 1.9.2 - 2016-09-27
- fixed a bug where the delete event would not be logged
## 1.9.1 - 2016-09-16
- fixed the return value of `activity()->log()`. It will now return the created `Activity`-model.
## 1.9.0 - 2016-09-16
- added `Macroable` to `ActivityLogger`
## 1.8.0 - 2016-09-12
- added `causedBy` and `forSubject` scopes
## 1.7.1 - 2016-08-23
- Added L5.3 compatibility
## 1.7.0 - 2016-08-17
- Added `enabled` option in the config file.
## 1.6.0 - 2016-08-11
- Added `ignoreChangedAttributes`
## 1.5.0 - 2016-08-11
- Added support for using a custom `Activity` model
## 1.4.0 - 2016-08-10
- Added support for soft deletes
## 1.3.2 - 2016-08-09
- This version replaces version `1.3.0`
- Dropped L5.1 compatibility
## 1.3.1 - 2016-08-09
- this version removes the features introduced in 1.3.0 and is compatible with L5.1
## 1.3.0 - 2016-07-29
**DO NOT USE THIS VERSION IF YOU'RE ON L5.1**
Please upgrade to:
- `1.3.1` for Laravel 5.1
- `1.3.2` for Laravel 5.2 and higher
Introduced features
- made the auth driver configurable
## 1.3.0 - 2016-07-29
- made the auth driver configurable
## 1.2.1 - 2016-07-09
- use config repo contract
## 1.2.0 - 2016-07-08
- added `getLogNameToUse`
## 1.1.0 - 2016-07-04
- added `activity`-method on both the `CausesActivity` and `LogsActivity`-trait
## 1.0.3 - 2016-07-01
- the package is now compatible with Laravel 5.1
## 1.0.2 - 2016-06-29
- fixed naming of `inLog` scope
- add `inLog` function alias
## 1.0.1 - 2016-06-29
- fixed error when publishing migrations
## 1.0.0 - 2016-06-28
- initial release
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Spatie bvba <info@spatie.be>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,155 @@
<div align="left">
<a href="https://spatie.be/open-source?utm_source=github&utm_medium=banner&utm_campaign=laravel-activitylog">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://spatie.be/packages/header/laravel-activitylog/html/dark.webp">
<img alt="Logo for laravel-activitylog" src="https://spatie.be/packages/header/laravel-activitylog/html/light.webp">
</picture>
</a>
<h1>Log activity inside your Laravel app</h1>
[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-activitylog.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-activitylog)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/spatie/laravel-activitylog/run-tests.yml?branch=main&label=Tests)](https://github.com/spatie/laravel-activitylog/actions/workflows/run-tests.yml)
[![Check & fix styling](https://github.com/spatie/laravel-activitylog/workflows/Check%20&%20fix%20styling/badge.svg)](https://github.com/spatie/laravel-activitylog/actions/workflows/php-cs-fixer.yml)
[![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-activitylog.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-activitylog)
</div>
The `spatie/laravel-activitylog` package provides easy to use functions to log the activities of the users of your app. It can also automatically log model events.
The Package stores all activity in the `activity_log` table.
Here's a demo of how you can use it:
```php
activity()->log('Look, I logged something');
```
You can retrieve all activity using the `Spatie\Activitylog\Models\Activity` model.
```php
Activity::all();
```
Here's a more advanced example:
```php
activity()
->performedOn($anEloquentModel)
->causedBy($user)
->withProperties(['customProperty' => 'customValue'])
->log('Look, I logged something');
$lastLoggedActivity = Activity::all()->last();
$lastLoggedActivity->subject; //returns an instance of an eloquent model
$lastLoggedActivity->causer; //returns an instance of your user model
$lastLoggedActivity->getExtraProperty('customProperty'); //returns 'customValue'
$lastLoggedActivity->description; //returns 'Look, I logged something'
```
Here's an example on [event logging](https://spatie.be/docs/laravel-activitylog/advanced-usage/logging-model-events).
```php
$newsItem->name = 'updated name';
$newsItem->save();
//updating the newsItem will cause the logging of an activity
$activity = Activity::all()->last();
$activity->description; //returns 'updated'
$activity->subject; //returns the instance of NewsItem that was saved
```
Calling `$activity->changes()` will return this array:
```php
[
'attributes' => [
'name' => 'updated name',
'text' => 'Lorum',
],
'old' => [
'name' => 'original name',
'text' => 'Lorum',
],
];
```
## Support us
[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/laravel-activitylog.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/laravel-activitylog)
We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
## Documentation
You'll find the documentation on [https://spatie.be/docs/laravel-activitylog/introduction](https://spatie.be/docs/laravel-activitylog/introduction).
Find yourself stuck using the package? Found a bug? Do you have general questions or suggestions for improving the activity log? Feel free to [create an issue on GitHub](https://github.com/spatie/laravel-activitylog/issues), we'll try to address it as soon as possible.
## Installation
You can install the package via composer:
```bash
composer require spatie/laravel-activitylog
```
The package will automatically register itself.
You can publish the migration with:
```bash
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
```
_Note_: The default migration assumes you are using integers for your model IDs. If you are using UUIDs, or some other format, adjust the format of the `subject_id` and `causer_id` fields in the published migration before continuing.
After publishing the migration you can create the `activity_log` table by running the migrations:
```bash
php artisan migrate
```
You can optionally publish the config file with:
```bash
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-config"
```
## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information about recent changes.
## Upgrading
Please see [UPGRADING](UPGRADING.md) for details.
## Testing
```bash
composer test
```
## Contributing
Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.
## Security
If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker.
## Credits
- [Freek Van der Herten](https://github.com/freekmurze)
- [Sebastian De Deyne](https://github.com/sebastiandedeyne)
- [Tom Witkowski](https://github.com/Gummibeer)
- [All Contributors](../../contributors)
Special thanks to [Ahmed Nagi](https://github.com/nagi1) for all the work he put in `v4` and to [Caneco](https://twitter.com/caneco) for the original logo.
## License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
@@ -0,0 +1,33 @@
## From v3 to v4
``` bash
composer require spatie/laravel-activitylog "^4.0.0"
```
### Publish migrations & migrate new tables
``` bash
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="activitylog-migrations"
php artisan migrate
```
### Model event Logging
- All models now need to define a `getActivitylogOptions()` method to configure and return the models options as a `LogOptions` instance.
- To control what attributes are logged, instead of defining a `$logAttributes` property this is defined in the `getActivitylogOptions()` method using the `logOnly()` method of `LogOptions`.
- The `getDescriptionForEvent()` method is no longer used to customize the description. Instead, use the `setDescriptionForEvent()` method for `LogOptions` class.
- When customizing the log's name instead of defining a `$logName` property, call the `useLogName()` method when configuring the `LogOptions`.
- Instead of the `$ignoreChangedAttributes` property the ` dontLogIfAttributesChangedOnly()` method should be used.
- If you only need to log the dirty attributes use `logOnlyDirty()` since the `$logOnlyDirty` property is no longer used.
- For instances where you do not want to store empty log events use `dontSubmitEmptyLogs()` instead of setting `$submitEmptyLogs` to `false`.
- When you use a `*` (wildcard) and want to ignore specific elements use the `dontLogIfAttributesChangedOnly()` method instead of the `$logAttributesToIgnore` property.
## From v2 to v3
- if you are using a custom `Activity` model, you should let it implement the new `Spatie\Activitylog\Contracts\Activity` interface
- the preferred way to get changes on an `Activity` model is through the `changes` property instead of the `changes()` function. Change all usages from
`$activity->changes()` to `$activity->changes`
- the `activity` relation of the `CausesActivity` trait has been renamed to `actions`. Rename all uses from `$user->activity` to `$user->actions`
- the `activity` relation of the `LogsActivity` trait has been renamed to `activities`. Rename all uses from `$yourModel->activity` to `$yourModel->activities`.
- the deprecated `loggedActivity` relation has been removed. Use `activities` instead.
- the `HasActivity` trait has been removed. Use both `CausesActivity` and `LogsActivity` traits instead.
@@ -0,0 +1,58 @@
<p align="center">
<img src="/art/socialcard.png" width="1280" title="Social Card of Laravel Activity Log">
</p>
# Laravel Activity Log Art
The logo was inspired by the [Spatie](https://spatie.be) brand, and the well known minimal design of Laravel packages.
## Fonts
The logo is using the following fonts:
- [Inter 500](https://fonts.google.com/specimen/Inter#500)
- [Inter 600](https://fonts.google.com/specimen/Inter#600)
## Colors
| |#hex |rgb() |
|--- |--- |--- |
|![100](/art/palette/100.png)|`#E8F1F4`|`rgb(232,241,244)`|
|![200](/art/palette/200.png)|`#C6DDE4`|`rgb(198,221,228)`|
|![300](/art/palette/300.png)|`#A3C8D4`|`rgb(163,200,212)`|
|![400](/art/palette/400.png)|`#5E9EB3`|`rgb(94,158,179)` |
|![500](/art/palette/500.png)|`#197593`|`rgb(25,117,147)` |
|![600](/art/palette/600.png)|`#176984`|`rgb(23,105,132)` |
|![700](/art/palette/700.png)|`#0F4658`|`rgb(15,70,88)` |
|![800](/art/palette/800.png)|`#0B3542`|`rgb(11,53,66)` |
|![900](/art/palette/900.png)|`#08232C`|`rgb(8,35,44)` |
## Requirements
- A screen or a printer
## Installation
- Open the file
- *Right-click* on the image
- Choose **"Save image as…"** option
## Maintainers
**Laravel Activity Log** logo is designed and maintained by [Caneco](https://twitter.com/caneco).
## License
All rights reserved, but with the following extra conditions:
- It is **OK** to use the Laravel Activity Log logo in the following cases:
- In marketing materials for technical events, e.g. meetups, hackathons, conferences and workshops that are related to Laravel.
- In open source projects related to Laravel.
- In technical articles/videos/books/papers for educational purposes.
- To illustrate a commercial product.
- It is **NOT OK** to use the Laravel Activity Log logo in the following cases without prior written consent from the copyright owners:
- Using the Laravel Activity Log logo in a commercial product for purposes other than illustrating its integration.
- Sell physical products that uses the Laravel Activity Log logo or its variants, e.g. t-shirts.
By any means the owner reserves the right of final explanation for any use case not explicitly stated above.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<linearGradient id="a" x1="21.25" x2="121.25" y1="-21.25" y2="78.75" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#A3C8D4"/>
<stop offset=".95" stop-color="#197593"/>
</linearGradient>
<path fill="url(#a)" d="M100 100h-15v-85h-85v-15h100z"/>
<linearGradient id="b" x1="14" x2="72" y1="50" y2="50" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#104C60"/>
<stop offset="1" stop-color="#0B3542"/>
</linearGradient>
<path fill="url(#b)" d="M14 28h58v44h-44v-15h29v-14h-43z"/>
<linearGradient id="c" x1="-10.75" x2="68.25" y1="38.75" y2="117.75" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#0F4658"/>
<stop offset="1" stop-color="#277D99"/>
</linearGradient>
<path fill="url(#c)" d="M0 100v-72h15v57h71v15z"/>
</svg>

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

@@ -0,0 +1,76 @@
{
"name": "spatie/laravel-activitylog",
"description": "A very simple activity logger to monitor the users of your website or application",
"license": "MIT",
"keywords": [
"spatie",
"log",
"user",
"activity",
"laravel"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
},
{
"name": "Sebastian De Deyne",
"email": "sebastian@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
},
{
"name": "Tom Witkowski",
"email": "dev.gummibeer@gmail.com",
"homepage": "https://gummibeer.de",
"role": "Developer"
}
],
"homepage": "https://github.com/spatie/activitylog",
"require": {
"php": "^8.1",
"illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
"illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0 || ^12.0",
"illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
"spatie/laravel-package-tools": "^1.6.3"
},
"require-dev": {
"ext-json": "*",
"orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.0 || ^10.0",
"pestphp/pest": "^1.20 || ^2.0 || ^3.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Spatie\\Activitylog\\": "src"
},
"files": [
"src/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Spatie\\Activitylog\\Test\\": "tests"
}
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true
},
"sort-packages": true
},
"extra": {
"laravel": {
"providers": [
"Spatie\\Activitylog\\ActivitylogServiceProvider"
]
}
},
"scripts": {
"test": "vendor/bin/pest"
}
}
@@ -0,0 +1,52 @@
<?php
return [
/*
* If set to false, no activities will be saved to the database.
*/
'enabled' => env('ACTIVITY_LOGGER_ENABLED', true),
/*
* When the clean-command is executed, all recording activities older than
* the number of days specified here will be deleted.
*/
'delete_records_older_than_days' => 365,
/*
* If no log name is passed to the activity() helper
* we use this default log name.
*/
'default_log_name' => 'default',
/*
* You can specify an auth driver here that gets user models.
* If this is null we'll use the current Laravel auth driver.
*/
'default_auth_driver' => null,
/*
* If set to true, the subject returns soft deleted models.
*/
'subject_returns_soft_deleted_models' => false,
/*
* This model will be used to log activity.
* It should implement the Spatie\Activitylog\Contracts\Activity interface
* and extend Illuminate\Database\Eloquent\Model.
*/
'activity_model' => \Spatie\Activitylog\Models\Activity::class,
/*
* This is the name of the table that will be created by the migration and
* used by the Activity model shipped with this package.
*/
'table_name' => env('ACTIVITY_LOGGER_TABLE_NAME', 'activity_log'),
/*
* This is the database connection that will be used by the migration and
* the Activity model shipped with this package. In case it's not set
* Laravel's database.default will be used instead.
*/
'database_connection' => env('ACTIVITY_LOGGER_DB_CONNECTION'),
];
@@ -0,0 +1,22 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddBatchUuidColumnToActivityLogTable extends Migration
{
public function up()
{
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
$table->uuid('batch_uuid')->nullable()->after('properties');
});
}
public function down()
{
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
$table->dropColumn('batch_uuid');
});
}
}
@@ -0,0 +1,22 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddEventColumnToActivityLogTable extends Migration
{
public function up()
{
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
$table->string('event')->nullable()->after('subject_type');
});
}
public function down()
{
Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) {
$table->dropColumn('event');
});
}
}
@@ -0,0 +1,27 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateActivityLogTable extends Migration
{
public function up()
{
Schema::connection(config('activitylog.database_connection'))->create(config('activitylog.table_name'), function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('log_name')->nullable();
$table->text('description');
$table->nullableMorphs('subject', 'subject');
$table->nullableMorphs('causer', 'causer');
$table->json('properties')->nullable();
$table->timestamps();
$table->index('log_name');
});
}
public function down()
{
Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name'));
}
}
@@ -0,0 +1,30 @@
<?php
namespace Spatie\Activitylog;
use Illuminate\Contracts\Config\Repository;
class ActivityLogStatus
{
protected $enabled = true;
public function __construct(Repository $config)
{
$this->enabled = $config['activitylog.enabled'];
}
public function enable(): bool
{
return $this->enabled = true;
}
public function disable(): bool
{
return $this->enabled = false;
}
public function disabled(): bool
{
return $this->enabled === false;
}
}
@@ -0,0 +1,233 @@
<?php
namespace Spatie\Activitylog;
use Closure;
use DateTimeInterface;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use Spatie\Activitylog\Contracts\Activity as ActivityContract;
class ActivityLogger
{
use Conditionable;
use Macroable;
protected ?string $defaultLogName = null;
protected CauserResolver $causerResolver;
protected ActivityLogStatus $logStatus;
protected ?ActivityContract $activity = null;
protected LogBatch $batch;
public function __construct(Repository $config, ActivityLogStatus $logStatus, LogBatch $batch, CauserResolver $causerResolver)
{
$this->causerResolver = $causerResolver;
$this->batch = $batch;
$this->defaultLogName = $config['activitylog']['default_log_name'];
$this->logStatus = $logStatus;
}
public function setLogStatus(ActivityLogStatus $logStatus): static
{
$this->logStatus = $logStatus;
return $this;
}
public function performedOn(Model $model): static
{
$this->getActivity()->subject()->associate($model);
return $this;
}
public function on(Model $model): static
{
return $this->performedOn($model);
}
public function causedBy(Model | int | string | null $modelOrId): static
{
if ($modelOrId === null) {
return $this;
}
$model = $this->causerResolver->resolve($modelOrId);
$this->getActivity()->causer()->associate($model);
return $this;
}
public function by(Model | int | string | null $modelOrId): static
{
return $this->causedBy($modelOrId);
}
public function causedByAnonymous(): static
{
$this->activity->causer_id = null;
$this->activity->causer_type = null;
return $this;
}
public function byAnonymous(): static
{
return $this->causedByAnonymous();
}
public function event(string $event): static
{
return $this->setEvent($event);
}
public function setEvent(string $event): static
{
$this->activity->event = $event;
return $this;
}
public function withProperties(mixed $properties): static
{
$this->getActivity()->properties = collect($properties);
return $this;
}
public function withProperty(string $key, mixed $value): static
{
$this->getActivity()->properties = $this->getActivity()->properties->put($key, $value);
return $this;
}
public function createdAt(DateTimeInterface $dateTime): static
{
$this->getActivity()->created_at = Carbon::instance($dateTime);
return $this;
}
public function useLog(?string $logName): static
{
$this->getActivity()->log_name = $logName;
return $this;
}
public function inLog(?string $logName): static
{
return $this->useLog($logName);
}
public function tap(callable $callback, ?string $eventName = null): static
{
call_user_func($callback, $this->getActivity(), $eventName);
return $this;
}
public function enableLogging(): static
{
$this->logStatus->enable();
return $this;
}
public function disableLogging(): static
{
$this->logStatus->disable();
return $this;
}
public function log(string $description): ?ActivityContract
{
if ($this->logStatus->disabled()) {
return null;
}
$activity = $this->activity;
$activity->description = $this->replacePlaceholders(
$activity->description ?? $description,
$activity
);
if (isset($activity->subject) && method_exists($activity->subject, 'tapActivity')) {
$this->tap([$activity->subject, 'tapActivity'], $activity->event ?? '');
}
$activity->save();
$this->activity = null;
return $activity;
}
public function withoutLogs(Closure $callback): mixed
{
if ($this->logStatus->disabled()) {
return $callback();
}
$this->logStatus->disable();
try {
return $callback();
} finally {
$this->logStatus->enable();
}
}
protected function replacePlaceholders(string $description, ActivityContract $activity): string
{
return preg_replace_callback('/:[a-z0-9._-]+(?<![.])/i', function ($match) use ($activity) {
$match = $match[0];
$attribute = Str::before(Str::after($match, ':'), '.');
if (! in_array($attribute, ['subject', 'causer', 'properties'])) {
return $match;
}
$propertyName = substr($match, strpos($match, '.') + 1);
$attributeValue = $activity->$attribute;
if (is_null($attributeValue)) {
return $match;
}
return data_get($attributeValue, $propertyName, $match);
}, $description);
}
protected function getActivity(): ActivityContract
{
if (! $this->activity instanceof ActivityContract) {
$this->activity = ActivitylogServiceProvider::getActivityModelInstance();
$this
->useLog($this->defaultLogName)
->withProperties([])
->causedBy($this->causerResolver->resolve());
$this->activity->batch_uuid = $this->batch->getUuid();
}
return $this->activity;
}
}
@@ -0,0 +1,57 @@
<?php
namespace Spatie\Activitylog;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Contracts\Activity;
use Spatie\Activitylog\Contracts\Activity as ActivityContract;
use Spatie\Activitylog\Exceptions\InvalidConfiguration;
use Spatie\Activitylog\Models\Activity as ActivityModel;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
class ActivitylogServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
$package
->name('laravel-activitylog')
->hasConfigFile('activitylog')
->hasMigrations([
'create_activity_log_table',
'add_event_column_to_activity_log_table',
'add_batch_uuid_column_to_activity_log_table',
])
->hasCommand(CleanActivitylogCommand::class);
}
public function registeringPackage()
{
$this->app->bind(ActivityLogger::class);
$this->app->scoped(LogBatch::class);
$this->app->scoped(CauserResolver::class);
$this->app->scoped(ActivityLogStatus::class);
}
public static function determineActivityModel(): string
{
$activityModel = config('activitylog.activity_model') ?? ActivityModel::class;
if (! is_a($activityModel, Activity::class, true)
|| ! is_a($activityModel, Model::class, true)) {
throw InvalidConfiguration::modelIsNotValid($activityModel);
}
return $activityModel;
}
public static function getActivityModelInstance(): ActivityContract
{
$activityModelClassName = self::determineActivityModel();
return new $activityModelClassName();
}
}
@@ -0,0 +1,101 @@
<?php
namespace Spatie\Activitylog;
use Closure;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Exceptions\CouldNotLogActivity;
class CauserResolver
{
protected AuthManager $authManager;
protected string | null $authDriver;
protected Closure | null $resolverOverride = null;
protected Model | null $causerOverride = null;
public function __construct(Repository $config, AuthManager $authManager)
{
$this->authManager = $authManager;
$this->authDriver = $config['activitylog']['default_auth_driver'];
}
public function resolve(Model | int | string | null $subject = null): ?Model
{
if ($this->causerOverride !== null) {
return $this->causerOverride;
}
if ($this->resolverOverride !== null) {
$resultCauser = ($this->resolverOverride)($subject);
if (! $this->isResolvable($resultCauser)) {
throw CouldNotLogActivity::couldNotDetermineUser($resultCauser);
}
return $resultCauser;
}
return $this->getCauser($subject);
}
protected function resolveUsingId(int | string $subject): Model
{
$guard = $this->authManager->guard($this->authDriver);
$provider = method_exists($guard, 'getProvider') ? $guard->getProvider() : null;
$model = method_exists($provider, 'retrieveById') ? $provider->retrieveById($subject) : null;
throw_unless($model instanceof Model, CouldNotLogActivity::couldNotDetermineUser($subject));
return $model;
}
protected function getCauser(Model | int | string | null $subject = null): ?Model
{
if ($subject instanceof Model) {
return $subject;
}
if (is_null($subject)) {
return $this->getDefaultCauser();
}
return $this->resolveUsingId($subject);
}
/**
* Override the resover using callback.
*/
public function resolveUsing(Closure $callback): static
{
$this->resolverOverride = $callback;
return $this;
}
/**
* Override default causer.
*/
public function setCauser(?Model $causer): static
{
$this->causerOverride = $causer;
return $this;
}
protected function isResolvable(mixed $model): bool
{
return $model instanceof Model || is_null($model);
}
protected function getDefaultCauser(): ?Model
{
return $this->authManager->guard($this->authDriver)->user();
}
}
@@ -0,0 +1,48 @@
<?php
namespace Spatie\Activitylog;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Console\ConfirmableTrait;
use Illuminate\Database\Eloquent\Builder;
class CleanActivitylogCommand extends Command
{
use ConfirmableTrait;
protected $signature = 'activitylog:clean
{log? : (optional) The log name that will be cleaned.}
{--days= : (optional) Records older than this number of days will be cleaned.}
{--force : (optional) Force the operation to run when in production.}';
protected $description = 'Clean up old records from the activity log.';
public function handle()
{
if (! $this->confirmToProceed()) {
return 1;
}
$this->comment('Cleaning activity log...');
$log = $this->argument('log');
$maxAgeInDays = $this->option('days') ?? config('activitylog.delete_records_older_than_days');
$cutOffDate = Carbon::now()->subDays($maxAgeInDays)->format('Y-m-d H:i:s');
$activity = ActivitylogServiceProvider::getActivityModelInstance();
$amountDeleted = $activity::query()
->where('created_at', '<', $cutOffDate)
->when($log !== null, function (Builder $query) use ($log) {
$query->inLog($log);
})
->delete();
$this->info("Deleted {$amountDeleted} record(s) from the activity log.");
$this->comment('All done!');
}
}
@@ -0,0 +1,27 @@
<?php
namespace Spatie\Activitylog\Contracts;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Collection;
interface Activity
{
public function subject(): MorphTo;
public function causer(): MorphTo;
public function getExtraProperty(string $propertyName, mixed $defaultValue): mixed;
public function changes(): Collection;
public function scopeInLog(Builder $query, ...$logNames): Builder;
public function scopeCausedBy(Builder $query, Model $causer): Builder;
public function scopeForEvent(Builder $query, string $event): Builder;
public function scopeForSubject(Builder $query, Model $subject): Builder;
}
@@ -0,0 +1,11 @@
<?php
namespace Spatie\Activitylog\Contracts;
use Closure;
use Spatie\Activitylog\EventLogBag;
interface LoggablePipe
{
public function handle(EventLogBag $event, Closure $next): EventLogBag;
}
@@ -0,0 +1,17 @@
<?php
namespace Spatie\Activitylog;
use Illuminate\Database\Eloquent\Model;
class EventLogBag
{
public function __construct(
public string $event,
public Model $model,
public array $changes,
public ?LogOptions $options = null
) {
$this->options ??= $model->getActivitylogOptions();
}
}
@@ -0,0 +1,13 @@
<?php
namespace Spatie\Activitylog\Exceptions;
use Exception;
class CouldNotLogActivity extends Exception
{
public static function couldNotDetermineUser($id): self
{
return new static("Could not determine a user with identifier `{$id}`.");
}
}
@@ -0,0 +1,13 @@
<?php
namespace Spatie\Activitylog\Exceptions;
use Exception;
class CouldNotLogChanges extends Exception
{
public static function invalidAttribute($attribute): self
{
return new static("Cannot log attribute `{$attribute}`. Can only log attributes of a model or a directly related model.");
}
}
@@ -0,0 +1,15 @@
<?php
namespace Spatie\Activitylog\Exceptions;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Contracts\Activity;
class InvalidConfiguration extends Exception
{
public static function modelIsNotValid(string $className): self
{
return new static("The given model class `{$className}` does not implement `".Activity::class.'` or it does not extend `'.Model::class.'`');
}
}
@@ -0,0 +1,43 @@
<?php
namespace Spatie\Activitylog\Facades;
use Illuminate\Support\Facades\Facade;
use Spatie\Activitylog\PendingActivityLog;
/**
* @method static \Spatie\Activitylog\ActivityLogger setLogStatus(\Spatie\Activitylog\ActivityLogStatus $logStatus)
* @method static \Spatie\Activitylog\ActivityLogger performedOn(\Illuminate\Database\Eloquent\Model $model)
* @method static \Spatie\Activitylog\ActivityLogger on(\Illuminate\Database\Eloquent\Model $model)
* @method static \Spatie\Activitylog\ActivityLogger causedBy(\Illuminate\Database\Eloquent\Model|string|int|null $modelOrId)
* @method static \Spatie\Activitylog\ActivityLogger by(\Illuminate\Database\Eloquent\Model|string|int|null $modelOrId)
* @method static \Spatie\Activitylog\ActivityLogger causedByAnonymous()
* @method static \Spatie\Activitylog\ActivityLogger byAnonymous()
* @method static \Spatie\Activitylog\ActivityLogger event(string $event)
* @method static \Spatie\Activitylog\ActivityLogger setEvent(string $event)
* @method static \Spatie\Activitylog\ActivityLogger withProperties(mixed $properties)
* @method static \Spatie\Activitylog\ActivityLogger withProperty(string $key, mixed $value)
* @method static \Spatie\Activitylog\ActivityLogger createdAt(\DateTimeInterface $dateTime)
* @method static \Spatie\Activitylog\ActivityLogger useLog(string|null $logName)
* @method static \Spatie\Activitylog\ActivityLogger inLog(string|null $logName)
* @method static \Spatie\Activitylog\ActivityLogger tap(callable $callback, string|null $eventName = null)
* @method static \Spatie\Activitylog\ActivityLogger enableLogging()
* @method static \Spatie\Activitylog\ActivityLogger disableLogging()
* @method static \Spatie\Activitylog\Contracts\Activity|null log(string $description)
* @method static mixed withoutLogs(\Closure $callback)
* @method static \Spatie\Activitylog\ActivityLogger|mixed when(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null)
* @method static \Spatie\Activitylog\ActivityLogger|mixed unless(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null)
* @method static void macro(string $name, object|callable $macro)
* @method static void mixin(object $mixin, bool $replace = true)
* @method static bool hasMacro(string $name)
* @method static void flushMacros()
*
* @see \Spatie\Activitylog\PendingActivityLog
*/
class Activity extends Facade
{
protected static function getFacadeAccessor(): string
{
return PendingActivityLog::class;
}
}
@@ -0,0 +1,21 @@
<?php
namespace Spatie\Activitylog\Facades;
use Illuminate\Support\Facades\Facade;
use Spatie\Activitylog\CauserResolver as ActivitylogCauserResolver;
/**
* @method static \Illuminate\Database\Eloquent\Model|null resolve(\Illuminate\Database\Eloquent\Model|int|string|null $subject = null)
* @method static \Spatie\Activitylog\CauserResolver resolveUsing(\Closure $callback)
* @method static \Spatie\Activitylog\CauserResolver setCauser(\Illuminate\Database\Eloquent\Model|null $causer)
*
* @see \Spatie\Activitylog\CauserResolver
*/
class CauserResolver extends Facade
{
protected static function getFacadeAccessor(): string
{
return ActivitylogCauserResolver::class;
}
}
@@ -0,0 +1,24 @@
<?php
namespace Spatie\Activitylog\Facades;
use Illuminate\Support\Facades\Facade;
use Spatie\Activitylog\LogBatch as ActivityLogBatch;
/**
* @method static string getUuid()
* @method static mixed withinBatch(\Closure $callback)
* @method static void startBatch()
* @method static void setBatch(string $uuid): void
* @method static bool isOpen()
* @method static void endBatch()
*
* @see \Spatie\Activitylog\LogBatch
*/
class LogBatch extends Facade
{
protected static function getFacadeAccessor(): string
{
return ActivityLogBatch::class;
}
}
@@ -0,0 +1,61 @@
<?php
namespace Spatie\Activitylog;
use Closure;
use Ramsey\Uuid\Uuid;
class LogBatch
{
public ?string $uuid = null;
public int $transactions = 0;
protected function generateUuid(): string
{
return Uuid::uuid4()->toString();
}
public function getUuid(): ?string
{
return $this->uuid;
}
public function setBatch(string $uuid): void
{
$this->uuid = $uuid;
$this->transactions = 1;
}
public function withinBatch(Closure $callback): mixed
{
$this->startBatch();
$result = $callback($this->getUuid());
$this->endBatch();
return $result;
}
public function startBatch(): void
{
if (! $this->isOpen()) {
$this->uuid = $this->generateUuid();
}
$this->transactions++;
}
public function isOpen(): bool
{
return $this->transactions > 0;
}
public function endBatch(): void
{
$this->transactions = max(0, $this->transactions - 1);
if ($this->transactions === 0) {
$this->uuid = null;
}
}
}
@@ -0,0 +1,166 @@
<?php
namespace Spatie\Activitylog;
use Closure;
class LogOptions
{
public ?string $logName = null;
public bool $submitEmptyLogs = true;
public bool $logFillable = false;
public bool $logOnlyDirty = false;
public bool $logUnguarded = false;
public array $logAttributes = [];
public array $logExceptAttributes = [];
public array $dontLogIfAttributesChangedOnly = [];
public array $attributeRawValues = [];
public ?Closure $descriptionForEvent = null;
/**
* Start configuring model with the default options.
*/
public static function defaults(): self
{
return new static();
}
/**
* Log all attributes on the model.
*/
public function logAll(): self
{
return $this->logOnly(['*']);
}
/**
* Log all attributes that are not listed in $guarded.
*/
public function logUnguarded(): self
{
$this->logUnguarded = true;
return $this;
}
/**
* log changes to all the $fillable attributes of the model.
*/
public function logFillable(): self
{
$this->logFillable = true;
return $this;
}
/**
* Stop logging $fillable attributes of the model.
*/
public function dontLogFillable(): self
{
$this->logFillable = false;
return $this;
}
/**
* Log changes that has actually changed after the update.
*/
public function logOnlyDirty(): self
{
$this->logOnlyDirty = true;
return $this;
}
/**
* Log changes only if these attributes changed.
*/
public function logOnly(array $attributes): self
{
$this->logAttributes = $attributes;
return $this;
}
/**
* Exclude these attributes from being logged.
*/
public function logExcept(array $attributes): self
{
$this->logExceptAttributes = $attributes;
return $this;
}
/**
* Don't trigger an activity if these attributes changed logged.
*/
public function dontLogIfAttributesChangedOnly(array $attributes): self
{
$this->dontLogIfAttributesChangedOnly = $attributes;
return $this;
}
/**
* Don't store empty logs. Storing empty logs can happen when you only
* want to log a certain attribute but only another changes.
*/
public function dontSubmitEmptyLogs(): self
{
$this->submitEmptyLogs = false;
return $this;
}
/**
* Allow storing empty logs. Storing empty logs can happen when you only
* want to log a certain attribute but only another changes.
*/
public function submitEmptyLogs(): self
{
$this->submitEmptyLogs = true;
return $this;
}
/**
* Customize log name.
*/
public function useLogName(?string $logName): self
{
$this->logName = $logName;
return $this;
}
/**
* Customize log description using callback.
*/
public function setDescriptionForEvent(Closure $callback): self
{
$this->descriptionForEvent = $callback;
return $this;
}
/**
* Exclude these attributes from being casted.
*/
public function useAttributeRawValues(array $attributes): self
{
$this->attributeRawValues = $attributes;
return $this;
}
}
@@ -0,0 +1,138 @@
<?php
namespace Spatie\Activitylog\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Spatie\Activitylog\Contracts\Activity as ActivityContract;
/**
* Spatie\Activitylog\Models\Activity.
*
* @property int $id
* @property string|null $log_name
* @property string $description
* @property string|null $subject_type
* @property int|null $subject_id
* @property string|null $causer_type
* @property int|null $causer_id
* @property string|null $event
* @property string|null $batch_uuid
* @property \Illuminate\Support\Collection|null $properties
* @property \Carbon\Carbon|null $created_at
* @property \Carbon\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Model|\Eloquent|null $causer
* @property-read \Illuminate\Support\Collection $changes
* @property-read \Illuminate\Database\Eloquent\Model|\Eloquent|null $subject
*
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity causedBy(\Illuminate\Database\Eloquent\Model $causer)
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity forBatch(string $batchUuid)
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity forEvent(string $event)
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity forSubject(\Illuminate\Database\Eloquent\Model $subject)
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity hasBatch()
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity inLog($logNames)
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\Spatie\Activitylog\Models\Activity query()
*/
class Activity extends Model implements ActivityContract
{
public $guarded = [];
protected $casts = [
'properties' => 'collection',
];
public function __construct(array $attributes = [])
{
if (! isset($this->connection)) {
$this->setConnection(config('activitylog.database_connection'));
}
if (! isset($this->table)) {
$this->setTable(config('activitylog.table_name'));
}
parent::__construct($attributes);
}
/**
* @return MorphTo<Model, $this>
*/
public function subject(): MorphTo
{
if (config('activitylog.subject_returns_soft_deleted_models')) {
return $this->morphTo()->withTrashed();
}
return $this->morphTo();
}
/**
* @return MorphTo<Model, $this>
*/
public function causer(): MorphTo
{
return $this->morphTo();
}
public function getExtraProperty(string $propertyName, mixed $defaultValue = null): mixed
{
return Arr::get($this->properties->toArray(), $propertyName, $defaultValue);
}
public function changes(): Collection
{
if (! $this->properties instanceof Collection) {
return new Collection();
}
return $this->properties->only(['attributes', 'old']);
}
public function getChangesAttribute(): Collection
{
return $this->changes();
}
public function scopeInLog(Builder $query, ...$logNames): Builder
{
if (is_array($logNames[0])) {
$logNames = $logNames[0];
}
return $query->whereIn('log_name', $logNames);
}
public function scopeCausedBy(Builder $query, Model $causer): Builder
{
return $query
->where('causer_type', $causer->getMorphClass())
->where('causer_id', $causer->getKey());
}
public function scopeForSubject(Builder $query, Model $subject): Builder
{
return $query
->where('subject_type', $subject->getMorphClass())
->where('subject_id', $subject->getKey());
}
public function scopeForEvent(Builder $query, string $event): Builder
{
return $query->where('event', $event);
}
public function scopeHasBatch(Builder $query): Builder
{
return $query->whereNotNull('batch_uuid');
}
public function scopeForBatch(Builder $query, string $batchUuid): Builder
{
return $query->where('batch_uuid', $batchUuid);
}
}
@@ -0,0 +1,32 @@
<?php
namespace Spatie\Activitylog;
use Illuminate\Support\Traits\ForwardsCalls;
/**
* @mixin \Spatie\Activitylog\ActivityLogger
*/
class PendingActivityLog
{
use ForwardsCalls;
protected ActivityLogger $logger;
public function __construct(ActivityLogger $logger, ActivityLogStatus $status)
{
$this->logger = $logger
->setLogStatus($status)
->useLog(config('activitylog.default_log_name'));
}
public function logger(): ActivityLogger
{
return $this->logger;
}
public function __call(string $method, array $parameters): mixed
{
return $this->forwardCallTo($this->logger, $method, $parameters);
}
}
@@ -0,0 +1,19 @@
<?php
namespace Spatie\Activitylog\Traits;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Spatie\Activitylog\ActivitylogServiceProvider;
use Spatie\Activitylog\Models\Activity;
trait CausesActivity
{
/** @return MorphMany<Activity, $this> */
public function actions(): MorphMany
{
return $this->morphMany(
ActivitylogServiceProvider::determineActivityModel(),
'causer'
);
}
}
@@ -0,0 +1,426 @@
<?php
namespace Spatie\Activitylog\Traits;
use Carbon\CarbonInterval;
use DateInterval;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Pipeline\Pipeline;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Spatie\Activitylog\ActivityLogger;
use Spatie\Activitylog\ActivitylogServiceProvider;
use Spatie\Activitylog\ActivityLogStatus;
use Spatie\Activitylog\Contracts\LoggablePipe;
use Spatie\Activitylog\EventLogBag;
use Spatie\Activitylog\LogOptions;
trait LogsActivity
{
public static array $changesPipes = [];
protected array $oldAttributes = [];
protected ?LogOptions $activitylogOptions;
public bool $enableLoggingModelsEvents = true;
abstract public function getActivitylogOptions(): LogOptions;
protected static function bootLogsActivity(): void
{
// Hook into eloquent events that only specified in $eventToBeRecorded array,
// checking for "updated" event hook explicitly to temporary hold original
// attributes on the model as we'll need them later to compare against.
static::eventsToBeRecorded()->each(function ($eventName) {
if ($eventName === 'updated') {
static::updating(function (Model $model) {
$oldValues = (new static())->setRawAttributes($model->getRawOriginal());
$model->oldAttributes = static::logChanges($oldValues);
});
}
static::$eventName(function (Model $model) use ($eventName) {
$model->activitylogOptions = $model->getActivitylogOptions();
if (! $model->shouldLogEvent($eventName)) {
return;
}
$changes = $model->attributeValuesToBeLogged($eventName);
$description = $model->getDescriptionForEvent($eventName);
$logName = $model->getLogNameToUse();
// Submitting empty description will cause place holder replacer to fail.
if ($description == '') {
return;
}
if ($model->isLogEmpty($changes) && ! $model->activitylogOptions->submitEmptyLogs) {
return;
}
// User can define a custom pipelines to mutate, add or remove from changes
// each pipe receives the event carrier bag with changes and the model in
// question every pipe should manipulate new and old attributes.
$event = app(Pipeline::class)
->send(new EventLogBag($eventName, $model, $changes, $model->activitylogOptions))
->through(static::$changesPipes)
->thenReturn();
// Actual logging
$logger = app(ActivityLogger::class)
->useLog($logName)
->event($eventName)
->performedOn($model)
->withProperties($event->changes);
if (method_exists($model, 'tapActivity')) {
$logger->tap([$model, 'tapActivity'], $eventName);
}
$logger->log($description);
// Reset log options so the model can be serialized.
$model->activitylogOptions = null;
});
});
}
public static function addLogChange(LoggablePipe $pipe): void
{
static::$changesPipes[] = $pipe;
}
public function isLogEmpty(array $changes): bool
{
return empty($changes['attributes'] ?? []) && empty($changes['old'] ?? []);
}
public function disableLogging(): self
{
$this->enableLoggingModelsEvents = false;
return $this;
}
public function enableLogging(): self
{
$this->enableLoggingModelsEvents = true;
return $this;
}
public function activities(): MorphMany
{
return $this->morphMany(ActivitylogServiceProvider::determineActivityModel(), 'subject');
}
public function getDescriptionForEvent(string $eventName): string
{
if (! empty($this->activitylogOptions->descriptionForEvent)) {
return ($this->activitylogOptions->descriptionForEvent)($eventName);
}
return $eventName;
}
public function getLogNameToUse(): ?string
{
if (! empty($this->activitylogOptions->logName)) {
return $this->activitylogOptions->logName;
}
return config('activitylog.default_log_name');
}
/**
* Get the event names that should be recorded.
**/
protected static function eventsToBeRecorded(): Collection
{
if (isset(static::$recordEvents)) {
return collect(static::$recordEvents);
}
$events = collect([
'created',
'updated',
'deleted',
]);
if (collect(class_uses_recursive(static::class))->contains(SoftDeletes::class)) {
$events->push('restored');
}
return $events;
}
protected function shouldLogEvent(string $eventName): bool
{
$logStatus = app(ActivityLogStatus::class);
if (! $this->enableLoggingModelsEvents || $logStatus->disabled()) {
return false;
}
if (! in_array($eventName, ['created', 'updated'])) {
return true;
}
// Do not log update event if the model is restoring
if ($this->isRestoring()) {
return false;
}
// Do not log update event if only ignored attributes are changed.
return (bool) count(Arr::except($this->getDirty(), $this->activitylogOptions->dontLogIfAttributesChangedOnly));
}
/**
* Determines if the model is restoring.
**/
protected function isRestoring(): bool
{
$deletedAtColumn = method_exists($this, 'getDeletedAtColumn')
? $this->getDeletedAtColumn()
: 'deleted_at';
return $this->isDirty($deletedAtColumn) && count($this->getDirty()) === 1;
}
/**
* Determines what attributes needs to be logged based on the configuration.
**/
public function attributesToBeLogged(): array
{
$this->activitylogOptions = $this->getActivitylogOptions();
$attributes = [];
// Check if fillable attributes will be logged then merge it to the local attributes array.
if ($this->activitylogOptions->logFillable) {
$attributes = array_merge($attributes, $this->getFillable());
}
// Determine if unguarded attributes will be logged.
if ($this->shouldLogUnguarded()) {
// Get only attribute names, not intrested in the values here then guarded
// attributes. get only keys than not present in guarded array, because
// we are logging the unguarded attributes and we cant have both!
$attributes = array_merge($attributes, array_diff(array_keys($this->getAttributes()), $this->getGuarded()));
}
if (! empty($this->activitylogOptions->logAttributes)) {
// Filter * from the logAttributes because will deal with it separately
$attributes = array_merge($attributes, array_diff($this->activitylogOptions->logAttributes, ['*']));
// If there's * get all attributes then merge it, dont respect $guarded or $fillable.
if (in_array('*', $this->activitylogOptions->logAttributes)) {
$attributes = array_merge($attributes, array_keys($this->getAttributes()));
}
}
if ($this->activitylogOptions->logExceptAttributes) {
// Filter out the attributes defined in ignoredAttributes out of the local array
$attributes = array_diff($attributes, $this->activitylogOptions->logExceptAttributes);
}
return $attributes;
}
public function shouldLogUnguarded(): bool
{
if (! $this->activitylogOptions->logUnguarded) {
return false;
}
// This case means all of the attributes are guarded
// so we'll not have any unguarded anyway.
if (in_array('*', $this->getGuarded())) {
return false;
}
return true;
}
/**
* Determines values that will be logged based on the difference.
**/
public function attributeValuesToBeLogged(string $processingEvent): array
{
// no loggable attributes, no values to be logged!
if (! count($this->attributesToBeLogged())) {
return [];
}
$properties['attributes'] = static::logChanges(
// if the current event is retrieved, get the model itself
// else get the fresh default properties from database
// as wouldn't be part of the saved model instance.
$processingEvent == 'retrieved'
? $this
: (
$this->exists
? $this->fresh() ?? $this
: $this
)
);
if (static::eventsToBeRecorded()->contains('updated') && $processingEvent == 'updated') {
// Fill the attributes with null values.
$nullProperties = array_fill_keys(array_keys($properties['attributes']), null);
// Populate the old key with keys from database and from old attributes.
$properties['old'] = array_merge($nullProperties, $this->oldAttributes);
// Fail safe.
$this->oldAttributes = [];
}
if ($this->activitylogOptions->logOnlyDirty && isset($properties['old'])) {
// Get difference between the old and new attributes.
$properties['attributes'] = array_udiff_assoc(
$properties['attributes'],
$properties['old'],
function ($new, $old) {
// Strict check for php's weird behaviors
if ($old === null || $new === null) {
return $new === $old ? 0 : 1;
}
// Handles Date interval comparisons since php cannot use spaceship
// Operator to compare them and will throw ErrorException.
if ($old instanceof DateInterval) {
return CarbonInterval::make($old)->equalTo($new) ? 0 : 1;
} elseif ($new instanceof DateInterval) {
return CarbonInterval::make($new)->equalTo($old) ? 0 : 1;
}
return $new <=> $old;
}
);
$properties['old'] = collect($properties['old'])
->only(array_keys($properties['attributes']))
->all();
}
if (static::eventsToBeRecorded()->contains('deleted') && $processingEvent == 'deleted') {
$properties['old'] = $properties['attributes'];
unset($properties['attributes']);
}
return $properties;
}
public static function logChanges(Model $model): array
{
$changes = [];
$attributes = $model->attributesToBeLogged();
foreach ($attributes as $attribute) {
if (Str::contains($attribute, '.')) {
$changes += self::getRelatedModelAttributeValue($model, $attribute);
continue;
}
if (Str::contains($attribute, '->')) {
Arr::set(
$changes,
str_replace('->', '.', $attribute),
static::getModelAttributeJsonValue($model, $attribute)
);
continue;
}
$changes[$attribute] = in_array($attribute, $model->activitylogOptions->attributeRawValues)
? $model->getAttributeFromArray($attribute)
: $model->getAttribute($attribute);
if (is_null($changes[$attribute])) {
continue;
}
if ($model->isDateAttribute($attribute)) {
$changes[$attribute] = $model->serializeDate(
$model->asDateTime($changes[$attribute])
);
}
if ($model->hasCast($attribute)) {
$cast = $model->getCasts()[$attribute];
if ($model->isEnumCastable($attribute)) {
try {
$changes[$attribute] = $model->getStorableEnumValue($changes[$attribute]);
} catch (\ArgumentCountError $e) {
// In Laravel 11, this method has an extra argument
// https://github.com/laravel/framework/pull/47465
$changes[$attribute] = $model->getStorableEnumValue($cast, $changes[$attribute]);
}
}
if ($model->isCustomDateTimeCast($cast) || $model->isImmutableCustomDateTimeCast($cast)) {
$changes[$attribute] = $model->asDateTime($changes[$attribute])->format(explode(':', $cast, 2)[1]);
}
}
}
return $changes;
}
protected static function getRelatedModelAttributeValue(Model $model, string $attribute): array
{
$relatedModelNames = explode('.', $attribute);
$relatedAttribute = array_pop($relatedModelNames);
$attributeName = [];
$relatedModel = $model;
do {
$attributeName[] = $relatedModelName = static::getRelatedModelRelationName($relatedModel, array_shift($relatedModelNames));
$relatedModel = $relatedModel->$relatedModelName ?? $relatedModel->$relatedModelName();
} while (! empty($relatedModelNames));
$attributeName[] = $relatedAttribute;
return [implode('.', $attributeName) => $relatedModel->$relatedAttribute ?? null];
}
protected static function getRelatedModelRelationName(Model $model, string $relation): string
{
return Arr::first([
$relation,
Str::snake($relation),
Str::camel($relation),
], function (string $method) use ($model): bool {
return method_exists($model, $method);
}, $relation);
}
protected static function getModelAttributeJsonValue(Model $model, string $attribute): mixed
{
$path = explode('->', $attribute);
$modelAttribute = array_shift($path);
$modelAttribute = collect($model->getAttribute($modelAttribute));
return data_get($modelAttribute, implode('.', $path));
}
}
@@ -0,0 +1,18 @@
<?php
use Spatie\Activitylog\ActivityLogger;
use Spatie\Activitylog\PendingActivityLog;
if (! function_exists('activity')) {
function activity(?string $logName = null): ActivityLogger
{
/** @var PendingActivityLog $log */
$log = app(PendingActivityLog::class);
if ($logName) {
$log->useLog($logName);
}
return $log->logger();
}
}