Test Failed
Push — master ( bd39d1...7116ad )
by Gabor
08:54
created

ServiceAdapter   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 275
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 89
dl 0
loc 275
rs 9.68
c 0
b 0
f 0
ccs 85
cts 85
cp 1
wmc 34

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getSelectedApplicationName() 0 17 4
A checkSubDomain() 0 8 4
A setApplication() 0 29 3
A __construct() 0 41 4
A getRequestMethod() 0 3 1
A checkDomainIsValid() 0 7 2
A setDomain() 0 20 2
A setAdapterOptions() 0 11 4
A checkDirectoryIsValid() 0 9 4
A getRequestUri() 0 3 1
A getEnvironmentData() 0 7 2
A getClientIp() 0 11 3
1
<?php
2
/**
3
 * WebHemi.
4
 *
5
 * PHP version 7.2
6
 *
7
 * @copyright 2012 - 2019 Gixx-web (http://www.gixx-web.com)
8
 * @license   https://opensource.org/licenses/MIT The MIT License (MIT)
9
 *
10
 * @link http://www.gixx-web.com
11
 */
12
declare(strict_types = 1);
13
14
namespace WebHemi\Environment\ServiceAdapter\Base;
15
16
use Exception;
17
use InvalidArgumentException;
18
use LayerShifter\TLDExtract\Extract;
19
use LayerShifter\TLDExtract\Result;
20
use Throwable;
21
use WebHemi\Configuration\ServiceInterface as ConfigurationInterface;
22
use WebHemi\Environment\AbstractAdapter;
23
use UnexpectedValueException;
24
25
/**
26
 * Class ServiceAdapter.
27
 *
28
 * @SuppressWarnings(PHPMD.TooManyFields)
29
 */
