Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't log Translatable fields. #647

Closed
khaledBawz opened this issue Jan 5, 2020 · 11 comments
Closed

Can't log Translatable fields. #647

khaledBawz opened this issue Jan 5, 2020 · 11 comments
Assignees
Labels

Comments

@khaledBawz
Copy link

khaledBawz commented Jan 5, 2020

Hi,

I am trying to log only changed attributes by using protected static $logOnlyDirty = true; in my model, but this is not working if I'm using translatable attributes package by Spatie. it logs only English attributes and I think that's because my app locale is English.

I am using Laravel Framework 6.9.0 and spatie/activity-log ^3.9

Thank you.

@Gummibeer
Copy link
Collaborator

Hey,

thanks for reaching out. Right now there isn't any special implementation to work with the translatable package. And yes, you are right that it logs english because of your app locale. This package only logs the attribute like $model->attribute. And for a lot of other scenarios it's wanted to use the accessors.
To fix this there should be another trait to handle the change detection/logging if the attribute is translatable. But this is something for another package.

@Gummibeer Gummibeer self-assigned this Jan 5, 2020
@khaledBawz
Copy link
Author

@Gummibeer Thanks for your comment. Could you please explain some more info for the code in the trait?

@Gummibeer
Copy link
Collaborator

I think that you will have to override.

public static function logChanges(Model $model): array
{
$changes = [];
$attributes = $model->attributesToBeLogged();
foreach ($attributes as $attribute) {
if (Str::contains($attribute, '.')) {
$changes += self::getRelatedModelAttributeValue($model, $attribute);
} elseif (Str::contains($attribute, '->')) {
Arr::set(
$changes,
str_replace('->', '.', $attribute),
static::getModelAttributeJsonValue($model, $attribute)
);
} else {
$changes[$attribute] = $model->getAttribute($attribute);
if (
in_array($attribute, $model->getDates())
&& ! is_null($changes[$attribute])
) {
$changes[$attribute] = $model->serializeDate(
$model->asDateTime($changes[$attribute])
);
}
}
}
return $changes;
}

Inside the loop you will have to check if the attribute is translatable.
https://github.com/spatie/laravel-translatable/blob/3a07be9e1e3cc9566c6d7274b613552d5cbf4b1a/src/HasTranslations.php#L141-L144

If so you will have to retrieve all translation data for this attribute.
https://github.com/spatie/laravel-translatable/blob/3a07be9e1e3cc9566c6d7274b613552d5cbf4b1a/src/HasTranslations.php#L63-L78

After this you will already be done (I think in theory).
Keep in mind that the logChanges() method is also called with the before updating original values.

static::updating(function (Model $model) {
//temporary hold the original attributes on the model
//as we'll need these in the updating event
$oldValues = (new static)->setRawAttributes($model->getOriginal());
$model->oldAttributes = static::logChanges($oldValues);
});

I haven't tested the logging with arrays - so it could be that you will have to adjust following if you only want to log dirty fields.

public function attributeValuesToBeLogged(string $processingEvent): array
{
if (! count($this->attributesToBeLogged())) {
return [];
}
$properties['attributes'] = static::logChanges(
$this->exists
? $this->fresh() ?? $this
: $this
);
if (static::eventsToBeRecorded()->contains('updated') && $processingEvent == 'updated') {
$nullProperties = array_fill_keys(array_keys($properties['attributes']), null);
$properties['old'] = array_merge($nullProperties, $this->oldAttributes);
$this->oldAttributes = [];
}
if ($this->shouldLogOnlyDirty() && isset($properties['old'])) {
$properties['attributes'] = array_udiff_assoc(
$properties['attributes'],
$properties['old'],
function ($new, $old) {
if ($old === null || $new === null) {
return $new === $old ? 0 : 1;
}
return $new <=> $old;
}
);
$properties['old'] = collect($properties['old'])
->only(array_keys($properties['attributes']))
->all();
}
return $properties;
}

If you need more help just ask. I'm happy to help. Possibly we will keep a list or even maintain the code in this repo as some kind of other spatie compatibility traits.
cc @freekmurze

@romualdbrsn
Copy link

+1

1 similar comment
@DonDiegoAA
Copy link

+1

@Gummibeer
Copy link
Collaborator

@romualdbrisson & @DonDiegoAA could you please use the comment reactions or write full comments?
I've no idea what you want to upvote with your +1.
The only thing I see is that the combination of these two packages has some users which all seem unable to log the translated fields right now.
I would like to think about a solution/pattern that is extendable. For example something like the new Laravel custom casts.
But I still think about the responsibility of the class, the inputs/outputs and where we should hook it in.

Because this is a very specific issue that's based on a more general one I would like to solve the general one instead of implementing hundreds of custom solutions.

@DonDiegoAA
Copy link

DonDiegoAA commented Apr 13, 2020

Hello @Gummibeer ,

this +1 meant "I have this problem too and I'm interested in a solution".
Sorry, I realize it was the wrong way to do it.
I often use the spatie packages, and I take this opportunity to thank you for all this great work.
Since activitylog and translatable are two packages of spatie, I was hoping they would work well together, but I understand that it is not as easy as that.
For my part, when logOnlyDirty is true, if activitylog could record the entire json field if there has been a change, no matter the language, that would suit me very well. I will then take care in the activity history view to check which language has been changed.
But I also understand that this is not the ideal solution for you.
Thanks again

@indykoning
Copy link

In #801 i created a simple function you can override to implement your own way of collecting the data to report per attribute.
After this gets merged and released you could implement showing all translatable data by adding

    public function attributeDataToRecord($attribute)
    {
        if ($this->isTranslatableAttribute($attribute)) {
            return $this->getTranslations($attribute);
        }
    }

In your Model class, or in a trait that gets used in your models.

@ctf0
Copy link

ctf0 commented Feb 21, 2021

it seems tapActivity can stop the model event so based on that, we can hook into it and add the data we want ex.

  • create a new trait for translatables
  • include spatie trait
  • add the below
public function tapActivity(Activity $activity, string $eventName)
{
        if (
            $eventName != 'deleted' ||
            method_exists($this, 'isForceDeleting') && !$this->isForceDeleting()
        ) {
        $props   = $activity->properties;
        $props_o = $props['old']        ?? [];
        $props_a = $props['attributes'] ?? [];

        $trans = $this->getTranslatableAttributes();
        $keys  = $this->checkForTransKeys($props_o, $trans) ?: $this->checkForTransKeys($props_a, $trans);

        if ($keys) {
            $orig   = $this->getOriginal();
            $dirty  = $this->getDirty();
            $old    = [];
            $new    = [];

            foreach ($keys as $key) {
                $old += [$key => isset($orig[$key]) ? json_decode($orig[$key], true) : []];
                $new += [$key => isset($dirty[$key]) ? json_decode($dirty[$key], true) : []];
            }

            $activity->properties = [
                'old'        => array_merge($props_o, $old),
                'attributes' => array_merge($props_a, $new),
            ];
        }
    }

    return $activity;
}

protected function checkForTransKeys($arr, $trans)
{
    return array_intersect(array_keys($arr), $trans);
}

@ctf0
Copy link

ctf0 commented Feb 23, 2021

Update

if the changed key is not in the current locale, the tapActivity wont work, this is due to as @Gummibeer stated

it logs english because of your app locale. This package only logs the attribute like $model->attribute

so to get around this, $submitEmptyLogs have to enabled

@Gummibeer
Copy link
Collaborator

I will close this issue even if v4 isn't released yet. But the task itself is done and I want to check which tasks are really open. Please keep an eye on #787

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants