Repository navigation
Dev #390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Dev #390
Changes from all commits
3ebedfb
7327e72
2777d0e
54e8f5f
c52c7cb
3914068
c09147b
93a4548
deff331
ee412fd
6cbdced
8f30740
e37ba43
4e4c6d0
b7eb0a8
ba20091
3ec60da
c517d53
5e71eb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace PhpList\Core\Domain\Analytics\Model\Dto; | ||
|
|
||
| /** Identifies one tracked link of one campaign for one subscriber, as carried in a click-tracking URL. */ | ||
| final class LinkTrackToken | ||
| { | ||
| public const TYPE_HTML = 'H'; | ||
| public const TYPE_TEXT = 'T'; | ||
|
|
||
| public function __construct( | ||
| public readonly string $type, | ||
| public readonly string $forwardUuid, | ||
| public readonly string $messageUuid, | ||
| public readonly string $subscriberUuid, | ||
| ) { | ||
| } | ||
|
|
||
| public function isHtml(): bool | ||
| { | ||
| return $this->type === self::TYPE_HTML; | ||
| } | ||
|
|
||
| public static function isValidType(string $type): bool | ||
| { | ||
| return in_array($type, [self::TYPE_HTML, self::TYPE_TEXT], true); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,11 +4,88 @@ | |||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| namespace PhpList\Core\Domain\Analytics\Repository; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| use Doctrine\DBAL\Exception\UniqueConstraintViolationException; | ||||||||||||||||||||||||||||||||||||||||
| use Doctrine\DBAL\Types\Types; | ||||||||||||||||||||||||||||||||||||||||
| use PhpList\Core\Domain\Analytics\Model\LinkTrackForward; | ||||||||||||||||||||||||||||||||||||||||
| use PhpList\Core\Domain\Common\Repository\AbstractRepository; | ||||||||||||||||||||||||||||||||||||||||
| use PhpList\Core\Domain\Common\Repository\CursorPaginationTrait; | ||||||||||||||||||||||||||||||||||||||||
| use PhpList\Core\Domain\Common\Repository\Interfaces\PaginatableRepositoryInterface; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| class LinkTrackForwardRepository extends AbstractRepository implements PaginatableRepositoryInterface | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| use CursorPaginationTrait; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| public function findOneByUuid(string $uuid): ?LinkTrackForward | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| return $this->findOneBy(['uuid' => $uuid]); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| public function findOneByUrlHash(string $urlHash): ?LinkTrackForward | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| return $this->findOneBy(['urlHash' => $urlHash]); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||
| * Returns the forward for a (cleaned) URL, creating it when it does not exist yet. Creation uses a plain | ||||||||||||||||||||||||||||||||||||||||
| * insert, so two workers racing on the same URL end up with the one row enforced by the urlhash unique key | ||||||||||||||||||||||||||||||||||||||||
| * instead of a closed entity manager - this is an exception of the usual workflow of creating entities in manager | ||||||||||||||||||||||||||||||||||||||||
| * classes. | ||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||
| public function findOrCreateByUrl(string $url, bool $personalise): LinkTrackForward | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| $urlHash = md5($url); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| $forward = $this->findOneByUrlHash($urlHash); | ||||||||||||||||||||||||||||||||||||||||
| if ($forward === null) { | ||||||||||||||||||||||||||||||||||||||||
| // false when a concurrent worker created it first, the select then finds that row | ||||||||||||||||||||||||||||||||||||||||
| $this->insert($url, $urlHash, $personalise); | ||||||||||||||||||||||||||||||||||||||||
| $forward = $this->findOneByUrlHash($urlHash); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| if (empty($forward->getUuid())) { | ||||||||||||||||||||||||||||||||||||||||
| $metadata = $this->getClassMetadata(); | ||||||||||||||||||||||||||||||||||||||||
| $uuidColumn = $metadata->getColumnName('uuid'); | ||||||||||||||||||||||||||||||||||||||||
| // only fills an empty uuid, so a worker that got there first is never overwritten | ||||||||||||||||||||||||||||||||||||||||
| $this->getEntityManager()->getConnection()->executeStatement( | ||||||||||||||||||||||||||||||||||||||||
| sprintf( | ||||||||||||||||||||||||||||||||||||||||
| 'UPDATE %s SET %s = :uuid WHERE %s = :id AND (%s IS NULL OR %s = \'\')', | ||||||||||||||||||||||||||||||||||||||||
| $metadata->getTableName(), | ||||||||||||||||||||||||||||||||||||||||
| $uuidColumn, | ||||||||||||||||||||||||||||||||||||||||
| $metadata->getColumnName('id'), | ||||||||||||||||||||||||||||||||||||||||
| $uuidColumn, | ||||||||||||||||||||||||||||||||||||||||
| $uuidColumn | ||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||
| ['uuid' => self::generateUuid(), 'id' => $forward->getId()], | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| $this->getEntityManager()->refresh($forward); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace the
♻️ Proposed fix
As per path instructions: "Do not allow, flag ANY DB write / finalization: Committable suggestion
|
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return $forward; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private function insert(string $url, string $urlHash, bool $personalise): bool | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| $metadata = $this->getClassMetadata(); | ||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||
| $this->getEntityManager()->getConnection()->insert( | ||||||||||||||||||||||||||||||||||||||||
| $metadata->getTableName(), | ||||||||||||||||||||||||||||||||||||||||
| [ | ||||||||||||||||||||||||||||||||||||||||
| $metadata->getColumnName('url') => $url, | ||||||||||||||||||||||||||||||||||||||||
| $metadata->getColumnName('urlHash') => $urlHash, | ||||||||||||||||||||||||||||||||||||||||
| $metadata->getColumnName('personalise') => $personalise, | ||||||||||||||||||||||||||||||||||||||||
| $metadata->getColumnName('uuid') => self::generateUuid(), | ||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||
| [$metadata->getColumnName('personalise') => Types::BOOLEAN], | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
| } catch (UniqueConstraintViolationException) { | ||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| private static function generateUuid(): string | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| return bin2hex(random_bytes(18)); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide an upgrade path for
APP_WEBSITE.Existing
.envfiles are not updated from.env.dist. IfAPP_WEBSITEis absent, Symfony throws when it constructsDefaultConfigProvider, even when the database already contains website settings. Provide a backward-compatible fallback or populate the variable during upgrades. (raw.githubusercontent.com)