Mercurial > hg > isophonics-drupal-site
comparison core/modules/workspaces/src/WorkspaceManager.php @ 17:129ea1e6d783
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:21:36 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
16:c2387f117808 | 17:129ea1e6d783 |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\workspaces; | |
4 | |
5 use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface; | |
6 use Drupal\Core\DependencyInjection\ClassResolverInterface; | |
7 use Drupal\Core\Entity\EntityPublishedInterface; | |
8 use Drupal\Core\Entity\EntityTypeInterface; | |
9 use Drupal\Core\Entity\EntityTypeManagerInterface; | |
10 use Drupal\Core\Session\AccountProxyInterface; | |
11 use Drupal\Core\Site\Settings; | |
12 use Drupal\Core\State\StateInterface; | |
13 use Drupal\Core\StringTranslation\StringTranslationTrait; | |
14 use Psr\Log\LoggerInterface; | |
15 use Symfony\Component\HttpFoundation\RequestStack; | |
16 | |
17 /** | |
18 * Provides the workspace manager. | |
19 */ | |
20 class WorkspaceManager implements WorkspaceManagerInterface { | |
21 | |
22 use StringTranslationTrait; | |
23 | |
24 /** | |
25 * An array of entity type IDs that can not belong to a workspace. | |
26 * | |
27 * By default, only entity types which are revisionable and publishable can | |
28 * belong to a workspace. | |
29 * | |
30 * @var string[] | |
31 */ | |
32 protected $blacklist = [ | |
33 'workspace_association' => 'workspace_association', | |
34 'workspace' => 'workspace', | |
35 ]; | |
36 | |
37 /** | |
38 * The request stack. | |
39 * | |
40 * @var \Symfony\Component\HttpFoundation\RequestStack | |
41 */ | |
42 protected $requestStack; | |
43 | |
44 /** | |
45 * The entity type manager. | |
46 * | |
47 * @var \Drupal\Core\Entity\EntityTypeManagerInterface | |
48 */ | |
49 protected $entityTypeManager; | |
50 | |
51 /** | |
52 * The entity memory cache service. | |
53 * | |
54 * @var \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface | |
55 */ | |
56 protected $entityMemoryCache; | |
57 | |
58 /** | |
59 * The current user. | |
60 * | |
61 * @var \Drupal\Core\Session\AccountProxyInterface | |
62 */ | |
63 protected $currentUser; | |
64 | |
65 /** | |
66 * The state service. | |
67 * | |
68 * @var \Drupal\Core\State\StateInterface | |
69 */ | |
70 protected $state; | |
71 | |
72 /** | |
73 * A logger instance. | |
74 * | |
75 * @var \Psr\Log\LoggerInterface | |
76 */ | |
77 protected $logger; | |
78 | |
79 /** | |
80 * The class resolver. | |
81 * | |
82 * @var \Drupal\Core\DependencyInjection\ClassResolverInterface | |
83 */ | |
84 protected $classResolver; | |
85 | |
86 /** | |
87 * The workspace negotiator service IDs. | |
88 * | |
89 * @var array | |
90 */ | |
91 protected $negotiatorIds; | |
92 | |
93 /** | |
94 * The current active workspace. | |
95 * | |
96 * @var \Drupal\workspaces\WorkspaceInterface | |
97 */ | |
98 protected $activeWorkspace; | |
99 | |
100 /** | |
101 * Constructs a new WorkspaceManager. | |
102 * | |
103 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack | |
104 * The request stack. | |
105 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager | |
106 * The entity type manager. | |
107 * @param \Drupal\Core\Cache\MemoryCache\MemoryCacheInterface $entity_memory_cache | |
108 * The entity memory cache service. | |
109 * @param \Drupal\Core\Session\AccountProxyInterface $current_user | |
110 * The current user. | |
111 * @param \Drupal\Core\State\StateInterface $state | |
112 * The state service. | |
113 * @param \Psr\Log\LoggerInterface $logger | |
114 * A logger instance. | |
115 * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver | |
116 * The class resolver. | |
117 * @param array $negotiator_ids | |
118 * The workspace negotiator service IDs. | |
119 */ | |
120 public function __construct(RequestStack $request_stack, EntityTypeManagerInterface $entity_type_manager, MemoryCacheInterface $entity_memory_cache, AccountProxyInterface $current_user, StateInterface $state, LoggerInterface $logger, ClassResolverInterface $class_resolver, array $negotiator_ids) { | |
121 $this->requestStack = $request_stack; | |
122 $this->entityTypeManager = $entity_type_manager; | |
123 $this->entityMemoryCache = $entity_memory_cache; | |
124 $this->currentUser = $current_user; | |
125 $this->state = $state; | |
126 $this->logger = $logger; | |
127 $this->classResolver = $class_resolver; | |
128 $this->negotiatorIds = $negotiator_ids; | |
129 } | |
130 | |
131 /** | |
132 * {@inheritdoc} | |
133 */ | |
134 public function isEntityTypeSupported(EntityTypeInterface $entity_type) { | |
135 // First, check if we already determined whether this entity type is | |
136 // supported or not. | |
137 if (isset($this->blacklist[$entity_type->id()])) { | |
138 return FALSE; | |
139 } | |
140 | |
141 if ($entity_type->entityClassImplements(EntityPublishedInterface::class) && $entity_type->isRevisionable()) { | |
142 return TRUE; | |
143 } | |
144 | |
145 // This entity type can not belong to a workspace, add it to the blacklist. | |
146 $this->blacklist[$entity_type->id()] = $entity_type->id(); | |
147 return FALSE; | |
148 } | |
149 | |
150 /** | |
151 * {@inheritdoc} | |
152 */ | |
153 public function getSupportedEntityTypes() { | |
154 $entity_types = []; | |
155 foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { | |
156 if ($this->isEntityTypeSupported($entity_type)) { | |
157 $entity_types[$entity_type_id] = $entity_type; | |
158 } | |
159 } | |
160 return $entity_types; | |
161 } | |
162 | |
163 /** | |
164 * {@inheritdoc} | |
165 */ | |
166 public function getActiveWorkspace() { | |
167 if (!isset($this->activeWorkspace)) { | |
168 $request = $this->requestStack->getCurrentRequest(); | |
169 foreach ($this->negotiatorIds as $negotiator_id) { | |
170 $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); | |
171 if ($negotiator->applies($request)) { | |
172 if ($this->activeWorkspace = $negotiator->getActiveWorkspace($request)) { | |
173 break; | |
174 } | |
175 } | |
176 } | |
177 } | |
178 | |
179 // The default workspace negotiator always returns a valid workspace. | |
180 return $this->activeWorkspace; | |
181 } | |
182 | |
183 /** | |
184 * {@inheritdoc} | |
185 */ | |
186 public function setActiveWorkspace(WorkspaceInterface $workspace) { | |
187 $this->doSwitchWorkspace($workspace); | |
188 | |
189 // Set the workspace on the proper negotiator. | |
190 $request = $this->requestStack->getCurrentRequest(); | |
191 foreach ($this->negotiatorIds as $negotiator_id) { | |
192 $negotiator = $this->classResolver->getInstanceFromDefinition($negotiator_id); | |
193 if ($negotiator->applies($request)) { | |
194 $negotiator->setActiveWorkspace($workspace); | |
195 break; | |
196 } | |
197 } | |
198 | |
199 return $this; | |
200 } | |
201 | |
202 /** | |
203 * Switches the current workspace. | |
204 * | |
205 * @param \Drupal\workspaces\WorkspaceInterface $workspace | |
206 * The workspace to set as active. | |
207 * | |
208 * @throws \Drupal\workspaces\WorkspaceAccessException | |
209 * Thrown when the current user doesn't have access to view the workspace. | |
210 */ | |
211 protected function doSwitchWorkspace(WorkspaceInterface $workspace) { | |
212 // If the current user doesn't have access to view the workspace, they | |
213 // shouldn't be allowed to switch to it. | |
214 if (!$workspace->access('view') && !$workspace->isDefaultWorkspace()) { | |
215 $this->logger->error('Denied access to view workspace %workspace_label for user %uid', [ | |
216 '%workspace_label' => $workspace->label(), | |
217 '%uid' => $this->currentUser->id(), | |
218 ]); | |
219 throw new WorkspaceAccessException('The user does not have permission to view that workspace.'); | |
220 } | |
221 | |
222 $this->activeWorkspace = $workspace; | |
223 | |
224 // Clear the static entity cache for the supported entity types. | |
225 $cache_tags_to_invalidate = array_map(function ($entity_type_id) { | |
226 return 'entity.memory_cache:' . $entity_type_id; | |
227 }, array_keys($this->getSupportedEntityTypes())); | |
228 $this->entityMemoryCache->invalidateTags($cache_tags_to_invalidate); | |
229 } | |
230 | |
231 /** | |
232 * {@inheritdoc} | |
233 */ | |
234 public function executeInWorkspace($workspace_id, callable $function) { | |
235 /** @var \Drupal\workspaces\WorkspaceInterface $workspace */ | |
236 $workspace = $this->entityTypeManager->getStorage('workspace')->load($workspace_id); | |
237 | |
238 if (!$workspace) { | |
239 throw new \InvalidArgumentException('The ' . $workspace_id . ' workspace does not exist.'); | |
240 } | |
241 | |
242 $previous_active_workspace = $this->getActiveWorkspace(); | |
243 $this->doSwitchWorkspace($workspace); | |
244 $result = $function(); | |
245 $this->doSwitchWorkspace($previous_active_workspace); | |
246 | |
247 return $result; | |
248 } | |
249 | |
250 /** | |
251 * {@inheritdoc} | |
252 */ | |
253 public function shouldAlterOperations(EntityTypeInterface $entity_type) { | |
254 return $this->isEntityTypeSupported($entity_type) && !$this->getActiveWorkspace()->isDefaultWorkspace(); | |
255 } | |
256 | |
257 /** | |
258 * {@inheritdoc} | |
259 */ | |
260 public function purgeDeletedWorkspacesBatch() { | |
261 $deleted_workspace_ids = $this->state->get('workspace.deleted', []); | |
262 | |
263 // Bail out early if there are no workspaces to purge. | |
264 if (empty($deleted_workspace_ids)) { | |
265 return; | |
266 } | |
267 | |
268 $batch_size = Settings::get('entity_update_batch_size', 50); | |
269 | |
270 /** @var \Drupal\workspaces\WorkspaceAssociationStorageInterface $workspace_association_storage */ | |
271 $workspace_association_storage = $this->entityTypeManager->getStorage('workspace_association'); | |
272 | |
273 // Get the first deleted workspace from the list and delete the revisions | |
274 // associated with it, along with the workspace_association entries. | |
275 $workspace_id = reset($deleted_workspace_ids); | |
276 $workspace_association_ids = $this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size); | |
277 | |
278 if ($workspace_association_ids) { | |
279 $workspace_associations = $workspace_association_storage->loadMultipleRevisions(array_keys($workspace_association_ids)); | |
280 foreach ($workspace_associations as $workspace_association) { | |
281 $associated_entity_storage = $this->entityTypeManager->getStorage($workspace_association->target_entity_type_id->value); | |
282 // Delete the associated entity revision. | |
283 if ($entity = $associated_entity_storage->loadRevision($workspace_association->target_entity_revision_id->value)) { | |
284 if ($entity->isDefaultRevision()) { | |
285 $entity->delete(); | |
286 } | |
287 else { | |
288 $associated_entity_storage->deleteRevision($workspace_association->target_entity_revision_id->value); | |
289 } | |
290 } | |
291 | |
292 // Delete the workspace_association revision. | |
293 if ($workspace_association->isDefaultRevision()) { | |
294 $workspace_association->delete(); | |
295 } | |
296 else { | |
297 $workspace_association_storage->deleteRevision($workspace_association->getRevisionId()); | |
298 } | |
299 } | |
300 } | |
301 | |
302 // The purging operation above might have taken a long time, so we need to | |
303 // request a fresh list of workspace association IDs. If it is empty, we can | |
304 // go ahead and remove the deleted workspace ID entry from state. | |
305 if (!$this->getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size)) { | |
306 unset($deleted_workspace_ids[$workspace_id]); | |
307 $this->state->set('workspace.deleted', $deleted_workspace_ids); | |
308 } | |
309 } | |
310 | |
311 /** | |
312 * Gets a list of workspace association IDs to purge. | |
313 * | |
314 * @param string $workspace_id | |
315 * The ID of the workspace. | |
316 * @param int $batch_size | |
317 * The maximum number of records that will be purged. | |
318 * | |
319 * @return array | |
320 * An array of workspace association IDs, keyed by their revision IDs. | |
321 */ | |
322 protected function getWorkspaceAssociationRevisionsToPurge($workspace_id, $batch_size) { | |
323 return $this->entityTypeManager->getStorage('workspace_association') | |
324 ->getQuery() | |
325 ->allRevisions() | |
326 ->accessCheck(FALSE) | |
327 ->condition('workspace', $workspace_id) | |
328 ->sort('revision_id', 'ASC') | |
329 ->range(0, $batch_size) | |
330 ->execute(); | |
331 } | |
332 | |
333 } |