Completed
Push — master ( a128a1...e19108 )
by Craig
16:51
created

ExtensionController::modifyAction()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 45
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 25
c 2
b 0
f 0
nc 11
nop 5
dl 0
loc 45
rs 8.4444
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula Foundation - https://ziku.la/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Zikula\ExtensionsModule\Controller;
15
16
use RuntimeException;
17
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
18
use Symfony\Component\HttpFoundation\RedirectResponse;
19
use Symfony\Component\HttpFoundation\Request;
20
use Symfony\Component\HttpFoundation\Response;
21
use Symfony\Component\Routing\Annotation\Route;
22
use Symfony\Component\Routing\RouterInterface;
23
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
24
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
25
use Zikula\BlocksModule\Entity\RepositoryInterface\BlockRepositoryInterface;
26
use Zikula\Bundle\CoreBundle\CacheClearer;
27
use Zikula\Bundle\CoreBundle\Composer\MetaData;
28
use Zikula\Bundle\CoreBundle\Controller\AbstractController;
29
use Zikula\Bundle\CoreBundle\Event\GenericEvent;
30
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaHttpKernelInterface;
31
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaKernel;
32
use Zikula\Bundle\FormExtensionBundle\Form\Type\DeletionType;
33
use Zikula\Component\SortableColumns\Column;
34
use Zikula\Component\SortableColumns\SortableColumns;
35
use Zikula\ExtensionsModule\AbstractExtension;
36
use Zikula\ExtensionsModule\Constant;
37
use Zikula\ExtensionsModule\Entity\ExtensionEntity;
38
use Zikula\ExtensionsModule\Entity\RepositoryInterface\ExtensionRepositoryInterface;
39
use Zikula\ExtensionsModule\Event\ExtensionStateEvent;
40
use Zikula\ExtensionsModule\ExtensionEvents;
41
use Zikula\ExtensionsModule\Form\Type\ExtensionInstallType;
42
use Zikula\ExtensionsModule\Form\Type\ExtensionModifyType;
43
use Zikula\ExtensionsModule\Helper\BundleSyncHelper;
44
use Zikula\ExtensionsModule\Helper\ExtensionDependencyHelper;
45
use Zikula\ExtensionsModule\Helper\ExtensionHelper;
46
use Zikula\ExtensionsModule\Helper\ExtensionStateHelper;
47
use Zikula\ThemeModule\Engine\Annotation\Theme;
48
use Zikula\ThemeModule\Engine\Engine;
49
50
/**
51
 * Class ExtensionController
52
 * @Route("")
53
 */
54
class ExtensionController extends AbstractController
55
{
56
    private const NEW_ROUTES_AVAIL = 'new.routes.avail';
57
58
    /**
59
     * @Route("/list/{pos}")
60
     * @Theme("admin")
61
     * @Template("@ZikulaExtensionsModule/Extension/list.html.twig")
62
     *
63
     * @throws AccessDeniedException Thrown if the user doesn't have admin permissions for the module
64
     */
65
    public function listAction(
66
        Request $request,
67
        EventDispatcherInterface $eventDispatcher,
68
        ExtensionRepositoryInterface $extensionRepository,
69
        BundleSyncHelper $bundleSyncHelper,
70
        RouterInterface $router,
71
        int $pos = 1
72
    ): array {
73
        if (!$this->hasPermission('ZikulaExtensionsModule::', '::', ACCESS_ADMIN)) {
74
            throw new AccessDeniedException();
75
        }
76
        $modulesJustInstalled = $request->query->get('justinstalled');
77
        if (!empty($modulesJustInstalled)) {
78
            // notify the event dispatcher that new routes are available (ids of modules just installed avail as args)
79
            $event = new GenericEvent(null, json_decode($modulesJustInstalled));
80
            $eventDispatcher->dispatch($event, self::NEW_ROUTES_AVAIL);
81
        }
82
83
        $sortableColumns = new SortableColumns($router, 'zikulaextensionsmodule_extension_list');
84
        $sortableColumns->addColumns([new Column('displayname'), new Column('state')]);
85
        $sortableColumns->setOrderByFromRequest($request);
86
87
        $upgradedExtensions = [];
88
        $vetoEvent = new GenericEvent();
89
        $eventDispatcher->dispatch($vetoEvent, ExtensionEvents::REGENERATE_VETO);
90
        if (1 === $pos && !$vetoEvent->isPropagationStopped()) {
91
            // regenerate the extension list only when viewing the first page
92
            $extensionsInFileSystem = $bundleSyncHelper->scanForBundles();
93
            $upgradedExtensions = $bundleSyncHelper->syncExtensions($extensionsInFileSystem);
94
        }
95
96
        $pagedResult = $extensionRepository->getPagedCollectionBy([], [
97
            $sortableColumns->getSortColumn()->getName() => $sortableColumns->getSortDirection()
98
        ], $this->getVar('itemsperpage'), $pos);
0 ignored issues
show
Bug introduced by
It seems like $this->getVar('itemsperpage') can also be of type false; however, parameter $limit of Zikula\ExtensionsModule\...:getPagedCollectionBy() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

98
        ], /** @scrutinizer ignore-type */ $this->getVar('itemsperpage'), $pos);
