Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / Batch / BatchStorage.php
1 <?php
2
3 namespace Drupal\Core\Batch;
4
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\SchemaObjectExistsException;
7 use Symfony\Component\HttpFoundation\Session\SessionInterface;
8 use Drupal\Core\Access\CsrfTokenGenerator;
9
10 class BatchStorage implements BatchStorageInterface {
11
12   /**
13    * The table name.
14    */
15   const TABLE_NAME = 'batch';
16
17   /**
18    * The database connection.
19    *
20    * @var \Drupal\Core\Database\Connection
21    */
22   protected $connection;
23
24   /**
25    * The session.
26    *
27    * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
28    */
29   protected $session;
30
31   /**
32    * The CSRF token generator.
33    *
34    * @var \Drupal\Core\Access\CsrfTokenGenerator
35    */
36   protected $csrfToken;
37
38   /**
39    * Constructs the database batch storage service.
40    *
41    * @param \Drupal\Core\Database\Connection $connection
42    *   The database connection.
43    * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
44    *   The session.
45    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
46    *   The CSRF token generator.
47    */
48   public function __construct(Connection $connection, SessionInterface $session, CsrfTokenGenerator $csrf_token) {
49     $this->connection = $connection;
50     $this->session = $session;
51     $this->csrfToken = $csrf_token;
52   }
53
54   /**
55    * {@inheritdoc}
56    */
57   public function load($id) {
58     // Ensure that a session is started before using the CSRF token generator.
59     $this->session->start();
60     try {
61       $batch = $this->connection->query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", [
62         ':bid' => $id,
63         ':token' => $this->csrfToken->get($id),
64       ])->fetchField();
65     }
66     catch (\Exception $e) {
67       $this->catchException($e);
68       $batch = FALSE;
69     }
70     if ($batch) {
71       return unserialize($batch);
72     }
73     return FALSE;
74   }
75
76   /**
77    * {@inheritdoc}
78    */
79   public function delete($id) {
80     try {
81       $this->connection->delete('batch')
82         ->condition('bid', $id)
83         ->execute();
84     }
85     catch (\Exception $e) {
86       $this->catchException($e);
87     }
88   }
89
90   /**
91    * {@inheritdoc}
92    */
93   public function update(array $batch) {
94     try {
95       $this->connection->update('batch')
96         ->fields(['batch' => serialize($batch)])
97         ->condition('bid', $batch['id'])
98         ->execute();
99     }
100     catch (\Exception $e) {
101       $this->catchException($e);
102     }
103   }
104
105   /**
106    * {@inheritdoc}
107    */
108   public function cleanup() {
109     try {
110       // Cleanup the batch table and the queue for failed batches.
111       $this->connection->delete('batch')
112         ->condition('timestamp', REQUEST_TIME - 864000, '<')
113         ->execute();
114     }
115     catch (\Exception $e) {
116       $this->catchException($e);
117     }
118   }
119
120   /**
121    * {@inheritdoc}
122    */
123   public function create(array $batch) {
124     // Ensure that a session is started before using the CSRF token generator.
125     $this->session->start();
126     $try_again = FALSE;
127     try {
128       // The batch table might not yet exist.
129       $this->doCreate($batch);
130     }
131     catch (\Exception $e) {
132       // If there was an exception, try to create the table.
133       if (!$try_again = $this->ensureTableExists()) {
134         // If the exception happened for other reason than the missing table,
135         // propagate the exception.
136         throw $e;
137       }
138     }
139     // Now that the table has been created, try again if necessary.
140     if ($try_again) {
141       $this->doCreate($batch);
142     }
143   }
144
145   /**
146    * Saves a batch.
147    *
148    * @param array $batch
149    *   The array representing the batch to create.
150    */
151   protected function doCreate(array $batch) {
152     $this->connection->insert('batch')
153       ->fields([
154         'bid' => $batch['id'],
155         'timestamp' => REQUEST_TIME,
156         'token' => $this->csrfToken->get($batch['id']),
157         'batch' => serialize($batch),
158       ])
159       ->execute();
160   }
161
162   /**
163    * Check if the table exists and create it if not.
164    */
165   protected function ensureTableExists() {
166     try {
167       $database_schema = $this->connection->schema();
168       if (!$database_schema->tableExists(static::TABLE_NAME)) {
169         $schema_definition = $this->schemaDefinition();
170         $database_schema->createTable(static::TABLE_NAME, $schema_definition);
171         return TRUE;
172       }
173     }
174     // If another process has already created the batch table, attempting to
175     // recreate it will throw an exception. In this case just catch the
176     // exception and do nothing.
177     catch (SchemaObjectExistsException $e) {
178       return TRUE;
179     }
180     return FALSE;
181   }
182
183   /**
184    * Act on an exception when batch might be stale.
185    *
186    * If the table does not yet exist, that's fine, but if the table exists and
187    * yet the query failed, then the batch is stale and the exception needs to
188    * propagate.
189    *
190    * @param $e
191    *   The exception.
192    *
193    * @throws \Exception
194    */
195   protected function catchException(\Exception $e) {
196     if ($this->connection->schema()->tableExists(static::TABLE_NAME)) {
197       throw $e;
198     }
199   }
200
201   /**
202    * Defines the schema for the batch table.
203    *
204    * @internal
205    */
206   public function schemaDefinition() {
207     return [
208       'description' => 'Stores details about batches (processes that run in multiple HTTP requests).',
209       'fields' => [
210         'bid' => [
211           'description' => 'Primary Key: Unique batch ID.',
212           // This is not a serial column, to allow both progressive and
213           // non-progressive batches. See batch_process().
214           'type' => 'int',
215           'unsigned' => TRUE,
216           'not null' => TRUE,
217         ],
218         'token' => [
219           'description' => "A string token generated against the current user's session id and the batch id, used to ensure that only the user who submitted the batch can effectively access it.",
220           'type' => 'varchar_ascii',
221           'length' => 64,
222           'not null' => TRUE,
223         ],
224         'timestamp' => [
225           'description' => 'A Unix timestamp indicating when this batch was submitted for processing. Stale batches are purged at cron time.',
226           'type' => 'int',
227           'not null' => TRUE,
228         ],
229         'batch' => [
230           'description' => 'A serialized array containing the processing data for the batch.',
231           'type' => 'blob',
232           'not null' => FALSE,
233           'size' => 'big',
234         ],
235       ],
236       'primary key' => ['bid'],
237       'indexes' => [
238         'token' => ['token'],
239       ],
240     ];
241   }
242
243 }