audittrail2

A reload of the popular audittrail extension
23 followers

Please use the Github project home for this extension: HERE since the download here is out of date. Github is the officially supported platform for me to dish out updates.

Update

I have pushed this repo to composer as well:

https://packagist.org/packages/sammaye/auditrail2

This is basically a reload of the extension by MadSkillsTisdale here audittrail

I decided to re-release this instead of wait for a confirmation from the author to merge my changes since the code base has changed so much only about one file remains relatively/exactly the same as it did before as well as taking numerous suggestions from advice posted on the previous extensions thread, namely this one

You can find/fork/whatever the project and its documentation on Github:

Installing the extension

The method of installation has changed. I have removed the need to install a module class since:

  • It only provided global configration variables for the audit log widget
  • It was extra bloat that didn't justify the needs
  • I found that in a real system you wouldn't want a page showing all audit log entries since the audit logs would grow to unmanagable sizes greater than SQL could truely handle in one page
  • The audit log is quite easy to add to a page using CGridView

As such for these reasons the module itself has been deleted.

Step 1

To install you must first choose a folder in which to place this repository. I have chosen:

/root/backend/extensions/modules

Since this seems most right to me. Download the zip file and unzip it to a folder called "audittrail" within that directory.

Step 2

Time to install the table. You can use the migration file provided by the original author of this extension or you can use the SQL file bundled within the migrations folder. Simply run it on your DB server (using PHPMyAdmin or something) and watch the magic unfold.

Step 3

Reference the AuditTrail model within your configuration:

'import'=>array(
        'site.backend.extensions.modules.auditTrail.models.AuditTrail',
    ),

Note You can move AuditTrail to your models folder preventing you from having to link it like this.

Step 4

Simply use the behaviour within a model like:

'LoggableBehavior'=> array(
        'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
    )

Epilogue

If your user class is not User then you may (depending on your setup) need to change the relation within the AuditLog model to suite your needs.

API

Custom User Attributes

Some people don't actually have defined users but do have an attribute of the auditable model that would define a unique identification of who edited it. For this end you can use:

'LoggableBehavior'=> array(
        'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
        'userAttribute' => 'name'
    )

Storing Timestamps

The date of the audit can be changed to used timestamps instead using:

'LoggableBehavior'=> array(
        'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
        'storeTimestamp' => true
    )

Changing the date format

You can adjust the date format using the dateFormat property of the behaviour:

'LoggableBehavior'=> array(
        'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
        'dateFormat' => 'Y-m-d H:i:s'
    )

Ignoring and allowing specific fields

There is one interesting addition to this version. You can now specify an allowed set of fields and a ignored set of fields...or both.

To do this include the behaviour in your models like you normally would:

'LoggableBehavior'=> 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior'

But then add either a ignored or allowed (or both) to the behaviour like so:

'LoggableBehavior'=> array(
        'class' => 'site.backend.extensions.modules.auditTrail.behaviors.LoggableBehavior',
        'allowed' => array(
            'version',
            'ns_purchase_description'
        ),
        'ignored' => array(
            'ns_purchase_description',
            'ns_display_name',
            'update_time'
        )
    )

The names put into the allowed and ignored parameters of the behaviour represent field names.

As you will notice I allow the ns_purchase_description field but also ignore it. When you use the fields in this way ignored will replace the allowed and this field will be omitted.

Printing out the audit log

