CreaCaptcha

Analytics
in package

Records security events into per-day and per-hour aggregate counters and, when the detailed event log is enabled, into a dedicated database table.

See the module-6 and the analytics-dashboard design specs.

Table of Contents

Constants

COUNTER_RETENTION_DAYS  = 90
Aggregate daily-counter retention, in days.
HOURLY_RETENTION_HOURS  = 48
Aggregate hourly-counter retention, in hours.
OPTION  = 'creationell_captcha_analytics'
The option holding the aggregate daily counters.
OPTION_HOURLY  = 'creationell_captcha_analytics_hourly'
The option holding the aggregate hourly counters (24-hour view).
TYPES  = ['verified', 'failed', 'firewall', 'ratelimit', 'underattack', 'underattack_passed', 'challenge']
The seven recognised event types.

Properties

$daily_deltas  : array<string, array<string, int>>
Pending daily counter deltas: { bucket => { type => count } }. Flushed on `shutdown` so a request that triggers N events incurs at most one get_option/update_option round-trip per counter table, regardless of N.
$flush_hooked  : bool
Whether the shutdown flush has already been hooked for this request.
$hourly_deltas  : array<string, array<string, int>>
Pending hourly counter deltas: { bucket => { type => count } }.

Methods

clear_events()  : int
Deletes every row from the event-log table.
count_events()  : int
Counts the event-log rows matching the given filter.
ensure_table()  : void
Creates or updates the event-log table. Idempotent — safe to call repeatedly.
flush_pending_deltas()  : void
Flushes the accumulated daily and hourly counter deltas back to the options table. Reads the current value first and adds the deltas, so concurrent updates from parallel requests are not lost (the underlying get+update is still non-atomic, but only one round-trip per request narrows the race window considerably compared to one round-trip per event).
get_daily_counts()  : array<string, array<string, int>>
Returns the aggregate daily counters, keyed by date.
get_hourly_counts()  : array<string, array<string, int>>
Returns the aggregate hourly counters, keyed by 'Y-m-d H' (UTC).
prune_events()  : int
Deletes event-log rows older than the configured retention period.
query_events()  : array<int, array<string, string>>
Returns event-log rows matching the given filter, newest first.
record()  : void
Records one security event: bumps the daily and hourly counters and — when the detailed event log is enabled and the per-type toggle is on — writes a table row. Never fatals.
table_exists()  : bool
Whether the event-log table currently exists.
apply_deltas()  : void
Merges the given { bucket => { type => count } } deltas into the option value and prunes buckets older than the retention window.
build_event_filter()  : array{0: string, 1: array}
Builds the WHERE clause and bound parameters for an event-log filter.
bump_counter()  : void
Accumulates today's counter delta in the request-local cache.
bump_hourly_counter()  : void
Accumulates the current hour's counter delta in the request-local cache.
current_path()  : string
The current request path for the event log.
ensure_event_type_index()  : void
Idempotently adds the composite (event_type, created_at) index to the events table. Dashboard queries that filter by event_type benefit from a covering composite, whereas the standalone created_at index alone forces a filesort over a typed slice.
ensure_flush_hook()  : void
Idempotently registers the shutdown flush. Called the first time a delta is recorded in this request.
log_row()  : void
Writes one row into the event-log table and occasionally prunes it.
server_value()  : string
Reads a $_SERVER header value, sanitised and length-capped.
table_name()  : string
The fully-qualified event-log table name.
verification_data()  : string
The submitted ALTCHA solution payload for the current request, capped.

Constants

COUNTER_RETENTION_DAYS

Aggregate daily-counter retention, in days.

private mixed COUNTER_RETENTION_DAYS = 90

HOURLY_RETENTION_HOURS

Aggregate hourly-counter retention, in hours.

private mixed HOURLY_RETENTION_HOURS = 48

OPTION

The option holding the aggregate daily counters.

private mixed OPTION = 'creationell_captcha_analytics'

OPTION_HOURLY

The option holding the aggregate hourly counters (24-hour view).

private mixed OPTION_HOURLY = 'creationell_captcha_analytics_hourly'

TYPES

The seven recognised event types.

private mixed TYPES = ['verified', 'failed', 'firewall', 'ratelimit', 'underattack', 'underattack_passed', 'challenge']

Properties

$daily_deltas

Pending daily counter deltas: { bucket => { type => count } }. Flushed on `shutdown` so a request that triggers N events incurs at most one get_option/update_option round-trip per counter table, regardless of N.

private static array<string, array<string, int>> $daily_deltas = []

$flush_hooked

Whether the shutdown flush has already been hooked for this request.

private static bool $flush_hooked = false

$hourly_deltas

Pending hourly counter deltas: { bucket => { type => count } }.

