updated streamline-setup v2

This commit is contained in:
2025-01-15 08:53:49 -08:00
committed by alec.turner
parent a2ce9248f0
commit 4b569f81b0
20228 changed files with 2932048 additions and 63204 deletions
@@ -77,7 +77,7 @@ trait Audit
// Metadata
$this->data = [
'audit_id' => $this->id,
'audit_id' => $this->getKey(),
'audit_event' => $this->event,
'audit_tags' => $this->tags,
'audit_created_at' => $this->serializeDate($this->{$this->getCreatedAtColumn()}),
@@ -102,11 +102,11 @@ trait Audit
$this->metadata = array_keys($this->data);
// Modified Auditable attributes
foreach ($this->new_values as $key => $value) {
foreach ($this->new_values ?? [] as $key => $value) {
$this->data['new_' . $key] = $value;
}
foreach ($this->old_values as $key => $value) {
foreach ($this->old_values ?? [] as $key => $value) {
$this->data['old_' . $key] = $value;
}
@@ -2,9 +2,12 @@
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;
@@ -125,7 +128,7 @@ trait Auditable
foreach ($attributes as $attribute => $value) {
// Apart from null, non scalar values will be excluded
if (
is_array($value) ||
(is_array($value) && !Config::get('audit.allowed_array_values', false)) ||
(is_object($value) &&
!method_exists($value, '__toString') &&
!($value instanceof \UnitEnum))
@@ -256,7 +259,7 @@ trait Auditable
*/
public function readyForAuditing(): bool
{
if (static::$auditingDisabled) {
if (static::$auditingDisabled || Models\Audit::$auditingGloballyDisabled) {
return false;
}
@@ -365,8 +368,8 @@ trait Auditable
*
*/
protected function resolveUser()
{
if (! empty($this->preloadedResolverData['user'] ?? null)) {
{
if (!empty($this->preloadedResolverData['user'] ?? null)) {
return $this->preloadedResolverData['user'];
}
@@ -416,8 +419,9 @@ trait Auditable
{
$this->preloadedResolverData = $this->runResolvers();
if (!empty ($this->resolveUser())) {
$this->preloadedResolverData['user'] = $this->resolveUser();
$user = $this->resolveUser();
if (!empty($user)) {
$this->preloadedResolverData['user'] = $user;
}
return $this;
@@ -508,11 +512,21 @@ trait Auditable
public function getAuditEvents(): array
{
return $this->auditEvents ?? Config::get('audit.events', [
'created',
'updated',
'deleted',
'restored',
]);
'created',
'updated',
'deleted',
'restored',
]);
}
/**
* Is Auditing disabled.
*
* @return bool
*/
public static function isAuditingDisabled(): bool
{
return static::$auditingDisabled || Models\Audit::$auditingGloballyDisabled;
}
/**
@@ -535,6 +549,29 @@ trait Auditable
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.
*
@@ -668,18 +705,24 @@ trait Auditable
* @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 = ['*'])
public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['*'], $callback = null)
{
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'attach')) {
throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method attach');
$this->validateRelationshipMethodExistence($relationName, 'attach');
$relationCall = $this->{$relationName}();
if ($callback instanceof \Closure) {
$this->applyClosureToRelationship($relationCall, $callback);
}
$old = $this->{$relationName}()->get($columns);
$this->{$relationName}()->attach($id, $attributes, $touch);
$new = $this->{$relationName}()->get($columns);
$old = $relationCall->get($columns);
$relationCall->attach($id, $attributes, $touch);
$new = $relationCall->get($columns);
$this->dispatchRelationAuditEvent($relationName, 'attach', $old, $new);
}
@@ -688,44 +731,57 @@ trait Auditable
* @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 = ['*'])
public function auditDetach(string $relationName, $ids = null, $touch = true, $columns = ['*'], $callback = null)
{
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'detach')) {
throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method detach');
$this->validateRelationshipMethodExistence($relationName, 'detach');
$relationCall = $this->{$relationName}();
if ($callback instanceof \Closure) {
$this->applyClosureToRelationship($relationCall, $callback);
}
$old = $this->{$relationName}()->get($columns);
$results = $this->{$relationName}()->detach($ids, $touch);
$new = $this->{$relationName}()->get($columns);
$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 $relationName
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @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($relationName, $ids, $detaching = true, $columns = ['*'])
public function auditSync(string $relationName, $ids, $detaching = true, $columns = ['*'], $callback = null)
{
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'sync')) {
throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method sync');
$this->validateRelationshipMethodExistence($relationName, 'sync');
$relationCall = $this->{$relationName}();
if ($callback instanceof \Closure) {
$this->applyClosureToRelationship($relationCall, $callback);
}
$old = $this->{$relationName}()->get($columns);
$changes = $this->{$relationName}()->sync($ids, $detaching);
$old = $relationCall->get($columns);
$changes = $relationCall->sync($ids, $detaching);
if (collect($changes)->flatten()->isEmpty()) {
$old = $new = collect([]);
} else {
$new = $this->{$relationName}()->get($columns);
$new = $relationCall->get($columns);
}
$this->dispatchRelationAuditEvent($relationName, 'sync', $old, $new);
return $changes;
@@ -733,25 +789,50 @@ trait Auditable
/**
* @param string $relationName
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @param Collection|Model|array $ids
* @param array $columns
* @param \Closure|null $callback
* @return array
* @throws AuditingException
*/
public function auditSyncWithoutDetaching(string $relationName, $ids, $columns = ['*'])
public function auditSyncWithoutDetaching(string $relationName, $ids, $columns = ['*'], $callback = null)
{
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'syncWithoutDetaching')) {
throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method syncWithoutDetaching');
$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, $ids, false, $columns);
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 \Illuminate\Support\Collection $old
* @param \Illuminate\Support\Collection $new
* @param Collection $old
* @param Collection $new
* @return void
*/
private function dispatchRelationAuditEvent($relationName, $event, $old, $new)
@@ -769,6 +850,23 @@ trait Auditable
$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");
}
}
}
@@ -107,7 +107,8 @@ class AuditableObserver
$model->preloadResolverData();
if (!Config::get('audit.queue.enable', false)) {
return Auditor::execute($model);
Auditor::execute($model);
return;
}
if (!$this->fireDispatchingAuditEvent($model)) {
@@ -1,38 +0,0 @@
<?php
namespace OwenIt\Auditing;
if (app() instanceof \Illuminate\Foundation\Application) {
class_alias(\Illuminate\Foundation\Support\Providers\EventServiceProvider::class, '\OwenIt\Auditing\ServiceProvider');
} else {
class_alias(\Laravel\Lumen\Providers\EventServiceProvider::class, '\OwenIt\Auditing\ServiceProvider');
}
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Config;
use OwenIt\Auditing\Events\AuditCustom;
use OwenIt\Auditing\Events\DispatchAudit;
use OwenIt\Auditing\Listeners\RecordCustomAudit;
use OwenIt\Auditing\Listeners\ProcessDispatchAudit;
class AuditingEventServiceProvider extends ServiceProvider
{
protected $listen = [
AuditCustom::class => [
RecordCustomAudit::class,
],
DispatchAudit::class => [
ProcessDispatchAudit::class,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
}
}
@@ -2,12 +2,16 @@
namespace OwenIt\Auditing;
use Illuminate\Contracts\Support\DeferrableProvider;
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
{
@@ -20,6 +24,9 @@ class AuditingServiceProvider extends ServiceProvider
{
$this->registerPublishing();
$this->mergeConfigFrom(__DIR__ . '/../config/audit.php', 'audit');
Event::listen(AuditCustom::class, RecordCustomAudit::class);
Event::listen(DispatchAudit::class, ProcessDispatchAudit::class);
}
/**
@@ -38,8 +45,6 @@ class AuditingServiceProvider extends ServiceProvider
$this->app->singleton(Auditor::class, function ($app) {
return new \OwenIt\Auditing\Auditor($app);
});
$this->app->register(AuditingEventServiceProvider::class);
}
/**
@@ -6,18 +6,12 @@ interface Auditor
{
/**
* Get an audit driver instance.
*
* @param \OwenIt\Auditing\Contracts\Auditable $model
*
* @return AuditDriver
*/
public function auditDriver(Auditable $model): AuditDriver;
/**
* Perform an audit.
*
* @param \OwenIt\Auditing\Contracts\Auditable $model
*
* @return void
*/
public function execute(Auditable $model);
@@ -14,9 +14,7 @@ class Database implements AuditDriver
*/
public function audit(Auditable $model): ?Audit
{
$implementation = Config::get('audit.implementation', \OwenIt\Auditing\Models\Audit::class);
return call_user_func([$implementation, 'create'], $model->toAudit());
return call_user_func([get_class($model->audits()->getModel()), 'create'], $model->toAudit());
}
/**
@@ -25,17 +23,23 @@ class Database implements AuditDriver
public function prune(Auditable $model): bool
{
if (($threshold = $model->getAuditThreshold()) > 0) {
$forRemoval = $model->audits()
->latest()
->get()
->slice($threshold)
->pluck('id');
$auditClass = get_class($model->audits()->getModel());
$auditModel = new $auditClass;
if (!$forRemoval->isEmpty()) {
return $model->audits()
->whereIn('id', $forRemoval)
->delete() > 0;
}
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;
@@ -34,9 +34,9 @@ class Audited
*
* @param \OwenIt\Auditing\Contracts\Auditable $model
* @param \OwenIt\Auditing\Contracts\AuditDriver $driver
* @param \OwenIt\Auditing\Contracts\Audit $audit
* @param \OwenIt\Auditing\Contracts\Audit|null $audit
*/
public function __construct(Auditable $model, AuditDriver $driver, Audit $audit = null)
public function __construct(Auditable $model, AuditDriver $driver, ?Audit $audit = null)
{
$this->model = $model;
$this->driver = $driver;
@@ -3,6 +3,7 @@
namespace OwenIt\Auditing\Events;
use OwenIt\Auditing\Contracts\Auditable;
use ReflectionClass;
class DispatchAudit
{
@@ -22,4 +23,88 @@ class DispatchAudit
{
$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);
}
}
@@ -16,7 +16,7 @@ class AuditableTransitionException extends AuditingException
/**
* {@inheritdoc}
*/
public function __construct($message = '', array $incompatibilities = [], $code = 0, Throwable $previous = null)
public function __construct($message = '', array $incompatibilities = [], $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
@@ -21,6 +21,13 @@ class Audit extends Model implements \OwenIt\Auditing\Contracts\Audit
*/
protected $guarded = [];
/**
* Is globally auditing disabled?
*
* @var bool
*/
public static $auditingGloballyDisabled = false;
/**
* {@inheritdoc}
*/