PageController::setPublicRelativePath()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 3
nc 2
nop 1
1
<?php
2
3
namespace Columnis\Controller;
4
5
use Columnis\Exception\Page\PageWithoutTemplateException;
6
use Columnis\Model\Page;
7
use Columnis\Model\Template;
8
use Columnis\Utils\Directory;
9
use Zend\Mvc\Controller\AbstractActionController;
10
use Zend\View\Model\JsonModel;
11
use Zend\View\Model\ViewModel;
12
13
class PageController extends AbstractActionController
14
{
15
    public function indexAction()
16
    {
17
        $pageId = (int)$this->params()->fromRoute('pageId');
18
        $lang = $this->params()->fromRoute('lang');
19
        $queryParams = $this->params()->fromQuery();
20
        if (! is_array($queryParams)) {
21
            $queryParams = [];
22
        }
23
24
        if ($this->isAdminSessionSet() && $this->mustDestroyAdminSession($queryParams)) {
25
            $this->destroyAdminSession();
26
        } elseif (isset($queryParams['token']) && ! empty($queryParams['token'])) {
27
            $this->setAdminSession($queryParams);
28
        }
29
30
        $queryParams['lang'] = $lang;
31
        $page = $this->fetchPage($pageId, $queryParams, true);
32
33
        if ($page instanceof Page) {
34
            $viewVariables = $page->getData();
35
            $template = $page->getTemplate();
36
37
            if ((bool)$queryParams['debug']) {
38
                return new JsonModel($viewVariables);
39
            }
40
            if ($template->isValid()) {
41
                $this->setPageAssets($template, $viewVariables);
42
43
                $view = new ViewModel();
44
                $view->setTemplate($template->getMainFile(false));
45
                $view->setVariables($viewVariables);
46
                $view->setTerminal(true);
47
                return $view;
48
            }
49
        }
50
        $this->getResponse()->setStatusCode(404);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\Stdlib\ResponseInterface as the method setStatusCode() does only exist in the following implementations of said interface: ZF\ApiProblem\ApiProblemResponse, Zend\Http\PhpEnvironment\Response, Zend\Http\Response, Zend\Http\Response\Stream.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
51
    }
52
53
    private function isAdminSessionSet()
54
    {
55
        session_start();
56
        return isset($_SESSION['token']);
57
    }
58
59
    private function mustDestroyAdminSession($queryParams)
60
    {
61
        return isset($queryParams['logoutAdmin']) && ! empty($queryParams['logoutAdmin']);
62
    }
63
64
    private function destroyAdminSession()
65
    {
66
        session_start();
67
        if ($this->isAdminSessionSet()) {
68
            \session_unset($_SESSION['token']);
69
        }
70
    }
71
72
    private function setAdminSession(array $queryParams)
73
    {
74
        session_start();
75
        $_SESSION['token'] = $queryParams['token'];
76
    }
77
78
    protected function fetchPage($pageId, Array $params = null, $withBreakpoints = false)
79
    {
80
        $accessToken = $this->getAdminSession();
81
        if ($accessToken === null) {
82
            $accessToken = $this->getRequest()->getHeaders()->get('Cookie')->columnis_token;
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\Stdlib\RequestInterface as the method getHeaders() does only exist in the following implementations of said interface: ZF\ContentNegotiation\Request, Zend\Http\PhpEnvironment\Request, Zend\Http\Request.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
83
        }
84
        $page = new Page();
85
        $page->setId($pageId);
86
        $pageService = $this->getPageService($withBreakpoints);
87
        try {
88
            $apiResponse = $pageService->fetch($page, $params, $accessToken);
89
            if(!$apiResponse) {
90
                return null;
91
            }
92
        } catch(PageWithoutTemplateException $e) {
93
            return null;
94
        } 
95
        $headersApi = $apiResponse->getHeaders();
96
        $headers = $this->getResponse()->getHeaders();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\Stdlib\ResponseInterface as the method getHeaders() does only exist in the following implementations of said interface: ZF\ApiProblem\ApiProblemResponse, Zend\Http\PhpEnvironment\Response, Zend\Http\Response, Zend\Http\Response\Stream.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
97
98
        // Pass Api response headers to express response headers
99
	if (isset($headersApi["X-Cache"]) && !empty($headersApi["X-Cache"])) {
100
	    $headers->addHeaders([
101
		"X-Cache" => $headersApi["X-Cache"]
102
            ]);
103
        }
104
        if (isset($headersApi["Age"]) && !empty($headersApi["Age"])) {
105
            $headers->addHeaders([
106
                "Age" => $headersApi["Age"]
107
            ]);
108
        }
109
        return $page;
110
    }
111
112
    private function getAdminSession()
113
    {
114
        session_start();
115
        return $_SESSION['token'];
116
    }
117
118
    public function getPageService($withBreakpoints = false)
119
    {
120
        $serviceManager = $this->getServiceLocator();
121
        $pageService = $serviceManager->get('Columnis\Service\PageService');
122
        /* @var $pageService \Columnis\Service\PageService */
123
        $pageService->setPageBreakpointService($withBreakpoints ? $serviceManager->get('Columnis\Service\PageBreakpointService') : null);
0 ignored issues
show
Bug introduced by
It seems like $withBreakpoints ? $serv...akpointService') : null can also be of type array or object; however, Columnis\Service\PageSer...PageBreakpointService() does only seem to accept null|object<Columnis\Ser...\PageBreakpointService>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
124
        return $pageService;
125
    }
126
127
    private function setPageAssets(Template $template, &$pageData)
128
    {
129
        $excludes = $this->getExcludes();
130
        $jsAssets = $template->getAssets('js', $excludes);
131
        $cssAssets = $template->getAssets('css', $excludes);
132
133
        $fiexedJsAssets = [];
134
        $fixedCssAssets = [];
135
136
        $paths = $this->getAssetsPath();
137
138
        if (count($paths)) {
139
            foreach ($paths as $path) {
140
                if (strpos($path, 'css') > -1) {
141
                    $fixedCssAssets = array_merge($fixedCssAssets, $this->searchAssets($path, 'css', $excludes));
142
                    sort($fixedCssAssets);
143
                } else {
144
                    if (strpos($path, 'js') > -1) {
145
                        $fiexedJsAssets = array_merge($fiexedJsAssets, $this->searchAssets($path, 'js', $excludes));
146
                        sort($fiexedJsAssets);
147
                    }
148
                }
149
            }
150
        }
151
152
        //Merge fixed with templates
153
        $jsAssets = array_merge($fiexedJsAssets, $jsAssets);
154
        $cssAssets = array_merge($fixedCssAssets, $cssAssets);
155
156
        if (is_array($pageData) && $pageData['page']) {
157
            $pageData['page']['scripts'] = $this->setPublicRelativePath($jsAssets);
158
            $pageData['page']['stylesheets'] = $this->setPublicRelativePath($cssAssets);
159
        }
160
        return '';
161
    }
162
163
    private function getExcludes()
164
    {
165
        $ret = [];
166
        $config = $this->getServiceLocator()->get('Config');
167
        if (is_array($config) && isset($config['template_assets_resolver'])) {
168
            $ret = $config['template_assets_resolver']['search_exclude'];
169
        }
170
        return $ret;
171
    }
172
173
    private function getAssetsPath()
174
    {
175
        $config = $this->getServiceLocator()->get('Config');
176
        if (is_array($config)
177
            && isset($config['asset_manager'])
178
            && is_array($config['asset_manager']['resolver_configs'])
179
            && isset($config['asset_manager']['resolver_configs']['paths'])
180
        ) {
181
            return $config['asset_manager']['resolver_configs']['paths'];
182
        }
183
        return [];
184
    }
185
186
    private function searchAssets($path, $extension, Array $excludes = null)
187
    {
188
        $assets = [];
189
        $assetPath = realpath($path . DIRECTORY_SEPARATOR . 'fixed');
190
        if (is_dir($assetPath)) {
191
            $assets = Directory::recursiveSearchByExtension($assetPath, $extension, $excludes);
192
        }
193
        return $assets;
194
    }
195
196
    private function setPublicRelativePath(Array $assets = null)
197
    {
198
        $ret = [];
199
        if (count($assets) > 0) {
200
            $publicPath = realpath($this->getPublicPath()) . DIRECTORY_SEPARATOR;
201
            foreach ($assets as $asset) {
0 ignored issues
show
Bug introduced by
The expression $assets of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
202
                $ret[] = str_replace($publicPath, '', $asset);
203
            }
204
        }
205
        return $ret;
206
    }
207
208
    private function getPublicPath()
209
    {
210
        $ret = [];
211
        $config = $this->getServiceLocator()->get('Config');
212
        if (is_array($config) && isset($config['template_assets_resolver'])) {
213
            $ret = $config['template_assets_resolver']['public_path'];
214
        }
215
        return $ret;
216
    }
217
}
218