comparison core/lib/Drupal/Core/Flood/DatabaseBackend.php @ 0:4c8ae668cc8c

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