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