Pull merge.
[yaffs-website] / web / core / lib / Drupal / Core / TempStore / PrivateTempStore.php
1 <?php
2
3 namespace Drupal\Core\TempStore;
4
5 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
6 use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
7 use Drupal\Core\Lock\LockBackendInterface;
8 use Drupal\Core\Session\AccountProxyInterface;
9 use Symfony\Component\HttpFoundation\RequestStack;
10
11 /**
12  * Stores and retrieves temporary data for a given owner.
13  *
14  * A PrivateTempStore can be used to make temporary, non-cache data available
15  * across requests. The data for the PrivateTempStore is stored in one
16  * key/value collection. PrivateTempStore data expires automatically after a
17  * given timeframe.
18  *
19  * The PrivateTempStore is different from a cache, because the data in it is not
20  * yet saved permanently and so it cannot be rebuilt. Typically, the
21  * PrivateTempStore might be used to store work in progress that is later saved
22  * permanently elsewhere, e.g. autosave data, multistep forms, or in-progress
23  * changes to complex configuration that are not ready to be saved.
24  *
25  * The PrivateTempStore differs from the SharedTempStore in that all keys are
26  * ensured to be unique for a particular user and users can never share data. If
27  * you want to be able to share data between users or use it for locking, use
28  * \Drupal\Core\TempStore\SharedTempStore.
29  */
30 class PrivateTempStore {
31   use DependencySerializationTrait;
32
33   /**
34    * The key/value storage object used for this data.
35    *
36    * @var \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface
37    */
38   protected $storage;
39
40   /**
41    * The lock object used for this data.
42    *
43    * @var \Drupal\Core\Lock\LockBackendInterface
44    */
45   protected $lockBackend;
46
47   /**
48    * The current user.
49    *
50    * @var \Drupal\Core\Session\AccountProxyInterface
51    */
52   protected $currentUser;
53
54   /**
55    * The request stack.
56    *
57    * @var \Symfony\Component\HttpFoundation\RequestStack
58    */
59   protected $requestStack;
60
61   /**
62    * The time to live for items in seconds.
63    *
64    * By default, data is stored for one week (604800 seconds) before expiring.
65    *
66    * @var int
67    */
68   protected $expire;
69
70   /**
71    * Constructs a new object for accessing data from a key/value store.
72    *
73    * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $storage
74    *   The key/value storage object used for this data. Each storage object
75    *   represents a particular collection of data and will contain any number
76    *   of key/value pairs.
77    * @param \Drupal\Core\Lock\LockBackendInterface $lock_backend
78    *   The lock object used for this data.
79    * @param \Drupal\Core\Session\AccountProxyInterface $current_user
80    *   The current user account.
81    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
82    *   The request stack.
83    * @param int $expire
84    *   The time to live for items, in seconds.
85    */
86   public function __construct(KeyValueStoreExpirableInterface $storage, LockBackendInterface $lock_backend, AccountProxyInterface $current_user, RequestStack $request_stack, $expire = 604800) {
87     $this->storage = $storage;
88     $this->lockBackend = $lock_backend;
89     $this->currentUser = $current_user;
90     $this->requestStack = $request_stack;
91     $this->expire = $expire;
92   }
93
94   /**
95    * Retrieves a value from this PrivateTempStore for a given key.
96    *
97    * @param string $key
98    *   The key of the data to retrieve.
99    *
100    * @return mixed
101    *   The data associated with the key, or NULL if the key does not exist.
102    */
103   public function get($key) {
104     $key = $this->createkey($key);
105     if (($object = $this->storage->get($key)) && ($object->owner == $this->getOwner())) {
106       return $object->data;
107     }
108   }
109
110   /**
111    * Stores a particular key/value pair in this PrivateTempStore.
112    *
113    * @param string $key
114    *   The key of the data to store.
115    * @param mixed $value
116    *   The data to store.
117    *
118    * @throws \Drupal\Core\TempStore\TempStoreException
119    *   Thrown when a lock for the backend storage could not be acquired.
120    */
121   public function set($key, $value) {
122     // Ensure that an anonymous user has a session created for them, as
123     // otherwise subsequent page loads will not be able to retrieve their
124     // tempstore data.
125     if ($this->currentUser->isAnonymous()) {
126       // @todo when https://www.drupal.org/node/2865991 is resolved, use force
127       //   start session API rather than setting an arbitrary value directly.
128       $this->startSession();
129       $this->requestStack
130         ->getCurrentRequest()
131         ->getSession()
132         ->set('core.tempstore.private', TRUE);
133     }
134
135     $key = $this->createkey($key);
136     if (!$this->lockBackend->acquire($key)) {
137       $this->lockBackend->wait($key);
138       if (!$this->lockBackend->acquire($key)) {
139         throw new TempStoreException("Couldn't acquire lock to update item '$key' in '{$this->storage->getCollectionName()}' temporary storage.");
140       }
141     }
142
143     $value = (object) [
144       'owner' => $this->getOwner(),
145       'data' => $value,
146       'updated' => (int) $this->requestStack->getMasterRequest()->server->get('REQUEST_TIME'),
147     ];
148     $this->storage->setWithExpire($key, $value, $this->expire);
149     $this->lockBackend->release($key);
150   }
151
152   /**
153    * Returns the metadata associated with a particular key/value pair.
154    *
155    * @param string $key
156    *   The key of the data to store.
157    *
158    * @return mixed
159    *   An object with the owner and updated time if the key has a value, or
160    *   NULL otherwise.
161    */
162   public function getMetadata($key) {
163     $key = $this->createkey($key);
164     // Fetch the key/value pair and its metadata.
165     $object = $this->storage->get($key);
166     if ($object) {
167       // Don't keep the data itself in memory.
168       unset($object->data);
169       return $object;
170     }
171   }
172
173   /**
174    * Deletes data from the store for a given key and releases the lock on it.
175    *
176    * @param string $key
177    *   The key of the data to delete.
178    *
179    * @return bool
180    *   TRUE if the object was deleted or does not exist, FALSE if it exists but
181    *   is not owned by $this->owner.
182    *
183    * @throws \Drupal\Core\TempStore\TempStoreException
184    *   Thrown when a lock for the backend storage could not be acquired.
185    */
186   public function delete($key) {
187     $key = $this->createkey($key);
188     if (!$object = $this->storage->get($key)) {
189       return TRUE;
190     }
191     elseif ($object->owner != $this->getOwner()) {
192       return FALSE;
193     }
194     if (!$this->lockBackend->acquire($key)) {
195       $this->lockBackend->wait($key);
196       if (!$this->lockBackend->acquire($key)) {
197         throw new TempStoreException("Couldn't acquire lock to delete item '$key' from '{$this->storage->getCollectionName()}' temporary storage.");
198       }
199     }
200     $this->storage->delete($key);
201     $this->lockBackend->release($key);
202     return TRUE;
203   }
204
205   /**
206    * Ensures that the key is unique for a user.
207    *
208    * @param string $key
209    *   The key.
210    *
211    * @return string
212    *   The unique key for the user.
213    */
214   protected function createkey($key) {
215     return $this->getOwner() . ':' . $key;
216   }
217
218   /**
219    * Gets the current owner based on the current user or the session ID.
220    *
221    * @return string
222    *   The owner.
223    */
224   protected function getOwner() {
225     $owner = $this->currentUser->id();
226     if ($this->currentUser->isAnonymous()) {
227       $this->startSession();
228       $owner = $this->requestStack->getCurrentRequest()->getSession()->getId();
229     }
230     return $owner;
231   }
232
233   /**
234    * Start session because it is required for a private temp store.
235    *
236    * Ensures that an anonymous user has a session created for them, as
237    * otherwise subsequent page loads will not be able to retrieve their
238    * tempstore data.
239    *
240    * @todo when https://www.drupal.org/node/2865991 is resolved, use force
241    * start session API.
242    */
243   protected function startSession() {
244     $has_session = $this->requestStack
245       ->getCurrentRequest()
246       ->hasSession();
247     if (!$has_session) {
248       /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
249       $session = \Drupal::service('session');
250       $this->requestStack->getCurrentRequest()->setSession($session);
251       $session->start();
252     }
253   }
254
255 }