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
@@ -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->id,
'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,774 @@
<?php
namespace OwenIt\Auditing;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Arr;
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) ||
(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) {
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();
if (!empty ($this->resolveUser())) {
$this->preloadedResolverData['user'] = $this->resolveUser();
}
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',
]);
}
/**
* Disable Auditing.
*
* @return void
*/
public static function disableAuditing()
{
static::$auditingDisabled = true;
}
/**
* Enable Auditing.
*
* @return void
*/
public static function enableAuditing()
{
static::$auditingDisabled = false;
}
/**
* 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
* @return void
* @throws AuditingException
*/
public function auditAttach(string $relationName, $id, array $attributes = [], $touch = true, $columns = ['*'])
{
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'attach')) {
throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method attach');
}
$old = $this->{$relationName}()->get($columns);
$this->{$relationName}()->attach($id, $attributes, $touch);
$new = $this->{$relationName}()->get($columns);
$this->dispatchRelationAuditEvent($relationName, 'attach', $old, $new);
}
/**
* @param string $relationName
* @param mixed $ids
* @param bool $touch
* @param array $columns
* @return int
* @throws AuditingException
*/
public function auditDetach(string $relationName, $ids = null, $touch = true, $columns = ['*'])
{
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'detach')) {
throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method detach');
}
$old = $this->{$relationName}()->get($columns);
$results = $this->{$relationName}()->detach($ids, $touch);
$new = $this->{$relationName}()->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 bool $detaching
* @param array $columns
* @return array
* @throws AuditingException
*/
public function auditSync($relationName, $ids, $detaching = true, $columns = ['*'])
{
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'sync')) {
throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method sync');
}
$old = $this->{$relationName}()->get($columns);
$changes = $this->{$relationName}()->sync($ids, $detaching);
if (collect($changes)->flatten()->isEmpty()) {
$old = $new = collect([]);
} else {
$new = $this->{$relationName}()->get($columns);
}
$this->dispatchRelationAuditEvent($relationName, 'sync', $old, $new);
return $changes;
}
/**
* @param string $relationName
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @param array $columns
* @return array
* @throws AuditingException
*/
public function auditSyncWithoutDetaching(string $relationName, $ids, $columns = ['*'])
{
if (!method_exists($this, $relationName) || !method_exists($this->{$relationName}(), 'syncWithoutDetaching')) {
throw new AuditingException('Relationship ' . $relationName . ' was not found or does not support method syncWithoutDetaching');
}
return $this->auditSync($relationName, $ids, false, $columns);
}
/**
* @param string $relationName
* @param string $event
* @param \Illuminate\Support\Collection $old
* @param \Illuminate\Support\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->isCustomEvent = false;
}
}
@@ -0,0 +1,133 @@
<?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)) {
return Auditor::execute($model);
}
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;
}
}
@@ -0,0 +1,38 @@
<?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();
}
}
@@ -0,0 +1,67 @@
<?php
namespace OwenIt\Auditing;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use OwenIt\Auditing\Console\AuditDriverCommand;
use OwenIt\Auditing\Console\AuditResolverCommand;
use OwenIt\Auditing\Console\InstallCommand;
use OwenIt\Auditing\Contracts\Auditor;
class AuditingServiceProvider extends ServiceProvider
{
/**
* Bootstrap the service provider.
*
* @return void
*/
public function boot()
{
$this->registerPublishing();
$this->mergeConfigFrom(__DIR__ . '/../config/audit.php', 'audit');
}
/**
* 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);
});
$this->app->register(AuditingEventServiceProvider::class);
}
/**
* 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');
}
}
}
}
@@ -0,0 +1,120 @@
<?php
namespace OwenIt\Auditing;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Manager;
use InvalidArgumentException;
use OwenIt\Auditing\Contracts\Auditable;
use OwenIt\Auditing\Contracts\AuditDriver;
use OwenIt\Auditing\Drivers\Database;
use OwenIt\Auditing\Events\Audited;
use OwenIt\Auditing\Events\Auditing;
use OwenIt\Auditing\Exceptions\AuditingException;
class Auditor extends Manager implements Contracts\Auditor
{
/**
* {@inheritdoc}
*/
public function getDefaultDriver()
{
return 'database';
}
/**
* {@inheritdoc}
*/
protected function createDriver($driver)
{
try {
return parent::createDriver($driver);
} catch (InvalidArgumentException $exception) {
if (class_exists($driver)) {
return $this->container->make($driver);
}
throw $exception;
}
}
/**
* {@inheritdoc}
*/
public function auditDriver(Auditable $model): AuditDriver
{
$driver = $this->driver($model->getAuditDriver());
if (!$driver instanceof AuditDriver) {
throw new AuditingException('The driver must implement the AuditDriver contract');
}
return $driver;
}
/**
* {@inheritdoc}
*/
public function execute(Auditable $model): void
{
if (!$model->readyForAuditing()) {
return;
}
$driver = $this->auditDriver($model);
if (!$this->fireAuditingEvent($model, $driver)) {
return;
}
// Check if we want to avoid storing empty values
$allowEmpty = Config::get('audit.empty_values');
$explicitAllowEmpty = in_array($model->getAuditEvent(), Config::get('audit.allowed_empty_values', []));
if (!$allowEmpty && !$explicitAllowEmpty) {
if (
empty($model->toAudit()['new_values']) &&
empty($model->toAudit()['old_values'])
) {
return;
}
}
$audit = $driver->audit($model);
if (!$audit) {
return;
}
$driver->prune($model);
$this->container->make('events')->dispatch(
new Audited($model, $driver, $audit)
);
}
/**
* Create an instance of the Database audit driver.
*
* @return \OwenIt\Auditing\Drivers\Database
*/
protected function createDatabaseDriver(): Database
{
return $this->container->make(Database::class);
}
/**
* Fire the Auditing event.
*
* @param \OwenIt\Auditing\Contracts\Auditable $model
* @param \OwenIt\Auditing\Contracts\AuditDriver $driver
*
* @return bool
*/
protected function fireAuditingEvent(Auditable $model, AuditDriver $driver): bool
{
return $this
->container
->make('events')
->until(new Auditing($model, $driver)) !== false;
}
}
@@ -0,0 +1,39 @@
<?php
namespace OwenIt\Auditing\Console;
use Illuminate\Console\GeneratorCommand;
class AuditDriverCommand extends GeneratorCommand
{
/**
* {@inheritdoc}
*/
protected $name = 'auditing:audit-driver';
/**
* {@inheritdoc}
*/
protected $description = 'Create a new audit driver';
/**
* {@inheritdoc}
*/
protected $type = 'AuditDriver';
/**
* {@inheritdoc}
*/
protected function getStub()
{
return __DIR__ . '/../../stubs/driver.stub';
}
/**
* {@inheritdoc}
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace . '\AuditDrivers';
}
}
@@ -0,0 +1,45 @@
<?php
namespace OwenIt\Auditing\Console;
use Illuminate\Console\GeneratorCommand;
class AuditResolverCommand extends GeneratorCommand
{
/**
* {@inheritdoc}
*/
protected $name = 'auditing:audit-resolver';
/**
* {@inheritdoc}
*/
protected $description = 'Create a new audit resolver';
/**
* {@inheritdoc}
*/
protected $type = 'AuditResolver';
/**
* {@inheritdoc}
*/
protected function getStub()
{
return __DIR__ . '/../../stubs/resolver.stub';
}
/**
* {@inheritdoc}
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace . '\AuditResolvers';
}
public function handle()
{
$this->info('Add your new resolver to the resolvers array in audit.php config file.');
return parent::handle();
}
}
@@ -0,0 +1,57 @@
<?php
namespace OwenIt\Auditing\Console;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
class InstallCommand extends Command
{
/**
* {@inheritdoc}
*/
protected $signature = 'auditing:install';
/**
* {@inheritdoc}
*/
protected $description = 'Install all of the Auditing resources';
/**
* {@inheritdoc}
*/
public function handle()
{
$this->comment('Publishing Auditing Configuration...');
$this->callSilent('vendor:publish', ['--tag' => 'config']);
$this->comment('Publishing Auditing Migrations...');
$this->callSilent('vendor:publish', ['--tag' => 'migrations']);
$this->registerAuditingServiceProvider();
$this->info('Auditing installed successfully.');
}
/**
* Register the Auditing service provider in the application configuration file.
*
* @return void
*/
protected function registerAuditingServiceProvider()
{
$namespace = Str::replaceLast('\\', '', app()->getNamespace());
$appConfig = file_get_contents(config_path('app.php'));
if (Str::contains($appConfig, 'OwenIt\\Auditing\\AuditingServiceProvider::class')) {
return;
}
file_put_contents(config_path('app.php'), str_replace(
"{$namespace}\\Providers\EventServiceProvider::class," . PHP_EOL,
"{$namespace}\\Providers\EventServiceProvider::class," . PHP_EOL . " OwenIt\Auditing\AuditingServiceProvider::class," . PHP_EOL,
$appConfig
));
}
}
@@ -0,0 +1,24 @@
<?php
namespace OwenIt\Auditing\Contracts;
interface AttributeEncoder extends AttributeModifier
{
/**
* Encode an attribute value.
*
* @param mixed $value
*
* @return mixed
*/
public static function encode($value);
/**
* Decode an attribute value.
*
* @param mixed $value
*
* @return mixed
*/
public static function decode($value);
}
@@ -0,0 +1,8 @@
<?php
namespace OwenIt\Auditing\Contracts;
interface AttributeModifier
{
//
}
@@ -0,0 +1,15 @@
<?php
namespace OwenIt\Auditing\Contracts;
interface AttributeRedactor extends AttributeModifier
{
/**
* Redact an attribute value.
*
* @param mixed $value
*
* @return string
*/
public static function redact($value): string;
}
@@ -0,0 +1,75 @@
<?php
namespace OwenIt\Auditing\Contracts;
/**
* @mixin \OwenIt\Auditing\Models\Audit
*/
interface Audit
{
/**
* Get the current connection name for the model.
*
* @return string|null
*/
public function getConnectionName();
/**
* Get the table associated with the model.
*
* @return string
*/
public function getTable();
/**
* Get the auditable model to which this Audit belongs.
*
* @return mixed
*/
public function auditable();
/**
* User responsible for the changes.
*
* @return mixed
*/
public function user();
/**
* Audit data resolver.
*
* @return array
*/
public function resolveData(): array;
/**
* Get an Audit data value.
*
* @param string $key
*
* @return mixed
*/
public function getDataValue(string $key);
/**
* Get the Audit metadata.
*
* @param bool $json
* @param int $options
* @param int $depth
*
* @return array|string
*/
public function getMetadata(bool $json = false, int $options = 0, int $depth = 512);
/**
* Get the Auditable modified attributes.
*
* @param bool $json
* @param int $options
* @param int $depth
*
* @return array|string
*/
public function getModified(bool $json = false, int $options = 0, int $depth = 512);
}
@@ -0,0 +1,24 @@
<?php
namespace OwenIt\Auditing\Contracts;
interface AuditDriver
{
/**
* Perform an audit.
*
* @param \OwenIt\Auditing\Contracts\Auditable $model
*
* @return \OwenIt\Auditing\Contracts\Audit
*/
public function audit(Auditable $model): ?Audit;
/**
* Remove older audits that go over the threshold.
*
* @param \OwenIt\Auditing\Contracts\Auditable $model
*
* @return bool
*/
public function prune(Auditable $model): bool;
}
@@ -0,0 +1,131 @@
<?php
namespace OwenIt\Auditing\Contracts;
use Illuminate\Database\Eloquent\Relations\MorphMany;
interface Auditable
{
/**
* Auditable Model audits.
*
* @return MorphMany<\OwenIt\Auditing\Models\Audit>
*/
public function audits(): MorphMany;
/**
* Set the Audit event.
*
* @param string $event
*
* @return Auditable
*/
public function setAuditEvent(string $event): Auditable;
/**
* Get the Audit event that is set.
*
* @return string|null
*/
public function getAuditEvent();
/**
* Get the events that trigger an Audit.
*
* @return array
*/
public function getAuditEvents(): array;
/**
* Is the model ready for auditing?
*
* @return bool
*/
public function readyForAuditing(): bool;
/**
* Return data for an Audit.
*
* @throws \OwenIt\Auditing\Exceptions\AuditingException
*
* @return array
*/
public function toAudit(): array;
/**
* Get the (Auditable) attributes included in audit.
*
* @return array
*/
public function getAuditInclude(): array;
/**
* Get the (Auditable) attributes excluded from audit.
*
* @return array
*/
public function getAuditExclude(): array;
/**
* Get the strict audit status.
*
* @return bool
*/
public function getAuditStrict(): bool;
/**
* Get the audit (Auditable) timestamps status.
*
* @return bool
*/
public function getAuditTimestamps(): bool;
/**
* Get the Audit Driver.
*
* @return string|null
*/
public function getAuditDriver();
/**
* Get the Audit threshold.
*
* @return int
*/
public function getAuditThreshold(): int;
/**
* Get the Attribute modifiers.
*
* @return array
*/
public function getAttributeModifiers(): array;
/**
* Transform the data before performing an audit.
*
* @param array $data
*
* @return array
*/
public function transformAudit(array $data): array;
/**
* Generate an array with the model tags.
*
* @return array
*/
public function generateTags(): array;
/**
* Transition to another model state from an Audit.
*
* @param Audit $audit
* @param bool $old
*
* @throws \OwenIt\Auditing\Exceptions\AuditableTransitionException
*
* @return Auditable
*/
public function transitionTo(Audit $audit, bool $old = false): Auditable;
}
@@ -0,0 +1,24 @@
<?php
namespace OwenIt\Auditing\Contracts;
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);
}
@@ -0,0 +1,17 @@
<?php
namespace OwenIt\Auditing\Contracts;
/**
* @deprecated
* @see Resolver
*/
interface IpAddressResolver
{
/**
* Resolve the IP Address.
*
* @return string
*/
public static function resolve(): string;
}
@@ -0,0 +1,8 @@
<?php
namespace OwenIt\Auditing\Contracts;
interface Resolver
{
public static function resolve(Auditable $auditable);
}
@@ -0,0 +1,17 @@
<?php
namespace OwenIt\Auditing\Contracts;
/**
* @deprecated
* @see Resolver
*/
interface UrlResolver
{
/**
* Resolve the URL.
*
* @return string
*/
public static function resolve(): string;
}
@@ -0,0 +1,17 @@
<?php
namespace OwenIt\Auditing\Contracts;
/**
* @deprecated
* @see Resolver
*/
interface UserAgentResolver
{
/**
* Resolve the User Agent.
*
* @return string|null
*/
public static function resolve();
}
@@ -0,0 +1,13 @@
<?php
namespace OwenIt\Auditing\Contracts;
interface UserResolver
{
/**
* Resolve the User.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public static function resolve();
}
@@ -0,0 +1,43 @@
<?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
{
$implementation = Config::get('audit.implementation', \OwenIt\Auditing\Models\Audit::class);
return call_user_func([$implementation, 'create'], $model->toAudit());
}
/**
* {@inheritdoc}
*/
public function prune(Auditable $model): bool
{
if (($threshold = $model->getAuditThreshold()) > 0) {
$forRemoval = $model->audits()
->latest()
->get()
->slice($threshold)
->pluck('id');
if (!$forRemoval->isEmpty()) {
return $model->audits()
->whereIn('id', $forRemoval)
->delete() > 0;
}
}
return false;
}
}
@@ -0,0 +1,22 @@
<?php
namespace OwenIt\Auditing\Encoders;
class Base64Encoder implements \OwenIt\Auditing\Contracts\AttributeEncoder
{
/**
* {@inheritdoc}
*/
public static function encode($value)
{
return base64_encode($value);
}
/**
* {@inheritdoc}
*/
public static function decode($value)
{
return base64_decode($value);
}
}
@@ -0,0 +1,25 @@
<?php
namespace OwenIt\Auditing\Events;
use OwenIt\Auditing\Contracts\Auditable;
class AuditCustom
{
/**
* The Auditable model.
*
* @var \OwenIt\Auditing\Contracts\Auditable
*/
public $model;
/**
* Create a new Auditing event instance.
*
* @param \OwenIt\Auditing\Contracts\Auditable $model
*/
public function __construct(Auditable $model)
{
$this->model = $model;
}
}
@@ -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 $audit
*/
public function __construct(Auditable $model, AuditDriver $driver, Audit $audit = null)
{
$this->model = $model;
$this->driver = $driver;
$this->audit = $audit;
}
}
@@ -0,0 +1,35 @@
<?php
namespace OwenIt\Auditing\Events;
use OwenIt\Auditing\Contracts\Auditable;
use OwenIt\Auditing\Contracts\AuditDriver;
class Auditing
{
/**
* The Auditable model.
*
* @var \OwenIt\Auditing\Contracts\Auditable
*/
public $model;
/**
* Audit driver.
*
* @var \OwenIt\Auditing\Contracts\AuditDriver
*/
public $driver;
/**
* Create a new Auditing event instance.
*
* @param \OwenIt\Auditing\Contracts\Auditable $model
* @param \OwenIt\Auditing\Contracts\AuditDriver $driver
*/
public function __construct(Auditable $model, AuditDriver $driver)
{
$this->model = $model;
$this->driver = $driver;
}
}
@@ -0,0 +1,25 @@
<?php
namespace OwenIt\Auditing\Events;
use OwenIt\Auditing\Contracts\Auditable;
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;
}
}
@@ -0,0 +1,25 @@
<?php
namespace OwenIt\Auditing\Events;
use OwenIt\Auditing\Contracts\Auditable;
class DispatchingAudit
{
/**
* The Auditable model.
*
* @var Auditable
*/
public $model;
/**
* Create a new DispatchingAudit event instance.
*
* @param Auditable $model
*/
public function __construct(Auditable $model)
{
$this->model = $model;
}
}
@@ -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,9 @@
<?php
namespace OwenIt\Auditing\Exceptions;
use Exception;
class AuditingException extends Exception
{
}
@@ -0,0 +1,20 @@
<?php
namespace OwenIt\Auditing\Facades;
use Illuminate\Support\Facades\Facade;
/**
* @method static \OwenIt\Auditing\Contracts\AuditDriver auditDriver(\OwenIt\Auditing\Contracts\Auditable $model);
* @method static void execute(\OwenIt\Auditing\Contracts\Auditable $model);
*/
class Auditor extends Facade
{
/**
* {@inheritdoc}
*/
protected static function getFacadeAccessor()
{
return \OwenIt\Auditing\Contracts\Auditor::class;
}
}
@@ -0,0 +1,31 @@
<?php
namespace OwenIt\Auditing\Listeners;
use OwenIt\Auditing\Facades\Auditor;
use Illuminate\Support\Facades\Config;
use OwenIt\Auditing\Events\DispatchAudit;
use Illuminate\Contracts\Queue\ShouldQueue;
class ProcessDispatchAudit implements ShouldQueue
{
public function viaConnection(): string
{
return Config::get('audit.queue.connection', 'sync');
}
public function viaQueue(): string
{
return Config::get('audit.queue.queue', 'default');
}
public function withDelay(DispatchAudit $event): int
{
return Config::get('audit.queue.delay', 0);
}
public function handle(DispatchAudit $event)
{
Auditor::execute($event->model);
}
}
@@ -0,0 +1,13 @@
<?php
namespace OwenIt\Auditing\Listeners;
use OwenIt\Auditing\Facades\Auditor;
class RecordCustomAudit
{
public function handle(\OwenIt\Auditing\Contracts\Auditable $model)
{
Auditor::execute($model);
}
}
@@ -0,0 +1,37 @@
<?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 = [];
/**
* {@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);
}
}
@@ -0,0 +1,20 @@
<?php
namespace OwenIt\Auditing\Redactors;
class LeftRedactor implements \OwenIt\Auditing\Contracts\AttributeRedactor
{
/**
* {@inheritdoc}
*/
public static function redact($value): string
{
$total = strlen($value);
$tenth = ceil($total / 10);
// Make sure single character strings get redacted
$length = ($total > $tenth) ? ($total - $tenth) : 1;
return str_pad(substr($value, $length), $total, '#', STR_PAD_LEFT);
}
}
@@ -0,0 +1,20 @@
<?php
namespace OwenIt\Auditing\Redactors;
class RightRedactor implements \OwenIt\Auditing\Contracts\AttributeRedactor
{
/**
* {@inheritdoc}
*/
public static function redact($value): string
{
$total = strlen($value);
$tenth = ceil($total / 10);
// Make sure single character strings get redacted
$length = ($total > $tenth) ? ($total - $tenth) : 1;
return str_pad(substr($value, 0, -$length), $total, '#', STR_PAD_RIGHT);
}
}
@@ -0,0 +1,14 @@
<?php
namespace OwenIt\Auditing\Resolvers;
use OwenIt\Auditing\Contracts\Auditable;
use OwenIt\Auditing\Contracts\Resolver;
class DumpResolver implements Resolver
{
public static function resolve(Auditable $auditable): string
{
return '';
}
}
@@ -0,0 +1,15 @@
<?php
namespace OwenIt\Auditing\Resolvers;
use Illuminate\Support\Facades\Request;
use OwenIt\Auditing\Contracts\Auditable;
use OwenIt\Auditing\Contracts\Resolver;
class IpAddressResolver implements Resolver
{
public static function resolve(Auditable $auditable): string
{
return $auditable->preloadedResolverData['ip_address'] ?? Request::ip();
}
}
@@ -0,0 +1,36 @@
<?php
namespace OwenIt\Auditing\Resolvers;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Request;
use OwenIt\Auditing\Contracts\Auditable;
class UrlResolver implements \OwenIt\Auditing\Contracts\Resolver
{
/**
* @return string
*/
public static function resolve(Auditable $auditable): string
{
if (! empty($auditable->preloadedResolverData['url'] ?? null)) {
return $auditable->preloadedResolverData['url'];
}
if (App::runningInConsole()) {
return self::resolveCommandLine();
}
return Request::fullUrl();
}
public static function resolveCommandLine(): string
{
$command = Request::server('argv', null);
if (is_array($command)) {
return implode(' ', $command);
}
return 'console';
}
}
@@ -0,0 +1,15 @@
<?php
namespace OwenIt\Auditing\Resolvers;
use Illuminate\Support\Facades\Request;
use OwenIt\Auditing\Contracts\Auditable;
use OwenIt\Auditing\Contracts\Resolver;
class UserAgentResolver implements Resolver
{
public static function resolve(Auditable $auditable)
{
return $auditable->preloadedResolverData['user_agent'] ?? Request::header('User-Agent');
}
}
@@ -0,0 +1,34 @@
<?php
namespace OwenIt\Auditing\Resolvers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Config;
use OwenIt\Auditing\Contracts\Auditable;
class UserResolver implements \OwenIt\Auditing\Contracts\UserResolver
{
/**
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public static function resolve()
{
$guards = Config::get('audit.user.guards', [
\config('auth.defaults.guard')
]);
foreach ($guards as $guard) {
try {
$authenticated = Auth::guard($guard)->check();
} catch (\Exception $exception) {
continue;
}
if (true === $authenticated) {
return Auth::guard($guard)->user();
}
}
return null;
}
}