Loading history...
99
100
        return [
101
            'sort' => $sortableColumns->generateSortableColumns(),
102
            'pager' => [
103
                'limit' => $this->getVar('itemsperpage'),
104
                'count' => count($pagedResult)
105
            ],
106
            'extensions' => $pagedResult,
107
            'upgradedExtensions' => $upgradedExtensions
108
        ];
109
    }
110
111
    /**
112
     * @Route("/activate/{id}/{token}", methods = {"GET"}, requirements={"id" = "^[1-9]\d*$"})
113
     *
114
     * Activate an extension.
115
     *
116
     * @throws AccessDeniedException Thrown if the user doesn't have admin permissions for the module
117
     */
118
    public function activateAction(
119
        int $id,
120
        string $token,
121
        ExtensionRepositoryInterface $extensionRepository,
122
        ExtensionStateHelper $extensionStateHelper,
123
        CacheClearer $cacheClearer
124
    ): RedirectResponse {
125
        if (!$this->hasPermission('ZikulaExtensionsModule::', '::', ACCESS_ADMIN)) {
126
            throw new AccessDeniedException();
127
        }
128
129
        if (!$this->isCsrfTokenValid('activate-extension', $token)) {
130
            throw new AccessDeniedException();
131
        }
132
133
        /** @var ExtensionEntity $extension */
134
        $extension = $extensionRepository->find($id);
135
        if (Constant::STATE_NOTALLOWED === $extension->getState()) {
136
            $this->addFlash('error', $this->trans('Error! Activation of %name% not allowed.', ['%name%' => $extension->getName()]));
137
        } else {
138
            // Update state
139
            $extensionStateHelper->updateState($id, Constant::STATE_ACTIVE);
140
            $cacheClearer->clear('symfony');
141
            $this->addFlash('status', $this->trans('Done! Activated %name%.', ['%name%' => $extension->getName()]));
142
        }
143
144
        return $this->redirectToRoute('zikulaextensionsmodule_extension_list');
145
    }
146
147
    /**
148
     * @Route("/deactivate/{id}/{token}", methods = {"GET"}, requirements={"id" = "^[1-9]\d*$"})
149
     *
150
     * Deactivate an extension
151
     *
152
     * @throws AccessDeniedException Thrown if the user doesn't have admin permissions for the module
153
     */
154
    public function deactivateAction(
155
        int $id,
156
        string $token,
157
        ExtensionRepositoryInterface $extensionRepository,
158
        ExtensionStateHelper $extensionStateHelper,
159
        CacheClearer $cacheClearer
160
    ): RedirectResponse {
161
        if (!$this->hasPermission('ZikulaExtensionsModule::', '::', ACCESS_ADMIN)) {
162
            throw new AccessDeniedException();
163
        }
164
165
        if (!$this->isCsrfTokenValid('deactivate-extension', $token)) {
166
            throw new AccessDeniedException();
167
        }
168
        // @todo check if this is a theme and currently set as default or admin theme
169
170
        /** @var ExtensionEntity $extension */
171
        $extension = $extensionRepository->find($id);
172
        if (null !== $extension) {
173
            if (ZikulaKernel::isCoreExtension($extension->getName())) {
174
                $this->addFlash('error', $this->trans('Error! You cannot deactivate the %name%. It is required by the system.', ['%name%' => $extension->getName()]));
175
            } else {
176
                // Update state
177
                $extensionStateHelper->updateState($id, Constant::STATE_INACTIVE);
178
                $cacheClearer->clear('symfony');
179
                $this->addFlash('status', $this->trans('Done! Deactivated %name%.', ['%name%' => $extension->getName()]));
180
            }
181
        }
182
183
        return $this->redirectToRoute('zikulaextensionsmodule_extension_list');
184
    }
