Mercurial > hg > isophonics-drupal-site
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 } |