This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Kunstmaan\NodeBundle\EventListener; |
||
4 | |||
5 | use Doctrine\Common\Persistence\Mapping\MappingException; |
||
6 | use Doctrine\ORM\EntityManagerInterface; |
||
7 | use Doctrine\ORM\Event\LifecycleEventArgs; |
||
8 | use Doctrine\ORM\Event\OnFlushEventArgs; |
||
9 | use Doctrine\ORM\Mapping\ClassMetadata; |
||
10 | use Kunstmaan\AdminBundle\FlashMessages\FlashTypes; |
||
11 | use Kunstmaan\AdminBundle\Helper\DomainConfigurationInterface; |
||
12 | use Kunstmaan\NodeBundle\Entity\HasNodeInterface; |
||
13 | use Kunstmaan\NodeBundle\Entity\Node; |
||
14 | use Kunstmaan\NodeBundle\Entity\NodeTranslation; |
||
15 | use Kunstmaan\NodeBundle\Helper\PagesConfiguration; |
||
16 | use Kunstmaan\NodeBundle\Repository\NodeTranslationRepository; |
||
17 | use Kunstmaan\UtilitiesBundle\Helper\SlugifierInterface; |
||
18 | use Psr\Log\LoggerInterface; |
||
19 | use Symfony\Component\HttpFoundation\RequestStack; |
||
20 | use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; |
||
21 | use Symfony\Component\HttpFoundation\Session\SessionInterface; |
||
22 | |||
23 | /** |
||
24 | * Class NodeTranslationListener |
||
25 | * Listens to doctrine postFlush event and updates the urls if the entities are nodetranslations |
||
26 | */ |
||
27 | class NodeTranslationListener |
||
28 | { |
||
29 | /** @var SessionInterface|FlashBagInterface */ |
||
30 | private $flashBag; |
||
31 | |||
32 | /** @var LoggerInterface */ |
||
33 | private $logger; |
||
34 | |||
35 | /** @var SlugifierInterface */ |
||
36 | private $slugifier; |
||
37 | |||
38 | /** @var RequestStack */ |
||
39 | private $requestStack; |
||
40 | |||
41 | /** @var DomainConfigurationInterface */ |
||
42 | private $domainConfiguration; |
||
43 | |||
44 | /** @var PagesConfiguration */ |
||
45 | private $pagesConfiguration; |
||
46 | |||
47 | /** |
||
48 | * NodeTranslationListener constructor. |
||
49 | * |
||
50 | * @param SessionInterface|FlashBagInterface $session |
||
51 | * @param LoggerInterface $logger |
||
52 | * @param SlugifierInterface $slugifier |
||
53 | * @param RequestStack $requestStack |
||
54 | * @param DomainConfigurationInterface $domainConfiguration |
||
55 | * @param PagesConfiguration $pagesConfiguration |
||
56 | */ |
||
57 | public function __construct( |
||
58 | /* SessionInterface */ $flashBag, |
||
59 | LoggerInterface $logger, |
||
60 | SlugifierInterface $slugifier, |
||
61 | RequestStack $requestStack, |
||
62 | DomainConfigurationInterface $domainConfiguration, |
||
63 | PagesConfiguration $pagesConfiguration |
||
64 | ) { |
||
65 | $this->flashBag = $flashBag; |
||
66 | |||
67 | if ($flashBag instanceof FlashBagInterface) { |
||
68 | @trigger_error('Passing the "@session.flash_bag" service as first argument is deprecated since KunstmaanNodeBundle 5.6 and will be replaced by the session in KunstmaanNodeBundle 6.0. Inject the "@session" service instead.', E_USER_DEPRECATED); |
||
69 | } |
||
70 | |||
71 | $this->logger = $logger; |
||
72 | $this->slugifier = $slugifier; |
||
73 | $this->requestStack = $requestStack; |
||
74 | $this->domainConfiguration = $domainConfiguration; |
||
75 | $this->pagesConfiguration = $pagesConfiguration; |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * @param LifecycleEventArgs $args |
||
80 | */ |
||
81 | View Code Duplication | public function prePersist(LifecycleEventArgs $args) |
|
82 | { |
||
83 | $entity = $args->getEntity(); |
||
84 | |||
85 | if ($entity instanceof NodeTranslation) { |
||
86 | $this->setSlugWhenEmpty($entity, $args->getEntityManager()); |
||
87 | $this->ensureSlugIsSlugified($entity); |
||
88 | } |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * @param LifecycleEventArgs $args |
||
93 | */ |
||
94 | View Code Duplication | public function preUpdate(LifecycleEventArgs $args) |
|
95 | { |
||
96 | $entity = $args->getEntity(); |
||
97 | |||
98 | if ($entity instanceof NodeTranslation) { |
||
99 | $this->setSlugWhenEmpty($entity, $args->getEntityManager()); |
||
100 | $this->ensureSlugIsSlugified($entity); |
||
101 | } |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * @param NodeTranslation $nodeTranslation |
||
106 | * @param EntityManagerInterface $em |
||
107 | */ |
||
108 | private function setSlugWhenEmpty(NodeTranslation $nodeTranslation, EntityManagerInterface $em) |
||
109 | { |
||
110 | $publicNode = $nodeTranslation->getRef($em); |
||
111 | |||
112 | // Do nothing for StructureNode objects, skip. |
||
113 | if ($publicNode instanceof HasNodeInterface && $publicNode->isStructureNode()) { |
||
114 | return; |
||
115 | } |
||
116 | |||
117 | // If no slug is set and no structure node, apply title as slug. |
||
118 | View Code Duplication | if ($nodeTranslation->getSlug() === null && $nodeTranslation->getNode()->getParent() !== null) { |
|
119 | $nodeTranslation->setSlug( |
||
120 | $this->slugifier->slugify($nodeTranslation->getTitle()) |
||
121 | ); |
||
122 | } |
||
123 | } |
||
124 | |||
125 | /** |
||
126 | * @param NodeTranslation $nodeTranslation |
||
127 | */ |
||
128 | private function ensureSlugIsSlugified(NodeTranslation $nodeTranslation) |
||
129 | { |
||
130 | if ($nodeTranslation->getSlug() !== null) { |
||
131 | $nodeTranslation->setSlug( |
||
132 | $this->slugifier->slugify($nodeTranslation->getSlug()) |
||
133 | ); |
||
134 | } |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * OnFlush doctrine event - updates the nodetranslation urls if needed |
||
139 | * |
||
140 | * @param OnFlushEventArgs $args |
||
141 | */ |
||
142 | public function onFlush(OnFlushEventArgs $args) |
||
143 | { |
||
144 | try { |
||
145 | $em = $args->getEntityManager(); |
||
146 | |||
147 | $class = $em->getClassMetadata(NodeTranslation::class); |
||
148 | |||
149 | // Collect all nodetranslations that are updated |
||
150 | foreach ($em->getUnitOfWork()->getScheduledEntityUpdates() as $entity) { |
||
151 | if ($entity instanceof NodeTranslation) { |
||
152 | /** @var Node $publicNode */ |
||
153 | $publicNode = $entity->getPublicNodeVersion()->getRef($em); |
||
154 | |||
155 | // Do nothing for StructureNode objects, skip. |
||
156 | if ($publicNode instanceof HasNodeInterface && $publicNode->isStructureNode()) { |
||
157 | continue; |
||
158 | } |
||
159 | |||
160 | $entity = $this->updateUrl($entity, $em); |
||
161 | |||
162 | View Code Duplication | if ($entity !== false) { |
|
0 ignored issues
–
show
|
|||
163 | $em->persist($entity); |
||
164 | $em->getUnitOfWork()->recomputeSingleEntityChangeSet($class, $entity); |
||
165 | |||
166 | $this->updateNodeChildren($entity, $em, $class); |
||
167 | } |
||
168 | } |
||
169 | } |
||
170 | } catch (MappingException $e) { |
||
171 | // Different entity manager without this entity configured in namespace chain. Ignore |
||
172 | } |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * Checks if a nodetranslation has children and update their url |
||
177 | * |
||
178 | * @param NodeTranslation $node The node |
||
179 | * @param EntityManagerInterface $em The entity manager |
||
180 | * @param ClassMetadata $class The class meta daat |
||
181 | */ |
||
182 | private function updateNodeChildren(NodeTranslation $node, EntityManagerInterface $em, ClassMetadata $class) |
||
183 | { |
||
184 | $children = $node->getNode()->getChildren(); |
||
185 | if (\count($children) > 0) { |
||
186 | /* @var Node $child */ |
||
187 | foreach ($children as $child) { |
||
188 | $translation = $child->getNodeTranslation($node->getLang(), true); |
||
189 | if ($translation) { |
||
190 | $translation = $this->updateUrl($translation, $em); |
||
191 | |||
192 | View Code Duplication | if ($translation !== false) { |
|
193 | $em->persist($translation); |
||
194 | $em->getUnitOfWork()->recomputeSingleEntityChangeSet($class, $translation); |
||
195 | |||
196 | $this->updateNodeChildren($translation, $em, $class); |
||
197 | } |
||
198 | } |
||
199 | } |
||
200 | } |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Update the url for a nodetranslation |
||
205 | * |
||
206 | * @param NodeTranslation $nodeTranslation The node translation |
||
207 | * @param EntityManagerInterface $em The entity manager |
||
208 | * |
||
209 | * @return NodeTranslation|bool returns the node when all is well because it has to be saved |
||
210 | */ |
||
211 | private function updateUrl(NodeTranslation $nodeTranslation, EntityManagerInterface $em) |
||
212 | { |
||
213 | $result = $this->ensureUniqueUrl($nodeTranslation, $em); |
||
214 | |||
215 | if ($result) { |
||
216 | return $nodeTranslation; |
||
217 | } |
||
218 | |||
219 | $this->logger->info( |
||
220 | sprintf('Found NT %s needed NO change', $nodeTranslation->getId()) |
||
221 | ); |
||
222 | |||
223 | return false; |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * A function that checks the URL and sees if it's unique. |
||
228 | * It's allowed to be the same when the node is a StructureNode. |
||
229 | * When a node is deleted it needs to be ignored in the check. |
||
230 | * Offline nodes need to be included as well. |
||
231 | * |
||
232 | * It sluggifies the slug, updates the URL |
||
233 | * and checks all existing NodeTranslations ([1]), excluding itself. If a |
||
234 | * URL existsthat has the same url. If an existing one is found the slug is |
||
235 | * modified, the URL is updated and the check is repeated until no prior |
||
236 | * urls exist. |
||
237 | * |
||
238 | * NOTE: We need a way to tell if the slug has been modified or not. |
||
239 | * NOTE: Would be cool if we could increment a number after the slug. Like |
||
240 | * check if it matches -v# and increment the number. |
||
241 | * |
||
242 | * [1] For all languages for now. The issue is that we need a way to know |
||
243 | * if a node's URL is prepended with the language or not. For now both |
||
244 | * scenarios are possible so we check for all languages. |
||
245 | * |
||
246 | * @param NodeTranslation $translation Reference to the NodeTranslation. |
||
247 | * This is modified in place. |
||
248 | * @param EntityManagerInterface $em The entity manager |
||
249 | * @param array $flashes The flash messages array |
||
250 | * |
||
251 | * @return bool |
||
252 | */ |
||
253 | private function ensureUniqueUrl(NodeTranslation $translation, EntityManagerInterface $em, array $flashes = []) |
||
254 | { |
||
255 | // Can't use GetRef here yet since the NodeVersions aren't loaded yet for some reason. |
||
256 | $nodeVersion = $translation->getPublicNodeVersion(); |
||
257 | $page = $em->getRepository($nodeVersion->getRefEntityName()) |
||
258 | ->find($nodeVersion->getRefId()); |
||
259 | |||
260 | if (null === $page) { |
||
261 | return false; |
||
262 | } |
||
263 | |||
264 | $isStructureNode = $page->isStructureNode(); |
||
265 | |||
266 | // If it's a StructureNode the slug and url should be empty. |
||
267 | if ($isStructureNode) { |
||
268 | $translation->setSlug(''); |
||
269 | $translation->setUrl($translation->getFullSlug()); |
||
270 | |||
271 | return true; |
||
272 | } |
||
273 | |||
274 | /* @var NodeTranslationRepository $nodeTranslationRepository */ |
||
275 | $nodeTranslationRepository = $em->getRepository(NodeTranslation::class); |
||
276 | |||
277 | if ($translation->getUrl() === $translation->getFullSlug()) { |
||
278 | $this->logger->debug( |
||
279 | sprintf( |
||
280 | 'Evaluating URL for NT %s getUrl: "%s" getFullSlug: "%s"', |
||
281 | $translation->getId(), |
||
282 | $translation->getUrl(), |
||
283 | $translation->getFullSlug() |
||
284 | ) |
||
285 | ); |
||
286 | |||
287 | return false; |
||
288 | } |
||
289 | |||
290 | // Adjust the URL. |
||
291 | $translation->setUrl($translation->getFullSlug()); |
||
292 | |||
293 | // Find all translations with this new URL, whose nodes are not deleted. |
||
294 | $translations = $nodeTranslationRepository->getAllNodeTranslationsForUrl( |
||
295 | $translation->getUrl(), |
||
296 | $translation->getLang(), |
||
297 | false, |
||
298 | $translation, |
||
299 | $this->domainConfiguration->getRootNode() |
||
300 | ); |
||
301 | |||
302 | $this->logger->debug( |
||
303 | sprintf( |
||
304 | 'Found %s node(s) that math url "%s"', |
||
305 | \count($translations), |
||
306 | $translation->getUrl() |
||
307 | ) |
||
308 | ); |
||
309 | |||
310 | $translationsWithSameUrl = []; |
||
311 | |||
312 | /** @var NodeTranslation $trans */ |
||
313 | foreach ($translations as $trans) { |
||
314 | if (!$this->pagesConfiguration->isStructureNode($trans->getPublicNodeVersion()->getRefEntityName())) { |
||
315 | $translationsWithSameUrl[] = $trans; |
||
316 | } |
||
317 | } |
||
318 | |||
319 | if (\count($translationsWithSameUrl) > 0) { |
||
320 | $oldUrl = $translation->getFullSlug(); |
||
321 | $translation->setSlug( |
||
322 | $this->slugifier->slugify( |
||
323 | $this->incrementString($translation->getSlug()) |
||
324 | ) |
||
325 | ); |
||
326 | $newUrl = $translation->getFullSlug(); |
||
327 | |||
328 | $message = sprintf( |
||
329 | 'The URL of the page has been changed from %s to %s since another page already uses this URL', |
||
330 | $oldUrl, |
||
331 | $newUrl |
||
332 | ); |
||
333 | $this->logger->info($message); |
||
334 | $flashes[] = $message; |
||
335 | |||
336 | $this->ensureUniqueUrl($translation, $em, $flashes); |
||
337 | } elseif (\count($flashes) > 0 && $this->isInRequestScope()) { |
||
338 | // No translations found so we're certain we can show this message. |
||
339 | $flash = current(\array_slice($flashes, -1)); |
||
340 | $this->getFlashBag()->add(FlashTypes::WARNING, $flash); |
||
341 | } |
||
342 | |||
343 | return true; |
||
344 | } |
||
345 | |||
346 | /** |
||
347 | * Increment a string that ends with a number. |
||
348 | * If the string does not end in a number we'll add the append and then add |
||
349 | * the first number. |
||
350 | * |
||
351 | * @param string $string the string we want to increment |
||
352 | * @param string $append the part we want to append before we start adding |
||
353 | * a number |
||
354 | * |
||
355 | * @return string incremented string |
||
356 | */ |
||
357 | View Code Duplication | private function incrementString($string, $append = '-v') |
|
358 | { |
||
359 | $finalDigitGrabberRegex = '/\d+$/'; |
||
360 | $matches = []; |
||
361 | |||
362 | preg_match($finalDigitGrabberRegex, $string, $matches); |
||
363 | |||
364 | if (\count($matches) > 0) { |
||
365 | $digit = (int) $matches[0]; |
||
366 | ++$digit; |
||
367 | |||
368 | // Replace the integer with the new digit. |
||
369 | return preg_replace($finalDigitGrabberRegex, $digit, $string); |
||
370 | } |
||
371 | |||
372 | return $string.$append.'1'; |
||
373 | } |
||
374 | |||
375 | /** |
||
376 | * @return bool |
||
377 | */ |
||
378 | private function isInRequestScope() |
||
379 | { |
||
380 | return $this->requestStack && $this->requestStack->getCurrentRequest(); |
||
381 | } |
||
382 | |||
383 | private function getFlashBag() |
||
384 | { |
||
385 | if ($this->flashBag instanceof SessionInterface) { |
||
386 | return $this->flashBag->getFlashBag(); |
||
387 | } |
||
388 | |||
389 | return $this->flashBag; |
||
390 | } |
||
391 | } |
||
392 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.