gamify-1.1.x-dev/src/Plugin/Action/LogsSeriesHighFrequency.php
src/Plugin/Action/LogsSeriesHighFrequency.php
<?php
namespace Drupal\gamify\Plugin\Action;
use Drupal\Core\Form\FormStateInterface;
use Drupal\eca\Plugin\Action\ConfigurableActionBase;
use Drupal\gamify\Traits\DbLogResultTrait;
/**
* Provide all tamper plugins as ECA actions.
*
* @Action(
* id = "gamify_logs_series_high_frequency",
* label = @Translation("Gamify: Detect in logs high frequent repetition serieses."),
* description = @Translation("Find user behahiour of high frequency repetition of same action, what might indicate a destructive behaviour.")
* )
*/
class LogsSeriesHighFrequency extends ConfigurableActionBase {
use DbLogResultTrait;
/**
* {@inheritdoc}
*
* @throws \Drupal\Component\Plugin\Exception\PluginException | \Drupal\Core\TypedData\Exception\MissingDataException
*/
public function execute(): void {
$dataKey = $this->configuration['eca_data'] ?? NULL;
$entries = $dataKey ? $this->tokenServices->getTokenData($dataKey)->toArray() : NULL;
$interim_result = [];
if ($entries) {
// If prev_result is multiple array, we combine it to a single result stack.
if ($this->getNestingLevel($entries) === 3) {
$combined = [];
foreach ($entries as $entry) {
$combined = array_merge($combined, $entry);
}
$entries = $entries[array_key_first($entries)];
}
$min_series_length = (int) $this->configuration['min_series_length'];
$time_interval = (int) $this->configuration['time_interval'];
if (count($entries) >= $min_series_length) {
$interim_result = $this->seriesSearch($entries, $time_interval, $min_series_length);
}
}
$this->tokenServices->addTokenData($this->configuration['eca_token_name'], $interim_result);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration(): array {
return [
'eca_data' => '',
'time_interval' => 10,
'min_series_length' => 8,
'eca_token_name' => '',
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {
$form['eca_data'] = [
'#type' => 'textfield',
'#title' => $this->t('Log entries to be filtered'),
'#description' => $this->t('This field supports tokens.'),
'#default_value' => $this->configuration['eca_data'],
'#required' => TRUE,
'#weight' => 10,
];
$form['time_interval'] = [
'#type' => 'number',
'#title' => $this->t('Time interval (av.) between events'),
'#description' => $this->t('Filter logs by RegEx or search string.'),
'#default_value' => $this->configuration['time_interval'],
'#required' => TRUE,
'#weight' => 20,
];
$form['min_series_length'] = [
'#type' => 'number',
'#title' => $this->t('Min Series length'),
'#description' => $this->t('Tolerance concerning the min length of items in a series.'),
'#default_value' => $this->configuration['min_series_length'],
'#weight' => 30,
];
$form['eca_token_name'] = [
'#type' => 'textfield',
'#title' => $this->t('Result token name'),
'#description' => $this->t('Provide a token name under which the tampered result will be made available for subsequent actions.'),
'#default_value' => $this->configuration['eca_token_name'],
'#required' => TRUE,
'#weight' => 40,
];
return parent::buildConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state): void {
$this->configuration['eca_data'] = $form_state->getValue('eca_data');
$this->configuration['time_interval'] = $form_state->getValue('time_interval');
$this->configuration['min_series_length'] = $form_state->getValue('min_series_length');
$this->configuration['eca_token_name'] = $form_state->getValue('eca_token_name');
parent::submitConfigurationForm($form, $form_state);
}
/**
* Search for continued series in given log entries by time interval.
*
* @param array $entries
* Entries to search.
* @param int $range
* Max duration between 2 log entries. If time diff is greater than $range
* a new series opens.
* @param int $min_length
* Minimum number of entries in a serial.
*
* @return array
* An array of all series found.
*/
public function seriesSearch(array $entries, int $time_span = 60, int $min_length = 3): array {
$series = [];
$collector = [];
$serial_begin = 0;
foreach ($entries as $wid => $entry) {
$current_timestamp = (int) $entry['timestamp'];
// First entry.
if (!count($collector)) {
$collector[$wid] = $entry;
$serial_begin = $current_timestamp;
continue;
}
// Follow-up entry.
$serial_duration = $current_timestamp - $serial_begin;
$divisor = count($collector) - 1;
if (($divisor == 0) || ($serial_duration / $divisor) <= $time_span) {
$collector[$wid] = $entry;
}
else {
// Close current collection and create a serial.
if (count($collector) >= $min_length) {
$series[] = $collector;
}
// Restart with new serial.
$collector = [$wid => $entry];
$serial_begin = $current_timestamp;
}
}
if (count($collector) >= $min_length) {
$series[] = $collector;
}
return $series;
}
}
