mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-11 09:43:13 +08:00
Added logic to persist device long URLs while creating/editing a short URL
This commit is contained in:
@@ -51,7 +51,7 @@ class DomainService implements DomainServiceInterface
|
||||
$repo = $this->em->getRepository(Domain::class);
|
||||
$groups = group(
|
||||
$repo->findDomains($apiKey),
|
||||
fn (Domain $domain) => $domain->getAuthority() === $this->defaultDomain ? 'default' : 'domains',
|
||||
fn (Domain $domain) => $domain->authority() === $this->defaultDomain ? 'default' : 'domains',
|
||||
);
|
||||
|
||||
return [first($groups['default'] ?? []), $groups['domains'] ?? []];
|
||||
|
||||
@@ -24,14 +24,14 @@ class Domain extends AbstractEntity implements JsonSerializable, NotFoundRedirec
|
||||
return new self($authority);
|
||||
}
|
||||
|
||||
public function getAuthority(): string
|
||||
public function authority(): string
|
||||
{
|
||||
return $this->authority;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->getAuthority();
|
||||
return $this->authority;
|
||||
}
|
||||
|
||||
public function invalidShortUrlRedirect(): ?string
|
||||
|
||||
@@ -20,7 +20,7 @@ final class DomainItem implements JsonSerializable
|
||||
|
||||
public static function forNonDefaultDomain(Domain $domain): self
|
||||
{
|
||||
return new self($domain->getAuthority(), $domain, false);
|
||||
return new self($domain->authority(), $domain, false);
|
||||
}
|
||||
|
||||
public static function forDefaultDomain(string $defaultDomain, NotFoundRedirectConfigInterface $config): self
|
||||
|
||||
@@ -10,17 +10,16 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\DeviceLongUrlPair;
|
||||
|
||||
class DeviceLongUrl extends AbstractEntity
|
||||
{
|
||||
private ShortUrl $shortUrl; // @phpstan-ignore-line
|
||||
|
||||
private function __construct(
|
||||
private readonly ShortUrl $shortUrl, // No need to read this field. It's used by doctrine
|
||||
public readonly DeviceType $deviceType,
|
||||
private string $longUrl,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromPair(DeviceLongUrlPair $pair): self
|
||||
public static function fromShortUrlAndPair(ShortUrl $shortUrl, DeviceLongUrlPair $pair): self
|
||||
{
|
||||
return new self($pair->deviceType, $pair->longUrl);
|
||||
return new self($shortUrl, $pair->deviceType, $pair->longUrl);
|
||||
}
|
||||
|
||||
public function longUrl(): string
|
||||
|
||||
@@ -12,6 +12,7 @@ use Doctrine\Common\Collections\Selectable;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
|
||||
use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\DeviceLongUrlPair;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter;
|
||||
@@ -24,6 +25,7 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
|
||||
use function count;
|
||||
use function Functional\map;
|
||||
use function Shlinkio\Shlink\Core\generateRandomShortCode;
|
||||
use function Shlinkio\Shlink\Core\normalizeDate;
|
||||
use function Shlinkio\Shlink\Core\normalizeOptionalDate;
|
||||
@@ -35,6 +37,8 @@ class ShortUrl extends AbstractEntity
|
||||
private Chronos $dateCreated;
|
||||
/** @var Collection<int, Visit> */
|
||||
private Collection $visits;
|
||||
/** @var Collection<int, DeviceLongUrl> */
|
||||
private Collection $deviceLongUrls;
|
||||
/** @var Collection<int, Tag> */
|
||||
private Collection $tags;
|
||||
private ?Chronos $validSince = null;
|
||||
@@ -81,6 +85,10 @@ class ShortUrl extends AbstractEntity
|
||||
$instance->longUrl = $creation->getLongUrl();
|
||||
$instance->dateCreated = Chronos::now();
|
||||
$instance->visits = new ArrayCollection();
|
||||
$instance->deviceLongUrls = new ArrayCollection(map(
|
||||
$creation->deviceLongUrls,
|
||||
fn (DeviceLongUrlPair $pair) => DeviceLongUrl::fromShortUrlAndPair($instance, $pair),
|
||||
));
|
||||
$instance->tags = $relationResolver->resolveTags($creation->tags);
|
||||
$instance->validSince = $creation->validSince;
|
||||
$instance->validUntil = $creation->validUntil;
|
||||
@@ -126,6 +134,53 @@ class ShortUrl extends AbstractEntity
|
||||
return $instance;
|
||||
}
|
||||
|
||||
public function update(
|
||||
ShortUrlEdition $shortUrlEdit,
|
||||
?ShortUrlRelationResolverInterface $relationResolver = null,
|
||||
): void {
|
||||
if ($shortUrlEdit->validSinceWasProvided()) {
|
||||
$this->validSince = $shortUrlEdit->validSince;
|
||||
}
|
||||
if ($shortUrlEdit->validUntilWasProvided()) {
|
||||
$this->validUntil = $shortUrlEdit->validUntil;
|
||||
}
|
||||
if ($shortUrlEdit->maxVisitsWasProvided()) {
|
||||
$this->maxVisits = $shortUrlEdit->maxVisits;
|
||||
}
|
||||
if ($shortUrlEdit->longUrlWasProvided()) {
|
||||
$this->longUrl = $shortUrlEdit->longUrl ?? $this->longUrl;
|
||||
}
|
||||
if ($shortUrlEdit->tagsWereProvided()) {
|
||||
$relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver();
|
||||
$this->tags = $relationResolver->resolveTags($shortUrlEdit->tags);
|
||||
}
|
||||
if ($shortUrlEdit->crawlableWasProvided()) {
|
||||
$this->crawlable = $shortUrlEdit->crawlable;
|
||||
}
|
||||
if (
|
||||
$this->title === null
|
||||
|| $shortUrlEdit->titleWasProvided()
|
||||
|| ($this->titleWasAutoResolved && $shortUrlEdit->titleWasAutoResolved())
|
||||
) {
|
||||
$this->title = $shortUrlEdit->title;
|
||||
$this->titleWasAutoResolved = $shortUrlEdit->titleWasAutoResolved();
|
||||
}
|
||||
if ($shortUrlEdit->forwardQueryWasProvided()) {
|
||||
$this->forwardQuery = $shortUrlEdit->forwardQuery;
|
||||
}
|
||||
foreach ($shortUrlEdit->deviceLongUrls as $deviceLongUrlPair) {
|
||||
$deviceLongUrl = $this->deviceLongUrls->findFirst(
|
||||
fn ($_, DeviceLongUrl $d) => $d->deviceType === $deviceLongUrlPair->deviceType,
|
||||
);
|
||||
|
||||
if ($deviceLongUrl !== null) {
|
||||
$deviceLongUrl->updateLongUrl($deviceLongUrlPair->longUrl);
|
||||
} else {
|
||||
$this->deviceLongUrls->add(DeviceLongUrl::fromShortUrlAndPair($this, $deviceLongUrlPair));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getLongUrl(): string
|
||||
{
|
||||
return $this->longUrl;
|
||||
@@ -224,42 +279,6 @@ class ShortUrl extends AbstractEntity
|
||||
return $this->forwardQuery;
|
||||
}
|
||||
|
||||
public function update(
|
||||
ShortUrlEdition $shortUrlEdit,
|
||||
?ShortUrlRelationResolverInterface $relationResolver = null,
|
||||
): void {
|
||||
if ($shortUrlEdit->validSinceWasProvided()) {
|
||||
$this->validSince = $shortUrlEdit->validSince;
|
||||
}
|
||||
if ($shortUrlEdit->validUntilWasProvided()) {
|
||||
$this->validUntil = $shortUrlEdit->validUntil;
|
||||
}
|
||||
if ($shortUrlEdit->maxVisitsWasProvided()) {
|
||||
$this->maxVisits = $shortUrlEdit->maxVisits;
|
||||
}
|
||||
if ($shortUrlEdit->longUrlWasProvided()) {
|
||||
$this->longUrl = $shortUrlEdit->longUrl ?? $this->longUrl;
|
||||
}
|
||||
if ($shortUrlEdit->tagsWereProvided()) {
|
||||
$relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver();
|
||||
$this->tags = $relationResolver->resolveTags($shortUrlEdit->tags);
|
||||
}
|
||||
if ($shortUrlEdit->crawlableWasProvided()) {
|
||||
$this->crawlable = $shortUrlEdit->crawlable;
|
||||
}
|
||||
if (
|
||||
$this->title === null
|
||||
|| $shortUrlEdit->titleWasProvided()
|
||||
|| ($this->titleWasAutoResolved && $shortUrlEdit->titleWasAutoResolved())
|
||||
) {
|
||||
$this->title = $shortUrlEdit->title;
|
||||
$this->titleWasAutoResolved = $shortUrlEdit->titleWasAutoResolved();
|
||||
}
|
||||
if ($shortUrlEdit->forwardQueryWasProvided()) {
|
||||
$this->forwardQuery = $shortUrlEdit->forwardQuery;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ShortCodeCannotBeRegeneratedException
|
||||
*/
|
||||
@@ -298,4 +317,14 @@ class ShortUrl extends AbstractEntity
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deviceLongUrls(): array
|
||||
{
|
||||
$data = [];
|
||||
foreach ($this->deviceLongUrls as $deviceUrl) {
|
||||
$data[$deviceUrl->deviceType->value] = $deviceUrl->longUrl();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use function sprintf;
|
||||
|
||||
class ShortUrlStringifier implements ShortUrlStringifierInterface
|
||||
{
|
||||
public function __construct(private array $domainConfig, private string $basePath = '')
|
||||
public function __construct(private readonly array $domainConfig, private readonly string $basePath = '')
|
||||
{
|
||||
}
|
||||
|
||||
@@ -28,6 +28,6 @@ class ShortUrlStringifier implements ShortUrlStringifierInterface
|
||||
|
||||
private function resolveDomain(ShortUrl $shortUrl): string
|
||||
{
|
||||
return $shortUrl->getDomain()?->getAuthority() ?? $this->domainConfig['hostname'] ?? '';
|
||||
return $shortUrl->getDomain()?->authority() ?? $this->domainConfig['hostname'] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ final class ShortUrlEdition implements TitleResolutionModelInterface
|
||||
{
|
||||
/**
|
||||
* @param string[] $tags
|
||||
* @param DeviceLongUrlPair[] $deviceLongUrls
|
||||
*/
|
||||
private function __construct(
|
||||
private readonly bool $longUrlPropWasProvided = false,
|
||||
|
||||
@@ -45,7 +45,7 @@ final class ShortUrlIdentifier
|
||||
public static function fromShortUrl(ShortUrl $shortUrl): self
|
||||
{
|
||||
$domain = $shortUrl->getDomain();
|
||||
$domainAuthority = $domain?->getAuthority();
|
||||
$domainAuthority = $domain?->authority();
|
||||
|
||||
return new self($shortUrl->getShortCode(), $domainAuthority);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Shlinkio\Shlink\Core\ShortUrl\Resolver;
|
||||
|
||||
use Doctrine\Common\Collections;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
|
||||
use Shlinkio\Shlink\Core\Tag\Entity\Tag;
|
||||
|
||||
@@ -20,7 +19,7 @@ class SimpleShortUrlRelationResolver implements ShortUrlRelationResolverInterfac
|
||||
|
||||
/**
|
||||
* @param string[] $tags
|
||||
* @return Collection<int, Tag>
|
||||
* @return Collections\Collection<int, Tag>
|
||||
*/
|
||||
public function resolveTags(array $tags): Collections\Collection
|
||||
{
|
||||
|
||||
@@ -76,7 +76,7 @@ class UrlShortener implements UrlShortenerInterface
|
||||
|
||||
if (! $couldBeMadeUnique) {
|
||||
$domain = $shortUrlToBeCreated->getDomain();
|
||||
$domainAuthority = $domain?->getAuthority();
|
||||
$domainAuthority = $domain?->authority();
|
||||
|
||||
throw NonUniqueSlugException::fromSlug($shortUrlToBeCreated->getShortCode(), $domainAuthority);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ use Shlinkio\Shlink\Core\Visit\Model\Visitor;
|
||||
class VisitsTracker implements VisitsTrackerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ORM\EntityManagerInterface $em,
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
private TrackingOptions $options,
|
||||
private readonly ORM\EntityManagerInterface $em,
|
||||
private readonly EventDispatcherInterface $eventDispatcher,
|
||||
private readonly TrackingOptions $options,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -62,6 +62,9 @@ class VisitsTracker implements VisitsTrackerInterface
|
||||
$this->trackVisit($createVisit, $visitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(Visitor $visitor): Visit $createVisit
|
||||
*/
|
||||
private function trackVisit(callable $createVisit, Visitor $visitor): void
|
||||
{
|
||||
if ($this->options->disableTracking) {
|
||||
|
||||
Reference in New Issue
Block a user