mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Created new action to track visits, which returns an empty pixel
This commit is contained in:
parent
1b2a0820e5
commit
42fe4bd5ce
7 changed files with 140 additions and 67 deletions
|
@ -22,10 +22,9 @@
|
|||
"doctrine/dbal": "^2.5",
|
||||
"doctrine/migrations": "^1.4",
|
||||
"doctrine/orm": "^2.5",
|
||||
"endroid/qrcode": "^1.7",
|
||||
"endroid/qr-code": "^1.7",
|
||||
"firebase/php-jwt": "^4.0",
|
||||
"guzzlehttp/guzzle": "^6.2",
|
||||
"http-interop/http-middleware": "^0.4.1",
|
||||
"mikehaertl/phpwkhtmltopdf": "^2.2",
|
||||
"monolog/monolog": "^1.21",
|
||||
"roave/security-advisories": "dev-master",
|
||||
|
|
33
module/Common/src/Response/PixelResponse.php
Normal file
33
module/Common/src/Response/PixelResponse.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Common\Response;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\Stream;
|
||||
|
||||
class PixelResponse extends Response
|
||||
{
|
||||
private const BASE_64_IMAGE = 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw==';
|
||||
private const CONTENT_TYPE = 'image/gif';
|
||||
|
||||
public function __construct(int $status = 200, array $headers = [])
|
||||
{
|
||||
$headers['content-type'] = self::CONTENT_TYPE;
|
||||
parent::__construct($this->createBody(), $status, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the message body.
|
||||
*
|
||||
* @return StreamInterface
|
||||
*/
|
||||
private function createBody(): StreamInterface
|
||||
{
|
||||
$body = new Stream('php://temp', 'wb+');
|
||||
$body->write(\base64_decode(self::BASE_64_IMAGE));
|
||||
$body->rewind();
|
||||
return $body;
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ return [
|
|||
|
||||
// Middleware
|
||||
Action\RedirectAction::class => ConfigAbstractFactory::class,
|
||||
Action\PixelAction::class => ConfigAbstractFactory::class,
|
||||
Action\QrCodeAction::class => ConfigAbstractFactory::class,
|
||||
Action\PreviewAction::class => ConfigAbstractFactory::class,
|
||||
Middleware\QrCodeCacheMiddleware::class => ConfigAbstractFactory::class,
|
||||
|
@ -60,6 +61,11 @@ return [
|
|||
Service\VisitsTracker::class,
|
||||
Options\AppOptions::class,
|
||||
],
|
||||
Action\PixelAction::class => [
|
||||
Service\UrlShortener::class,
|
||||
Service\VisitsTracker::class,
|
||||
Options\AppOptions::class,
|
||||
],
|
||||
Action\QrCodeAction::class => [RouterInterface::class, Service\UrlShortener::class, 'Logger_Shlink'],
|
||||
Action\PreviewAction::class => [PreviewGenerator::class, Service\UrlShortener::class],
|
||||
Middleware\QrCodeCacheMiddleware::class => [Cache::class],
|
||||
|
|
|
@ -13,6 +13,12 @@ return [
|
|||
'middleware' => Action\RedirectAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'pixel-tracking',
|
||||
'path' => '/{shortCode}/track',
|
||||
'middleware' => Action\PixelAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'short-url-qr-code',
|
||||
'path' => '/{shortCode}/qr-code[/{size:[0-9]+}]',
|
||||
|
|
74
module/Core/src/Action/AbstractTrackingAction.php
Normal file
74
module/Core/src/Action/AbstractTrackingAction.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
|
||||
abstract class AbstractTrackingAction implements MiddlewareInterface
|
||||
{
|
||||
use ErrorResponseBuilderTrait;
|
||||
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
private $visitTracker;
|
||||
/**
|
||||
* @var AppOptions
|
||||
*/
|
||||
private $appOptions;
|
||||
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
VisitsTrackerInterface $visitTracker,
|
||||
AppOptions $appOptions
|
||||
) {
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->visitTracker = $visitTracker;
|
||||
$this->appOptions = $appOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode', '');
|
||||
$query = $request->getQueryParams();
|
||||
$disableTrackParam = $this->appOptions->getDisableTrackParam();
|
||||
|
||||
try {
|
||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
|
||||
// Track visit to this short code
|
||||
if ($disableTrackParam === null || ! \array_key_exists($disableTrackParam, $query)) {
|
||||
$this->visitTracker->track($shortCode, $request);
|
||||
}
|
||||
|
||||
return $this->createResp($longUrl);
|
||||
} catch (InvalidShortCodeException | EntityDoesNotExistException $e) {
|
||||
return $this->buildErrorResponse($request, $handler);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function createResp(string $longUrl): ResponseInterface;
|
||||
}
|
15
module/Core/src/Action/PixelAction.php
Normal file
15
module/Core/src/Action/PixelAction.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Shlinkio\Shlink\Common\Response\PixelResponse;
|
||||
|
||||
class PixelAction extends AbstractTrackingAction
|
||||
{
|
||||
protected function createResp(string $longUrl): ResponseInterface
|
||||
{
|
||||
return new PixelResponse();
|
||||
}
|
||||
}
|
|
@ -4,74 +4,14 @@ declare(strict_types=1);
|
|||
namespace Shlinkio\Shlink\Core\Action;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Shlinkio\Shlink\Core\Action\Util\ErrorResponseBuilderTrait;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
||||
use Shlinkio\Shlink\Core\Options\AppOptions;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Zend\Diactoros\Response\RedirectResponse;
|
||||
|
||||
class RedirectAction implements MiddlewareInterface
|
||||
class RedirectAction extends AbstractTrackingAction
|
||||
{
|
||||
use ErrorResponseBuilderTrait;
|
||||
|
||||
/**
|
||||
* @var UrlShortenerInterface
|
||||
*/
|
||||
private $urlShortener;
|
||||
/**
|
||||
* @var VisitsTrackerInterface
|
||||
*/
|
||||
private $visitTracker;
|
||||
/**
|
||||
* @var AppOptions
|
||||
*/
|
||||
private $appOptions;
|
||||
|
||||
public function __construct(
|
||||
UrlShortenerInterface $urlShortener,
|
||||
VisitsTrackerInterface $visitTracker,
|
||||
AppOptions $appOptions
|
||||
) {
|
||||
$this->urlShortener = $urlShortener;
|
||||
$this->visitTracker = $visitTracker;
|
||||
$this->appOptions = $appOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an incoming server request and return a response, optionally delegating
|
||||
* to the next middleware component to create the response.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function process(Request $request, RequestHandlerInterface $handler): Response
|
||||
protected function createResp(string $longUrl): Response
|
||||
{
|
||||
$shortCode = $request->getAttribute('shortCode', '');
|
||||
$query = $request->getQueryParams();
|
||||
$disableTrackParam = $this->appOptions->getDisableTrackParam();
|
||||
|
||||
try {
|
||||
$longUrl = $this->urlShortener->shortCodeToUrl($shortCode);
|
||||
|
||||
// Track visit to this short code
|
||||
if ($disableTrackParam === null || ! \array_key_exists($disableTrackParam, $query)) {
|
||||
$this->visitTracker->track($shortCode, $request);
|
||||
}
|
||||
|
||||
// Return a redirect response to the long URL.
|
||||
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
|
||||
return new RedirectResponse($longUrl);
|
||||
} catch (InvalidShortCodeException $e) {
|
||||
return $this->buildErrorResponse($request, $handler);
|
||||
} catch (EntityDoesNotExistException $e) {
|
||||
return $this->buildErrorResponse($request, $handler);
|
||||
}
|
||||
// Return a redirect response to the long URL.
|
||||
// Use a temporary redirect to make sure browsers always hit the server for analytics purposes
|
||||
return new RedirectResponse($longUrl);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue