mirror of
https://gitlab.com/signalytic/client-external/streamline/streamline-emr.git
synced 2026-09-11 18:51:31 +00:00
resolved conflicts
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "owen-it/laravel-auditing",
|
||||
"description": "Audit changes of your Eloquent models in Laravel/Lumen",
|
||||
"keywords": [
|
||||
"accountability",
|
||||
"audit",
|
||||
"auditing",
|
||||
"changes",
|
||||
"eloquent",
|
||||
"history",
|
||||
"log",
|
||||
"logging",
|
||||
"observer",
|
||||
"laravel",
|
||||
"lumen",
|
||||
"record",
|
||||
"revision",
|
||||
"tracking"
|
||||
],
|
||||
"homepage": "https://laravel-auditing.com",
|
||||
"type": "package",
|
||||
"license": "MIT",
|
||||
"support": {
|
||||
"issues": "https://github.com/owen-it/laravel-auditing/issues",
|
||||
"source": "https://github.com/owen-it/laravel-auditing"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Antério Vieira",
|
||||
"email": "anteriovieira@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Raphael França",
|
||||
"email": "raphaelfrancabsb@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Morten D. Hansen",
|
||||
"email": "morten@visia.dk"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.3|^8.0",
|
||||
"illuminate/console": "^7.0|^8.0|^9.0|^10.0|^11.0",
|
||||
"illuminate/database": "^7.0|^8.0|^9.0|^10.0|^11.0",
|
||||
"illuminate/filesystem": "^7.0|^8.0|^9.0|^10.0|^11.0",
|
||||
"ext-json": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6|^10.5|^11.0",
|
||||
"mockery/mockery": "^1.0",
|
||||
"orchestra/testbench": "^5.0|^6.0|^7.0|^8.0|^9.0",
|
||||
"laravel/legacy-factories": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"OwenIt\\Auditing\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"OwenIt\\Auditing\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"irazasyed/larasupport": "Needed to publish the package configuration in Lumen"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "v13-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"OwenIt\\Auditing\\AuditingServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'enabled' => env('AUDITING_ENABLED', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Implementation
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Define which Audit model implementation should be used.
|
||||
|
|
||||
*/
|
||||
|
||||
'implementation' => OwenIt\Auditing\Models\Audit::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Morph prefix & Guards
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Define the morph prefix and authentication guards for the User resolver.
|
||||
|
|
||||
*/
|
||||
|
||||
'user' => [
|
||||
'morph_prefix' => 'user',
|
||||
'guards' => [
|
||||
'web',
|
||||
'api'
|
||||
],
|
||||
'resolver' => OwenIt\Auditing\Resolvers\UserResolver::class
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Resolvers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Define the IP Address, User Agent and URL resolver implementations.
|
||||
|
|
||||
*/
|
||||
'resolvers' => [
|
||||
'ip_address' => OwenIt\Auditing\Resolvers\IpAddressResolver::class,
|
||||
'user_agent' => OwenIt\Auditing\Resolvers\UserAgentResolver::class,
|
||||
'url' => OwenIt\Auditing\Resolvers\UrlResolver::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Events
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The Eloquent events that trigger an Audit.
|
||||
|
|
||||
*/
|
||||
|
||||
'events' => [
|
||||
'created',
|
||||
'updated',
|
||||
'deleted',
|
||||
'restored'
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Strict Mode
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Enable the strict mode when auditing?
|
||||
|
|
||||
*/
|
||||
|
||||
'strict' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global exclude
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Have something you always want to exclude by default? - add it here.
|
||||
| Note that this is overwritten (not merged) with local exclude
|
||||
|
|
||||
*/
|
||||
|
||||
'exclude' => [],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Empty Values
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Should Audit records be stored when the recorded old_values & new_values
|
||||
| are both empty?
|
||||
|
|
||||
| Some events may be empty on purpose. Use allowed_empty_values to exclude
|
||||
| those from the empty values check. For example when auditing
|
||||
| model retrieved events which will never have new and old values.
|
||||
|
|
||||
|
|
||||
*/
|
||||
|
||||
'empty_values' => true,
|
||||
'allowed_empty_values' => [
|
||||
'retrieved'
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Allowed Array Values
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Should the array values be audited?
|
||||
|
|
||||
| By default, array values are not allowed. This is to prevent performance
|
||||
| issues when storing large amounts of data. You can override this by
|
||||
| setting allow_array_values to true.
|
||||
*/
|
||||
'allowed_array_values' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Timestamps
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Should the created_at, updated_at and deleted_at timestamps be audited?
|
||||
|
|
||||
*/
|
||||
|
||||
'timestamps' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Threshold
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify a threshold for the amount of Audit records a model can have.
|
||||
| Zero means no limit.
|
||||
|
|
||||
*/
|
||||
|
||||
'threshold' => 0,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The default audit driver used to keep track of changes.
|
||||
|
|
||||
*/
|
||||
|
||||
'driver' => 'database',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Driver Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Available audit drivers and respective configurations.
|
||||
|
|
||||
*/
|
||||
|
||||
'drivers' => [
|
||||
'database' => [
|
||||
'table' => 'audits',
|
||||
'connection' => null,
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Queue Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Available audit queue configurations.
|
||||
|
|
||||
*/
|
||||
|
||||
'queue' => [
|
||||
'enable' => false,
|
||||
'connection' => 'sync',
|
||||
'queue' => 'default',
|
||||
'delay' => 0,
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Audit Console
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Whether console events should be audited (eg. php artisan db:seed).
|
||||
|
|
||||
*/
|
||||
|
||||
'console' => false,
|
||||
];
|
||||
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use OwenIt\Auditing\Contracts\AttributeEncoder;
|
||||
|
||||
trait Audit
|
||||
{
|
||||
/**
|
||||
* Audit data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* The Audit attributes that belong to the metadata.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $metadata = [];
|
||||
|
||||
/**
|
||||
* The Auditable attributes that were modified.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $modified = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function auditable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
$morphPrefix = Config::get('audit.user.morph_prefix', 'user');
|
||||
|
||||
return $this->morphTo(__FUNCTION__, $morphPrefix . '_type', $morphPrefix . '_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConnectionName()
|
||||
{
|
||||
return Config::get('audit.drivers.database.connection');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTable(): string
|
||||
{
|
||||
return Config::get('audit.drivers.database.table', parent::getTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolveData(): array
|
||||
{
|
||||
$morphPrefix = Config::get('audit.user.morph_prefix', 'user');
|
||||
|
||||
// Metadata
|
||||
$this->data = [
|
||||
'audit_id' => $this->getKey(),
|
||||
'audit_event' => $this->event,
|
||||
'audit_tags' => $this->tags,
|
||||
'audit_created_at' => $this->serializeDate($this->{$this->getCreatedAtColumn()}),
|
||||
'audit_updated_at' => $this->serializeDate($this->{$this->getUpdatedAtColumn()}),
|
||||
'user_id' => $this->getAttribute($morphPrefix . '_id'),
|
||||
'user_type' => $this->getAttribute($morphPrefix . '_type'),
|
||||
];
|
||||
|
||||
// add resolvers data to metadata
|
||||
$resolverData = [];
|
||||
foreach (array_keys(Config::get('audit.resolvers', [])) as $name) {
|
||||
$resolverData['audit_' . $name] = $this->$name;
|
||||
}
|
||||
$this->data = array_merge($this->data, $resolverData);
|
||||
|
||||
if ($this->user) {
|
||||
foreach ($this->user->getArrayableAttributes() as $attribute => $value) {
|
||||
$this->data['user_' . $attribute] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->metadata = array_keys($this->data);
|
||||
|
||||
// Modified Auditable attributes
|
||||
foreach ($this->new_values ?? [] as $key => $value) {
|
||||
$this->data['new_' . $key] = $value;
|
||||
}
|
||||
|
||||
foreach ($this->old_values ?? [] as $key => $value) {
|
||||
$this->data['old_' . $key] = $value;
|
||||
}
|
||||
|
||||
$this->modified = array_diff_key(array_keys($this->data), $this->metadata);
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted value of an Eloquent model.
|
||||
*
|
||||
* @param Model $model
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getFormattedValue(Model $model, string $key, $value)
|
||||
{
|
||||
// Apply defined get mutator
|
||||
if ($model->hasGetMutator($key)) {
|
||||
return $model->mutateAttribute($key, $value);
|
||||
}
|
||||
|
||||
if (method_exists($model, 'hasAttributeMutator') && $model->hasAttributeMutator($key)) {
|
||||
return $model->mutateAttributeMarkedAttribute($key, $value);
|
||||
}
|
||||
|
||||
if (array_key_exists(
|
||||
$key,
|
||||
$model->getCasts()
|
||||
) && $model->getCasts()[$key] == 'Illuminate\Database\Eloquent\Casts\AsArrayObject') {
|
||||
$arrayObject = new \Illuminate\Database\Eloquent\Casts\ArrayObject(json_decode($value, true) ?: []);
|
||||
return $arrayObject;
|
||||
}
|
||||
|
||||
// Cast to native PHP type
|
||||
if ($model->hasCast($key)) {
|
||||
if ($model->getCastType($key) == 'datetime' ) {
|
||||
$value = $this->castDatetimeUTC($model, $value);
|
||||
}
|
||||
|
||||
unset($model->classCastCache[$key]);
|
||||
|
||||
return $model->castAttribute($key, $value);
|
||||
}
|
||||
|
||||
// Honour DateTime attribute
|
||||
if ($value !== null && in_array($key, $model->getDates(), true)) {
|
||||
return $model->asDateTime($this->castDatetimeUTC($model, $value));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function castDatetimeUTC($model, $value)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value)) {
|
||||
return Date::instance(Carbon::createFromFormat('Y-m-d', $value, Date::now('UTC')->getTimezone())->startOfDay());
|
||||
}
|
||||
|
||||
if (preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $value)) {
|
||||
return Date::instance(Carbon::createFromFormat('Y-m-d H:i:s', $value, Date::now('UTC')->getTimezone()));
|
||||
}
|
||||
|
||||
try {
|
||||
return Date::createFromFormat($model->getDateFormat(), $value, Date::now('UTC')->getTimezone());
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDataValue(string $key)
|
||||
{
|
||||
if (!array_key_exists($key, $this->data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $this->data[$key];
|
||||
|
||||
// User value
|
||||
if ($this->user && Str::startsWith($key, 'user_')) {
|
||||
return $this->getFormattedValue($this->user, substr($key, 5), $value);
|
||||
}
|
||||
|
||||
// Auditable value
|
||||
if ($this->auditable && Str::startsWith($key, ['new_', 'old_'])) {
|
||||
$attribute = substr($key, 4);
|
||||
|
||||
return $this->getFormattedValue(
|
||||
$this->auditable,
|
||||
$attribute,
|
||||
$this->decodeAttributeValue($this->auditable, $attribute, $value)
|
||||
);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode attribute value.
|
||||
*
|
||||
* @param Contracts\Auditable $auditable
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function decodeAttributeValue(Contracts\Auditable $auditable, string $attribute, $value)
|
||||
{
|
||||
$attributeModifiers = $auditable->getAttributeModifiers();
|
||||
|
||||
if (!array_key_exists($attribute, $attributeModifiers)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$attributeDecoder = $attributeModifiers[$attribute];
|
||||
|
||||
if (is_subclass_of($attributeDecoder, AttributeEncoder::class)) {
|
||||
return call_user_func([$attributeDecoder, 'decode'], $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetadata(bool $json = false, int $options = 0, int $depth = 512)
|
||||
{
|
||||
if (empty($this->data)) {
|
||||
$this->resolveData();
|
||||
}
|
||||
|
||||
$metadata = [];
|
||||
|
||||
foreach ($this->metadata as $key) {
|
||||
$value = $this->getDataValue($key);
|
||||
$metadata[$key] = $value;
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
$metadata[$key] = !is_null($this->auditable) ? $this->auditable->serializeDate($value) : $this->serializeDate($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $json ? json_encode($metadata, $options, $depth) : $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getModified(bool $json = false, int $options = 0, int $depth = 512)
|
||||
{
|
||||
if (empty($this->data)) {
|
||||
$this->resolveData();
|
||||
}
|
||||
|
||||
$modified = [];
|
||||
|
||||
foreach ($this->modified as $key) {
|
||||
$attribute = substr($key, 4);
|
||||
$state = substr($key, 0, 3);
|
||||
|
||||
$value = $this->getDataValue($key);
|
||||
$modified[$attribute][$state] = $value;
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
$modified[$attribute][$state] = !is_null($this->auditable) ? $this->auditable->serializeDate($value) : $this->serializeDate($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $json ? json_encode($modified, $options, $depth) : $modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Audit tags as an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTags(): array
|
||||
{
|
||||
return preg_split('/,/', $this->tags, -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,872 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use OwenIt\Auditing\Contracts\AttributeEncoder;
|
||||
use OwenIt\Auditing\Contracts\AttributeRedactor;
|
||||
use OwenIt\Auditing\Contracts\Resolver;
|
||||
use OwenIt\Auditing\Events\AuditCustom;
|
||||
use OwenIt\Auditing\Exceptions\AuditableTransitionException;
|
||||
use OwenIt\Auditing\Exceptions\AuditingException;
|
||||
|
||||
trait Auditable
|
||||
{
|
||||
/**
|
||||
* Auditable attributes excluded from the Audit.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $excludedAttributes = [];
|
||||
|
||||
/**
|
||||
* Audit event name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $auditEvent;
|
||||
|
||||
/**
|
||||
* Is auditing disabled?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $auditingDisabled = false;
|
||||
|
||||
/**
|
||||
* Property may set custom event data to register
|
||||
* @var null|array
|
||||
*/
|
||||
public $auditCustomOld = null;
|
||||
|
||||
/**
|
||||
* Property may set custom event data to register
|
||||
* @var null|array
|
||||
*/
|
||||
public $auditCustomNew = null;
|
||||
|
||||
/**
|
||||
* If this is a custom event (as opposed to an eloquent event
|
||||
* @var bool
|
||||
*/
|
||||
public $isCustomEvent = false;
|
||||
|
||||
/**
|
||||
* @var array Preloaded data to be used by resolvers
|
||||
*/
|
||||
public $preloadedResolverData = [];
|
||||
|
||||
/**
|
||||
* Auditable boot logic.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bootAuditable()
|
||||
{
|
||||
if (static::isAuditingEnabled()) {
|
||||
static::observe(new AuditableObserver());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function audits(): MorphMany
|
||||
{
|
||||
return $this->morphMany(
|
||||
Config::get('audit.implementation', Models\Audit::class),
|
||||
'auditable'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the Auditable attributes to exclude from the Audit.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function resolveAuditExclusions()
|
||||
{
|
||||
$this->excludedAttributes = $this->getAuditExclude();
|
||||
|
||||
// When in strict mode, hidden and non visible attributes are excluded
|
||||
if ($this->getAuditStrict()) {
|
||||
// Hidden attributes
|
||||
$this->excludedAttributes = array_merge($this->excludedAttributes, $this->hidden);
|
||||
|
||||
// Non visible attributes
|
||||
if ($this->visible) {
|
||||
$invisible = array_diff(array_keys($this->attributes), $this->visible);
|
||||
|
||||
$this->excludedAttributes = array_merge($this->excludedAttributes, $invisible);
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude Timestamps
|
||||
if (!$this->getAuditTimestamps()) {
|
||||
if ($this->getCreatedAtColumn()) {
|
||||
$this->excludedAttributes[] = $this->getCreatedAtColumn();
|
||||
}
|
||||
if ($this->getUpdatedAtColumn()) {
|
||||
$this->excludedAttributes[] = $this->getUpdatedAtColumn();
|
||||
}
|
||||
if (in_array(SoftDeletes::class, class_uses_recursive(get_class($this)))) {
|
||||
$this->excludedAttributes[] = $this->getDeletedAtColumn();
|
||||
}
|
||||
}
|
||||
|
||||
// Valid attributes are all those that made it out of the exclusion array
|
||||
$attributes = Arr::except($this->attributes, $this->excludedAttributes);
|
||||
|
||||
foreach ($attributes as $attribute => $value) {
|
||||
// Apart from null, non scalar values will be excluded
|
||||
if (
|
||||
(is_array($value) && !Config::get('audit.allowed_array_values', false)) ||
|
||||
(is_object($value) &&
|
||||
!method_exists($value, '__toString') &&
|
||||
!($value instanceof \UnitEnum))
|
||||
) {
|
||||
$this->excludedAttributes[] = $attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAuditExclude(): array
|
||||
{
|
||||
return $this->auditExclude ?? Config::get('audit.exclude', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAuditInclude(): array
|
||||
{
|
||||
return $this->auditInclude ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old/new attributes of a retrieved event.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getRetrievedEventAttributes(): array
|
||||
{
|
||||
// This is a read event with no attribute changes,
|
||||
// only metadata will be stored in the Audit
|
||||
|
||||
return [
|
||||
[],
|
||||
[],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old/new attributes of a created event.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCreatedEventAttributes(): array
|
||||
{
|
||||
$new = [];
|
||||
|
||||
foreach ($this->attributes as $attribute => $value) {
|
||||
if ($this->isAttributeAuditable($attribute)) {
|
||||
$new[$attribute] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
[],
|
||||
$new,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getCustomEventAttributes(): array
|
||||
{
|
||||
return [
|
||||
$this->auditCustomOld,
|
||||
$this->auditCustomNew
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old/new attributes of an updated event.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getUpdatedEventAttributes(): array
|
||||
{
|
||||
$old = [];
|
||||
$new = [];
|
||||
|
||||
foreach ($this->getDirty() as $attribute => $value) {
|
||||
if ($this->isAttributeAuditable($attribute)) {
|
||||
$old[$attribute] = Arr::get($this->original, $attribute);
|
||||
$new[$attribute] = Arr::get($this->attributes, $attribute);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
$old,
|
||||
$new,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old/new attributes of a deleted event.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDeletedEventAttributes(): array
|
||||
{
|
||||
$old = [];
|
||||
|
||||
foreach ($this->attributes as $attribute => $value) {
|
||||
if ($this->isAttributeAuditable($attribute)) {
|
||||
$old[$attribute] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
$old,
|
||||
[],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the old/new attributes of a restored event.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getRestoredEventAttributes(): array
|
||||
{
|
||||
// A restored event is just a deleted event in reverse
|
||||
return array_reverse($this->getDeletedEventAttributes());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function readyForAuditing(): bool
|
||||
{
|
||||
if (static::$auditingDisabled || Models\Audit::$auditingGloballyDisabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isCustomEvent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->isEventAuditable($this->auditEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify attribute value.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
* @throws AuditingException
|
||||
*
|
||||
*/
|
||||
protected function modifyAttributeValue(string $attribute, $value)
|
||||
{
|
||||
$attributeModifiers = $this->getAttributeModifiers();
|
||||
|
||||
if (!array_key_exists($attribute, $attributeModifiers)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$attributeModifier = $attributeModifiers[$attribute];
|
||||
|
||||
if (is_subclass_of($attributeModifier, AttributeRedactor::class)) {
|
||||
return call_user_func([$attributeModifier, 'redact'], $value);
|
||||
}
|
||||
|
||||
if (is_subclass_of($attributeModifier, AttributeEncoder::class)) {
|
||||
return call_user_func([$attributeModifier, 'encode'], $value);
|
||||
}
|
||||
|
||||
throw new AuditingException(sprintf('Invalid AttributeModifier implementation: %s', $attributeModifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toAudit(): array
|
||||
{
|
||||
if (!$this->readyForAuditing()) {
|
||||
throw new AuditingException('A valid audit event has not been set');
|
||||
}
|
||||
|
||||
$attributeGetter = $this->resolveAttributeGetter($this->auditEvent);
|
||||
|
||||
if (!method_exists($this, $attributeGetter)) {
|
||||
throw new AuditingException(sprintf(
|
||||
'Unable to handle "%s" event, %s() method missing',
|
||||
$this->auditEvent,
|
||||
$attributeGetter
|
||||
));
|
||||
}
|
||||
|
||||
$this->resolveAuditExclusions();
|
||||
|
||||
list($old, $new) = $this->$attributeGetter();
|
||||
|
||||
if ($this->getAttributeModifiers() && !$this->isCustomEvent) {
|
||||
foreach ($old as $attribute => $value) {
|
||||
$old[$attribute] = $this->modifyAttributeValue($attribute, $value);
|
||||
}
|
||||
|
||||
foreach ($new as $attribute => $value) {
|
||||
$new[$attribute] = $this->modifyAttributeValue($attribute, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$morphPrefix = Config::get('audit.user.morph_prefix', 'user');
|
||||
|
||||
$tags = implode(',', $this->generateTags());
|
||||
|
||||
$user = $this->resolveUser();
|
||||
|
||||
return $this->transformAudit(array_merge([
|
||||
'old_values' => $old,
|
||||
'new_values' => $new,
|
||||
'event' => $this->auditEvent,
|
||||
'auditable_id' => $this->getKey(),
|
||||
'auditable_type' => $this->getMorphClass(),
|
||||
$morphPrefix . '_id' => $user ? $user->getAuthIdentifier() : null,
|
||||
$morphPrefix . '_type' => $user ? $user->getMorphClass() : null,
|
||||
'tags' => empty($tags) ? null : $tags,
|
||||
], $this->runResolvers()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transformAudit(array $data): array
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the User.
|
||||
*
|
||||
* @return mixed|null
|
||||
* @throws AuditingException
|
||||
*
|
||||
*/
|
||||
protected function resolveUser()
|
||||
{
|
||||
if (!empty($this->preloadedResolverData['user'] ?? null)) {
|
||||
return $this->preloadedResolverData['user'];
|
||||
}
|
||||
|
||||
$userResolver = Config::get('audit.user.resolver');
|
||||
|
||||
if (is_null($userResolver) && Config::has('audit.resolver') && !Config::has('audit.user.resolver')) {
|
||||
trigger_error(
|
||||
'The config file audit.php is not updated to the new version 13.0. Please see https://laravel-auditing.com/guide/upgrading.html',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$userResolver = Config::get('audit.resolver.user');
|
||||
}
|
||||
|
||||
if (is_subclass_of($userResolver, \OwenIt\Auditing\Contracts\UserResolver::class)) {
|
||||
return call_user_func([$userResolver, 'resolve'], $this);
|
||||
}
|
||||
|
||||
throw new AuditingException('Invalid UserResolver implementation');
|
||||
}
|
||||
|
||||
protected function runResolvers(): array
|
||||
{
|
||||
$resolved = [];
|
||||
$resolvers = Config::get('audit.resolvers', []);
|
||||
if (empty($resolvers) && Config::has('audit.resolver')) {
|
||||
trigger_error(
|
||||
'The config file audit.php is not updated to the new version 13.0. Please see https://laravel-auditing.com/guide/upgrading.html',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
$resolvers = Config::get('audit.resolver', []);
|
||||
}
|
||||
|
||||
foreach ($resolvers as $name => $implementation) {
|
||||
if (empty($implementation)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_subclass_of($implementation, Resolver::class)) {
|
||||
throw new AuditingException('Invalid Resolver implementation for: ' . $name);
|
||||
}
|
||||
$resolved[$name] = call_user_func([$implementation, 'resolve'], $this);
|
||||
}
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
public function preloadResolverData()
|
||||
{
|
||||
$this->preloadedResolverData = $this->runResolvers();
|
||||
|
||||
$user = $this->resolveUser();
|
||||
if (!empty($user)) {
|
||||
$this->preloadedResolverData['user'] = $user;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an attribute is eligible for auditing.
|
||||
*
|
||||
* @param string $attribute
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAttributeAuditable(string $attribute): bool
|
||||
{
|
||||
// The attribute should not be audited
|
||||
if (in_array($attribute, $this->excludedAttributes, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The attribute is auditable when explicitly
|
||||
// listed or when the include array is empty
|
||||
$include = $this->getAuditInclude();
|
||||
|
||||
return empty($include) || in_array($attribute, $include, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an event is auditable.
|
||||
*
|
||||
* @param string $event
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isEventAuditable($event): bool
|
||||
{
|
||||
return is_string($this->resolveAttributeGetter($event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute getter method resolver.
|
||||
*
|
||||
* @param string $event
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function resolveAttributeGetter($event)
|
||||
{
|
||||
if (empty($event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->isCustomEvent) {
|
||||
return 'getCustomEventAttributes';
|
||||
}
|
||||
|
||||
foreach ($this->getAuditEvents() as $key => $value) {
|
||||
$auditableEvent = is_int($key) ? $value : $key;
|
||||
|
||||
$auditableEventRegex = sprintf('/%s/', preg_replace('/\*+/', '.*', $auditableEvent));
|
||||
|
||||
if (preg_match($auditableEventRegex, $event)) {
|
||||
return is_int($key) ? sprintf('get%sEventAttributes', ucfirst($event)) : $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAuditEvent(string $event): Contracts\Auditable
|
||||
{
|
||||
$this->auditEvent = $this->isEventAuditable($event) ? $event : null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuditEvent()
|
||||
{
|
||||
return $this->auditEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuditEvents(): array
|
||||
{
|
||||
return $this->auditEvents ?? Config::get('audit.events', [
|
||||
'created',
|
||||
'updated',
|
||||
'deleted',
|
||||
'restored',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is Auditing disabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAuditingDisabled(): bool
|
||||
{
|
||||
return static::$auditingDisabled || Models\Audit::$auditingGloballyDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable Auditing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function disableAuditing()
|
||||
{
|
||||
static::$auditingDisabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable Auditing.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enableAuditing()
|
||||
{
|
||||
static::$auditingDisabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a callback while auditing is disabled.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param bool $globally
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function withoutAuditing(callable $callback, bool $globally = false)
|
||||
{
|
||||
$auditingDisabled = static::$auditingDisabled;
|
||||
|
||||
static::disableAuditing();
|
||||
Models\Audit::$auditingGloballyDisabled = $globally;
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
Models\Audit::$auditingGloballyDisabled = false;
|
||||
static::$auditingDisabled = $auditingDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether auditing is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isAuditingEnabled(): bool
|
||||
{
|
||||
if (App::runningInConsole()) {
|
||||
return Config::get('audit.enabled', true) && Config::get('audit.console', false);
|
||||
}
|
||||
|
||||
return Config::get('audit.enabled', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuditStrict(): bool
|
||||
{
|
||||
return $this->auditStrict ?? Config::get('audit.strict', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuditTimestamps(): bool
|
||||
{
|
||||
return $this->auditTimestamps ?? Config::get('audit.timestamps', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuditDriver()
|
||||
{
|
||||
return $this->auditDriver ?? Config::get('audit.driver', 'database');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuditThreshold(): int
|
||||
{
|
||||
return $this->auditThreshold ?? Config::get('audit.threshold', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAttributeModifiers(): array
|
||||
{
|
||||
return $this->attributeModifiers ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generateTags(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transitionTo(Contracts\Audit $audit, bool $old = false): Contracts\Auditable
|
||||
{
|
||||
// The Audit must be for an Auditable model of this type
|
||||
if ($this->getMorphClass() !== $audit->auditable_type) {
|
||||
throw new AuditableTransitionException(sprintf(
|
||||
'Expected Auditable type %s, got %s instead',
|
||||
$this->getMorphClass(),
|
||||
$audit->auditable_type
|
||||
));
|
||||
}
|
||||
|
||||
// The Audit must be for this specific Auditable model
|
||||
if ($this->getKey() !== $audit->auditable_id) {
|
||||
throw new AuditableTransitionException(sprintf(
|
||||
'Expected Auditable id (%s)%s, got (%s)%s instead',
|
||||
gettype($this->getKey()),
|
||||
$this->getKey(),
|
||||
gettype($audit->auditable_id),
|
||||
$audit->auditable_id
|
||||
));
|
||||
}
|
||||
|
||||
// Redacted data should not be used when transitioning states
|
||||
foreach ($this->getAttributeModifiers() as $attribute => $modifier) {
|
||||
if (is_subclass_of($modifier, AttributeRedactor::class)) {
|
||||
throw new AuditableTransitionException('Cannot transition states when an AttributeRedactor is set');
|
||||
}
|
||||
}
|
||||
|
||||
// The attribute compatibility between the Audit and the Auditable model must be met
|
||||
$modified = $audit->getModified();
|
||||
|
||||
if ($incompatibilities = array_diff_key($modified, $this->getAttributes())) {
|
||||
throw new AuditableTransitionException(sprintf(
|
||||
'Incompatibility between [%s:%s] and [%s:%s]',
|
||||
$this->getMorphClass(),
|
||||
$this->getKey(),
|
||||
get_class($audit),
|
||||
$audit->getKey()
|
||||
), array_keys($incompatibilities));
|
||||
}
|
||||
|
||||
$key = $old ? 'old' : 'new';
|
||||
|
||||
foreach ($modified as $attribute => $value) {
|
||||
if (array_key_exists($key, $value)) {
|
||||
$this->setAttribute($attribute, $value[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Pivot help methods
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Methods for auditing pivot actions
|
||||
|
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param string $relationName
|
||||
* @param mixed $id
|
||||
* @param array $attributes
|
||||
* @param bool $touch
|
||||
* @param array $columns
|
||||
* @param \Closure|null $callback
|
||||
* @return void
|
||||
* @throws AuditingException
|
||||
*/
|
||||
public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['*'], $callback = null)
|
||||
{
|
||||
$this->validateRelationshipMethodExistence($relationName, 'attach');
|
||||
|
||||
$relationCall = $this->{$relationName}();
|
||||
|
||||
if ($callback instanceof \Closure) {
|
||||
$this->applyClosureToRelationship($relationCall, $callback);
|
||||
}
|
||||
|
||||
$old = $relationCall->get($columns);
|
||||
$relationCall->attach($id, $attributes, $touch);
|
||||
$new = $relationCall->get($columns);
|
||||
|
||||
$this->dispatchRelationAuditEvent($relationName, 'attach', $old, $new);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $relationName
|
||||
* @param mixed $ids
|
||||
* @param bool $touch
|
||||
* @param array $columns
|
||||
* @param \Closure|null $callback
|
||||
* @return int
|
||||
* @throws AuditingException
|
||||
*/
|
||||
public function auditDetach(string $relationName, $ids = null, $touch = true, $columns = ['*'], $callback = null)
|
||||
{
|
||||
$this->validateRelationshipMethodExistence($relationName, 'detach');
|
||||
|
||||
$relationCall = $this->{$relationName}();
|
||||
|
||||
if ($callback instanceof \Closure) {
|
||||
$this->applyClosureToRelationship($relationCall, $callback);
|
||||
}
|
||||
|
||||
$old = $relationCall->get($columns);
|
||||
$results = $relationCall->detach($ids, $touch);
|
||||
$new = $relationCall->get($columns);
|
||||
|
||||
$this->dispatchRelationAuditEvent($relationName, 'detach', $old, $new);
|
||||
|
||||
return empty($results) ? 0 : $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $relationName
|
||||
* @param Collection|Model|array $ids
|
||||
* @param bool $detaching
|
||||
* @param array $columns
|
||||
* @param \Closure|null $callback
|
||||
* @return array
|
||||
* @throws AuditingException
|
||||
*/
|
||||
public function auditSync(string $relationName, $ids, $detaching = true, $columns = ['*'], $callback = null)
|
||||
{
|
||||
$this->validateRelationshipMethodExistence($relationName, 'sync');
|
||||
|
||||
$relationCall = $this->{$relationName}();
|
||||
|
||||
if ($callback instanceof \Closure) {
|
||||
$this->applyClosureToRelationship($relationCall, $callback);
|
||||
}
|
||||
|
||||
$old = $relationCall->get($columns);
|
||||
$changes = $relationCall->sync($ids, $detaching);
|
||||
|
||||
if (collect($changes)->flatten()->isEmpty()) {
|
||||
$old = $new = collect([]);
|
||||
} else {
|
||||
$new = $relationCall->get($columns);
|
||||
}
|
||||
|
||||
$this->dispatchRelationAuditEvent($relationName, 'sync', $old, $new);
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $relationName
|
||||
* @param Collection|Model|array $ids
|
||||
* @param array $columns
|
||||
* @param \Closure|null $callback
|
||||
* @return array
|
||||
* @throws AuditingException
|
||||
*/
|
||||
public function auditSyncWithoutDetaching(string $relationName, $ids, $columns = ['*'], $callback = null)
|
||||
{
|
||||
$this->validateRelationshipMethodExistence($relationName, 'syncWithoutDetaching');
|
||||
|
||||
return $this->auditSync($relationName, $ids, false, $columns, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $relationName
|
||||
* @param Collection|Model|array $ids
|
||||
* @param array $values
|
||||
* @param bool $detaching
|
||||
* @param array $columns
|
||||
* @param \Closure|null $callback
|
||||
* @return array
|
||||
*/
|
||||
public function auditSyncWithPivotValues(string $relationName, $ids, array $values, bool $detaching = true, $columns = ['*'], $callback = null)
|
||||
{
|
||||
$this->validateRelationshipMethodExistence($relationName, 'syncWithPivotValues');
|
||||
|
||||
if ($ids instanceof Model) {
|
||||
$ids = $ids->getKey();
|
||||
} elseif ($ids instanceof \Illuminate\Database\Eloquent\Collection) {
|
||||
$ids = $ids->isEmpty() ? [] : $ids->pluck($ids->first()->getKeyName())->toArray();
|
||||
} elseif ($ids instanceof Collection) {
|
||||
$ids = $ids->toArray();
|
||||
}
|
||||
|
||||
return $this->auditSync($relationName, collect(Arr::wrap($ids))->mapWithKeys(function ($id) use ($values) {
|
||||
return [$id => $values];
|
||||
}), $detaching, $columns, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $relationName
|
||||
* @param string $event
|
||||
* @param Collection $old
|
||||
* @param Collection $new
|
||||
* @return void
|
||||
*/
|
||||
private function dispatchRelationAuditEvent($relationName, $event, $old, $new)
|
||||
{
|
||||
$this->auditCustomOld[$relationName] = $old->diff($new)->toArray();
|
||||
$this->auditCustomNew[$relationName] = $new->diff($old)->toArray();
|
||||
|
||||
if (
|
||||
empty($this->auditCustomOld[$relationName]) &&
|
||||
empty($this->auditCustomNew[$relationName])
|
||||
) {
|
||||
$this->auditCustomOld = $this->auditCustomNew = [];
|
||||
}
|
||||
|
||||
$this->auditEvent = $event;
|
||||
$this->isCustomEvent = true;
|
||||
Event::dispatch(AuditCustom::class, [$this]);
|
||||
$this->auditCustomOld = $this->auditCustomNew = [];
|
||||
$this->isCustomEvent = false;
|
||||
}
|
||||
|
||||
private function validateRelationshipMethodExistence(string $relationName, string $methodName): void
|
||||
{
|
||||
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), $methodName)) {
|
||||
throw new AuditingException("Relationship $relationName was not found or does not support method $methodName");
|
||||
}
|
||||
}
|
||||
|
||||
private function applyClosureToRelationship(BelongsToMany $relation, \Closure $closure): void
|
||||
{
|
||||
try {
|
||||
$closure($relation);
|
||||
} catch (\Throwable $exception) {
|
||||
throw new AuditingException("Invalid Closure for {$relation->getRelationName()} Relationship");
|
||||
}
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing;
|
||||
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use OwenIt\Auditing\Contracts\Auditable;
|
||||
use OwenIt\Auditing\Events\DispatchAudit;
|
||||
use OwenIt\Auditing\Events\DispatchingAudit;
|
||||
use OwenIt\Auditing\Facades\Auditor;
|
||||
|
||||
class AuditableObserver
|
||||
{
|
||||
/**
|
||||
* Is the model being restored?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $restoring = false;
|
||||
|
||||
/**
|
||||
* Handle the retrieved event.
|
||||
*
|
||||
* @param \OwenIt\Auditing\Contracts\Auditable $model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function retrieved(Auditable $model)
|
||||
{
|
||||
$this->dispatchAudit($model->setAuditEvent('retrieved'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the created event.
|
||||
*
|
||||
* @param \OwenIt\Auditing\Contracts\Auditable $model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function created(Auditable $model)
|
||||
{
|
||||
$this->dispatchAudit($model->setAuditEvent('created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the updated event.
|
||||
*
|
||||
* @param \OwenIt\Auditing\Contracts\Auditable $model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updated(Auditable $model)
|
||||
{
|
||||
// Ignore the updated event when restoring
|
||||
if (!static::$restoring) {
|
||||
$this->dispatchAudit($model->setAuditEvent('updated'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the deleted event.
|
||||
*
|
||||
* @param \OwenIt\Auditing\Contracts\Auditable $model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(Auditable $model)
|
||||
{
|
||||
$this->dispatchAudit($model->setAuditEvent('deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the restoring event.
|
||||
*
|
||||
* @param \OwenIt\Auditing\Contracts\Auditable $model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function restoring(Auditable $model)
|
||||
{
|
||||
// When restoring a model, an updated event is also fired.
|
||||
// By keeping track of the main event that took place,
|
||||
// we avoid creating a second audit with wrong values
|
||||
static::$restoring = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the restored event.
|
||||
*
|
||||
* @param \OwenIt\Auditing\Contracts\Auditable $model
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function restored(Auditable $model)
|
||||
{
|
||||
$this->dispatchAudit($model->setAuditEvent('restored'));
|
||||
|
||||
// Once the model is restored, we need to put everything back
|
||||
// as before, in case a legitimate update event is fired
|
||||
static::$restoring = false;
|
||||
}
|
||||
|
||||
protected function dispatchAudit(Auditable $model)
|
||||
{
|
||||
if (!$model->readyForAuditing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$model->preloadResolverData();
|
||||
if (!Config::get('audit.queue.enable', false)) {
|
||||
Auditor::execute($model);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->fireDispatchingAuditEvent($model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unload the relations to prevent large amounts of unnecessary data from being serialized.
|
||||
app()->make('events')->dispatch(new DispatchAudit($model->withoutRelations()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the Auditing event.
|
||||
*
|
||||
* @param \OwenIt\Auditing\Contracts\Auditable $model
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function fireDispatchingAuditEvent(Auditable $model): bool
|
||||
{
|
||||
return app()->make('events')
|
||||
->until(new DispatchingAudit($model)) !== false;
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing;
|
||||
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use OwenIt\Auditing\Console\AuditDriverCommand;
|
||||
use OwenIt\Auditing\Console\AuditResolverCommand;
|
||||
use OwenIt\Auditing\Console\InstallCommand;
|
||||
use OwenIt\Auditing\Contracts\Auditor;
|
||||
use OwenIt\Auditing\Events\AuditCustom;
|
||||
use OwenIt\Auditing\Events\DispatchAudit;
|
||||
use OwenIt\Auditing\Listeners\ProcessDispatchAudit;
|
||||
use OwenIt\Auditing\Listeners\RecordCustomAudit;
|
||||
|
||||
class AuditingServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->registerPublishing();
|
||||
$this->mergeConfigFrom(__DIR__ . '/../config/audit.php', 'audit');
|
||||
|
||||
Event::listen(AuditCustom::class, RecordCustomAudit::class);
|
||||
Event::listen(DispatchAudit::class, ProcessDispatchAudit::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->commands([
|
||||
AuditDriverCommand::class,
|
||||
AuditResolverCommand::class,
|
||||
InstallCommand::class,
|
||||
]);
|
||||
|
||||
$this->app->singleton(Auditor::class, function ($app) {
|
||||
return new \OwenIt\Auditing\Auditor($app);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the package's publishable resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function registerPublishing()
|
||||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
// Lumen lacks a config_path() helper, so we use base_path()
|
||||
$this->publishes([
|
||||
__DIR__ . '/../config/audit.php' => base_path('config/audit.php'),
|
||||
], 'config');
|
||||
|
||||
if (!class_exists('CreateAuditsTable')) {
|
||||
$this->publishes([
|
||||
__DIR__ . '/../database/migrations/audits.stub' => database_path(
|
||||
sprintf('migrations/%s_create_audits_table.php', date('Y_m_d_His'))
|
||||
),
|
||||
], 'migrations');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing\Contracts;
|
||||
|
||||
interface Auditor
|
||||
{
|
||||
/**
|
||||
* Get an audit driver instance.
|
||||
*/
|
||||
public function auditDriver(Auditable $model): AuditDriver;
|
||||
|
||||
/**
|
||||
* Perform an audit.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute(Auditable $model);
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing\Drivers;
|
||||
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use OwenIt\Auditing\Contracts\Audit;
|
||||
use OwenIt\Auditing\Contracts\Auditable;
|
||||
use OwenIt\Auditing\Contracts\AuditDriver;
|
||||
|
||||
class Database implements AuditDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function audit(Auditable $model): ?Audit
|
||||
{
|
||||
return call_user_func([get_class($model->audits()->getModel()), 'create'], $model->toAudit());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prune(Auditable $model): bool
|
||||
{
|
||||
if (($threshold = $model->getAuditThreshold()) > 0) {
|
||||
$auditClass = get_class($model->audits()->getModel());
|
||||
$auditModel = new $auditClass;
|
||||
|
||||
return $model->audits()
|
||||
->leftJoinSub(
|
||||
$model->audits()->select($auditModel->getKeyName())->limit($threshold)->latest(),
|
||||
'audit_threshold',
|
||||
function ($join) use ($auditModel) {
|
||||
$join->on(
|
||||
$auditModel->gettable().'.'.$auditModel->getKeyName(),
|
||||
'=',
|
||||
'audit_threshold.'.$auditModel->getKeyName()
|
||||
);
|
||||
}
|
||||
)
|
||||
->whereNull('audit_threshold.'.$auditModel->getKeyName())
|
||||
->delete() > 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing\Events;
|
||||
|
||||
use OwenIt\Auditing\Contracts\Audit;
|
||||
use OwenIt\Auditing\Contracts\Auditable;
|
||||
use OwenIt\Auditing\Contracts\AuditDriver;
|
||||
|
||||
class Audited
|
||||
{
|
||||
/**
|
||||
* The Auditable model.
|
||||
*
|
||||
* @var \OwenIt\Auditing\Contracts\Auditable
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* Audit driver.
|
||||
*
|
||||
* @var \OwenIt\Auditing\Contracts\AuditDriver
|
||||
*/
|
||||
public $driver;
|
||||
|
||||
/**
|
||||
* The Audit model.
|
||||
*
|
||||
* @var \OwenIt\Auditing\Contracts\Audit|null
|
||||
*/
|
||||
public $audit;
|
||||
|
||||
/**
|
||||
* Create a new Audited event instance.
|
||||
*
|
||||
* @param \OwenIt\Auditing\Contracts\Auditable $model
|
||||
* @param \OwenIt\Auditing\Contracts\AuditDriver $driver
|
||||
* @param \OwenIt\Auditing\Contracts\Audit|null $audit
|
||||
*/
|
||||
public function __construct(Auditable $model, AuditDriver $driver, ?Audit $audit = null)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->driver = $driver;
|
||||
$this->audit = $audit;
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing\Events;
|
||||
|
||||
use OwenIt\Auditing\Contracts\Auditable;
|
||||
use ReflectionClass;
|
||||
|
||||
class DispatchAudit
|
||||
{
|
||||
/**
|
||||
* The Auditable model.
|
||||
*
|
||||
* @var Auditable
|
||||
*/
|
||||
public $model;
|
||||
|
||||
/**
|
||||
* Create a new DispatchAudit event instance.
|
||||
*
|
||||
* @param Auditable $model
|
||||
*/
|
||||
public function __construct(Auditable $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the instance values for serialization.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __serialize()
|
||||
{
|
||||
$values = [
|
||||
'class' => get_class($this->model),
|
||||
'model_data' => [
|
||||
'exists' => true,
|
||||
'connection' => $this->model->getQueueableConnection()
|
||||
]
|
||||
];
|
||||
|
||||
$customProperties = array_merge([
|
||||
'attributes',
|
||||
'original',
|
||||
'excludedAttributes',
|
||||
'auditEvent',
|
||||
'auditExclude',
|
||||
'auditCustomOld',
|
||||
'auditCustomNew',
|
||||
'isCustomEvent',
|
||||
'preloadedResolverData',
|
||||
], $this->model->auditEventSerializedProperties ?? []);
|
||||
|
||||
$reflection = new ReflectionClass($this->model);
|
||||
|
||||
foreach ($customProperties as $key) {
|
||||
try {
|
||||
$values['model_data'][$key] = $this->getModelPropertyValue($reflection, $key);
|
||||
} catch (\Throwable $e){
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the model after serialization.
|
||||
*
|
||||
* @param array $values
|
||||
* @return array
|
||||
*/
|
||||
public function __unserialize(array $values)
|
||||
{
|
||||
$this->model = new $values['class'];
|
||||
|
||||
$reflection = new ReflectionClass($this->model);
|
||||
foreach ($values['model_data'] as $key => $value) {
|
||||
$this->setModelPropertyValue($reflection, $key, $value);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property value for the given property.
|
||||
*/
|
||||
protected function setModelPropertyValue(ReflectionClass $reflection, string $name, $value)
|
||||
{
|
||||
$property = $reflection->getProperty($name);
|
||||
|
||||
$property->setAccessible(true);
|
||||
|
||||
$property->setValue($this->model, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property value for the given property.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getModelPropertyValue(ReflectionClass $reflection, string $name)
|
||||
{
|
||||
$property = $reflection->getProperty($name);
|
||||
|
||||
$property->setAccessible(true);
|
||||
|
||||
return $property->getValue($this->model);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class AuditableTransitionException extends AuditingException
|
||||
{
|
||||
/**
|
||||
* Attribute incompatibilities.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $incompatibilities = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($message = '', array $incompatibilities = [], $code = 0, ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->incompatibilities = $incompatibilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attribute incompatibilities.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIncompatibilities(): array
|
||||
{
|
||||
return $this->incompatibilities;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace OwenIt\Auditing\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property string $tags
|
||||
* @property string $event
|
||||
* @property array $new_values
|
||||
* @property array $old_values
|
||||
* @property mixed $user
|
||||
* @property mixed $auditable.
|
||||
*/
|
||||
class Audit extends Model implements \OwenIt\Auditing\Contracts\Audit
|
||||
{
|
||||
use \OwenIt\Auditing\Audit;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* Is globally auditing disabled?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public static $auditingGloballyDisabled = false;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $casts = [
|
||||
'old_values' => 'json',
|
||||
'new_values' => 'json',
|
||||
// Note: Please do not add 'auditable_id' in here, as it will break non-integer PK models
|
||||
];
|
||||
|
||||
public function getSerializedDate($date)
|
||||
{
|
||||
return $this->serializeDate($date);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user