annotate core/lib/Drupal/Core/Flood/DatabaseBackend.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 4c8ae668cc8c
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Flood;
Chris@0 4
Chris@0 5 use Drupal\Core\Database\SchemaObjectExistsException;
Chris@0 6 use Symfony\Component\HttpFoundation\RequestStack;
Chris@0 7 use Drupal\Core\Database\Connection;
Chris@0 8
Chris@0 9 /**
Chris@0 10 * Defines the database flood backend. This is the default Drupal backend.
Chris@0 11 */
Chris@0 12 class DatabaseBackend implements FloodInterface {
Chris@0 13
Chris@0 14 /**
Chris@0 15 * The database table name.
Chris@0 16 */
Chris@0 17 const TABLE_NAME = 'flood';
Chris@0 18
Chris@0 19 /**
Chris@0 20 * The database connection used to store flood event information.
Chris@0 21 *
Chris@0 22 * @var \Drupal\Core\Database\Connection
Chris@0 23 */
Chris@0 24 protected $connection;
Chris@0 25
Chris@0 26 /**
Chris@0 27 * The request stack.
Chris@0 28 *
Chris@0 29 * @var \Symfony\Component\HttpFoundation\RequestStack
Chris@0 30 */
Chris@0 31 protected $requestStack;
Chris@0 32
Chris@0 33 /**
Chris@0 34 * Construct the DatabaseBackend.
Chris@0 35 *
Chris@0 36 * @param \Drupal\Core\Database\Connection $connection
Chris@0 37 * The database connection which will be used to store the flood event
Chris@0 38 * information.
Chris@0 39 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
Chris@0 40 * The request stack used to retrieve the current request.
Chris@0 41 */
Chris@0 42 public function __construct(Connection $connection, RequestStack $request_stack) {
Chris@0 43 $this->connection = $connection;
Chris@0 44 $this->requestStack = $request_stack;
Chris@0 45 }
Chris@0 46
Chris@0 47 /**
Chris@0 48 * {@inheritdoc}
Chris@0 49 */
Chris@0 50 public function register($name, $window = 3600, $identifier = NULL) {
Chris@0 51 if (!isset($identifier)) {
Chris@0 52 $identifier = $this->requestStack->getCurrentRequest()->getClientIp();
Chris@0 53 }
Chris@0 54 $try_again = FALSE;
Chris@0 55 try {
Chris@0 56 $this->doInsert($name, $window, $identifier);
Chris@0 57 }
Chris@0 58 catch (\Exception $e) {
Chris@0 59 $try_again = $this->ensureTableExists();
Chris@0 60 if (!$try_again) {
Chris@0 61 throw $e;
Chris@0 62 }
Chris@0 63 }
Chris@0 64 if ($try_again) {
Chris@0 65 $this->doInsert($name, $window, $identifier);
Chris@0 66 }
Chris@0 67 }
Chris@0 68
Chris@0 69 /**
Chris@0 70 * Inserts an event into the flood table
Chris@0 71 *
Chris@0 72 * @param string $name
Chris@0 73 * The name of an event.
Chris@0 74 * @param int $window
Chris@0 75 * Number of seconds before this event expires.
Chris@0 76 * @param string $identifier
Chris@0 77 * Unique identifier of the current user.
Chris@0 78 *
Chris@0 79 * @see \Drupal\Core\Flood\DatabaseBackend::register
Chris@0 80 */
Chris@0 81 protected function doInsert($name, $window, $identifier) {
Chris@0 82 $this->connection->insert(static::TABLE_NAME)
Chris@0 83 ->fields([
Chris@0 84 'event' => $name,
Chris@0 85 'identifier' => $identifier,
Chris@0 86 'timestamp' => REQUEST_TIME,
Chris@0 87 'expiration' => REQUEST_TIME + $window,
Chris@0 88 ])
Chris@0 89 ->execute();
Chris@0 90 }
Chris@0 91
Chris@0 92 /**
Chris@0 93 * {@inheritdoc}
Chris@0 94 */
Chris@0 95 public function clear($name, $identifier = NULL) {
Chris@0 96 if (!isset($identifier)) {
Chris@0 97 $identifier = $this->requestStack->getCurrentRequest()->getClientIp();
Chris@0 98 }
Chris@0 99 try {
Chris@0 100 $this->connection->delete(static::TABLE_NAME)
Chris@0 101 ->condition('event', $name)
Chris@0 102 ->condition('identifier', $identifier)
Chris@0 103 ->execute();
Chris@0 104 }
Chris@0 105 catch (\Exception $e) {
Chris@0 106 $this->catchException($e);
Chris@0 107 }
Chris@0 108 }
Chris@0 109
Chris@0 110 /**
Chris@0 111 * {@inheritdoc}
Chris@0 112 */
Chris@0 113 public function isAllowed($name, $threshold, $window = 3600, $identifier = NULL) {
Chris@0 114 if (!isset($identifier)) {
Chris@0 115 $identifier = $this->requestStack->getCurrentRequest()->getClientIp();
Chris@0 116 }
Chris@0 117 try {
Chris@0 118 $number = $this->connection->select(static::TABLE_NAME, 'f')
Chris@0 119 ->condition('event', $name)
Chris@0 120 ->condition('identifier', $identifier)
Chris@0 121 ->condition('timestamp', REQUEST_TIME - $window, '>')
Chris@0 122 ->countQuery()
Chris@0 123 ->execute()
Chris@0 124 ->fetchField();
Chris@0 125 return ($number < $threshold);
Chris@0 126 }
Chris@0 127 catch (\Exception $e) {
Chris@0 128 $this->catchException($e);
Chris@0 129 return TRUE;
Chris@0 130 }
Chris@0 131 }
Chris@0 132
Chris@0 133 /**
Chris@0 134 * {@inheritdoc}
Chris@0 135 */
Chris@0 136 public function garbageCollection() {
Chris@0 137 try {
Chris@0 138 $return = $this->connection->delete(static::TABLE_NAME)
Chris@0 139 ->condition('expiration', REQUEST_TIME, '<')
Chris@0 140 ->execute();
Chris@0 141 }
Chris@0 142 catch (\Exception $e) {
Chris@0 143 $this->catchException($e);
Chris@0 144 }
Chris@0 145 }
Chris@0 146
Chris@0 147 /**
Chris@0 148 * Check if the flood table exists and create it if not.
Chris@0 149 */
Chris@0 150 protected function ensureTableExists() {
Chris@0 151 try {
Chris@0 152 $database_schema = $this->connection->schema();
Chris@0 153 if (!$database_schema->tableExists(static::TABLE_NAME)) {
Chris@0 154 $schema_definition = $this->schemaDefinition();
Chris@0 155 $database_schema->createTable(static::TABLE_NAME, $schema_definition);
Chris@0 156 return TRUE;
Chris@0 157 }
Chris@0 158 }
Chris@0 159 // If another process has already created the table, attempting to create
Chris@0 160 // it will throw an exception. In this case just catch the exception and do
Chris@0 161 // nothing.
Chris@0 162 catch (SchemaObjectExistsException $e) {
Chris@0 163 return TRUE;
Chris@0 164 }
Chris@0 165 return FALSE;
Chris@0 166 }
Chris@0 167
Chris@0 168 /**
Chris@0 169 * Act on an exception when flood might be stale.
Chris@0 170 *
Chris@0 171 * If the table does not yet exist, that's fine, but if the table exists and
Chris@0 172 * yet the query failed, then the flood is stale and the exception needs to
Chris@0 173 * propagate.
Chris@0 174 *
Chris@0 175 * @param $e
Chris@0 176 * The exception.
Chris@0 177 *
Chris@0 178 * @throws \Exception
Chris@0 179 */
Chris@0 180 protected function catchException(\Exception $e) {
Chris@0 181 if ($this->connection->schema()->tableExists(static::TABLE_NAME)) {
Chris@0 182 throw $e;
Chris@0 183 }
Chris@0 184 }
Chris@0 185
Chris@0 186 /**
Chris@0 187 * Defines the schema for the flood table.
Chris@0 188 *
Chris@0 189 * @internal
Chris@0 190 */
Chris@0 191 public function schemaDefinition() {
Chris@0 192 return [
Chris@0 193 'description' => 'Flood controls the threshold of events, such as the number of contact attempts.',
Chris@0 194 'fields' => [
Chris@0 195 'fid' => [
Chris@0 196 'description' => 'Unique flood event ID.',
Chris@0 197 'type' => 'serial',
Chris@0 198 'not null' => TRUE,
Chris@0 199 ],
Chris@0 200 'event' => [
Chris@0 201 'description' => 'Name of event (e.g. contact).',
Chris@0 202 'type' => 'varchar_ascii',
Chris@0 203 'length' => 64,
Chris@0 204 'not null' => TRUE,
Chris@0 205 'default' => '',
Chris@0 206 ],
Chris@0 207 'identifier' => [
Chris@0 208 'description' => 'Identifier of the visitor, such as an IP address or hostname.',
Chris@0 209 'type' => 'varchar_ascii',
Chris@0 210 'length' => 128,
Chris@0 211 'not null' => TRUE,
Chris@0 212 'default' => '',
Chris@0 213 ],
Chris@0 214 'timestamp' => [
Chris@0 215 'description' => 'Timestamp of the event.',
Chris@0 216 'type' => 'int',
Chris@0 217 'not null' => TRUE,
Chris@0 218 'default' => 0,
Chris@0 219 ],
Chris@0 220 'expiration' => [
Chris@0 221 'description' => 'Expiration timestamp. Expired events are purged on cron run.',
Chris@0 222 'type' => 'int',
Chris@0 223 'not null' => TRUE,
Chris@0 224 'default' => 0,
Chris@0 225 ],
Chris@0 226 ],
Chris@0 227 'primary key' => ['fid'],
Chris@0 228 'indexes' => [
Chris@0 229 'allow' => ['event', 'identifier', 'timestamp'],
Chris@0 230 'purge' => ['expiration'],
Chris@0 231 ],
Chris@0 232 ];
Chris@0 233 }
Chris@0 234
Chris@0 235 }