Developers

This plugin has been developed with the main focus of being easy to extend, you can define your own recorders, log types, field handlers and record any activity you like.

Recorders

Recorders are initialised when all plugins are registered, their roles is to initialise event listeners and commit them. At the end of the request the committed logs will be saved. In some situations you may need to save logs instantly.

You'll find a list of recorders defined by the system in the class Ryssbowh\Activity\events\RegisterRecordersEvent.

Define a new recorder :

use Ryssbowh\Activity\base\recorders\Recorder;
use yii\base\Event;
use Ryssbowh\Activity\Activity;

class MyRecorder extends Recorder
{
    public function init()
    {
        Event::on(SomeClass::class, SomeClass::EVENT_SOME_EVENT, function (Event $event) {
            Activity::getRecorder('my-recorder')->somethingChanged($event);
        });
    }

    public function somethingChanged(Event $event)
    {
        $logType = 'somethingCreated'; //This is the log handle
        $params = [
            'myObject' => $event->object
        ];
        //Always check if that log should be saved, it may be ignored in the config
        if ($this->shouldSaveLog($logType)) {
            $this->commitLog($logType, $params);
        }
    }
}

And register it :

use Ryssbowh\Activity\services\Recorders;

Event::on(Recorders::class, Recorders::EVENT_REGISTER, function (Event $event) {
    $event->add('my-recorder', new MyRecorder);
})

You'll then be able to get your recorder instance at any point in the application with Activity::getRecorder('my-recorder')

In some cases, several events will happen at the same time and you need to control whether your recorder (or others) record or not. Several ways to do that :

You can start/stop the recording of any recorder with Activity::getRecorder('my-recorder')->stopRecording() at any point.

You can also empty a recorder log queue (which will only be saved at the end of the request) : Activity::getRecorder('my-recorder')->emptyQueue()

Log types

Define a new log type :

use Ryssbowh\Activity\base\logs\ActivityLog;

class SomethingCreated extends ActivityLog
{
    /**
     * This is optional, but shows you how to set data on your log
     *
     * @param mixed $object
     */
    public function setMyObject($object)
    {
        $this->target_uid = $object->uid;
        $this->target_name = $object->name;
        $this->target_class = get_class($object);
        $this->data = [
            'my-field' => $object->field
        ];
    }
}

The handle will be automatically determined from the class name, change it by overriding the getHandle() method. In this example it will be somethingCreated.

The name will also be automatically determined from the class name, change it by overriding the getName() method. In this example it will be Something created.

The title will also be automatically determined from the class name, change it by overriding the getTitle() method. In this example it will be Created something.

Register it :

use Ryssbowh\Activity\services\Types;

Event::on(Types::class, Types::EVENT_REGISTER, function (Event $event) {
    $event->add(new SomethingCreated);
})

Field handlers

A field handler can change how changes are calculated on a field, and how the value is saved in database, they also can save "fancy" values to be displayed to the user.

The system typically defines handlers for things like arrays, where the label of the value changed also needs to be saved, to display a more user friendly value to the user.

Field handlers can completely override how the changes made to a field are saved, a good example would be entry types field layouts, where calculating changes can be a bit complex.

Each field handler has a target which defines which entity it can handle.

Project Config fields

For config fields, the target is defined by its Yaml path, example if you wanted to add a handler for the General System Status setting, the path would be system.live.

Define a new handler :

use Ryssbowh\Activity\base\fieldHandlers\FieldHandler;

class MyHandler extends FieldHandler
{
    /**
     * @inheritDoc
     */
    public function init()
    {
        $this->fancyValue = $this->value ? 'Live' : 'Offline';
    }

    /**
     * @inheritDoc
     */
    public function hasFancyValue(): bool
    {
        return true;
    }

    /**
     * @inheritDoc
     */
    public static function getTargets(): array
    {
        return [
            'system.live'
        ];
    }
}

Register it:

use Ryssbowh\Activity\services\FieldHandlers;

Event::on(FieldHandlers::class, FieldHandlers::EVENT_REGISTER_PROJECTCONFIG_HANDLERS, function (Event $e) {
    $e->add(MyHandler::class);
});

That's enough to trigger your field handler when the path system.live is changed. This will cause the activity log to save a fancy value ('Live' or 'Offline') in the database alongside the original value (true or false). That fancy value will be displayed to the user instead.