30
class ServiceAdapter extends AbstractAdapter
31
{
32
    /**
33
     * @var Extract
34
     */
35
    private $domainAdapter;
36
37
    /**
38
     * ServiceAdapter constructor.
39
     *
40
     * @param  ConfigurationInterface $configuration
41
     * @param  array                  $getData
42
     * @param  array                  $postData
43
     * @param  array                  $serverData
44
     * @param  array                  $cookieData
45
     * @param  array                  $filesData
46
     * @param  array                  $optionsData
47
     * @throws Exception
48
     */
49 12
    public function __construct(
50
        ConfigurationInterface $configuration,
51
        array $getData,
52
        array $postData,
53
        array $serverData,
54
        array $cookieData,
55
        array $filesData,
56
        array $optionsData
57
    ) {
58 12
        $this->configuration = $configuration->getConfig('applications');
59 12
        $this->domainAdapter = new Extract();
60 12
        $this->applicationRoot = realpath(__DIR__.'/../../../../../');
61
        // In case when the backend sources are out of the document root.
62 12
        $this->documentRoot = realpath($this->applicationRoot.'/');
63 12
        $this->options = $optionsData;
64
65 12
        if (isset($serverData['HTTP_REFERER'])) {
66 12
            $serverData['HTTP_REFERER'] = urldecode($serverData['HTTP_REFERER']);
67
        }
68
69 12
        $this->environmentData = [
70 12
            'GET'    => $getData,
71 12
            'POST'   => $postData,
72 12
            'SERVER' => $serverData,
73 12
            'COOKIE' => $cookieData,
74 12
            'FILES'  => $filesData
75
        ];
76
77 12
        $this->isHttps = isset($this->environmentData['SERVER']['HTTPS']) && $this->environmentData['SERVER']['HTTPS'];
78 12
        $this->url = 'http'.($this->isHttps ? 's' : '').'://'
79 12
            .$this->environmentData['SERVER']['HTTP_HOST']
80 12
            .$this->environmentData['SERVER']['REQUEST_URI']; // contains also the query string
81
82 12
        $this->selectedModule = self::DEFAULT_MODULE;
83 12
        $this->selectedApplication = self::DEFAULT_APPLICATION;
84 12
        $this->selectedTheme = self::DEFAULT_THEME;
85 12
        $this->selectedThemeResourcePath = self::DEFAULT_THEME_RESOURCE_PATH;
86 12
        $this->selectedApplicationUri = self::DEFAULT_APPLICATION_URI;
87
88 12
        $this->setDomain()
89 10
            ->setApplication();
90 10
    }
91
92
    /**
93
     * Gets the request URI
94
     *
95
     * @return string
96
     */
97 2
    public function getRequestUri() : string
98
    {
99 2
        return rtrim($this->environmentData['SERVER']['REQUEST_URI'], '/');
100
    }
101
102
    /**
103
     * Gets the request method.
104
     *
105
     * @return string
106
     */
107 2
    public function getRequestMethod(): string
108
    {
109 2
        return $this->environmentData['SERVER']['REQUEST_METHOD'] ?? 'GET';
110
    }
111
112
    /**
113
     * Gets environment data.
114
     *
115
     * @param  string $key
116
     * @return array
117
     */
118 32
    public function getEnvironmentData(string $key) : array
119
    {
120 32
        if (!isset($this->environmentData[$key])) {
121 2
            throw new InvalidArgumentException(sprintf('The "%s" is not a valid environment key.', $key));
122
        }
123
124 32
        return $this->environmentData[$key];
125
    }
126
127
    /**
128
     * Gets the client IP address.
129
     *
130
     * @return string
131
     */
132 2
    public function getClientIp() : string
133
    {
134 2
        $ipAddress = '';
135
136 2
        if (!empty($this->environmentData['SERVER']['HTTP_X_FORWARDED_FOR'])) {
137 2
            $ipAddress = $this->environmentData['SERVER']['HTTP_X_FORWARDED_FOR'];
138 2
        } elseif (!empty($this->environmentData['SERVER']['REMOTE_ADDR'])) {
139 2
            $ipAddress = $this->environmentData['SERVER']['REMOTE_ADDR'];
140
        }
141
142 2
        return $ipAddress;
143
    }
144
145
    /**
146
     * Parses server data and tries to set domain information.
147
     *
148
     * @throws Exception
149
     * @return ServiceAdapter
150
     */
151 12
    private function setDomain() : ServiceAdapter
152
    {
153 12
        $this->setAdapterOptions();
154
155
        /**
156
         * @var Result $domainParts
157
         */
158 12
        $domainParts = $this->domainAdapter->parse($this->url);
159
160 12
        if ($domainParts->getSuffix() === null) {
161 2
            throw new UnexpectedValueException('This application does not support IP access');
162
        }
163
164 10
        $this->checkSubDomain($domainParts);
165
166 10
        $this->subDomain = $domainParts->getSubdomain();
167 10
        $this->topDomain = $domainParts->getHostname().'.'.$domainParts->getSuffix();
168 10
        $this->applicationDomain = $domainParts->getFullHost();
169
170 10
        return $this;
171
    }
172
173
    /**
174
     * Set some adapter specific options.
175
     *
176
     * @return int
177
     *
178
     * @codeCoverageIgnore - don't test third party library
179
     */
180
    private function setAdapterOptions() : int
181
    {
182
        try {
183
            if (!\defined('PHPUNIT_WEBHEMI_TESTSUITE') && getenv('APPLICATION_ENV') === 'dev') {
184
                $this->domainAdapter->setExtractionMode(Extract::MODE_ALLOW_NOT_EXISTING_SUFFIXES);
185
            }
186
        } catch (Throwable $exception) {
187
            return $exception->getCode();
188
        }
189
190
        return 0;
191
    }
192
193
    /**
194
     * Checks whether the subdomain exists, and rediretcs if no.
195
     *
196
     * @param Result $domainParts
197
     *
198
     * @codeCoverageIgnore - don't test redirect
199
     */
200
    private function checkSubDomain(Result $domainParts) : void
201
    {
202
        // Redirecting to www when no sub-domain is present
203
        if (!\defined('PHPUNIT_WEBHEMI_TESTSUITE') && $domainParts->getSubdomain() === null) {
204
            $schema = 'http'.($this->isSecuredApplication() ? 's' : '').'://';
205
            $uri = $this->environmentData['SERVER']['REQUEST_URI'];
206
            header('Location: '.$schema.'www.'.$domainParts->getFullHost().$uri);
207
            exit;
208
        }
209
    }
210
211
    /**
212
     * Sets application related data.
213
     *
214
     * @throws Exception
215
     * @return ServiceAdapter
216
     */
217 10
    private function setApplication() : ServiceAdapter
218
    {
219
        // @codeCoverageIgnoreStart
220
        if (!isset($this->applicationDomain)) {
221
            // For safety purposes only, But it can't happen unless somebody change/overwrite the constructor.
222
            throw new UnexpectedValueException('Domain is not set');
223
        }
224
        // @codeCoverageIgnoreEnd
225
226 10
        $urlParts = parse_url($this->url);
227 10
        [$subDirectory] = explode('/', ltrim($urlParts['path'], '/'), 2);
228
229 10
        $applications = $this->configuration->toArray();
230 10
        $applicationNames = array_keys($applications);
231 10
        $selectedApplication = $this->getSelectedApplicationName($applicationNames, $subDirectory);
232
233 10
        $applicationData = $applications[$selectedApplication];
234
235 10
        $this->selectedApplication = $selectedApplication;
236 10
        $this->selectedModule = $applicationData['module'] ?? self::DEFAULT_MODULE;
237 10
        $this->selectedTheme = $applicationData['theme'] ?? self::DEFAULT_THEME;
238 10
        $this->selectedApplicationUri = $applicationData['path'] ?? '/';
239
240
        // Final check for config and resources.
241 10
        if ($this->selectedTheme !== self::DEFAULT_THEME) {
242 2
            $this->selectedThemeResourcePath = '/resources/vendor_themes/'.$this->selectedTheme;
243
        }
244
245 10
        return $this;
246
    }
247
248
    /**
249
     * Gets the selected application's name.
250
     *
251
     * @param  array  $applicationNames
252
     * @param  string $subDirectory
253
     * @return string
254
     */
255 10
    private function getSelectedApplicationName(array $applicationNames, string $subDirectory) : string
256
    {
257 10
        $selectedApplication = self::DEFAULT_APPLICATION;
258
259
        /**
260
         * @var string $applicationName
261
         */
262 10
        foreach ($applicationNames as $applicationName) {
263 10
            if ($this->checkDirectoryIsValid($applicationName, $subDirectory)
264 10
                || $this->checkDomainIsValid($applicationName)
265
            ) {
266 10
                $selectedApplication = $applicationName;
267 10
                break;
268
            }
269
        }
270
271 10
        return $selectedApplication;
272
    }
273
274
    /**
275
     * Checks from type, path it the current URI segment is valid.
276
     *
277
     * @param  string $applicationName
278
     * @param  string $subDirectory
279
     * @return bool
280
     */
281 10
    private function checkDirectoryIsValid(string $applicationName, string $subDirectory) : bool
282
    {
283 10
        $applications = $this->configuration->toArray();
284 10
        $applicationData = $applications[$applicationName];
285
286 10
        return !empty($subDirectory)
287 10
            && $applicationName !== 'website'
288 10
            && $this->applicationDomain === $applicationData['domain']
289 10
            && $applicationData['path'] === '/'.$subDirectory;
290
    }
291
292
    /**
293
     * Checks from type and path if the domain is valid. If so, it sets the $subDirectory to the default.
294
     *
295
     * @param  string $applicationName
296
     * @return bool
297
     */
298 8
    private function checkDomainIsValid(string $applicationName) : bool
299
    {
300 8
        $applications = $this->configuration->toArray();
301 8
        $applicationData = $applications[$applicationName];
302
303 8
        return $this->applicationDomain === $applicationData['domain']
304 8
            && $applicationData['path'] === '/';
305
    }
306
}
307