|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* This file is part of the Zikula package. |
|
5
|
|
|
* |
|
6
|
|
|
* Copyright Zikula Foundation - http://zikula.org/ |
|
7
|
|
|
* |
|
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
9
|
|
|
* file that was distributed with this source code. |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace Zikula\RoutesModule\Routing; |
|
13
|
|
|
|
|
14
|
|
|
use Doctrine\Common\Persistence\ObjectManager; |
|
15
|
|
|
use Symfony\Component\Config\Loader\Loader; |
|
16
|
|
|
use Symfony\Component\Routing\Route; |
|
17
|
|
|
use Symfony\Component\Routing\RouteCollection; |
|
18
|
|
|
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaHttpKernelInterface; |
|
19
|
|
|
use Symfony\Component\Translation\TranslatorInterface; |
|
20
|
|
|
use Zikula\Core\AbstractBundle; |
|
21
|
|
|
use Zikula\Core\AbstractModule; |
|
22
|
|
|
use Zikula\RoutesModule\Helper\SanitizeHelper; |
|
23
|
|
|
use Zikula\ThemeModule\AbstractTheme; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* Custom loader following http://symfony.com/doc/current/routing/custom_route_loader.html |
|
27
|
|
|
*/ |
|
28
|
|
|
class RouteLoader extends Loader |
|
29
|
|
|
{ |
|
30
|
|
|
/** |
|
31
|
|
|
* @var bool |
|
32
|
|
|
*/ |
|
33
|
|
|
private $loaded = false; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* @var ObjectManager |
|
37
|
|
|
*/ |
|
38
|
|
|
private $objectManager; |
|
39
|
|
|
|
|
40
|
|
|
/** |
|
41
|
|
|
* @var TranslatorInterface |
|
42
|
|
|
*/ |
|
43
|
|
|
private $translator; |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* @var ZikulaHttpKernelInterface |
|
47
|
|
|
*/ |
|
48
|
|
|
private $kernel; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* @var SanitizeHelper |
|
52
|
|
|
*/ |
|
53
|
|
|
private $sanitizeHelper; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* @var string |
|
57
|
|
|
*/ |
|
58
|
|
|
private $locale; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* RouteLoader constructor. |
|
62
|
|
|
* |
|
63
|
|
|
* @param ObjectManager $objectManager Doctrine object manager |
|
64
|
|
|
* @param TranslatorInterface $translator Translator |
|
65
|
|
|
* @param ZikulaHttpKernelInterface $kernel Zikula kernel |
|
66
|
|
|
* @param SanitizeHelper $sanitizeHelper Sanitize helper |
|
67
|
|
|
* @param string $locale |
|
68
|
|
|
*/ |
|
69
|
|
|
public function __construct( |
|
70
|
|
|
ObjectManager $objectManager, |
|
71
|
|
|
TranslatorInterface $translator, |
|
72
|
|
|
ZikulaHttpKernelInterface $kernel, |
|
73
|
|
|
SanitizeHelper $sanitizeHelper, |
|
74
|
|
|
$locale) |
|
75
|
|
|
{ |
|
76
|
|
|
$this->objectManager = $objectManager; |
|
77
|
|
|
$this->kernel = $kernel; |
|
78
|
|
|
$this->translator = $translator; |
|
79
|
|
|
$this->sanitizeHelper = $sanitizeHelper; |
|
80
|
|
|
$this->locale = $locale; |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* Finds all routes of all Zikula themes and modules. |
|
85
|
|
|
* |
|
86
|
|
|
* @return RouteCollection[] |
|
87
|
|
|
*/ |
|
88
|
|
|
private function findAll() |
|
89
|
|
|
{ |
|
90
|
|
|
$modules = $this->kernel->getModules(); |
|
91
|
|
|
$themes = $this->kernel->getThemes(); |
|
92
|
|
|
$bundles = array_merge($modules, $themes); |
|
93
|
|
|
|
|
94
|
|
|
$topRouteCollection = new RouteCollection(); |
|
95
|
|
|
$middleRouteCollection = new RouteCollection(); |
|
96
|
|
|
$bottomRouteCollection = new RouteCollection(); |
|
97
|
|
|
foreach ($bundles as $bundle) { |
|
98
|
|
|
list ($currentMiddleRouteCollection, $currentTopRouteCollection, $currentBottomRouteCollection) = $this->find($bundle); |
|
99
|
|
|
$middleRouteCollection->addCollection($currentMiddleRouteCollection); |
|
100
|
|
|
$topRouteCollection->addCollection($currentTopRouteCollection); |
|
101
|
|
|
$bottomRouteCollection->addCollection($currentBottomRouteCollection); |
|
102
|
|
|
} |
|
103
|
|
|
|
|
104
|
|
|
return [$middleRouteCollection, $topRouteCollection, $bottomRouteCollection]; |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
/** |
|
108
|
|
|
* Load routes of the specified module from the module's configuration file. |
|
109
|
|
|
* |
|
110
|
|
|
* @param AbstractBundle $bundle |
|
111
|
|
|
* |
|
112
|
|
|
* @return RouteCollection[] |
|
113
|
|
|
*/ |
|
114
|
|
|
private function find(AbstractBundle $bundle) |
|
115
|
|
|
{ |
|
116
|
|
|
try { |
|
117
|
|
|
$path = $this->kernel->locateResource($bundle->getRoutingConfig()); |
|
118
|
|
|
} catch (\InvalidArgumentException $e) { |
|
119
|
|
|
// Routing file does not exist (e.g. because the bundle could not be located). |
|
120
|
|
|
return [new RouteCollection(), new RouteCollection(), new RouteCollection()]; |
|
121
|
|
|
} |
|
122
|
|
|
$name = $bundle->getName(); |
|
123
|
|
|
|
|
124
|
|
|
$topRouteCollection = new RouteCollection(); |
|
125
|
|
|
$middleRouteCollection = new RouteCollection(); |
|
126
|
|
|
$bottomRouteCollection = new RouteCollection(); |
|
127
|
|
|
|
|
128
|
|
|
/** |
|
129
|
|
|
* These are all routes of the module, as loaded by Symfony. |
|
130
|
|
|
* @var RouteCollection $routeCollection |
|
131
|
|
|
*/ |
|
132
|
|
|
$routeCollection = $this->import($path); |
|
133
|
|
|
|
|
134
|
|
|
// Add all resources from the imported route collection to the middleRouteCollection. |
|
135
|
|
|
// The actual collection (top, middle, bottom) to add the resources too does not matter, |
|
136
|
|
|
// they just must be added to one of them, so that they don't get lost. |
|
137
|
|
|
foreach ($routeCollection->getResources() as $resource) { |
|
138
|
|
|
$middleRouteCollection->addResource($resource); |
|
139
|
|
|
} |
|
140
|
|
|
// It would be great to auto-reload routes here if the module version changes or a module is uninstalled. |
|
141
|
|
|
// @todo this seems to be possible in Symfony 2.8+ - check these out: |
|
142
|
|
|
// - https://github.com/symfony/symfony/issues/7176 |
|
143
|
|
|
// - https://github.com/symfony/symfony/pull/15738 |
|
144
|
|
|
// - https://github.com/symfony/symfony/pull/15692 |
|
145
|
|
|
// $routeCollection->addResource(new ZikulaResource()) |
|
|
|
|
|
|
146
|
|
|
|
|
147
|
|
|
/** @var Route $route */ |
|
148
|
|
|
foreach ($routeCollection as $oldRouteName => $route) { |
|
149
|
|
|
// set break here with $oldRouteName == 'zikula_routesmodule_route_renew' |
|
150
|
|
|
$this->fixRequirements($route); |
|
151
|
|
|
$this->prependBundlePrefix($route, $bundle); |
|
152
|
|
|
list($type, $func) = $this->setZikulaDefaults($route, $bundle, $name); |
|
153
|
|
|
$routeName = $this->getRouteName($oldRouteName, $name, $type, $func); |
|
154
|
|
|
|
|
155
|
|
|
if ($route->hasOption('zkPosition')) { |
|
156
|
|
|
switch ($route->getOption('zkPosition')) { |
|
157
|
|
|
case 'top': |
|
158
|
|
|
$topRouteCollection->add($routeName, $route); |
|
159
|
|
|
break; |
|
160
|
|
|
case 'bottom': |
|
161
|
|
|
$bottomRouteCollection->add($routeName, $route); |
|
162
|
|
|
break; |
|
163
|
|
|
default: |
|
164
|
|
|
throw new \RuntimeException('Unknown route position. Got "' . $route->getOption('zkPosition') . '", expected "top" or "bottom"'); |
|
165
|
|
|
} |
|
166
|
|
|
} else { |
|
167
|
|
|
$middleRouteCollection->add($routeName, $route); |
|
168
|
|
|
} |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
|
return [$middleRouteCollection, $topRouteCollection, $bottomRouteCollection]; |
|
172
|
|
|
} |
|
173
|
|
|
|
|
174
|
|
|
public function load($resource, $type = null) |
|
175
|
|
|
{ |
|
176
|
|
|
if (true === $this->loaded) { |
|
177
|
|
|
throw new \RuntimeException('Do not add the "zikularoutesmodule" loader twice'); |
|
178
|
|
|
} |
|
179
|
|
|
unset($type); |
|
180
|
|
|
|
|
181
|
|
|
$routeCollection = new RouteCollection(); |
|
182
|
|
|
|
|
183
|
|
|
list ($newRouteCollection, $topRouteCollection, $bottomRouteCollection) = $this->findAll(); |
|
184
|
|
|
|
|
185
|
|
|
$routeCollection->addCollection($topRouteCollection); |
|
186
|
|
|
|
|
187
|
|
|
// try { |
|
|
|
|
|
|
188
|
|
|
// $routes = $this->objectManager->getRepository('ZikulaRoutesModule:RouteEntity')->findBy([], ['group' => 'ASC', 'sort' => 'ASC']); |
|
189
|
|
|
// } catch (DBALException $e) { |
|
190
|
|
|
// // It seems like the module is not yet installed. Fail silently. |
|
191
|
|
|
// return $routeCollection; |
|
192
|
|
|
// } |
|
193
|
|
|
// |
|
194
|
|
|
// if (!empty($routes)) { |
|
195
|
|
|
// /** |
|
196
|
|
|
// * @var \Zikula\RoutesModule\Entity\RouteEntity $dbRoute |
|
197
|
|
|
// */ |
|
198
|
|
|
// foreach ($routes as $dbRoute) { |
|
199
|
|
|
// // Add modname, type and func to the route's default values. |
|
200
|
|
|
// $defaults = $dbRoute->getDefaults(); |
|
201
|
|
|
// $defaults['_zkModule'] = $dbRoute->getBundle(); |
|
202
|
|
|
// list (, $type) = $this->sanitizeHelper->sanitizeController($dbRoute->getController()); |
|
203
|
|
|
// list (, $func) = $this->sanitizeHelper->sanitizeAction($dbRoute->getAction()); |
|
204
|
|
|
// $defaults['_zkType'] = $type; |
|
205
|
|
|
// $defaults['_zkFunc'] = $func; |
|
206
|
|
|
// // @todo @cmfcmf when reimplementing loading routews from DB, see #2593 (i.e. ucfirst problems) |
|
207
|
|
|
// $defaults['_controller'] = $dbRoute->getBundle() . ":" . ucfirst($type) . ":" . ucfirst($func); |
|
208
|
|
|
// |
|
209
|
|
|
// // We have to prepend the bundle prefix (see detailed description in docblock of prependBundlePrefix() method). |
|
210
|
|
|
// $options = $dbRoute->getOptions(); |
|
211
|
|
|
// $prependBundle = !isset($GLOBALS['translation_extract_routes']) && isset($options['i18n']) && !$options['i18n']; |
|
212
|
|
|
// if ($prependBundle) { |
|
213
|
|
|
// $path = $dbRoute->getPathWithBundlePrefix(); |
|
214
|
|
|
// } else { |
|
215
|
|
|
// $path = $dbRoute->getPath(); |
|
216
|
|
|
// } |
|
217
|
|
|
// |
|
218
|
|
|
// $this->fixRequirements($dbRoute); |
|
219
|
|
|
// |
|
220
|
|
|
// $route = new Route( |
|
221
|
|
|
// $path, |
|
222
|
|
|
// $defaults, |
|
223
|
|
|
// $dbRoute->getRequirements(), |
|
224
|
|
|
// $options, |
|
225
|
|
|
// $dbRoute->getHost(), |
|
226
|
|
|
// $dbRoute->getSchemes(), |
|
227
|
|
|
// $dbRoute->getMethods(), |
|
228
|
|
|
// $dbRoute->getCondition() |
|
229
|
|
|
// ); |
|
230
|
|
|
// |
|
231
|
|
|
// $routeCollection->add($dbRoute->getName(), $route); |
|
232
|
|
|
// } |
|
233
|
|
|
// } |
|
234
|
|
|
$routeCollection->addCollection($newRouteCollection); |
|
235
|
|
|
$routeCollection->addCollection($bottomRouteCollection); |
|
236
|
|
|
|
|
237
|
|
|
$this->loaded = true; |
|
238
|
|
|
|
|
239
|
|
|
return $routeCollection; |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* Sets some Zikula-specific defaults for the routes. |
|
244
|
|
|
* |
|
245
|
|
|
* @param Route $route The route instance |
|
246
|
|
|
* @param AbstractBundle $bundle The bundle |
|
247
|
|
|
* @param string $bundleName The bundle's name |
|
248
|
|
|
* |
|
249
|
|
|
* @return array The legacy $type and $func parameters |
|
250
|
|
|
*/ |
|
251
|
|
|
private function setZikulaDefaults(Route $route, AbstractBundle $bundle, $bundleName) |
|
252
|
|
|
{ |
|
253
|
|
|
$defaults = $route->getDefaults(); |
|
254
|
|
|
|
|
255
|
|
|
$defaults['_zkBundle'] = $bundleName; |
|
256
|
|
|
if ($bundle instanceof AbstractModule) { |
|
257
|
|
|
$defaults['_zkModule'] = $bundleName; |
|
258
|
|
|
} else if ($bundle instanceof AbstractTheme) { |
|
259
|
|
|
$defaults['_zkTheme'] = $bundleName; |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
$controller = $this->sanitizeController($bundleName, $defaults['_controller']); |
|
263
|
|
|
$controller = explode(':', $controller); |
|
264
|
|
|
$defaults['_zkType'] = $type = lcfirst($controller[1]); |
|
265
|
|
|
$defaults['_zkFunc'] = $func = lcfirst($controller[2]); |
|
266
|
|
|
$defaults['_controller'] = $bundleName . ':' . $controller[1] . ':' . $func; |
|
267
|
|
|
|
|
268
|
|
|
$route->setDefaults($defaults); |
|
269
|
|
|
|
|
270
|
|
|
return [$type, $func]; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
/** |
|
274
|
|
|
* Removes some deprecated requirements which cause depreciation notices. |
|
275
|
|
|
* |
|
276
|
|
|
* @param Route $route |
|
277
|
|
|
* |
|
278
|
|
|
* @todo Remove when Symfony 3.0 is used. |
|
279
|
|
|
*/ |
|
280
|
|
|
private function fixRequirements(Route $route) |
|
281
|
|
|
{ |
|
282
|
|
|
$requirements = $route->getRequirements(); |
|
283
|
|
|
if (isset($requirements['_method'])) { |
|
284
|
|
|
unset($requirements['_method']); |
|
285
|
|
|
} |
|
286
|
|
|
if (isset($requirements['_scheme'])) { |
|
287
|
|
|
unset($requirements['_scheme']); |
|
288
|
|
|
} |
|
289
|
|
|
$route->setRequirements($requirements); |
|
290
|
|
|
} |
|
291
|
|
|
|
|
292
|
|
|
/** |
|
293
|
|
|
* Prepends the bundle prefix to the route. |
|
294
|
|
|
* |
|
295
|
|
|
* @param Route $route |
|
296
|
|
|
* @param AbstractBundle $bundle |
|
297
|
|
|
* |
|
298
|
|
|
* We have to prepend the bundle prefix if |
|
299
|
|
|
* - routes are _not_ currently extracted via the command line and |
|
300
|
|
|
* - the route has i18n set to false. |
|
301
|
|
|
* This is because when extracting the routes, a bundle author only wants to translate the bare route |
|
302
|
|
|
* patterns, without a redundant and potentially customized bundle prefix in front of them. |
|
303
|
|
|
* If i18n is set to true, Zikula's customized pattern generation strategy will take care of it. |
|
304
|
|
|
* See Zikula\RoutesModule\Translation\ZikulaPatternGenerationStrategy |
|
305
|
|
|
*/ |
|
306
|
|
|
private function prependBundlePrefix(Route $route, AbstractBundle $bundle) |
|
|
|
|
|
|
307
|
|
|
{ |
|
308
|
|
|
$prefix = ''; |
|
309
|
|
|
$options = $route->getOptions(); |
|
310
|
|
|
$prependBundle = !isset($GLOBALS['translation_extract_routes']) && isset($options['i18n']) && !$options['i18n']; |
|
311
|
|
|
if (!$prependBundle || (isset($options['zkNoBundlePrefix']) && $options['zkNoBundlePrefix'])) { |
|
312
|
|
|
return; |
|
313
|
|
|
} |
|
314
|
|
|
|
|
315
|
|
|
// get url from MetaData first. May be empty. |
|
316
|
|
|
$untranslatedPrefix = $bundle->getMetaData()->getUrl(false); |
|
317
|
|
|
if (empty($untranslatedPrefix)) { |
|
318
|
|
|
// @todo remove in 2.0 |
|
319
|
|
|
try { |
|
320
|
|
|
// MetaData will be empty for extensions not Spec-2.0. Try to get from modinfo. |
|
321
|
|
|
// this calls the DB which is not available during install. |
|
322
|
|
|
$modinfo = \ModUtil::getInfoFromName($bundle->getName()); |
|
323
|
|
|
$prefix = $modinfo['url']; |
|
324
|
|
|
} catch (\Exception $e) { |
|
|
|
|
|
|
325
|
|
|
} |
|
326
|
|
|
} else { |
|
327
|
|
|
if ($this->translator->getCatalogue($this->locale)->has($untranslatedPrefix, strtolower($bundle->getName()))) { |
|
328
|
|
|
$prefix = $this->translator->trans(/** @Ignore */$untranslatedPrefix, [], strtolower($bundle->getName()), $this->locale); |
|
329
|
|
|
} else { |
|
330
|
|
|
$prefix = $untranslatedPrefix; |
|
331
|
|
|
} |
|
332
|
|
|
} |
|
333
|
|
|
|
|
334
|
|
|
$path = '/' . $prefix . $route->getPath(); |
|
335
|
|
|
$route->setPath($path); |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
/** |
|
339
|
|
|
* Converts the controller identifier into a unified form. |
|
340
|
|
|
* |
|
341
|
|
|
* @param string $bundleName The name of the bundle |
|
342
|
|
|
* @param string $controllerString The given controller identifier |
|
343
|
|
|
* |
|
344
|
|
|
* @return string The controller identifier in a Bundle:Type:func form |
|
345
|
|
|
*/ |
|
346
|
|
|
private function sanitizeController($bundleName, $controllerString) |
|
347
|
|
|
{ |
|
348
|
|
|
if (0 === preg_match('#^(.*?\\\\Controller\\\\(.+)Controller)::(.+)Action$#', $controllerString, $match)) { |
|
349
|
|
|
return $controllerString; |
|
350
|
|
|
} |
|
351
|
|
|
|
|
352
|
|
|
// Bundle:controller:action |
|
353
|
|
|
return $bundleName . ':' . $match[2] . ':' . $match[3]; |
|
354
|
|
|
} |
|
355
|
|
|
|
|
356
|
|
|
/** |
|
357
|
|
|
* Generates the route's new name. |
|
358
|
|
|
* |
|
359
|
|
|
* @param string $oldRouteName The old route name |
|
360
|
|
|
* @param string $bundleName The bundle name |
|
361
|
|
|
* @param string $type The legacy type |
|
362
|
|
|
* @param string $func The legacy func |
|
363
|
|
|
* |
|
364
|
|
|
* @return string The route's new name |
|
365
|
|
|
*/ |
|
366
|
|
|
private function getRouteName($oldRouteName, $bundleName, $type, $func) |
|
367
|
|
|
{ |
|
368
|
|
|
$suffix = ''; |
|
369
|
|
|
$lastPart = substr($oldRouteName, strrpos($oldRouteName, '_')); |
|
370
|
|
|
if (is_numeric($lastPart)) { |
|
371
|
|
|
// If the last part of the old route name is numeric, also append it to the new route name. |
|
372
|
|
|
// This allows multiple routes for the same action. |
|
373
|
|
|
$suffix = '_' . $lastPart; |
|
374
|
|
|
} |
|
375
|
|
|
return strtolower($bundleName . '_' . $type . '_' . $func) . $suffix; |
|
376
|
|
|
} |
|
377
|
|
|
|
|
378
|
|
|
/** |
|
379
|
|
|
* Checks whether this route loader supports a given route type or not. |
|
380
|
|
|
* |
|
381
|
|
|
* @return boolean |
|
382
|
|
|
*/ |
|
383
|
|
|
public function supports($resource, $type = null) |
|
384
|
|
|
{ |
|
385
|
|
|
return 'zikularoutesmodule' === $type; |
|
386
|
|
|
} |
|
387
|
|
|
} |
|
388
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.