Since this no longer uses a module to do its work there is no global configuration for the previously inbuilt audit log to work from. Instead you can insert an audit log onto a models page using (as an example only, showing an audit of changes to a book title and it's products on a book title page) the code below.

Edit

As Per the comments the old code that used to display here did not take model name into account so here is a better piece of code to do so (from a little help on Stackoverflow cos I got turned around by the CDbCriteria class):

<?php
$model_ids = array(array($model->id, 'Title'));
foreach($model->products as $id => $product){
    $model_ids[] = array($product->id, 'Product');
}
 
$criteria=new CDbCriteria(array(
    'order'=>'stamp DESC',
    'with'=>array('user'),
));
$param_id = 0;
foreach( $model_ids as $id_pair ) {
    $criteria->addCondition( '( model_id = :id' . $param_id . ' AND model = :model' . $param_id . ' )', 'OR' );
    $criteria->params[ ':id' . $param_id ] = $id_pair[0];
    $criteria->params[ ':model' . $param_id ] = $id_pair[1];
    $param_id++;
}
 
$this->widget('zii.widgets.grid.CGridView', array(
    'id'=>'title-grid',
    'dataProvider'=>new CActiveDataProvider('AuditTrail', array(
        'criteria'=>$criteria,
        'pagination'=>array(
            'pageSize'=>100,
        )
    )),
    'columns'=>array(
        array(
            'name' => 'Author',
            'value' => '$data->user ? $data->user->email : ""'
        ),
        'model',
        'model_id',
        'action',
        array(
            'name' => 'field',
            'value' => '$data->getParent()->getAttributeLabel($data->field)'
        ),
        'old_value',
        'new_value',
        array(
            'name' => 'Date Changed',
            'value' => 'date("d-m-Y H:i:s", strtotime($data->stamp))'
        )
    ),
)); ?>

Total 15 comments

#11960 report it
Sammaye at 2013/02/15 07:14am
RE: Old Value

I have opened a new toic here: http://www.yiiframework.com/forum/index.php/topic/40416-extension-audittrail-2/

You can also post an issue on github: https://github.com/Sammaye/audittrail/issues?state=open

#11959 report it
Paul_Kish at 2013/02/15 06:41am
RE RE: Old Value

Can you open a forum topic for this extension, or if one exist post the link?

#11958 report it
Sammaye at 2013/02/15 06:12am
RE: Old Value

Yeah, that is very weird.

I would try and see what getAttributes returns and if it returns fields that are not empty I would iterate through auditAttributes to see why it ditches them.

I can't see anything obvious from the classes.

#11957 report it
Paul_Kish at 2013/02/15 06:00am
Old Value

@Sammaye It seems to get the old value on some models but on others nothing is stored, let me investigate, here are dropbox link to the two different models,does not work on this one Example One Works on this one Example Two . I have also tried playing with

public $skipNulls = true;

nothing much, I am using the github version

#11954 report it
Sammaye at 2013/02/15 05:08am
RE: Old Value

Strange indeed.

Can you post a code snippet that can replicate this behaviour?

Maybe it is how it is being used in the model as a behaviour in the parent model.

Also can you just tell me if getAttributes() actually returns anything in this case?

It might not be recording old values because those values are either '' or null, these values I omit to save database space; that option can be turned off however.

#11953 report it
Paul_Kish at 2013/02/15 04:59am
Old Value

I cant seem to get Old value to be recorded, its seems this is tied to

$this->Owner->getAttributes()

Any pointers, thanks again, the allowed and ignored functionality is really awesome

#9764 report it
Sammaye at 2012/09/08 06:26pm
RE: create delete trail

I will look into that getIsNewRecord one though it does work on my system.

Ah I see, it is running the validation rules. Strange I don't see that behaviour in my system but you are right that is a bug. I have fixed it now :).

#9763 report it
onemoverx at 2012/09/08 06:15pm
create delete trail

you have this in your rules for model AuditTrail array('action, model, field, stamp, model_id', 'required'),

but when you create a trail for create (there another issue here I think) or delete only pass variable $action and save fails. another thing I changed is that you use afterSave to save the trail and you use isNewRecord, I'm not sure right now cause I can't check it right away but that will always be false, because the model is already saved so you have to check that flag in beforeSave and ask latter in the afterSave.

Hope it helps sorry if I'm wrong :D

#9762 report it
Sammaye at 2012/09/08 05:34pm
RE: some bugs

Fixed time bug.

Would be awesome if you can give an example of the Delete thing you said about :)

#9761 report it
Sammaye at 2012/09/08 05:29pm
RE: some bugs

