Completed
Push — master ( 293912...8cf026 )
by Craig
06:09
created

Engine::wrapBcBlockInTheme()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Engine::wrapBlockContentInTheme() 0 7 3
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\ThemeModule\Engine;
13
14
use Symfony\Component\HttpFoundation\RequestStack;
15
use Symfony\Component\HttpFoundation\Response;
16
use Doctrine\Common\Annotations\Reader;
17
use Zikula\BlocksModule\Api\BlockApi;
18
use Zikula\Bundle\CoreBundle\HttpKernel\ZikulaKernel;
19
use Zikula\ExtensionsModule\Api\VariableApi;
20
21
/**
22
 * Class Engine
23
 *
24
 * The Theme Engine class is responsible to manage all aspects of theme management using the classes referenced below.
25
 * @see \Zikula\ThemeModule\Engine\*
26
 * @see \Zikula\ThemeModule\EventListener\*
27
 * @see \Zikula\ThemeModule\AbstractTheme
28
 *
29
 * The Engine works by intercepting the Response sent by the module controller (the controller action is the
30
 * 'primary actor'). It takes this response and "wraps" the theme around it and filters the resulting html to add
31
 * required page assets and variables and then sends the resulting Response to the browser. e.g.
32
 *     Request -> Controller -> CapturedResponse -> Filter -> ThemedResponse
33
 *
34
 * In this altered Symfony Request/Response cycle, the theme can be altered by the Controller Action through Annotation
35
 * @see \Zikula\ThemeModule\Engine\Annotation\Theme
36
 * The annotation only excepts defined values.
37
 *
38
 * Themes are fully-qualified Symfony bundles with specific requirements
39
 * @see https://github.com/zikula/SpecTheme
40
 * Themes can define 'realms' which determine specific templates based on Request
41
 */
