123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- <?php
- namespace Drupal\Core\Flood;
- use Drupal\Core\Database\SchemaObjectExistsException;
- use Symfony\Component\HttpFoundation\RequestStack;
- use Drupal\Core\Database\Connection;
- /**
- * Defines the database flood backend. This is the default Drupal backend.
- */
- class DatabaseBackend implements FloodInterface {
- /**
- * The database table name.
- */
- const TABLE_NAME = 'flood';
- /**
- * The database connection used to store flood event information.
- *
- * @var \Drupal\Core\Database\Connection
- */
- protected $connection;
- /**
- * The request stack.
- *
- * @var \Symfony\Component\HttpFoundation\RequestStack
- */
- protected $requestStack;
- /**
- * Construct the DatabaseBackend.
- *
- * @param \Drupal\Core\Database\Connection $connection
- * The database connection which will be used to store the flood event
- * information.
- * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
- * The request stack used to retrieve the current request.
- */
- public function __construct(Connection $connection, RequestStack $request_stack) {
- $this->connection = $connection;
- $this->requestStack = $request_stack;
- }
- /**
- * {@inheritdoc}
- */
- public function register($name, $window = 3600, $identifier = NULL) {
- if (!isset($identifier)) {
- $identifier = $this->requestStack->getCurrentRequest()->getClientIp();
- }
- $try_again = FALSE;
- try {
- $this->doInsert($name, $window, $identifier);
- }
- catch (\Exception $e) {
- $try_again = $this->ensureTableExists();
- if (!$try_again) {
- throw $e;
- }
- }
- if ($try_again) {
- $this->doInsert($name, $window, $identifier);
- }
- }
- /**
- * Inserts an event into the flood table
- *
- * @param string $name
- * The name of an event.
- * @param int $window
- * Number of seconds before this event expires.
- * @param string $identifier
- * Unique identifier of the current user.
- *
- * @see \Drupal\Core\Flood\DatabaseBackend::register
- */
- protected function doInsert($name, $window, $identifier) {
- $this->connection->insert(static::TABLE_NAME)
- ->fields([
- 'event' => $name,
- 'identifier' => $identifier,
- 'timestamp' => REQUEST_TIME,
- 'expiration' => REQUEST_TIME + $window,
- ])
- ->execute();
- }
- /**
- * {@inheritdoc}
- */
- public function clear($name, $identifier = NULL) {
- if (!isset($identifier)) {
- $identifier = $this->requestStack->getCurrentRequest()->getClientIp();
- }
- try {
- $this->connection->delete(static::TABLE_NAME)
- ->condition('event', $name)
- ->condition('identifier', $identifier)
- ->execute();
- }
- catch (\Exception $e) {
- $this->catchException($e);
- }
- }
- /**
- * {@inheritdoc}
- */
- public function isAllowed($name, $threshold, $window = 3600, $identifier = NULL) {
- if (!isset($identifier)) {
- $identifier = $this->requestStack->getCurrentRequest()->getClientIp();
- }
- try {
- $number = $this->connection->select(static::TABLE_NAME, 'f')
- ->condition('event', $name)
- ->condition('identifier', $identifier)
- ->condition('timestamp', REQUEST_TIME - $window, '>')
- ->countQuery()
- ->execute()
- ->fetchField();
- return ($number < $threshold);
- }
- catch (\Exception $e) {
- $this->catchException($e);
- return TRUE;
- }
- }
- /**
- * {@inheritdoc}
- */
- public function garbageCollection() {
- try {
- $return = $this->connection->delete(static::TABLE_NAME)
- ->condition('expiration', REQUEST_TIME, '<')
- ->execute();
- }
- catch (\Exception $e) {
- $this->catchException($e);
- }
- }
- /**
- * Check if the flood table exists and create it if not.
- */
- protected function ensureTableExists() {
- try {
- $database_schema = $this->connection->schema();
- if (!$database_schema->tableExists(static::TABLE_NAME)) {
- $schema_definition = $this->schemaDefinition();
- $database_schema->createTable(static::TABLE_NAME, $schema_definition);
- return TRUE;
- }
- }
- // If another process has already created the table, attempting to create
- // it will throw an exception. In this case just catch the exception and do
- // nothing.
- catch (SchemaObjectExistsException $e) {
- return TRUE;
- }
- return FALSE;
- }
- /**
- * Act on an exception when flood might be stale.
- *
- * If the table does not yet exist, that's fine, but if the table exists and
- * yet the query failed, then the flood is stale and the exception needs to
- * propagate.
- *
- * @param $e
- * The exception.
- *
- * @throws \Exception
- */
- protected function catchException(\Exception $e) {
- if ($this->connection->schema()->tableExists(static::TABLE_NAME)) {
- throw $e;
- }
- }
- /**
- * Defines the schema for the flood table.
- *
- * @internal
- */
- public function schemaDefinition() {
- return [
- 'description' => 'Flood controls the threshold of events, such as the number of contact attempts.',
- 'fields' => [
- 'fid' => [
- 'description' => 'Unique flood event ID.',
- 'type' => 'serial',
- 'not null' => TRUE,
- ],
- 'event' => [
- 'description' => 'Name of event (e.g. contact).',
- 'type' => 'varchar_ascii',
- 'length' => 64,
- 'not null' => TRUE,
- 'default' => '',
- ],
- 'identifier' => [
- 'description' => 'Identifier of the visitor, such as an IP address or hostname.',
- 'type' => 'varchar_ascii',
- 'length' => 128,
- 'not null' => TRUE,
- 'default' => '',
- ],
- 'timestamp' => [
- 'description' => 'Timestamp of the event.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- ],
- 'expiration' => [
- 'description' => 'Expiration timestamp. Expired events are purged on cron run.',
- 'type' => 'int',
- 'not null' => TRUE,
- 'default' => 0,
- ],
- ],
- 'primary key' => ['fid'],
- 'indexes' => [
- 'allow' => ['event', 'identifier', 'timestamp'],
- 'purge' => ['expiration'],
- ],
- ];
- }
- }
|