AbstractTrackingAction::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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