Completed
Push — master ( 08bb42...22e813 )
by
unknown
23:44
created

PageController::mustDestroyAdminSession()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
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) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
93
            
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
	if (isset($headersApi["X-Cache"]) && !empty($headersApi["X-Cache"])) {
98
	    $headers->addHeaders([
99
		"X-Cache" => $headersApi["X-Cache"]
100
            ]);
101
        }
102
        if (isset($headersApi["Age"]) && !empty($headersApi["Age"])) {
103
            $headers->addHeaders([
104
                "Age" => $headersApi["Age"]
105
            ]);
106
        }
107
        return $page;
108
    }
109
110
    private function getAdminSession()
111
    {
112
        session_start();
113
        return $_SESSION['token'];
114
    }
115
116
    public function getPageService($withBreakpoints = false)
117
    {
118
        $serviceManager = $this->getServiceLocator();
119
        $pageService = $serviceManager->get('Columnis\Service\PageService');
120
        /* @var $pageService \Columnis\Service\PageService */
121
        $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...
122
        return $pageService;
123
    }
124
125
    private function setPageAssets(Template $template, &$pageData)
126
    {
127
        $excludes = $this->getExcludes();
128
        $jsAssets = $template->getAssets('js', $excludes);
129
        $cssAssets = $template->getAssets('css', $excludes);
130
131
        $fiexedJsAssets = [];
132
        $fixedCssAssets = [];
133
134
        $paths = $this->getAssetsPath();
135
136
        if (count($paths)) {
137
            foreach ($paths as $path) {
138
                if (strpos($path, 'css') > -1) {
139
                    $fixedCssAssets = array_merge($fixedCssAssets, $this->searchAssets($path, 'css', $excludes));
140
                    sort($fixedCssAssets);
141
                } else {
142
                    if (strpos($path, 'js') > -1) {
143
                        $fiexedJsAssets = array_merge($fiexedJsAssets, $this->searchAssets($path, 'js', $excludes));
144
                        sort($fiexedJsAssets);
145
                    }
146
                }
147
            }
148
        }
149
150
        //Merge fixed with templates
151
        $jsAssets = array_merge($fiexedJsAssets, $jsAssets);
152
        $cssAssets = array_merge($fixedCssAssets, $cssAssets);
153
154
        if (is_array($pageData) && $pageData['page']) {
155
            $pageData['page']['scripts'] = $this->setPublicRelativePath($jsAssets);
156
            $pageData['page']['stylesheets'] = $this->setPublicRelativePath($cssAssets);
157
        }
158
        return '';
159
    }
160
161
    private function getExcludes()
162
    {
163
        $ret = [];
164
        $config = $this->getServiceLocator()->get('Config');
165
        if (is_array($config) && isset($config['template_assets_resolver'])) {
166
            $ret = $config['template_assets_resolver']['search_exclude'];
167
        }
168
        return $ret;
169
    }
170
171
    private function getAssetsPath()
172
    {
173
        $config = $this->getServiceLocator()->get('Config');
174
        if (is_array($config)
175
            && isset($config['asset_manager'])
176
            && is_array($config['asset_manager']['resolver_configs'])
177
            && isset($config['asset_manager']['resolver_configs']['paths'])
178
        ) {
179
            return $config['asset_manager']['resolver_configs']['paths'];
180
        }
181
        return [];
182
    }
183
184
    private function searchAssets($path, $extension, Array $excludes = null)
185
    {
186
        $assets = [];
187
        $assetPath = realpath($path . DIRECTORY_SEPARATOR . 'fixed');
188
        if (is_dir($assetPath)) {
189
            $assets = Directory::recursiveSearchByExtension($assetPath, $extension, $excludes);
190
        }
191
        return $assets;
192
    }
193
194
    private function setPublicRelativePath(Array $assets = null)
195
    {
196
        $ret = [];
197
        if (count($assets) > 0) {
198
            $publicPath = realpath($this->getPublicPath()) . DIRECTORY_SEPARATOR;
199
            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...
200
                $ret[] = str_replace($publicPath, '', $asset);
201
            }
202
        }
203
        return $ret;
204
    }
205
206
    private function getPublicPath()
207
    {
208
        $ret = [];
209
        $config = $this->getServiceLocator()->get('Config');
210
        if (is_array($config) && isset($config['template_assets_resolver'])) {
211
            $ret = $config['template_assets_resolver']['public_path'];
212
        }
213
        return $ret;
214
    }
215
}
216