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