Strange I use delete and what not and it works fine for me. It should place "Create" (or "Delete") and the field attribute, as for the model_id: that is indeed a required attribute. Is there a reason you are using this without activeRecord since all active record models should have an id (primary key).

I shall look into that time thing. I must admit I had not tested that functionality, however $time() is not a valid variable. Can you give me an example code which causes the bugable behaviour?

Edit: I see what you meant now by $time(), lol. I will change that asap.

#9760 report it
onemoverx at 2012/09/08 04:36pm
some bugs

There are some issues with audittrails2, for example is unable to record create and delete trails because model_id and field attributes in model AuditTrail are required so save fails every time. Another bug is found in LoggableBehavior in line 101, when using variable $time() instead of function time(), by default it would not give error because the default is to not save timestamp.

thanks for your extension, very usefull to me!

#9548 report it
Sammaye at 2012/08/22 08:05am
Further RE:

Yea it is quite bad example but the alternative is a really complex PDO statement with lots of ORs which is bad. I may proposed something like:

/**
     * This function is not in use yet, the field is not applied to the general schema so don't use it
     * @example serializedSearch(array(array(1, 'User'), array(1, 'Company'), array('{"d": 1, "e": 2}', 'Other')))
     */
    function serializedSearch($serializedIds = array()){
 
        $crtieria = new CDbCriteria(array(
            'order'=>'stamp DESC',
            'with'=>array('user'),
        ));
 
        $in = array();
        foreach($serializedIds as $k => $val){
            if(isset($val[0]) && isset($val[1]))
                $in[] = json_encode(array($val[0], $v[1]));
        }
        $criteria->addInCondition('serialized_id', $in);
 
        return new CActiveDataProvider('AuditTrail', array(
            'criteria'=>$criteria,
            'pagination'=>
                array(
                'pageSize'=>100,
                )
            )
        );
    }

Anyway let's see where it goes :).

#9546 report it
redguy at 2012/08/22 07:42am
re: RE: model reference

aa, ok. I did not check source codes, just slightly misunderstood 'printing out' snippet, because you are preparing there criteria with just model id's and without model names...

#9544 report it
Sammaye at 2012/08/22 06:10am
RE: model reference

Indeed normally you cannot differentiate the model however this too stores the model name.

So it stores:

model_id: 1
model: Company
model_id: 1
model: User

If you check the AuditTrail model and the function getParent() within that you will see this is how I get the parent model (The line for it here: https://github.com/Sammaye/audittrail/blob/master/behaviors/LoggableBehavior.php#L98).

The field name is a little confusing and could probably do with changing. I originally made this to work on the same schema as the previous extension.

The last snippet was just an example of getting all audit logs by a specific primary key. Probably a bad one since I too realised this key confliction and have since changed the code I show here to go on the "model" field as well. I might also allow extra descriptive fields as a future addition since I realised to get a group of audit logs for a specific context you might need to know more about the subobjects of a parent. However I am unsure how this will be done atm.

When the Primary is an array it should JSON encode the primary key (https://github.com/Sammaye/audittrail/blob/master/behaviors/LoggableBehavior.php#L122), I am unsure as to whether that is the right thing to do since I personally have never had an array based primary, in Yii yet.

Hope it helps,

#9543 report it
redguy at 2012/08/22 05:42am
model reference

I did notice, that you just store primary key of model in your audit trail (looking at $criteria in last snippet). It is rather wrong, because you cannoc assign this behavior to more than one model, or its keys will collide. To be more clear:

Lets say you have two models: User, Company. Both have attached your behavior. If I change User with id=1 AuditTrail record is created with model_id=1. When I change Company with id=1 - same thing (audittrail is created with model_id=1), but both have same foreign key reference and you cannot say whoch is for User trail and witch for Company...

I did write very similiar behavior for my project, but I store both class name of model and it's primary key. In fact there are also cases when primary key is an array (multiple column primary key) and in such case your behavior will fail as well...

Leave a comment

Please to leave your comment.

Create extension