123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- <?php
- declare(strict_types=1);
- namespace Shlinkio\Shlink\Core\Entity;
- use Cake\Chronos\Chronos;
- use Doctrine\Common\Collections\ArrayCollection;
- use Doctrine\Common\Collections\Collection;
- use Laminas\Diactoros\Uri;
- use Shlinkio\Shlink\Common\Entity\AbstractEntity;
- use Shlinkio\Shlink\Core\Domain\Resolver\DomainResolverInterface;
- use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver;
- use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException;
- use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
- use function array_reduce;
- use function count;
- use function Functional\contains;
- use function Functional\invoke;
- use function Shlinkio\Shlink\Core\generateRandomShortCode;
- class ShortUrl extends AbstractEntity
- {
- private string $longUrl;
- private string $shortCode;
- private Chronos $dateCreated;
- /** @var Collection|Visit[] */
- private Collection $visits;
- /** @var Collection|Tag[] */
- private Collection $tags;
- private ?Chronos $validSince;
- private ?Chronos $validUntil;
- private ?int $maxVisits;
- private ?Domain $domain;
- private bool $customSlugWasProvided;
- public function __construct(
- string $longUrl,
- ?ShortUrlMeta $meta = null,
- ?DomainResolverInterface $domainResolver = null
- ) {
- $meta = $meta ?? ShortUrlMeta::createEmpty();
- $this->longUrl = $longUrl;
- $this->dateCreated = Chronos::now();
- $this->visits = new ArrayCollection();
- $this->tags = new ArrayCollection();
- $this->validSince = $meta->getValidSince();
- $this->validUntil = $meta->getValidUntil();
- $this->maxVisits = $meta->getMaxVisits();
- $this->customSlugWasProvided = $meta->hasCustomSlug();
- $this->shortCode = $meta->getCustomSlug() ?? generateRandomShortCode();
- $this->domain = ($domainResolver ?? new SimpleDomainResolver())->resolveDomain($meta->getDomain());
- }
- public function getLongUrl(): string
- {
- return $this->longUrl;
- }
- public function getShortCode(): string
- {
- return $this->shortCode;
- }
- public function getDateCreated(): Chronos
- {
- return $this->dateCreated;
- }
- /**
- * @return Collection|Tag[]
- */
- public function getTags(): Collection
- {
- return $this->tags;
- }
- /**
- * @param Collection|Tag[] $tags
- */
- public function setTags(Collection $tags): self
- {
- $this->tags = $tags;
- return $this;
- }
- public function updateMeta(ShortUrlMeta $shortCodeMeta): void
- {
- if ($shortCodeMeta->hasValidSince()) {
- $this->validSince = $shortCodeMeta->getValidSince();
- }
- if ($shortCodeMeta->hasValidUntil()) {
- $this->validUntil = $shortCodeMeta->getValidUntil();
- }
- if ($shortCodeMeta->hasMaxVisits()) {
- $this->maxVisits = $shortCodeMeta->getMaxVisits();
- }
- }
- /**
- * @throws ShortCodeCannotBeRegeneratedException
- */
- public function regenerateShortCode(): self
- {
- // In ShortUrls where a custom slug was provided, do nothing
- if ($this->customSlugWasProvided) {
- throw ShortCodeCannotBeRegeneratedException::forShortUrlWithCustomSlug();
- }
- // The short code can be regenerated only on ShortUrl which have not been persisted yet
- if ($this->id !== null) {
- throw ShortCodeCannotBeRegeneratedException::forShortUrlAlreadyPersisted();
- }
- $this->shortCode = generateRandomShortCode();
- return $this;
- }
- public function getValidSince(): ?Chronos
- {
- return $this->validSince;
- }
- public function getValidUntil(): ?Chronos
- {
- return $this->validUntil;
- }
- public function getVisitsCount(): int
- {
- return count($this->visits);
- }
- /**
- * @param Collection|Visit[] $visits
- * @return ShortUrl
- * @internal
- */
- public function setVisits(Collection $visits): self
- {
- $this->visits = $visits;
- return $this;
- }
- public function getMaxVisits(): ?int
- {
- return $this->maxVisits;
- }
- public function maxVisitsReached(): bool
- {
- return $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits;
- }
- public function toString(array $domainConfig): string
- {
- return (string) (new Uri())->withPath($this->shortCode)
- ->withScheme($domainConfig['schema'] ?? 'http')
- ->withHost($this->resolveDomain($domainConfig['hostname'] ?? ''));
- }
- private function resolveDomain(string $fallback = ''): string
- {
- if ($this->domain === null) {
- return $fallback;
- }
- return $this->domain->getAuthority();
- }
- public function matchesCriteria(ShortUrlMeta $meta, array $tags): bool
- {
- if ($meta->hasMaxVisits() && $meta->getMaxVisits() !== $this->maxVisits) {
- return false;
- }
- if ($meta->hasDomain() && $meta->getDomain() !== $this->resolveDomain()) {
- return false;
- }
- if ($meta->hasValidSince() && ! $meta->getValidSince()->eq($this->validSince)) {
- return false;
- }
- if ($meta->hasValidUntil() && ! $meta->getValidUntil()->eq($this->validUntil)) {
- return false;
- }
- $shortUrlTags = invoke($this->getTags(), '__toString');
- $hasAllTags = count($shortUrlTags) === count($tags) && array_reduce(
- $tags,
- fn (bool $hasAllTags, string $tag) => $hasAllTags && contains($shortUrlTags, $tag),
- true,
- );
- return $hasAllTags;
- }
- }
|