185
186
    /**
187
     * @Route("/modify/{id}/{forceDefaults}", requirements={"id" = "^[1-9]\d*$", "forceDefaults" = "0|1"})
188
     * @Theme("admin")
189
     * @Template("@ZikulaExtensionsModule/Extension/modify.html.twig")
190
     *
191
     * Modify a module.
192
     *
193
     * @return array|RedirectResponse
194
     * @throws AccessDeniedException Thrown if the user doesn't have admin permissions for modifying the extension
195
     */
196
    public function modifyAction(
197
        Request $request,
198
        ZikulaHttpKernelInterface $kernel,
199
        ExtensionEntity $extension,
200
        CacheClearer $cacheClearer,
201
        bool $forceDefaults = false
202
    ) {
203
        if (!$this->hasPermission('ZikulaExtensionsModule::modify', $extension->getName() . '::' . $extension->getId(), ACCESS_ADMIN)) {
204
            throw new AccessDeniedException();
205
        }
206
207
        /** @var AbstractExtension $extensionBundle */
208
        $extensionBundle = $kernel->getBundle($extension->getName());
209
        $metaData = $extensionBundle->getMetaData()->getFilteredVersionInfoArray();
210
211
        if ($forceDefaults) {
212
            $extension->setName($metaData['name']);
213
            $extension->setUrl($metaData['url']);
214
            $extension->setDescription($metaData['description']);
215
        }
216
217
        $form = $this->createForm(ExtensionModifyType::class, $extension);
218
        $form->handleRequest($request);
219
        if ($form->isSubmitted() && $form->isValid()) {
220
            if ($form->get('defaults')->isClicked()) {
0 ignored issues
show
Bug introduced by
The method isClicked() does not exist on Symfony\Component\Form\FormInterface. It seems like you code against a sub-type of Symfony\Component\Form\FormInterface such as Symfony\Component\Form\SubmitButton. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

220
            if ($form->get('defaults')->/** @scrutinizer ignore-call */ isClicked()) {
Loading history...
221
                $this->addFlash('info', 'Default values reloaded. Save to confirm.');
222
223
                return $this->redirectToRoute('zikulaextensionsmodule_extension_modify', ['id' => $extension->getId(), 'forceDefaults' => 1]);
224
            }
225
            if ($form->get('save')->isClicked()) {
226
                $em = $this->getDoctrine()->getManager();
227
                $em->persist($extension);
228
                $em->flush();
229
230
                $cacheClearer->clear('symfony');
231
                $this->addFlash('status', 'Done! Extension updated.');
232
            } elseif ($form->get('cancel')->isClicked()) {
233
                $this->addFlash('status', 'Operation cancelled.');
234
            }
235
236
            return $this->redirectToRoute('zikulaextensionsmodule_extension_list');
237
        }
238
239
        return [
240
            'form' => $form->createView()
241
        ];
242
    }
243
244
    /**
245
     * @Route("/compatibility/{id}", methods = {"GET"}, requirements={"id" = "^[1-9]\d*$"})
246
     * @Theme("admin")
247
     * @Template("@ZikulaExtensionsModule/Extension/compatibility.html.twig")
248
     *
249
     * Display information of a module compatibility with the version of the core
250
     *
251
     * @throws AccessDeniedException Thrown if the user doesn't have admin permission to the requested module
252
     */
253
    public function compatibilityAction(ExtensionEntity $extension): array
254
    {
255
        if (!$this->hasPermission('ZikulaExtensionsModule::', $extension->getName() . '::' . $extension->getId(), ACCESS_ADMIN)) {
256
            throw new AccessDeniedException();
257
        }
258
259
        return [
260
            'extension' => $extension
261
        ];
262
    }
263
264
    /**
265
     * @Route("/install/{id}/{token}", requirements={"id" = "^[1-9]\d*$"})
266
     * @Theme("admin")
267
     * @Template("@ZikulaExtensionsModule/Extension/install.html.twig")
268
     *
269
     * Install and initialise an extension.
270
     *
271
     * @return array|RedirectResponse
272
     * @throws AccessDeniedException Thrown if the user doesn't have admin permission for the module
273
     */
274
    public function installAction(
275
        Request $request,
276
        ExtensionEntity $extension,
277
        string $token,
278
        ZikulaHttpKernelInterface $kernel,
279
        ExtensionRepositoryInterface $extensionRepository,
280
        ExtensionHelper $extensionHelper,
281
        ExtensionStateHelper $extensionStateHelper,
282
        ExtensionDependencyHelper $dependencyHelper,
283
        CacheClearer $cacheClearer
284
    ) {
285
        if (!$this->hasPermission('ZikulaExtensionsModule::', '::', ACCESS_ADMIN)) {
286
            throw new AccessDeniedException();
287
        }
288
289
        $id = $extension->getId();
290
        if (!$this->isCsrfTokenValid('install-extension', $token)) {
291
            throw new AccessDeniedException();
292
        }
293
294
        if (!$kernel->isBundle($extension->getName())) {
295
            $extensionStateHelper->updateState($id, Constant::STATE_TRANSITIONAL);
296
            $cacheClearer->clear('symfony');
297
298
            return $this->redirectToRoute('zikulaextensionsmodule_extension_install', ['id' => $id, 'token' => $token]);
299
        }
300
        $unsatisfiedDependencies = $dependencyHelper->getUnsatisfiedExtensionDependencies($extension);
301
        $form = $this->createForm(ExtensionInstallType::class, [
302
            'dependencies' => $this->formatDependencyCheckboxArray($extensionRepository, $unsatisfiedDependencies)
303
        ]);
304
        $hasNoUnsatisfiedDependencies = empty($unsatisfiedDependencies);
305
        $form->handleRequest($request);
306
        if ($hasNoUnsatisfiedDependencies || ($form->isSubmitted() && $form->isValid())) {
307
            if ($hasNoUnsatisfiedDependencies || $form->get('install')->isClicked()) {
308
                $extensionsInstalled = [];
309
                $data = $form->getData();
310
                foreach ($data['dependencies'] as $dependencyId => $installSelected) {
311
                    if (!$installSelected && MetaData::DEPENDENCY_REQUIRED !== $unsatisfiedDependencies[$dependencyId]->getStatus()) {
312
                        continue;
313
                    }
314
                    $dependencyExtensionEntity = $extensionRepository->get($unsatisfiedDependencies[$dependencyId]->getModname());
315
                    if (isset($dependencyExtensionEntity)) {
316
                        if (!$extensionHelper->install($dependencyExtensionEntity)) {
317
                            $this->addFlash('error', $this->trans('Failed to install dependency "%name%"!', ['%name%' => $dependencyExtensionEntity->getName()]));
318
319
                            return $this->redirectToRoute('zikulaextensionsmodule_extension_list');
320
                        }
321
                        $extensionsInstalled[] = $dependencyExtensionEntity->getId();
322
                        $this->addFlash('status', $this->trans('Installed dependency "%name%".', ['%name%' => $dependencyExtensionEntity->getName()]));
323
                    } else {
324
                        $this->addFlash('warning', $this->trans('Warning: could not install selected dependency "%name%".', ['%name%' => $unsatisfiedDependencies[$dependencyId]->getModname()]));
325
                    }
326
                }
327
                if ($extensionHelper->install($extension)) {
328
                    $this->addFlash('status', $this->trans('Done! Installed "%name%".', ['%name%' => $extension->getName()]));
329
                    $extensionsInstalled[] = $id;
330
                    $cacheClearer->clear('symfony');
331
332
                    return $this->redirectToRoute('zikulaextensionsmodule_extension_postinstall', ['extensions' => json_encode($extensionsInstalled)]);
333
                }
334
                $extensionStateHelper->updateState($id, Constant::STATE_UNINITIALISED);
335
                $this->addFlash('error', $this->trans('Initialization of "%name%" failed!', ['%name%' => $extension->getName()]));
336
            }
337
            if ($form->get('cancel')->isClicked()) {
338
                $extensionStateHelper->updateState($id, Constant::STATE_UNINITIALISED);
339
                $this->addFlash('status', 'Operation cancelled.');
340
            }
341
342
            return $this->redirectToRoute('zikulaextensionsmodule_extension_list');
343
        }
344
345
        return [
346
            'dependencies' => $unsatisfiedDependencies,
347
            'extension' => $extension,
348
            'form' => $form->createView()
349
        ];
350
    }
351
352
    /**
353
     * Post-installation action to trigger the MODULE_POSTINSTALL event.
354
     * The additional Action is required because this event must occur AFTER the rebuild of the cache which occurs on Request.
355
     *
356
     * @Route("/postinstall/{extensions}", methods = {"GET"})
357
     */
358
    public function postInstallAction(
359
        ExtensionRepositoryInterface $extensionRepository,
360
        ZikulaHttpKernelInterface $kernel,
361
        EventDispatcherInterface $eventDispatcher,
362
        string $extensions = null
363
    ): RedirectResponse {
364
        if (!empty($extensions)) {
365
            $extensions = json_decode($extensions);
366
            foreach ($extensions as $extensionId) {
367
                /** @var ExtensionEntity $extensionEntity */
368
                $extensionEntity = $extensionRepository->find($extensionId);
369
                if (null === $extensionRepository) {
370
                    continue;
371
                }
372
                /** @var AbstractExtension $extensionBundle */
373
                $extensionBundle = $kernel->getBundle($extensionEntity->getName());
374
                if (null === $extensionBundle) {
375
                    continue;
376
                }
377
                $event = new ExtensionStateEvent($extensionBundle, $extensionEntity->toArray());
378
                $eventDispatcher->dispatch($event, ExtensionEvents::EXTENSION_POSTINSTALL);
379
            }
380
        }
381
382
        return $this->redirectToRoute('zikulaextensionsmodule_extension_list', ['justinstalled' => json_encode($extensions)]);
383
    }
384
385
    /**
386
     * Create array suitable for checkbox FormType [[ID => bool][ID => bool]].
387
     */
388
    private function formatDependencyCheckboxArray(
389
        ExtensionRepositoryInterface $extensionRepository,
390
        array $dependencies
391
    ): array {
392
        $return = [];
393
        foreach ($dependencies as $dependency) {
394
            /** @var ExtensionEntity $dependencyExtension */
395
            $dependencyExtension = $extensionRepository->get($dependency->getModname());
396
            $return[$dependency->getId()] = null !== $dependencyExtension;
397
        }
398
399
        return $return;
400
    }
401
402
    /**
403
     * @Route("/upgrade/{id}/{token}", requirements={"id" = "^[1-9]\d*$"})
404
     *
405
     * Upgrade an extension.
406
     *
407
     * @throws AccessDeniedException Thrown if the user doesn't have admin permission for the module
408
     */
409
    public function upgradeAction(
410
        ExtensionEntity $extension,
411
        $token,
412
        ExtensionHelper $extensionHelper
413
    ): RedirectResponse {
414
        if (!$this->hasPermission('ZikulaExtensionsModule::', '::', ACCESS_ADMIN)) {
415
            throw new AccessDeniedException();
416
        }
417
418
        if (!$this->isCsrfTokenValid('upgrade-extension', $token)) {
419
            throw new AccessDeniedException();
420
        }
421
422
        $result = $extensionHelper->upgrade($extension);
423
        if ($result) {
424
            $this->addFlash('status', $this->trans('%name% upgraded to new version and activated.', ['%name%' => $extension->getDisplayname()]));
425
        } else {
426
            $this->addFlash('error', 'Extension upgrade failed!');
427
        }
428
429
        return $this->redirectToRoute('zikulaextensionsmodule_extension_list');
430
    }
431
432
    /**
433
     * @Route("/uninstall/{id}/{token}", requirements={"id" = "^[1-9]\d*$"})
434
     * @Theme("admin")
435
     * @Template("@ZikulaExtensionsModule/Extension/uninstall.html.twig")
436
     *
437
     * Uninstall an extension.
438
     *
439
     * @return array|Response|RedirectResponse
440
     * @throws AccessDeniedException Thrown if the user doesn't have admin permission for the module
441
     */
442
    public function uninstallAction(
443
        Request $request,
444
        ExtensionEntity $extension,
445
        string $token,
446
        ZikulaHttpKernelInterface $kernel,
447
        BlockRepositoryInterface $blockRepository,
448
        ExtensionHelper $extensionHelper,
449
        ExtensionStateHelper $extensionStateHelper,
450
        ExtensionDependencyHelper $dependencyHelper,
451
        CacheClearer $cacheClearer
452
    ) {
453
        if (!$this->hasPermission('ZikulaExtensionsModule::', '::', ACCESS_ADMIN)) {
454
            throw new AccessDeniedException();
455
        }
456
457
        if (!$this->isCsrfTokenValid('uninstall-extension', $token)) {
458
            throw new AccessDeniedException();
459
        }
460
461
        if (Constant::STATE_MISSING === $extension->getState()) {
462
            throw new RuntimeException($this->trans('Error! The requested extension cannot be uninstalled because its files are missing!'));
463
        }
464
        if (!$kernel->isBundle($extension->getName())) {
465
            $extensionStateHelper->updateState($extension->getId(), Constant::STATE_TRANSITIONAL);
466
            $cacheClearer->clear('symfony');
467
        }
468
        $requiredDependents = $dependencyHelper->getDependentExtensions($extension);
469
        $blocks = $blockRepository->findBy(['module' => $extension]);
470
471
        $form = $this->createForm(DeletionType::class, [], [
472
            'action' => $this->generateUrl('zikulaextensionsmodule_extension_uninstall', [
473
                'id' => $extension->getId(),
474
                'token' => $token
475
            ]),
476
        ]);
477
        $form->handleRequest($request);
478
        if ($form->isSubmitted() && $form->isValid()) {
479
            if ($form->get('delete')->isClicked()) {
480
                // remove dependent extensions
481
                if (!$extensionHelper->uninstallArray($requiredDependents)) {
482
                    $this->addFlash('error', 'Error: Could not uninstall dependent extensions.');
483
484
                    return $this->redirectToRoute('zikulaextensionsmodule_extension_list');
485
                }
486
                // remove blocks
487
                $blockRepository->remove($blocks);
488
489
                // remove the extension
490
                if ($extensionHelper->uninstall($extension)) {
491
                    $this->addFlash('status', 'Done! Uninstalled extension.');
492
                } else {
493
                    $this->addFlash('error', 'Extension removal failed! (note: blocks and dependents may have been removed)');
494
                }
495
            } elseif ($form->get('cancel')->isClicked()) {
496
                $this->addFlash('status', 'Operation cancelled.');
497
            }
498
499
            return $this->redirectToRoute('zikulaextensionsmodule_extension_list');
500
        }
501
502
        return [
503
            'form' => $form->createView(),
504
            'extension' => $extension,
505
            'blocks' => $blocks,
506
            'requiredDependents' => $requiredDependents
507
        ];
508
    }
509
510
    /**
511
     * @Route("/theme-preview/{themeName}")
512
     */
513
    public function previewAction(Engine $engine, string $themeName): Response
514
    {
515
        if (!$this->hasPermission('ZikulaExtensionsModule::', '::', ACCESS_ADMIN)) {
516
            throw new AccessDeniedException();
517
        }
518
519
        $engine->setActiveTheme($themeName);
520
        $this->addFlash('warning', 'Please note that blocks may appear out of place or even missing in a theme preview because position names are not consistent from theme to theme.');
521
522
        return $this->forward('Zikula\Bundle\CoreBundle\Controller\MainController::homeAction');
523
    }
524
}
525