private static array<string, array<string, int>> $hourly_deltas = []

Methods

clear_events()

Deletes every row from the event-log table.

public clear_events() : int
Return values
int

Number of rows deleted.

count_events()

Counts the event-log rows matching the given filter.

public count_events(array<string, mixed> $args) : int
Parameters
$args : array<string, mixed>

Filter args (id, search, event_type, date_from, date_to).

Return values
int

ensure_table()

Creates or updates the event-log table. Idempotent — safe to call repeatedly.

public ensure_table() : void

flush_pending_deltas()

Flushes the accumulated daily and hourly counter deltas back to the options table. Reads the current value first and adds the deltas, so concurrent updates from parallel requests are not lost (the underlying get+update is still non-atomic, but only one round-trip per request narrows the race window considerably compared to one round-trip per event).

public static flush_pending_deltas() : void

get_daily_counts()

Returns the aggregate daily counters, keyed by date.

public get_daily_counts() : array<string, array<string, int>>
Return values
array<string, array<string, int>>

get_hourly_counts()

Returns the aggregate hourly counters, keyed by 'Y-m-d H' (UTC).

public get_hourly_counts() : array<string, array<string, int>>
Return values
array<string, array<string, int>>

prune_events()

Deletes event-log rows older than the configured retention period.

public prune_events() : int
Return values
int

Number of rows deleted.

query_events()

Returns event-log rows matching the given filter, newest first.

public query_events(array<string, mixed> $args) : array<int, array<string, string>>
Parameters
$args : array<string, mixed>

Filter args (id, search, event_type, date_from, date_to) plus limit (1–1000) and offset (>= 0).

Return values
array<int, array<string, string>>

record()

Records one security event: bumps the daily and hourly counters and — when the detailed event log is enabled and the per-type toggle is on — writes a table row. Never fatals.

public record(string $type[, array<string, mixed> $context = [] ]) : void
Parameters
$type : string

One of the recognised event types.

$context : array<string, mixed> = []

Optional caller-supplied context.

table_exists()

Whether the event-log table currently exists.

public table_exists() : bool
Return values
bool

apply_deltas()

Merges the given { bucket => { type => count } } deltas into the option value and prunes buckets older than the retention window.

private static apply_deltas(string $option, array<string, array<string, int>> $deltas, int $retention, string $bucket_format, int $bucket_seconds) : void
Parameters
$option : string

Option name to read+write.

$deltas : array<string, array<string, int>>

Pending increments by bucket and type.

$retention : int

Retention amount (days or hours).

$bucket_format : string

gmdate() format for bucket keys.

$bucket_seconds : int

Seconds per retention unit (86400 or 3600).

build_event_filter()

Builds the WHERE clause and bound parameters for an event-log filter.

private build_event_filter(array<string, mixed> $args) : array{0: string, 1: array}
Parameters
$args : array<string, mixed>

Filter args: id, search, event_type, date_from, date_to.

Return values
array{0: string, 1: array}

SQL fragment ('' or ' WHERE …') and params.

bump_counter()

Accumulates today's counter delta in the request-local cache.

private bump_counter(string $type) : void
Parameters
$type : string

bump_hourly_counter()

Accumulates the current hour's counter delta in the request-local cache.

private bump_hourly_counter(string $type) : void
Parameters
$type : string

current_path()

The current request path for the event log.

private current_path() : string

Returns only the URI path component — the query string is dropped so tokens passed as GET parameters (magic-login links, API keys, …) do not leak into the persistent event log. Strips control characters (DB hygiene) and caps at 255 bytes. The dashboard escapes the value on output.

Return values
string

ensure_event_type_index()

Idempotently adds the composite (event_type, created_at) index to the events table. Dashboard queries that filter by event_type benefit from a covering composite, whereas the standalone created_at index alone forces a filesort over a typed slice.

private ensure_event_type_index() : void

ensure_flush_hook()

Idempotently registers the shutdown flush. Called the first time a delta is recorded in this request.

private ensure_flush_hook() : void

log_row()

Writes one row into the event-log table and occasionally prunes it.

private log_row(string $type[, array<string, mixed> $context = [] ]) : void

Honours the optional IP-anonymisation and request-body-fingerprint toggles from Modul 11c.

Parameters
$type : string

The event type.

$context : array<string, mixed> = []

Caller-supplied context.

server_value()

Reads a $_SERVER header value, sanitised and length-capped.

private server_value(string $key, int $max) : string
Parameters
$key : string

The $_SERVER key.

$max : int

Maximum length.

Return values
string

table_name()

The fully-qualified event-log table name.

private table_name() : string
Return values
string

verification_data()

The submitted ALTCHA solution payload for the current request, capped.

private verification_data() : string
Return values
string

        
On this page

Search results