42
class Engine
43
{
44
    /**
45
     * The instance of the currently active theme.
46
     * @var \Zikula\ThemeModule\AbstractTheme
47
     */
48
    private $activeThemeBundle = null;
49
50
    /**
51
     * Realm is a present value in the theme config determining which page templates to utilize.
52
     * @var string
53
     */
54
    private $realm;
55
56
    /**
57
     * AnnotationValue is the value of the active method Theme annotation.
58
     * @var null|string
59
     */
60
    private $annotationValue = null;
61
62
    /**
63
     * The requestStack.
64
     * @var RequestStack
65
     */
66
    private $requestStack;
67
68
    /**
69
     * The doctrine annotation reader service.
70
     * @var Reader
71
     */
72
    private $annotationReader;
73
74
    /**
75
     * @var \Zikula\Bundle\CoreBundle\HttpKernel\ZikulaKernel
76
     */
77
    private $kernel;
78
79
    /**
80
     * @var AssetFilter
81
     */
82
    private $filterService;
83
84
    /**
85
     * @var BlockApi
86
     */
87
    private $blockApi;
88
89
    /**
90
     * @var VariableApi
91
     */
92
    private $variableApi;
93
94
    /**
95
     * Engine constructor.
96
     * @param RequestStack $requestStack
97
     * @param Reader $annotationReader
98
     * @param ZikulaKernel $kernel
99
     * @param AssetFilter $filter
100
     * @param BlockApi $blockApi
101
     * @param VariableApi $variableApi
102
     */
103
    public function __construct(RequestStack $requestStack, Reader $annotationReader, ZikulaKernel $kernel, AssetFilter $filter, BlockApi $blockApi, VariableApi $variableApi)
104
    {
105
        $this->requestStack = $requestStack;
106
        $this->annotationReader = $annotationReader;
107
        $this->kernel = $kernel;
108
        $this->filterService = $filter;
109
        $this->blockApi = $blockApi;
110
        $this->variableApi = $variableApi;
111
    }
112
113
    /**
114
     * Wrap the response in the theme.
115
     * @api Core-2.0
116
     * @param Response $response
117
     * @return Response
118
     */
119
    public function wrapResponseInTheme(Response $response)
120
    {
121
        $activeTheme = $this->getTheme();
122
        $moduleName = $this->requestStack->getMasterRequest()->attributes->get('_zkModule');
123
        $themedResponse = $activeTheme->generateThemedResponse($this->getRealm(), $response, $moduleName);
124
        $filteredResponse = $this->filter($themedResponse);
125
126
        return $filteredResponse;
127
    }
128
129
    /**
130
     * Wrap the block content in the theme block template and wrap that with a unique div if required.
131
     * @api Core-2.0
132
     * @param string $content
133
     * @param string $title
134
     * @param string $blockType
135
     * @param integer $bid
136
     * @param string $positionName
137
     * @return string
138
     */
139
    public function wrapBlockContentInTheme($content, $title, $blockType, $bid, $positionName)
0 ignored issues
show
Unused Code introduced by
The parameter $title is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
140
    {
141
        $themeConfig = $this->getTheme()->getConfig();
142
        $wrap = isset($themeConfig['blockWrapping']) ? $themeConfig['blockWrapping'] : true;
143
144
        return $wrap ? $this->getTheme()->wrapBlockContentWithUniqueDiv($content, $positionName, $blockType, $bid) : $content;
145
    }
146
147
    /**
148
     * @api Core-2.0
149
     * @return \Zikula\ThemeModule\AbstractTheme
150
     */
151
    public function getTheme()
152
    {
153
        if (!isset($this->activeThemeBundle) && $this->kernel->getContainer()->getParameter('installed')) {
154
            $this->setActiveTheme();
155
        }
156
157
        return $this->activeThemeBundle;
158
    }
159
160
    /**
161
     * Get the template realm.
162
     * @api Core-2.0
163
     * @return string
164
     */
165
    public function getRealm()
166
    {
167
        if (!isset($this->realm)) {
168
            $this->setMatchingRealm();
169
        }
170
171
        return $this->realm;
172
    }
173
174
    /**
175
     * @api Core-2.0
176
     * @return null|string
177
     */
178
    public function getAnnotationValue()
179
    {
180
        return $this->annotationValue;
181
    }
182
183
    /**
184
     * Change a theme based on the annotationValue.
185
     * @api Core-2.0
186
     * @param string $controllerClassName
187
     * @param string $method
188
     * @return bool|string
189
     */
190
    public function changeThemeByAnnotation($controllerClassName, $method)
191
    {
192
        $reflectionClass = new \ReflectionClass($controllerClassName);
193
        $reflectionMethod = $reflectionClass->getMethod($method);
194
        $themeAnnotation = $this->annotationReader->getMethodAnnotation($reflectionMethod, 'Zikula\ThemeModule\Engine\Annotation\Theme');
195
        if (isset($themeAnnotation)) {
196
            // method annotations contain `@Theme` so set theme based on value
197
            $this->annotationValue = $themeAnnotation->value;
198
            switch ($themeAnnotation->value) {
199
                case 'admin':
200
                    $newThemeName = $this->variableApi->get('ZikulaAdminModule', 'admintheme', '');
201
                    break;
202
                case 'print':
203
                    $newThemeName = 'ZikulaPrinterTheme';
204
                    break;
205
                case 'atom':
206
                    $newThemeName = 'ZikulaAtomTheme';
207
                    break;
208
                case 'rss':
209
                    $newThemeName = 'ZikulaRssTheme';
210
                    break;
211
                default:
212
                    $newThemeName = $themeAnnotation->value;
213
            }
214
            if (!empty($newThemeName)) {
215
                $this->setActiveTheme($newThemeName);
216
217
                return $newThemeName;
218
            }
219
        }
220
221
        return false;
222
    }
223
224
    /**
225
     * @param $name
226
     * @return bool
227
     */
228
    public function positionIsAvailableInTheme($name)
229
    {
230
        $config = $this->getTheme()->getConfig();
231
        if (empty($config)) {
232
            return true;
233
        }
234
        foreach ($config as $realm => $definition) {
235
            if (isset($definition['block']['positions'][$name])) {
236
                return true;
237
            }
238
        }
239
240
        return false;
241
    }
242
243
    /**
244
     * Find the realm in the theme.yml that matches the given path, route or module.
245
     * Three 'alias' realms may be defined and do not require a pattern:
246
     *  1) 'master' (required) this is the default realm. any non-matching value will utilize the master realm
247
     *  2) 'home' (optional) will be used when the path matches `^/$`
248
     *  3) 'admin' (optional) will be used when the annotationValue is 'admin'
249
     * Uses regex to find the FIRST match to a pattern to one of three possible request attribute values.
250
     *  1) path   e.g. /pages/display/welcome-to-pages-content-manager
251
     *  2) route  e.g. zikulapagesmodule_user_display
252
     *  3) module e.g. zikulapagesmodule (case insensitive)
253
     *
254
     * @return int|string
255
     */
256
    private function setMatchingRealm()
257
    {
258
        $themeConfig = $this->getTheme()->getConfig();
259
        // defining an admin realm overrides all other options for 'admin' annotated methods
260
        if ($this->annotationValue == 'admin' && isset($themeConfig['admin'])) {
261
            $this->realm = 'admin';
262
263
            return;
264
        }
265
        $request = $this->requestStack->getMasterRequest();
266
        $requestAttributes = $request->attributes->all();
267
        // match `/` for home realm
268
        if (isset($requestAttributes['_route']) && $requestAttributes['_route'] == 'home') {
269
            $this->realm = 'home';
270
271
            return;
272
        }
273
274
        unset($themeConfig['admin'], $themeConfig['home'], $themeConfig['master']); // remove to avoid scanning/matching in loop
275
        $pathInfo = $request->getPathInfo();
276
        foreach ($themeConfig as $realm => $config) {
277
            // @todo is there a faster way to do this?
278
            if (!empty($config['pattern'])) {
279
                $pattern = ';' . str_replace('/', '\\/', $config['pattern']) . ';i'; // delimiters are ; and i means case-insensitive
280
                $valuesToMatch = [];
281
                if (isset($pathInfo)) {
282
                    $valuesToMatch[] = $pathInfo; // e.g. /pages/display/welcome-to-pages-content-manager
283
                }
284
                if (isset($requestAttributes['_route'])) {
285
                    $valuesToMatch[] = $requestAttributes['_route']; // e.g. zikulapagesmodule_user_display
286
                }
287
                if (isset($requestAttributes['_zkModule'])) {
288
                    $valuesToMatch[] = $requestAttributes['_zkModule']; // e.g. zikulapagesmodule
289
                }
290
                foreach ($valuesToMatch as $value) {
291
                    $match = preg_match($pattern, $value);
292
                    if ($match === 1) {
293
                        $this->realm = $realm;
0 ignored issues
show
Documentation Bug introduced by
It seems like $realm can also be of type integer. However, the property $realm is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
294
295
                        return; // use first match and do not continue to attempt to match patterns
296
                    }
297
                }
298
            }
299
        }
300
301
        $this->realm = 'master';
302
    }
303
304
    /**
305
     * Set the theme based on:
306
     *  1) manual setting
307
     *  2) the default system theme
308
     * @param string|null $newThemeName
309
     * @return mixed
310
     * kernel::getTheme() @throws \InvalidArgumentException if theme is invalid
311
     */
312
    private function setActiveTheme($newThemeName = null)
313
    {
314
        $activeTheme = !empty($newThemeName) ? $newThemeName : $this->variableApi->getSystemVar('Default_Theme');
315
        try {
316
            $this->activeThemeBundle = $this->kernel->getTheme($activeTheme);
317
            $this->activeThemeBundle->loadThemeVars();
318
        } catch (\Exception $e) {
319
            // fail silently, this is a Core < 1.4 theme.
320
        }
321
    }
322
323
    /**
324
     * Filter the Response to add page assets and vars and return.
325
     * @param Response $response
326
     * @return Response
327
     */
328
    private function filter(Response $response)
329
    {
330
        $jsAssets = [];
331
        $cssAssets = [];
332
        $filteredContent = $this->filterService->filter($response->getContent(), $jsAssets, $cssAssets);
333
        $response->setContent($filteredContent);
334
335
        return $response;
336
    }
337
}
338