diff --git a/typo3/sysext/backend/Classes/Controller/FormSlugAjaxController.php b/typo3/sysext/backend/Classes/Controller/FormSlugAjaxController.php
index 435664e2cd8548f6f5242a9e1108b295eaeda5b2..04cb72e32942693590bdd27a6d25fb6befc07023 100644
--- a/typo3/sysext/backend/Classes/Controller/FormSlugAjaxController.php
+++ b/typo3/sysext/backend/Classes/Controller/FormSlugAjaxController.php
@@ -18,6 +18,7 @@ namespace TYPO3\CMS\Backend\Controller;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
 use TYPO3\CMS\Core\DataHandling\SlugHelper;
 use TYPO3\CMS\Core\Http\JsonResponse;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -55,6 +56,7 @@ class FormSlugAjaxController extends AbstractFormEngineAjaxController
      *
      * @param ServerRequestInterface $request
      * @return ResponseInterface
+     * @throws \RuntimeException
      */
     public function suggestAction(ServerRequestInterface $request): ResponseInterface
     {
@@ -84,17 +86,17 @@ class FormSlugAjaxController extends AbstractFormEngineAjaxController
 
         $hasConflict = false;
 
+        $recordData = $values;
+        $recordData['pid'] = $pid;
+        if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
+            $recordData[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = $languageId;
+        }
+
         $slug = GeneralUtility::makeInstance(SlugHelper::class, $tableName, $fieldName, $fieldConfig);
         if ($mode === 'auto') {
             // New page - Feed incoming values to generator
-            $recordData = $values;
-            $recordData['pid'] = $pid;
-            $recordData[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = $languageId;
             $proposal = $slug->generate($recordData, $pid);
         } elseif ($mode === 'recreate') {
-            $recordData = $values;
-            $recordData['pid'] = $pid;
-            $recordData[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']] = $languageId;
             $proposal = $slug->generate($recordData, $parentPageId);
         } elseif ($mode === 'manual') {
             // Existing record - Fetch full record and only validate against the new "slug" field.
@@ -103,13 +105,15 @@ class FormSlugAjaxController extends AbstractFormEngineAjaxController
             throw new \RuntimeException('mode must be either "auto", "recreate" or "manual"', 1535835666);
         }
 
-        if ($hasToBeUniqueInSite && !$slug->isUniqueInSite($proposal, $recordId, $pid, $languageId)) {
+        $state = RecordStateFactory::forName($tableName)
+            ->fromArray($recordData, $pid, $recordId);
+        if ($hasToBeUniqueInSite && !$slug->isUniqueInSite($proposal, $state)) {
             $hasConflict = true;
-            $proposal = $slug->buildSlugForUniqueInSite($proposal, $recordId, $pid, $languageId);
+            $proposal = $slug->buildSlugForUniqueInSite($proposal, $state);
         }
-        if ($hasToBeUniqueInPid && !$slug->isUniqueInPid($proposal, $recordId, $pid, $languageId)) {
+        if ($hasToBeUniqueInPid && !$slug->isUniqueInPid($proposal, $state)) {
             $hasConflict = true;
-            $proposal = $slug->buildSlugForUniqueInPid($proposal, $recordId, $pid, $languageId);
+            $proposal = $slug->buildSlugForUniqueInPid($proposal, $state);
         }
 
         return new JsonResponse([
@@ -122,6 +126,7 @@ class FormSlugAjaxController extends AbstractFormEngineAjaxController
     /**
      * @param ServerRequestInterface $request
      * @return bool
+     * @throws \InvalidArgumentException
      */
     protected function checkRequest(ServerRequestInterface $request): bool
     {
diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
index 05ebd414309354f872c349328a05bce616325658..0f2e0f9fed0e9d1de40a6bf49fb9ab722648326e 100644
--- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php
+++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
@@ -47,6 +47,7 @@ use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Database\RelationHandler;
 use TYPO3\CMS\Core\DataHandling\History\RecordHistoryStore;
 use TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor;
+use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
 use TYPO3\CMS\Core\Html\RteHtmlParser;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
@@ -1976,9 +1977,6 @@ class DataHandler implements LoggerAwareInterface
             $value = $helper->sanitize($value);
         }
 
-        $languageId = (int)$fullRecord[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
-        $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
-
         // In case a workspace is given, and the $realPid(!) still is negative
         // this is most probably triggered by versionizeRecord() and a raw record
         // copy - thus, uniqueness cannot be determined without having the
@@ -1990,11 +1988,19 @@ class DataHandler implements LoggerAwareInterface
             return ['value' => $value];
         }
 
+        // Return directly in case no evaluations are defined
+        if (empty($tcaFieldConf['eval'])) {
+            return ['value' => $value];
+        }
+
+        $state = RecordStateFactory::forName($table)
+            ->fromArray($fullRecord, $realPid, $id);
+        $evalCodesArray = GeneralUtility::trimExplode(',', $tcaFieldConf['eval'], true);
         if (in_array('uniqueInSite', $evalCodesArray, true)) {
-            $value = $helper->buildSlugForUniqueInSite($value, $id, $realPid, $languageId);
+            $value = $helper->buildSlugForUniqueInSite($value, $state);
         }
         if (in_array('uniqueInPid', $evalCodesArray, true)) {
-            $value = $helper->buildSlugForUniqueInPid($value, $id, $realPid, $languageId);
+            $value = $helper->buildSlugForUniqueInPid($value, $state);
         }
 
         return ['value' => $value];
diff --git a/typo3/sysext/core/Classes/DataHandling/Model/EntityContext.php b/typo3/sysext/core/Classes/DataHandling/Model/EntityContext.php
new file mode 100644
index 0000000000000000000000000000000000000000..582a9183fac53d732e2baa9cdc82795fd759e861
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Model/EntityContext.php
@@ -0,0 +1,80 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\DataHandling\Model;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Represents the context of an entity
+ *
+ * A context defines a "variant" of an entity, currently by its language and workspace assignment. The EntityContext
+ * is bound to a RecordState.
+ */
+class EntityContext
+{
+    /**
+     * @var int
+     */
+    protected $workspaceId = 0;
+
+    /**
+     * @var int
+     */
+    protected $languageId = 0;
+
+    /**
+     * @return int
+     */
+    public function getWorkspaceId(): int
+    {
+        return $this->workspaceId;
+    }
+
+    /**
+     * @param int $workspaceId
+     * @return static
+     */
+    public function withWorkspaceId(int $workspaceId): self
+    {
+        if ($this->workspaceId === $workspaceId) {
+            return $this;
+        }
+        $target = clone $this;
+        $target->workspaceId = $workspaceId;
+        return $target;
+    }
+
+    /**
+     * @return int
+     */
+    public function getLanguageId(): int
+    {
+        return $this->languageId;
+    }
+
+    /**
+     * @param int $languageId
+     * @return static
+     */
+    public function withLanguageId(int $languageId): self
+    {
+        if ($this->languageId === $languageId) {
+            return $this;
+        }
+        $target = clone $this;
+        $target->languageId = $languageId;
+        return $target;
+    }
+}
diff --git a/typo3/sysext/core/Classes/DataHandling/Model/EntityPointer.php b/typo3/sysext/core/Classes/DataHandling/Model/EntityPointer.php
new file mode 100644
index 0000000000000000000000000000000000000000..a8b20995a5798eec6a5ee00c5683650f571e4b5e
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Model/EntityPointer.php
@@ -0,0 +1,44 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\DataHandling\Model;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Interface describing pointers to an entity
+ */
+interface EntityPointer
+{
+    /**
+     * @return string
+     */
+    public function getName(): string;
+
+    /**
+     * @return string
+     */
+    public function getIdentifier(): string;
+
+    /**
+     * @return bool
+     */
+    public function isNode(): bool;
+
+    /**
+     * @param EntityPointer $other
+     * @return bool
+     */
+    public function isEqualTo(EntityPointer $other): bool;
+}
diff --git a/typo3/sysext/core/Classes/DataHandling/Model/EntityPointerLink.php b/typo3/sysext/core/Classes/DataHandling/Model/EntityPointerLink.php
new file mode 100644
index 0000000000000000000000000000000000000000..92417f0c2b4ffde6b2e464f7b416f2276e6d0e8a
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Model/EntityPointerLink.php
@@ -0,0 +1,83 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\DataHandling\Model;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * An EntityPointerLink is used to connect EntityPointer instances
+ */
+class EntityPointerLink
+{
+    /**
+     * @var EntityPointer
+     */
+    protected $subject;
+
+    /**
+     * @var EntityPointerLink|null
+     */
+    protected $ancestor;
+
+    /**
+     * @param EntityPointer $subject
+     */
+    public function __construct(EntityPointer $subject)
+    {
+        $this->subject = $subject;
+    }
+
+    /**
+     * @return EntityPointer
+     */
+    public function getSubject(): EntityPointer
+    {
+        return $this->subject;
+    }
+
+    /**
+     * @return EntityPointerLink
+     */
+    public function getHead(): EntityPointerLink
+    {
+        $head = $this;
+        while ($head->ancestor !== null) {
+            $head = $head->ancestor;
+        }
+        return $head;
+    }
+
+    /**
+     * @return EntityPointerLink|null
+     */
+    public function getAncestor(): ?EntityPointerLink
+    {
+        return $this->ancestor;
+    }
+
+    /**
+     * @param EntityPointerLink $ancestor
+     * @return EntityPointerLink
+     */
+    public function withAncestor(EntityPointerLink $ancestor): self
+    {
+        if ($this->ancestor === $ancestor) {
+            return $this;
+        }
+        $target = clone $this;
+        $target->ancestor = $ancestor;
+        return $target;
+    }
+}
diff --git a/typo3/sysext/core/Classes/DataHandling/Model/EntityUidPointer.php b/typo3/sysext/core/Classes/DataHandling/Model/EntityUidPointer.php
new file mode 100644
index 0000000000000000000000000000000000000000..1032b123066402b071966d2ca17561d422c36d38
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Model/EntityUidPointer.php
@@ -0,0 +1,91 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\DataHandling\Model;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * The EntityUidPointer represents the concrete origin of the entity
+ */
+class EntityUidPointer implements EntityPointer
+{
+    /**
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * @var string
+     */
+    protected $identifier;
+
+    /**
+     * @param string $name
+     * @param string $identifier
+     */
+    public function __construct(string $name, string $identifier)
+    {
+        $this->name = $name;
+        $this->identifier = $identifier;
+    }
+
+    /**
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    /**
+     * @return string
+     */
+    public function getIdentifier(): string
+    {
+        return $this->identifier;
+    }
+
+    /**
+     * @param string $identifier
+     * @return static
+     */
+    public function withUid(string $identifier): self
+    {
+        if ($this->identifier === $identifier) {
+            return $this;
+        }
+        $target = clone $this;
+        $target->identifier = $identifier;
+        return $target;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isNode(): bool
+    {
+        return $this->name === 'pages';
+    }
+
+    /**
+     * @param EntityPointer $other
+     * @return bool
+     */
+    public function isEqualTo(EntityPointer $other): bool
+    {
+        return $this->identifier === $other->getIdentifier()
+            && $this->name === $other->getName();
+    }
+}
diff --git a/typo3/sysext/core/Classes/DataHandling/Model/RecordState.php b/typo3/sysext/core/Classes/DataHandling/Model/RecordState.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7a09358c2f1d01efedb985607b34a02a32d1e99
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Model/RecordState.php
@@ -0,0 +1,164 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\DataHandling\Model;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\MathUtility;
+
+/**
+ * A RecordState is an abstract description of a record that consists of
+ *
+ * - an EntityContext describing the "variant" of a record
+ * - an EntityPointer that describes the node where the record is stored
+ * - an EntityUidPointer of the record the RecordState instance represents
+ *
+ * Instances of this class are created by the RecordStateFactory.
+ */
+class RecordState
+{
+    /**
+     * @var EntityContext
+     */
+    protected $context;
+
+    /**
+     * @var EntityPointer
+     */
+    protected $node;
+
+    /**
+     * @var EntityUidPointer
+     */
+    protected $subject;
+
+    /**
+     * @var EntityPointerLink
+     */
+    protected $languageLink;
+
+    /**
+     * @var EntityPointerLink
+     */
+    protected $versionLink;
+
+    /**
+     * @param EntityContext $context
+     * @param EntityPointer $node
+     * @param EntityUidPointer $subject
+     */
+    public function __construct(EntityContext $context, EntityPointer $node, EntityUidPointer $subject)
+    {
+        $this->context = $context;
+        $this->node = $node;
+        $this->subject = $subject;
+    }
+
+    /**
+     * @return EntityContext
+     */
+    public function getContext(): EntityContext
+    {
+        return $this->context;
+    }
+
+    /**
+     * @return EntityPointer
+     */
+    public function getNode(): EntityPointer
+    {
+        return $this->node;
+    }
+
+    /**
+     * @return EntityUidPointer
+     */
+    public function getSubject(): EntityUidPointer
+    {
+        return $this->subject;
+    }
+
+    /**
+     * @return EntityPointerLink
+     */
+    public function getLanguageLink(): ?EntityPointerLink
+    {
+        return $this->languageLink;
+    }
+
+    /**
+     * @param EntityPointerLink|null $languageLink
+     * @return static
+     */
+    public function withLanguageLink(?EntityPointerLink $languageLink): self
+    {
+        if ($this->languageLink === $languageLink) {
+            return $this;
+        }
+        $target = clone $this;
+        $target->languageLink = $languageLink;
+        return $target;
+    }
+
+    /**
+     * @return EntityPointerLink
+     */
+    public function getVersionLink(): ?EntityPointerLink
+    {
+        return $this->versionLink;
+    }
+
+    /**
+     * @param EntityPointerLink|null $versionLink
+     * @return static
+     */
+    public function withVersionLink(?EntityPointerLink $versionLink): self
+    {
+        if ($this->versionLink === $versionLink) {
+            return $this;
+        }
+        $target = clone $this;
+        $target->versionLink = $versionLink;
+        return $target;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isNew(): bool
+    {
+        return !MathUtility::canBeInterpretedAsInteger(
+            $this->subject->getIdentifier()
+        );
+    }
+
+    /**
+     * Resolve identifier of node used as aggregate. For translated pages
+     * that would result in the `uid` of the outer-most language parent page.
+     *
+     * @return string
+     */
+    public function resolveAggregateNodeIdentifier(): string
+    {
+        if ($this->subject->isNode()
+            && $this->context->getLanguageId() > 0
+            && $this->languageLink !== null
+        ) {
+            return $this->languageLink->getHead()->getSubject()->getIdentifier();
+        }
+
+        return $this->node->getIdentifier();
+    }
+}
diff --git a/typo3/sysext/core/Classes/DataHandling/Model/RecordStateFactory.php b/typo3/sysext/core/Classes/DataHandling/Model/RecordStateFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..1d46225cfb642664d1919c01d5716cdbd27025d5
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Model/RecordStateFactory.php
@@ -0,0 +1,163 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\DataHandling\Model;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Factory class that creates a record state
+ */
+class RecordStateFactory
+{
+    /**
+     * @var string
+     */
+    protected $name;
+
+    /**
+     * @param string $name
+     * @return static
+     */
+    public static function forName(string $name): self
+    {
+        return GeneralUtility::makeInstance(
+            static::class,
+            $name
+        );
+    }
+
+    /**
+     * @param string $name
+     */
+    public function __construct(string $name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * @param array $data
+     * @param int|string|null $pageId
+     * @param int|string|null $recordId
+     * @return object|RecordState
+     */
+    public function fromArray(array $data, $pageId = null, $recordId = null): RecordState
+    {
+        $pageId = $pageId ?? $data['pid'] ?? null;
+        $recordId = $recordId ?? $data['uid'] ?? null;
+
+        $aspectFieldValues = $this->resolveAspectFieldValues($data);
+
+        $context = GeneralUtility::makeInstance(EntityContext::class)
+            ->withWorkspaceId($aspectFieldValues['workspace'])
+            ->withLanguageId($aspectFieldValues['language']);
+        $node = $this->createEntityPointer($pageId, 'pages');
+        $subject = $this->createEntityPointer($recordId);
+
+        /** @var RecordState $target */
+        $target = GeneralUtility::makeInstance(
+            RecordState::class,
+            $context,
+            $node,
+            $subject
+        );
+        return $target
+            ->withLanguageLink($this->resolveLanguageLink($aspectFieldValues))
+            ->withVersionLink($this->resolveLanguageLink($aspectFieldValues));
+    }
+
+    /**
+     * @return array
+     */
+    protected function resolveAspectFieldNames(): array
+    {
+        return [
+            'workspace' => 't3ver_wsid',
+            'versionParent' => 't3ver_oid',
+            'language' => $GLOBALS['TCA'][$this->name]['ctrl']['languageField'] ?? null,
+            'languageParent' => $GLOBALS['TCA'][$this->name]['ctrl']['transOrigPointerField'] ?? null,
+            'languageSource' => $GLOBALS['TCA'][$this->name]['ctrl']['translationSource'] ?? null,
+        ];
+    }
+
+    /**
+     * @param array $data
+     * @return array
+     */
+    protected function resolveAspectFieldValues(array $data): array
+    {
+        return array_map(
+            function (string $aspectFieldName) use ($data) {
+                return (int)($data[$aspectFieldName] ?? 0);
+            },
+            $this->resolveAspectFieldNames()
+        );
+    }
+
+    /**
+     * @param array $aspectFieldNames
+     * @return EntityPointerLink|null
+     */
+    protected function resolveLanguageLink(array $aspectFieldNames): ?EntityPointerLink
+    {
+        if (!empty($aspectFieldNames['languageSource'])) {
+            $languageSourceLink = GeneralUtility::makeInstance(
+                EntityPointerLink::class,
+                $this->createEntityPointer($aspectFieldNames['languageSource'])
+            );
+        }
+
+        if (!empty($aspectFieldNames['languageParent'])) {
+            $languageParentLink = GeneralUtility::makeInstance(
+                EntityPointerLink::class,
+                $this->createEntityPointer($aspectFieldNames['languageParent'])
+            );
+        }
+
+        if (empty($languageSourceLink) || empty($languageParentLink)
+            || $languageSourceLink->getSubject()->isEqualTo(
+                $languageParentLink->getSubject()
+            )
+        ) {
+            return $languageSourceLink ?? $languageParentLink ?? null;
+        }
+        return $languageSourceLink->withAncestor($languageParentLink);
+    }
+
+    /**
+     * @param string|int $identifier
+     * @param string|null $name
+     * @return EntityPointer
+     * @throws \LogicException
+     */
+    protected function createEntityPointer($identifier, string $name = null): EntityPointer
+    {
+        if ($identifier === null) {
+            throw new \LogicException(
+                'Cannot create null pointer',
+                1536407967
+            );
+        }
+
+        $identifier = (string)$identifier;
+
+        return GeneralUtility::makeInstance(
+            EntityUidPointer::class,
+            $name ?? $this->name,
+            $identifier
+        );
+    }
+}
diff --git a/typo3/sysext/core/Classes/DataHandling/SlugHelper.php b/typo3/sysext/core/Classes/DataHandling/SlugHelper.php
index 30b8bd0236902334fdd8144373ccf14d5fe92606..86cba5b398f40ad59718ba13c8036dc2aef6dc8a 100644
--- a/typo3/sysext/core/Classes/DataHandling/SlugHelper.php
+++ b/typo3/sysext/core/Classes/DataHandling/SlugHelper.php
@@ -21,6 +21,8 @@ use TYPO3\CMS\Core\Charset\CharsetConverter;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\DataHandling\Model\RecordState;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Routing\SiteMatcher;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -206,13 +208,15 @@ class SlugHelper
      * Checks if there are other records with the same slug that are located on the same PID.
      *
      * @param string $slug
-     * @param string|int $recordId
-     * @param int $pageId
-     * @param int $languageId
+     * @param RecordState $state
      * @return bool
      */
-    public function isUniqueInPid(string $slug, $recordId, int $pageId, int $languageId): bool
+    public function isUniqueInPid(string $slug, RecordState $state): bool
     {
+        $pageId = (int)$state->resolveAggregateNodeIdentifier();
+        $recordId = $state->getSubject()->getIdentifier();
+        $languageId = $state->getContext()->getLanguageId();
+
         if ($pageId < 0) {
             $pageId = $this->resolveLivePageId($recordId);
         }
@@ -235,14 +239,16 @@ class SlugHelper
      * Check if there are other records with the same slug that are located on the same site.
      *
      * @param string $slug
-     * @param string|int $recordId
-     * @param int $pageId
-     * @param int $languageId
+     * @param RecordState $state
      * @return bool
      * @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
      */
-    public function isUniqueInSite(string $slug, $recordId, int $pageId, int $languageId): bool
+    public function isUniqueInSite(string $slug, RecordState $state): bool
     {
+        $pageId = (int)$state->resolveAggregateNodeIdentifier();
+        $recordId = $state->getSubject()->getIdentifier();
+        $languageId = $state->getContext()->getLanguageId();
+
         if ($pageId < 0) {
             $pageId = $this->resolveLivePageId($recordId);
         }
@@ -266,7 +272,13 @@ class SlugHelper
         $siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
         $siteOfCurrentRecord = $siteMatcher->matchByPageId($pageId);
         foreach ($records as $record) {
-            $siteOfExistingRecord = $siteMatcher->matchByPageId((int)$record['uid']);
+            try {
+                $siteOfExistingRecord = $siteMatcher->matchByPageId((int)$record['uid']);
+            } catch (SiteNotFoundException $exception) {
+                // In case not site is found, the record is not
+                // organized in any site or pseudo-site
+                continue;
+            }
             if ($siteOfExistingRecord->getRootPageId() === $siteOfCurrentRecord->getRootPageId()) {
                 return false;
             }
@@ -280,13 +292,11 @@ class SlugHelper
      * Generate a slug with a suffix "/mytitle-1" if that is in use already.
      *
      * @param string $slug proposed slug
-     * @param mixed $recordId can be a new record (non-int) or an existing record ID
-     * @param int $realPid pageID (already workspace-resolved)
-     * @param int $languageId the language ID realm to be searched for
+     * @param RecordState $state
      * @return string
      * @throws \TYPO3\CMS\Core\Exception\SiteNotFoundException
      */
-    public function buildSlugForUniqueInSite(string $slug, $recordId, int $realPid, int $languageId): string
+    public function buildSlugForUniqueInSite(string $slug, RecordState $state): string
     {
         $slug = $this->sanitize($slug);
         $rawValue = $this->extract($slug);
@@ -294,9 +304,7 @@ class SlugHelper
         $counter = 0;
         while (!$this->isUniqueInSite(
                 $newValue,
-                $recordId,
-                $realPid,
-                $languageId
+                $state
             ) && $counter++ < 100
         ) {
             $newValue = $this->sanitize($rawValue . '-' . $counter);
@@ -311,12 +319,10 @@ class SlugHelper
      * Generate a slug with a suffix "/mytitle-1" if the suggested slug is in use already.
      *
      * @param string $slug proposed slug
-     * @param mixed $recordId can be a new record (non-int) or an existing record ID
-     * @param int $realPid pageID (already workspace-resolved)
-     * @param int $languageId the language ID realm to be searched for
+     * @param RecordState $state
      * @return string
      */
-    public function buildSlugForUniqueInPid(string $slug, $recordId, int $realPid, int $languageId): string
+    public function buildSlugForUniqueInPid(string $slug, RecordState $state): string
     {
         $slug = $this->sanitize($slug);
         $rawValue = $this->extract($slug);
@@ -324,9 +330,7 @@ class SlugHelper
         $counter = 0;
         while (!$this->isUniqueInPid(
                 $newValue,
-                $recordId,
-                $realPid,
-                $languageId
+                $state
             ) && $counter++ < 100
         ) {
             $newValue = $this->sanitize($rawValue . '-' . $counter);
diff --git a/typo3/sysext/install/Classes/Updates/PopulatePageSlugs.php b/typo3/sysext/install/Classes/Updates/PopulatePageSlugs.php
index f34479757bfb6709035a6937faad21759eb6a910..dd61de3c1cf8f84f2a2268dc61e5b861c928d350 100644
--- a/typo3/sysext/install/Classes/Updates/PopulatePageSlugs.php
+++ b/typo3/sysext/install/Classes/Updates/PopulatePageSlugs.php
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Install\Updates;
 
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
 use TYPO3\CMS\Core\DataHandling\SlugHelper;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -166,11 +167,13 @@ class PopulatePageSlugs implements UpgradeWizardInterface
                 $slug = $slugHelper->generate($record, $pid);
             }
 
-            if ($hasToBeUniqueInSite && !$slugHelper->isUniqueInSite($slug, $recordId, $pid, $languageId)) {
-                $slug = $slugHelper->buildSlugForUniqueInSite($slug, $recordId, $pid, $languageId);
+            $state = RecordStateFactory::forName($this->table)
+                ->fromArray($record, $pid, $recordId);
+            if ($hasToBeUniqueInSite && !$slugHelper->isUniqueInSite($slug, $state)) {
+                $slug = $slugHelper->buildSlugForUniqueInSite($slug, $state);
             }
-            if ($hasToBeUniqueInPid && !$slugHelper->isUniqueInPid($slug, $recordId, $pid, $languageId)) {
-                $slug = $slugHelper->buildSlugForUniqueInPid($slug, $recordId, $pid, $languageId);
+            if ($hasToBeUniqueInPid && !$slugHelper->isUniqueInPid($slug, $state)) {
+                $slug = $slugHelper->buildSlugForUniqueInPid($slug, $state);
             }
 
             $connection->update(