Skip to content
← Back to Functions
Code

Token Expiry Cleaner

Removes expired password reset, email verification, and CSRF records from storage.

Function signature

ogCleanExpiredSecurityTokens(conn, table, expiry_column = 'expires_at', batch_limit = 500, current_time = 0, execute = false)

Categories

  • Security

Parameters

connProcedural mysqli connection.tableToken table name.expiry_columnExpiration column name containing a Unix timestamp.batch_limitMaximum rows to delete.current_timeCurrent Unix timestamp. Uses time() when empty.executeWhether to execute the delete or only return a plan.

Return value

Short public-safe status message.

  • table
  • expiry_column
  • expired_count
  • deleted_count
  • execute

Compatibility

Existing function name and call order preserved; metadata signature corrected to source.

Minimum PHP version: 7.4

Security notes

Validate request method, identity, permissions, and caller-owned allowlists before use; keep secrets out of public output.

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Removes expired password reset, email verification, and CSRF records from storage.
 *
 * Primary use case: Maintenance jobs and login cleanup.
 * Typical inputs: token table, expiry column, batch limit.
 * Typical output: deleted count.
 *
 * Implementation note: Use batched deletes and indexes on expiry fields.
 *
 * @param mysqli $conn Procedural mysqli connection.
 * @param string $table Token table name.
 * @param string $expiry_column Expiration column name containing a Unix timestamp.
 * @param int $batch_limit Maximum rows to delete.
 * @param int $current_time Current Unix timestamp. Uses time() when empty.
 * @param bool $execute Whether to execute the delete or only return a plan.
 * @return array Structured result data with success, message, and data keys.
 */
function ogCleanExpiredSecurityTokens($conn, $table, $expiry_column = 'expires_at', $batch_limit = 500, $current_time = 0, $execute = false) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	$table = trim((string)$table);
	$expiry_column = trim((string)$expiry_column);
	$batch_limit = (int)$batch_limit;
	$current_time = (int)$current_time;

	if (!preg_match('/^[A-Za-z0-9_]+$/', $table)) {
		$result['message'] = 'Invalid token table name.';
		return $result;
	}

	if (!preg_match('/^[A-Za-z0-9_]+$/', $expiry_column)) {
		$result['message'] = 'Invalid expiration column name.';
		return $result;
	}

	if ($batch_limit < 1) {
		$batch_limit = 1;
	}
	if ($batch_limit > 5000) {
		$batch_limit = 5000;
	}
	if ($current_time <= 0) {
		$current_time = time();
	}

	$query = 'DELETE FROM `' . $table . '` WHERE `' . $expiry_column . '` < ? LIMIT ' . $batch_limit;
	$result['data'] = array(
		'query' => $query,
		'current_time' => $current_time,
		'batch_limit' => $batch_limit,
		'deleted' => 0
	);

	if (!$execute) {
		$result['success'] = true;
		$result['message'] = 'Expired token cleanup plan prepared.';
		return $result;
	}

	if (!is_object($conn)) {
		$result['message'] = 'A mysqli connection is required to execute cleanup.';
		return $result;
	}

	$stmt = mysqli_prepare($conn, $query);
	if (!$stmt) {
		$result['message'] = 'Expired token cleanup statement could not be prepared.';
		return $result;
	}

	mysqli_stmt_bind_param($stmt, 'i', $current_time);
	if (!mysqli_stmt_execute($stmt)) {
		mysqli_stmt_close($stmt);
		$result['message'] = 'Expired token cleanup failed.';
		return $result;
	}

	$deleted = mysqli_stmt_affected_rows($stmt);
	mysqli_stmt_close($stmt);

	$result['success'] = true;
	$result['message'] = 'Expired security tokens cleaned.';
	$result['data']['deleted'] = $deleted;

	return $result;
}