You can also change the description of the field being changed that will be displayed to the user by overriding the getTemplate(): ?string method.

Reuse :

You can reuse an existing field handler for a path by adding your path to its targets through an event :

Event::on(LongText::class, LongText::EVENT_REGISTER_TARGETS, function (Event $event) {
    $event->targets[] = 'spicyweb\\tinymce\\fields\\TinyMCE';
});

Fields

Fields are a special case since they have settings specific to each with their own labels and potentially their own typings. The class Ryssbowh\Activity\services\Fields handle this through 3 events :

Add/modify tracked fields :

Event::on(Fields::class, Fields::EVENT_REGISTER_TRACKED_FIELDS, function (Event $event) {
    $event->tracked['ether\\seo\\fields\\SeoField'] = ['settings.description', 'settings.hideSocial', 'settings.robots', 'settings.socialImage', 'settings.title'];
});

Add/modify labels :

Event::on(Fields::class, Fields::EVENT_REGISTER_FIELD_LABELS, function (Event $event) {
    $event->labels['ether\\seo\\fields\\SeoField'] = [
        'settings.description' => \Craft::t('app', 'Description'),
        'settings.hideSocial' => \Craft::t('activity', 'Hide Social Meta Tab'),
        'settings.robots' => \Craft::t('app', 'Robots'),
        'settings.socialImage' => \Craft::t('app', 'Social Image'),
        'settings.title' => \Craft::t('app', 'Page Title'),
    ];
});

Add/modify typings :

Event::on(Fields::class, Fields::EVENT_REGISTER_FIELD_TYPINGS, function (Event $event) {
    $event->typings['ether\\seo\\fields\\SeoField'] = [
        'settings.hideSocial' => 'bool'
    ];
});

Setting handlers paths :

Field settings handlers path is a special case, it will contain their class, example to handle the setting sources of the field Assets the path would be field.{uid}.settings[craft\fields\Assets].sources

Element fields

Same idea than project config, but here the targets are field classes. Example if you wanted to change how Matrix fields changes are calculated you would do something like this :

Define a new handler :

use Ryssbowh\Activity\base\fieldHandlers\ElementFieldHandler;
use craft\fields\Matrix;

class MyHandler extends ElementFieldHandler
{
    /**
     * @inheritDoc
     */
    public function init()
    {
        $this->fancyValue = $this->calculateFancyValue();
    }

    /**
     * @inheritDoc
     */
    public function hasFancyValue(): bool
    {
        return true;
    }

    /**
     * @inheritDoc
     */
    public static function getTargets(): array
    {
        return [
            Matrix::class
        ];
    }

    protected function calculateFancyValue()
    {

    }

}

Register it

use Ryssbowh\Activity\services\FieldHandlers;

Event::on(FieldHandlers::class, FieldHandlers::EVENT_REGISTER_ELEMENT_HANDLERS, function (Event $e) {
    $e->add(MyHandler::class);
});

In this example an exception would be thrown as Matrix fields already have a handler defined by this plugin, but you can replace it by doing $e->add(MyHandler::class, true); instead.

Templating

Each field handler can override (and should override) the template (static method getTemplate()) used to display the changes to the user on the dashboard.

This template will receive two variables :

field : A Ryssbowh\Activity\models\ChangedField instance where the calculated changes are stored

log : A log model

Exporters

You can define new exporters and register them on the service :

Define a new exporter :

<?php

use Ryssbowh\Activity\base\Exporter;

class MyExporter extends Exporter
{
    /**
     * @inheritDoc
     */
    public function getHandle(): string
    {
        return 'handle';
    }

    /**
     * @inheritDoc
     */
    public function getLabel(): string
    {
        return \Craft::t('activity', 'Export as my format');
    }

    /**
     * @inheritDoc
     */
    public function getExportContent(array $logs): string
    {
        $content = '';
        foreach ($logs as $log) {
            $content .= $log->title;
        }
        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getMimeType(): string
    {
        return 'mime/type';
    }

    /**
     * @inheritDoc
     */
    public function getExtension(): string
    {
        return 'extension';
    }
}

Register it :

<?php

use Ryssbowh\Activity\services\Exporters;
use yii\base\Event;

Event::on(Exporters::class, Exporters::EVENT_REGISTER, function (Event $event) {
    $event->add(new MyExporter);
});

The exporter will then be available in the Control Panel and console command.

You must enable javascript to view this website