AbstractTrackingAction.php 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. declare(strict_types=1);
  3. namespace Shlinkio\Shlink\Core\Action;
  4. use Laminas\Diactoros\Uri;
  5. use Psr\Http\Message\ResponseInterface;
  6. use Psr\Http\Message\ServerRequestInterface;
  7. use Psr\Http\Server\MiddlewareInterface;
  8. use Psr\Http\Server\RequestHandlerInterface;
  9. use Psr\Log\LoggerInterface;
  10. use Psr\Log\NullLogger;
  11. use Shlinkio\Shlink\Core\Entity\ShortUrl;
  12. use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
  13. use Shlinkio\Shlink\Core\Model\Visitor;
  14. use Shlinkio\Shlink\Core\Options\AppOptions;
  15. use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
  16. use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
  17. use function array_key_exists;
  18. use function array_merge;
  19. use function GuzzleHttp\Psr7\parse_query;
  20. use function http_build_query;
  21. abstract class AbstractTrackingAction implements MiddlewareInterface
  22. {
  23. private UrlShortenerInterface $urlShortener;
  24. private VisitsTrackerInterface $visitTracker;
  25. private AppOptions $appOptions;
  26. private LoggerInterface $logger;
  27. public function __construct(
  28. UrlShortenerInterface $urlShortener,
  29. VisitsTrackerInterface $visitTracker,
  30. AppOptions $appOptions,
  31. ?LoggerInterface $logger = null
  32. ) {
  33. $this->urlShortener = $urlShortener;
  34. $this->visitTracker = $visitTracker;
  35. $this->appOptions = $appOptions;
  36. $this->logger = $logger ?: new NullLogger();
  37. }
  38. /**
  39. * Process an incoming server request and return a response, optionally delegating
  40. * to the next middleware component to create the response.
  41. *
  42. *
  43. */
  44. public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
  45. {
  46. $shortCode = $request->getAttribute('shortCode', '');
  47. $domain = $request->getUri()->getAuthority();
  48. $query = $request->getQueryParams();
  49. $disableTrackParam = $this->appOptions->getDisableTrackParam();
  50. try {
  51. $url = $this->urlShortener->shortCodeToUrl($shortCode, $domain);
  52. // Track visit to this short code
  53. if ($disableTrackParam === null || ! array_key_exists($disableTrackParam, $query)) {
  54. $this->visitTracker->track($shortCode, Visitor::fromRequest($request));
  55. }
  56. return $this->createSuccessResp($this->buildUrlToRedirectTo($url, $query, $disableTrackParam));
  57. } catch (ShortUrlNotFoundException $e) {
  58. $this->logger->warning('An error occurred while tracking short code. {e}', ['e' => $e]);
  59. return $this->createErrorResp($request, $handler);
  60. }
  61. }
  62. private function buildUrlToRedirectTo(ShortUrl $shortUrl, array $currentQuery, ?string $disableTrackParam): string
  63. {
  64. $uri = new Uri($shortUrl->getLongUrl());
  65. $hardcodedQuery = parse_query($uri->getQuery());
  66. if ($disableTrackParam !== null) {
  67. unset($currentQuery[$disableTrackParam]);
  68. }
  69. $mergedQuery = array_merge($hardcodedQuery, $currentQuery);
  70. return (string) $uri->withQuery(http_build_query($mergedQuery));
  71. }
  72. abstract protected function createSuccessResp(string $longUrl): ResponseInterface;
  73. abstract protected function createErrorResp(
  74. ServerRequestInterface $request,
  75. RequestHandlerInterface $handler
  76. ): ResponseInterface;
  77. }