Passed
Pull Request — develop (#719)
by Alejandro
08:59
created

AbstractTrackingAction::__construct()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 1
nop 4
dl 0
loc 10
rs 10
c 0
b 0
f 0
ccs 5
cts 5
cp 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Shlinkio\Shlink\Core\Action;
6
7
use Fig\Http\Message\RequestMethodInterface;
8
use Laminas\Diactoros\Uri;
9
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use Psr\Http\Server\MiddlewareInterface;
13
use Psr\Http\Server\RequestHandlerInterface;
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\NullLogger;
16
use Shlinkio\Shlink\Core\Entity\ShortUrl;
17
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
18
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
19
use Shlinkio\Shlink\Core\Model\Visitor;
20
use Shlinkio\Shlink\Core\Options\AppOptions;
21
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
22
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
23
24
use function array_key_exists;
25
use function array_merge;
26
use function GuzzleHttp\Psr7\build_query;
27
use function GuzzleHttp\Psr7\parse_query;
28
29
abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMethodInterface
30
{
31
    private ShortUrlResolverInterface $urlResolver;
32
    private VisitsTrackerInterface $visitTracker;
33
    private AppOptions $appOptions;
34
    private LoggerInterface $logger;
35
36 9
    public function __construct(
37
        ShortUrlResolverInterface $urlResolver,
38
        VisitsTrackerInterface $visitTracker,
39
        AppOptions $appOptions,
40
        ?LoggerInterface $logger = null
41
    ) {
42 9
        $this->urlResolver = $urlResolver;
43 9
        $this->visitTracker = $visitTracker;
44 9
        $this->appOptions = $appOptions;
45 9
        $this->logger = $logger ?: new NullLogger();
46
    }
47
48 9
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
49
    {
50 9
        $identifier = ShortUrlIdentifier::fromRedirectRequest($request);
51 9
        $query = $request->getQueryParams();
52 9
        $disableTrackParam = $this->appOptions->getDisableTrackParam();
53
54
        try {
55 9
            $shortUrl = $this->urlResolver->resolveEnabledShortUrl($identifier);
56
57 8
            if ($this->shouldTrackRequest($request, $query, $disableTrackParam)) {
58 5
                $this->visitTracker->track($shortUrl, Visitor::fromRequest($request));
59
            }
60
61 8
            return $this->createSuccessResp($this->buildUrlToRedirectTo($shortUrl, $query, $disableTrackParam));
62 1
        } catch (ShortUrlNotFoundException $e) {
63 1
            $this->logger->warning('An error occurred while tracking short code. {e}', ['e' => $e]);
64 1
            return $this->createErrorResp($request, $handler);
65
        }
66
    }
67
68 8
    private function buildUrlToRedirectTo(ShortUrl $shortUrl, array $currentQuery, ?string $disableTrackParam): string
69
    {
70 8
        $uri = new Uri($shortUrl->getLongUrl());
71 8
        $hardcodedQuery = parse_query($uri->getQuery());
72 8
        if ($disableTrackParam !== null) {
73 7
            unset($currentQuery[$disableTrackParam]);
74
        }
75 8
        $mergedQuery = array_merge($hardcodedQuery, $currentQuery);
76
77 8
        return (string) $uri->withQuery(build_query($mergedQuery));
78
    }
79
80 8
    private function shouldTrackRequest(ServerRequestInterface $request, array $query, ?string $disableTrackParam): bool
81
    {
82 8
        $forwardedMethod = $request->getAttribute(ImplicitHeadMiddleware::FORWARDED_HTTP_METHOD_ATTRIBUTE);
83 8
        if ($forwardedMethod === self::METHOD_HEAD) {
84 1
            return false;
85
        }
86
87 7
        return $disableTrackParam === null || ! array_key_exists($disableTrackParam, $query);
88
    }
89
90
    abstract protected function createSuccessResp(string $longUrl): ResponseInterface;
91
92
    abstract protected function createErrorResp(
93
        ServerRequestInterface $request,
94
        RequestHandlerInterface $handler
95
    ): ResponseInterface;
96
}
97