vendor/ezsystems/ezplatform-kernel/eZ/Publish/Core/Repository/ContentTypeService.php line 50

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. declare(strict_types=1);
  7. namespace eZ\Publish\Core\Repository;
  8. use eZ\Publish\API\Repository\ContentTypeService as ContentTypeServiceInterface;
  9. use eZ\Publish\API\Repository\PermissionResolver;
  10. use eZ\Publish\API\Repository\Repository as RepositoryInterface;
  11. use eZ\Publish\Core\FieldType\FieldTypeRegistry;
  12. use eZ\Publish\SPI\Persistence\Content\Type\Handler;
  13. use eZ\Publish\API\Repository\Values\ContentType\ContentType;
  14. use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
  15. use eZ\Publish\API\Repository\Exceptions\BadStateException as APIBadStateException;
  16. use eZ\Publish\API\Repository\Values\User\User;
  17. use eZ\Publish\SPI\Persistence\User\Handler as UserHandler;
  18. use eZ\Publish\API\Repository\Values\ContentType\FieldDefinitionUpdateStruct;
  19. use eZ\Publish\API\Repository\Values\ContentType\FieldDefinition as APIFieldDefinition;
  20. use eZ\Publish\API\Repository\Values\ContentType\FieldDefinitionCreateStruct;
  21. use eZ\Publish\API\Repository\Values\ContentType\ContentType as APIContentType;
  22. use eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft as APIContentTypeDraft;
  23. use eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup as APIContentTypeGroup;
  24. use eZ\Publish\API\Repository\Values\ContentType\ContentTypeUpdateStruct;
  25. use eZ\Publish\API\Repository\Values\ContentType\ContentTypeCreateStruct as APIContentTypeCreateStruct;
  26. use eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroupUpdateStruct;
  27. use eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroupCreateStruct;
  28. use eZ\Publish\API\Repository\Values\Content\Location;
  29. use eZ\Publish\Core\Repository\Values\ContentType\ContentTypeCreateStruct;
  30. use eZ\Publish\SPI\Persistence\Content\Type as SPIContentType;
  31. use eZ\Publish\SPI\Persistence\Content\Type\CreateStruct as SPIContentTypeCreateStruct;
  32. use eZ\Publish\SPI\Persistence\Content\Type\Group\CreateStruct as SPIContentTypeGroupCreateStruct;
  33. use eZ\Publish\SPI\Persistence\Content\Type\Group\UpdateStruct as SPIContentTypeGroupUpdateStruct;
  34. use eZ\Publish\SPI\FieldType\FieldType as SPIFieldType;
  35. use eZ\Publish\Core\Base\Exceptions\NotFoundException;
  36. use eZ\Publish\Core\Base\Exceptions\BadStateException;
  37. use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
  38. use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType;
  39. use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
  40. use eZ\Publish\Core\Base\Exceptions\ContentTypeValidationException;
  41. use eZ\Publish\Core\Base\Exceptions\ContentTypeFieldDefinitionValidationException;
  42. use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
  43. use eZ\Publish\Core\FieldType\ValidationError;
  44. use DateTime;
  45. use Exception;
  46. class ContentTypeService implements ContentTypeServiceInterface
  47. {
  48.     /** @var \eZ\Publish\API\Repository\Repository */
  49.     protected $repository;
  50.     /** @var \eZ\Publish\SPI\Persistence\Content\Type\Handler */
  51.     protected $contentTypeHandler;
  52.     /** @var \eZ\Publish\SPI\Persistence\User\Handler */
  53.     protected $userHandler;
  54.     /** @var array */
  55.     protected $settings;
  56.     /** @var \eZ\Publish\Core\Repository\Mapper\ContentDomainMapper */
  57.     protected $contentDomainMapper;
  58.     /** @var \eZ\Publish\Core\Repository\Mapper\ContentTypeDomainMapper */
  59.     protected $contentTypeDomainMapper;
  60.     /** @var \eZ\Publish\Core\FieldType\FieldTypeRegistry */
  61.     protected $fieldTypeRegistry;
  62.     /** @var \eZ\Publish\API\Repository\PermissionResolver */
  63.     private $permissionResolver;
  64.     /**
  65.      * Setups service with reference to repository object that created it & corresponding handler.
  66.      *
  67.      * @param \eZ\Publish\API\Repository\Repository $repository
  68.      * @param \eZ\Publish\SPI\Persistence\Content\Type\Handler $contentTypeHandler
  69.      * @param \eZ\Publish\SPI\Persistence\User\Handler $userHandler
  70.      * @param \eZ\Publish\Core\Repository\Mapper\ContentDomainMapper $contentDomainMapper
  71.      * @param \eZ\Publish\Core\Repository\Mapper\ContentTypeDomainMapper $contentTypeDomainMapper
  72.      * @param \eZ\Publish\Core\FieldType\FieldTypeRegistry $fieldTypeRegistry
  73.      * @param \eZ\Publish\API\Repository\PermissionResolver $permissionResolver
  74.      * @param array $settings
  75.      */
  76.     public function __construct(
  77.         RepositoryInterface $repository,
  78.         Handler $contentTypeHandler,
  79.         UserHandler $userHandler,
  80.         Mapper\ContentDomainMapper $contentDomainMapper,
  81.         Mapper\ContentTypeDomainMapper $contentTypeDomainMapper,
  82.         FieldTypeRegistry $fieldTypeRegistry,
  83.         PermissionResolver $permissionResolver,
  84.         array $settings = []
  85.     ) {
  86.         $this->repository $repository;
  87.         $this->contentTypeHandler $contentTypeHandler;
  88.         $this->userHandler $userHandler;
  89.         $this->contentDomainMapper $contentDomainMapper;
  90.         $this->contentTypeDomainMapper $contentTypeDomainMapper;
  91.         $this->fieldTypeRegistry $fieldTypeRegistry;
  92.         // Union makes sure default settings are ignored if provided in argument
  93.         $this->settings $settings + [
  94.             //'defaultSetting' => array(),
  95.         ];
  96.         $this->permissionResolver $permissionResolver;
  97.     }
  98.     /**
  99.      * Create a Content Type Group object.
  100.      *
  101.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create a content type group
  102.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If a group with the same identifier already exists
  103.      *
  104.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroupCreateStruct $contentTypeGroupCreateStruct
  105.      *
  106.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup
  107.      */
  108.     public function createContentTypeGroup(ContentTypeGroupCreateStruct $contentTypeGroupCreateStruct): APIContentTypeGroup
  109.     {
  110.         if (!$this->permissionResolver->canUser('class''create'$contentTypeGroupCreateStruct)) {
  111.             throw new UnauthorizedException('ContentType''create');
  112.         }
  113.         try {
  114.             $this->loadContentTypeGroupByIdentifier($contentTypeGroupCreateStruct->identifier);
  115.             throw new InvalidArgumentException(
  116.                 '$contentTypeGroupCreateStruct',
  117.                 "A group with the identifier '{$contentTypeGroupCreateStruct->identifier}' already exists"
  118.             );
  119.         } catch (APINotFoundException $e) {
  120.             // Do nothing
  121.         }
  122.         if ($contentTypeGroupCreateStruct->creationDate === null) {
  123.             $timestamp time();
  124.         } else {
  125.             $timestamp $contentTypeGroupCreateStruct->creationDate->getTimestamp();
  126.         }
  127.         if ($contentTypeGroupCreateStruct->creatorId === null) {
  128.             $userId $this->permissionResolver->getCurrentUserReference()->getUserId();
  129.         } else {
  130.             $userId $contentTypeGroupCreateStruct->creatorId;
  131.         }
  132.         $spiGroupCreateStruct = new SPIContentTypeGroupCreateStruct(
  133.             [
  134.                 'identifier' => $contentTypeGroupCreateStruct->identifier,
  135.                 'created' => $timestamp,
  136.                 'modified' => $timestamp,
  137.                 'creatorId' => $userId,
  138.                 'modifierId' => $userId,
  139.             ]
  140.         );
  141.         $this->repository->beginTransaction();
  142.         try {
  143.             $spiContentTypeGroup $this->contentTypeHandler->createGroup(
  144.                 $spiGroupCreateStruct
  145.             );
  146.             $this->repository->commit();
  147.         } catch (Exception $e) {
  148.             $this->repository->rollback();
  149.             throw $e;
  150.         }
  151.         return $this->contentTypeDomainMapper->buildContentTypeGroupDomainObject($spiContentTypeGroup);
  152.     }
  153.     /**
  154.      * {@inheritdoc}
  155.      */
  156.     public function loadContentTypeGroup(int $contentTypeGroupId, array $prioritizedLanguages = []): APIContentTypeGroup
  157.     {
  158.         $spiGroup $this->contentTypeHandler->loadGroup(
  159.             $contentTypeGroupId
  160.         );
  161.         return $this->contentTypeDomainMapper->buildContentTypeGroupDomainObject($spiGroup$prioritizedLanguages);
  162.     }
  163.     /**
  164.      * {@inheritdoc}
  165.      */
  166.     public function loadContentTypeGroupByIdentifier(string $contentTypeGroupIdentifier, array $prioritizedLanguages = []): APIContentTypeGroup
  167.     {
  168.         $groups $this->loadContentTypeGroups($prioritizedLanguages);
  169.         foreach ($groups as $group) {
  170.             if ($group->identifier === $contentTypeGroupIdentifier) {
  171.                 return $group;
  172.             }
  173.         }
  174.         throw new NotFoundException('ContentTypeGroup'$contentTypeGroupIdentifier);
  175.     }
  176.     /**
  177.      * {@inheritdoc}
  178.      */
  179.     public function loadContentTypeGroups(array $prioritizedLanguages = []): iterable
  180.     {
  181.         $spiGroups $this->contentTypeHandler->loadAllGroups();
  182.         $groups = [];
  183.         foreach ($spiGroups as $spiGroup) {
  184.             $groups[] = $this->contentTypeDomainMapper->buildContentTypeGroupDomainObject($spiGroup$prioritizedLanguages);
  185.         }
  186.         return $groups;
  187.     }
  188.     /**
  189.      * Update a Content Type Group object.
  190.      *
  191.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create a content type group
  192.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the given identifier (if set) already exists
  193.      *
  194.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup $contentTypeGroup the content type group to be updated
  195.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroupUpdateStruct $contentTypeGroupUpdateStruct
  196.      */
  197.     public function updateContentTypeGroup(APIContentTypeGroup $contentTypeGroupContentTypeGroupUpdateStruct $contentTypeGroupUpdateStruct): void
  198.     {
  199.         if (!$this->permissionResolver->canUser('class''update'$contentTypeGroup)) {
  200.             throw new UnauthorizedException('ContentType''update');
  201.         }
  202.         $loadedContentTypeGroup $this->loadContentTypeGroup($contentTypeGroup->id);
  203.         if ($contentTypeGroupUpdateStruct->identifier !== null
  204.             && $contentTypeGroupUpdateStruct->identifier !== $loadedContentTypeGroup->identifier) {
  205.             try {
  206.                 $this->loadContentTypeGroupByIdentifier($contentTypeGroupUpdateStruct->identifier);
  207.                 throw new InvalidArgumentException(
  208.                     '$contentTypeGroupUpdateStruct->identifier',
  209.                     'given identifier already exists'
  210.                 );
  211.             } catch (APINotFoundException $e) {
  212.                 // Do nothing
  213.             }
  214.         }
  215.         if ($contentTypeGroupUpdateStruct->modificationDate !== null) {
  216.             $modifiedTimestamp $contentTypeGroupUpdateStruct->modificationDate->getTimestamp();
  217.         } else {
  218.             $modifiedTimestamp time();
  219.         }
  220.         $spiGroupUpdateStruct = new SPIContentTypeGroupUpdateStruct(
  221.             [
  222.                 'id' => $loadedContentTypeGroup->id,
  223.                 'identifier' => $contentTypeGroupUpdateStruct->identifier === null ?
  224.                     $loadedContentTypeGroup->identifier :
  225.                     $contentTypeGroupUpdateStruct->identifier,
  226.                 'modified' => $modifiedTimestamp,
  227.                 'modifierId' => $contentTypeGroupUpdateStruct->modifierId === null ?
  228.                     $this->permissionResolver->getCurrentUserReference()->getUserId() :
  229.                     $contentTypeGroupUpdateStruct->modifierId,
  230.             ]
  231.         );
  232.         $this->repository->beginTransaction();
  233.         try {
  234.             $this->contentTypeHandler->updateGroup(
  235.                 $spiGroupUpdateStruct
  236.             );
  237.             $this->repository->commit();
  238.         } catch (Exception $e) {
  239.             $this->repository->rollback();
  240.             throw $e;
  241.         }
  242.     }
  243.     /**
  244.      * Delete a Content Type Group.
  245.      *
  246.      * This method only deletes an content type group which has content types without any content instances
  247.      *
  248.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to delete a content type group
  249.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If  a to be deleted content type has instances
  250.      */
  251.     public function deleteContentTypeGroup(APIContentTypeGroup $contentTypeGroup): void
  252.     {
  253.         if (!$this->permissionResolver->canUser('class''delete'$contentTypeGroup)) {
  254.             throw new UnauthorizedException('ContentType''delete');
  255.         }
  256.         $loadedContentTypeGroup $this->loadContentTypeGroup($contentTypeGroup->id);
  257.         $this->repository->beginTransaction();
  258.         try {
  259.             $this->contentTypeHandler->deleteGroup(
  260.                 $loadedContentTypeGroup->id
  261.             );
  262.             $this->repository->commit();
  263.         } catch (APIBadStateException $e) {
  264.             $this->repository->rollback();
  265.             throw new InvalidArgumentException(
  266.                 '$contentTypeGroup',
  267.                 'Content Type group contains Content Types',
  268.                 $e
  269.             );
  270.         } catch (Exception $e) {
  271.             $this->repository->rollback();
  272.             throw $e;
  273.         }
  274.     }
  275.     /**
  276.      * Validates input ContentType create struct.
  277.      *
  278.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
  279.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType
  280.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue
  281.      *
  282.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeCreateStruct $contentTypeCreateStruct
  283.      */
  284.     protected function validateInputContentTypeCreateStruct(APIContentTypeCreateStruct $contentTypeCreateStruct): void
  285.     {
  286.         // Required properties
  287.         if ($contentTypeCreateStruct->identifier === null) {
  288.             throw new InvalidArgumentException('$contentTypeCreateStruct'"Property 'identifier' is required");
  289.         }
  290.         if (!is_string($contentTypeCreateStruct->identifier)) {
  291.             throw new InvalidArgumentType(
  292.                 '$contentTypeCreateStruct->identifier',
  293.                 'string',
  294.                 $contentTypeCreateStruct->identifier
  295.             );
  296.         }
  297.         if ($contentTypeCreateStruct->identifier === '') {
  298.             throw new InvalidArgumentValue(
  299.                 '$contentTypeCreateStruct->identifier',
  300.                 $contentTypeCreateStruct->identifier
  301.             );
  302.         }
  303.         if ($contentTypeCreateStruct->mainLanguageCode === null) {
  304.             throw new InvalidArgumentException('$contentTypeCreateStruct'"Property 'mainLanguageCode' is required");
  305.         }
  306.         if (!is_string($contentTypeCreateStruct->mainLanguageCode)) {
  307.             throw new InvalidArgumentType(
  308.                 '$contentTypeCreateStruct->mainLanguageCode',
  309.                 'string',
  310.                 $contentTypeCreateStruct->mainLanguageCode
  311.             );
  312.         }
  313.         if ($contentTypeCreateStruct->mainLanguageCode === '') {
  314.             throw new InvalidArgumentValue(
  315.                 '$contentTypeCreateStruct->mainLanguageCode',
  316.                 $contentTypeCreateStruct->mainLanguageCode
  317.             );
  318.         }
  319.         if ($contentTypeCreateStruct->names !== null) {
  320.             $this->contentDomainMapper->validateTranslatedList(
  321.                 $contentTypeCreateStruct->names,
  322.                 '$contentTypeCreateStruct->names'
  323.             );
  324.         }
  325.         if (!isset($contentTypeCreateStruct->names[$contentTypeCreateStruct->mainLanguageCode]) ||
  326.             $contentTypeCreateStruct->names[$contentTypeCreateStruct->mainLanguageCode] === ''
  327.         ) {
  328.             throw new InvalidArgumentException(
  329.                 '$contentTypeCreateStruct->names',
  330.                 'At least one name in the main language is required'
  331.             );
  332.         }
  333.         // Optional properties
  334.         if ($contentTypeCreateStruct->descriptions !== null) {
  335.             $this->contentDomainMapper->validateTranslatedList(
  336.                 $contentTypeCreateStruct->descriptions,
  337.                 '$contentTypeCreateStruct->descriptions'
  338.             );
  339.         }
  340.         if ($contentTypeCreateStruct->defaultSortField !== null && !$this->contentDomainMapper->isValidLocationSortField($contentTypeCreateStruct->defaultSortField)) {
  341.             throw new InvalidArgumentValue(
  342.                 '$contentTypeCreateStruct->defaultSortField',
  343.                 $contentTypeCreateStruct->defaultSortField
  344.             );
  345.         }
  346.         if ($contentTypeCreateStruct->defaultSortOrder !== null && !$this->contentDomainMapper->isValidLocationSortOrder($contentTypeCreateStruct->defaultSortOrder)) {
  347.             throw new InvalidArgumentValue(
  348.                 '$contentTypeCreateStruct->defaultSortOrder',
  349.                 $contentTypeCreateStruct->defaultSortOrder
  350.             );
  351.         }
  352.         if ($contentTypeCreateStruct->creatorId !== null) {
  353.             $this->repository->getUserService()->loadUser($contentTypeCreateStruct->creatorId);
  354.         }
  355.         if ($contentTypeCreateStruct->creationDate !== null && !$contentTypeCreateStruct->creationDate instanceof DateTime) {
  356.             throw new InvalidArgumentType(
  357.                 '$contentTypeCreateStruct->creationDate',
  358.                 'DateTime',
  359.                 $contentTypeCreateStruct->creationDate
  360.             );
  361.         }
  362.         if ($contentTypeCreateStruct->defaultAlwaysAvailable !== null && !is_bool($contentTypeCreateStruct->defaultAlwaysAvailable)) {
  363.             throw new InvalidArgumentType(
  364.                 '$contentTypeCreateStruct->defaultAlwaysAvailable',
  365.                 'boolean',
  366.                 $contentTypeCreateStruct->defaultAlwaysAvailable
  367.             );
  368.         }
  369.         if ($contentTypeCreateStruct->isContainer !== null && !is_bool($contentTypeCreateStruct->isContainer)) {
  370.             throw new InvalidArgumentType(
  371.                 '$contentTypeCreateStruct->isContainer',
  372.                 'boolean',
  373.                 $contentTypeCreateStruct->isContainer
  374.             );
  375.         }
  376.         if ($contentTypeCreateStruct->remoteId !== null && !is_string($contentTypeCreateStruct->remoteId)) {
  377.             throw new InvalidArgumentType(
  378.                 '$contentTypeCreateStruct->remoteId',
  379.                 'string',
  380.                 $contentTypeCreateStruct->remoteId
  381.             );
  382.         }
  383.         if ($contentTypeCreateStruct->nameSchema !== null && !is_string($contentTypeCreateStruct->nameSchema)) {
  384.             throw new InvalidArgumentType(
  385.                 '$contentTypeCreateStruct->nameSchema',
  386.                 'string',
  387.                 $contentTypeCreateStruct->nameSchema
  388.             );
  389.         }
  390.         if ($contentTypeCreateStruct->urlAliasSchema !== null && !is_string($contentTypeCreateStruct->urlAliasSchema)) {
  391.             throw new InvalidArgumentType(
  392.                 '$contentTypeCreateStruct->urlAliasSchema',
  393.                 'string',
  394.                 $contentTypeCreateStruct->urlAliasSchema
  395.             );
  396.         }
  397.         foreach ($contentTypeCreateStruct->fieldDefinitions as $key => $fieldDefinitionCreateStruct) {
  398.             if (!$fieldDefinitionCreateStruct instanceof FieldDefinitionCreateStruct) {
  399.                 throw new InvalidArgumentType(
  400.                     "\$contentTypeCreateStruct->fieldDefinitions[$key]",
  401.                     'eZ\\Publish\\API\\Repository\\Values\\ContentType\\FieldDefinitionCreateStruct',
  402.                     $fieldDefinitionCreateStruct
  403.                 );
  404.             }
  405.             $this->validateInputFieldDefinitionCreateStruct(
  406.                 $fieldDefinitionCreateStruct,
  407.                 "\$contentTypeCreateStruct->fieldDefinitions[$key]"
  408.             );
  409.         }
  410.     }
  411.     /**
  412.      * Validates input ContentTypeGroup array.
  413.      *
  414.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
  415.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType
  416.      *
  417.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup[] $contentTypeGroups
  418.      */
  419.     protected function validateInputContentTypeGroups(array $contentTypeGroups): void
  420.     {
  421.         if (empty($contentTypeGroups)) {
  422.             throw new InvalidArgumentException(
  423.                 '$contentTypeGroups',
  424.                 'The argument must contain at least one Content Type group'
  425.             );
  426.         }
  427.         foreach ($contentTypeGroups as $key => $contentTypeGroup) {
  428.             if (!$contentTypeGroup instanceof APIContentTypeGroup) {
  429.                 throw new InvalidArgumentType(
  430.                     "\$contentTypeGroups[{$key}]",
  431.                     'eZ\\Publish\\API\\Repository\\Values\\ContentType\\ContentTypeGroup',
  432.                     $contentTypeGroup
  433.                 );
  434.             }
  435.         }
  436.     }
  437.     /**
  438.      * Validates input FieldDefinitionCreateStruct.
  439.      *
  440.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
  441.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentType
  442.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue
  443.      *
  444.      * @param FieldDefinitionCreateStruct $fieldDefinitionCreateStruct
  445.      * @param string $argumentName
  446.      */
  447.     protected function validateInputFieldDefinitionCreateStruct(
  448.         FieldDefinitionCreateStruct $fieldDefinitionCreateStruct,
  449.         string $argumentName '$fieldDefinitionCreateStruct'
  450.     ): void {
  451.         // Required properties
  452.         if ($fieldDefinitionCreateStruct->fieldTypeIdentifier === null) {
  453.             throw new InvalidArgumentException($argumentName"Property 'fieldTypeIdentifier' is required");
  454.         }
  455.         if (!is_string($fieldDefinitionCreateStruct->fieldTypeIdentifier)) {
  456.             throw new InvalidArgumentType(
  457.                 $argumentName '->fieldTypeIdentifier',
  458.                 'string',
  459.                 $fieldDefinitionCreateStruct->fieldTypeIdentifier
  460.             );
  461.         }
  462.         if ($fieldDefinitionCreateStruct->fieldTypeIdentifier === '') {
  463.             throw new InvalidArgumentValue(
  464.                 $argumentName '->fieldTypeIdentifier',
  465.                 $fieldDefinitionCreateStruct->fieldTypeIdentifier
  466.             );
  467.         }
  468.         if ($fieldDefinitionCreateStruct->identifier === null) {
  469.             throw new InvalidArgumentException($argumentName"Property 'identifier' is required");
  470.         }
  471.         if (!is_string($fieldDefinitionCreateStruct->identifier)) {
  472.             throw new InvalidArgumentType(
  473.                 $argumentName '->identifier',
  474.                 'string',
  475.                 $fieldDefinitionCreateStruct->identifier
  476.             );
  477.         }
  478.         if ($fieldDefinitionCreateStruct->identifier === '') {
  479.             throw new InvalidArgumentValue(
  480.                 $argumentName '->identifier',
  481.                 $fieldDefinitionCreateStruct->identifier
  482.             );
  483.         }
  484.         // Optional properties
  485.         if ($fieldDefinitionCreateStruct->names !== null) {
  486.             $this->contentDomainMapper->validateTranslatedList(
  487.                 $fieldDefinitionCreateStruct->names,
  488.                 $argumentName '->names'
  489.             );
  490.         }
  491.         if ($fieldDefinitionCreateStruct->descriptions !== null) {
  492.             $this->contentDomainMapper->validateTranslatedList(
  493.                 $fieldDefinitionCreateStruct->descriptions,
  494.                 $argumentName '->descriptions'
  495.             );
  496.         }
  497.         if ($fieldDefinitionCreateStruct->fieldGroup !== null && !is_string($fieldDefinitionCreateStruct->fieldGroup)) {
  498.             throw new InvalidArgumentType(
  499.                 $argumentName '->fieldGroup',
  500.                 'string',
  501.                 $fieldDefinitionCreateStruct->fieldGroup
  502.             );
  503.         }
  504.         if ($fieldDefinitionCreateStruct->position !== null && !is_int($fieldDefinitionCreateStruct->position)) {
  505.             throw new InvalidArgumentType(
  506.                 $argumentName '->position',
  507.                 'integer',
  508.                 $fieldDefinitionCreateStruct->position
  509.             );
  510.         }
  511.         if ($fieldDefinitionCreateStruct->isTranslatable !== null && !is_bool($fieldDefinitionCreateStruct->isTranslatable)) {
  512.             throw new InvalidArgumentType(
  513.                 $argumentName '->isTranslatable',
  514.                 'boolean',
  515.                 $fieldDefinitionCreateStruct->isTranslatable
  516.             );
  517.         }
  518.         if ($fieldDefinitionCreateStruct->isRequired !== null && !is_bool($fieldDefinitionCreateStruct->isRequired)) {
  519.             throw new InvalidArgumentType(
  520.                 $argumentName '->isRequired',
  521.                 'boolean',
  522.                 $fieldDefinitionCreateStruct->isRequired
  523.             );
  524.         }
  525.         if ($fieldDefinitionCreateStruct->isThumbnail !== null && !is_bool($fieldDefinitionCreateStruct->isThumbnail)) {
  526.             throw new InvalidArgumentType(
  527.                 $argumentName '->isThumbnail',
  528.                 'boolean',
  529.                 $fieldDefinitionCreateStruct->isThumbnail
  530.             );
  531.         }
  532.         if ($fieldDefinitionCreateStruct->isInfoCollector !== null && !is_bool($fieldDefinitionCreateStruct->isInfoCollector)) {
  533.             throw new InvalidArgumentType(
  534.                 $argumentName '->isInfoCollector',
  535.                 'boolean',
  536.                 $fieldDefinitionCreateStruct->isInfoCollector
  537.             );
  538.         }
  539.         if ($fieldDefinitionCreateStruct->isSearchable !== null && !is_bool($fieldDefinitionCreateStruct->isSearchable)) {
  540.             throw new InvalidArgumentType(
  541.                 $argumentName '->isSearchable',
  542.                 'boolean',
  543.                 $fieldDefinitionCreateStruct->isSearchable
  544.             );
  545.         }
  546.         // These properties are of type 'mixed' and are validated separately by the corresponding field type
  547.         // validatorConfiguration
  548.         // fieldSettings
  549.         // defaultValue
  550.     }
  551.     /**
  552.      * Create a Content Type object.
  553.      *
  554.      * The content type is created in the state STATUS_DRAFT.
  555.      *
  556.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to create a content type
  557.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException In case when
  558.      *         - array of content type groups does not contain at least one content type group
  559.      *         - identifier or remoteId in the content type create struct already exists
  560.      *         - there is a duplicate field identifier in the content type create struct
  561.      * @throws \eZ\Publish\API\Repository\Exceptions\ContentTypeFieldDefinitionValidationException
  562.      *         if a field definition in the $contentTypeCreateStruct is not valid
  563.      * @throws \eZ\Publish\API\Repository\Exceptions\ContentTypeValidationException
  564.      *         if a multiple field definitions of a same singular type are given
  565.      *
  566.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeCreateStruct $contentTypeCreateStruct
  567.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup[] $contentTypeGroups Required array of {@link APIContentTypeGroup} to link type with (must contain one)
  568.      *
  569.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft
  570.      */
  571.     public function createContentType(APIContentTypeCreateStruct $contentTypeCreateStruct, array $contentTypeGroups): APIContentTypeDraft
  572.     {
  573.         if (!$this->permissionResolver->canUser('class''create'$contentTypeCreateStruct$contentTypeGroups)) {
  574.             throw new UnauthorizedException('ContentType''create');
  575.         }
  576.         // Prevent argument mutation
  577.         $contentTypeCreateStruct = clone $contentTypeCreateStruct;
  578.         $this->validateInputContentTypeCreateStruct($contentTypeCreateStruct);
  579.         $this->validateInputContentTypeGroups($contentTypeGroups);
  580.         $initialLanguageId $this->repository->getContentLanguageService()->loadLanguage(
  581.             $contentTypeCreateStruct->mainLanguageCode
  582.         )->id;
  583.         try {
  584.             $this->contentTypeHandler->loadByIdentifier(
  585.                 $contentTypeCreateStruct->identifier
  586.             );
  587.             throw new InvalidArgumentException(
  588.                 '$contentTypeCreateStruct',
  589.                 "Another Content Type with identifier '{$contentTypeCreateStruct->identifier}' exists"
  590.             );
  591.         } catch (APINotFoundException $e) {
  592.             // Do nothing
  593.         }
  594.         if ($contentTypeCreateStruct->remoteId !== null) {
  595.             try {
  596.                 $this->contentTypeHandler->loadByRemoteId(
  597.                     $contentTypeCreateStruct->remoteId
  598.                 );
  599.                 throw new InvalidArgumentException(
  600.                     '$contentTypeCreateStruct',
  601.                     "Another Content Type with remoteId '{$contentTypeCreateStruct->remoteId}' exists"
  602.                 );
  603.             } catch (APINotFoundException $e) {
  604.                 // Do nothing
  605.             }
  606.         }
  607.         $fieldDefinitionIdentifierSet = [];
  608.         $fieldDefinitionPositionSet = [];
  609.         foreach ($contentTypeCreateStruct->fieldDefinitions as $fieldDefinitionCreateStruct) {
  610.             // Check for duplicate identifiers
  611.             if (!isset($fieldDefinitionIdentifierSet[$fieldDefinitionCreateStruct->identifier])) {
  612.                 $fieldDefinitionIdentifierSet[$fieldDefinitionCreateStruct->identifier] = true;
  613.             } else {
  614.                 throw new InvalidArgumentException(
  615.                     '$contentTypeCreateStruct',
  616.                     "The argument contains duplicate Field definition identifier '{$fieldDefinitionCreateStruct->identifier}'"
  617.                 );
  618.             }
  619.             // Check for duplicate positions
  620.             if (!isset($fieldDefinitionPositionSet[$fieldDefinitionCreateStruct->position])) {
  621.                 $fieldDefinitionPositionSet[$fieldDefinitionCreateStruct->position] = true;
  622.             } else {
  623.                 throw new InvalidArgumentException(
  624.                     '$contentTypeCreateStruct',
  625.                     "The argument contains duplicate Field definition position '{$fieldDefinitionCreateStruct->position}'"
  626.                 );
  627.             }
  628.         }
  629.         $allValidationErrors = [];
  630.         $spiFieldDefinitions = [];
  631.         $fieldTypeIdentifierSet = [];
  632.         foreach ($contentTypeCreateStruct->fieldDefinitions as $fieldDefinitionCreateStruct) {
  633.             /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
  634.             $fieldType $this->fieldTypeRegistry->getFieldType(
  635.                 $fieldDefinitionCreateStruct->fieldTypeIdentifier
  636.             );
  637.             if ($fieldType->isSingular() && isset($fieldTypeIdentifierSet[$fieldDefinitionCreateStruct->fieldTypeIdentifier])) {
  638.                 throw new ContentTypeValidationException(
  639.                     "Field Type '%identifier%' is singular and cannot be used more than once in a Content Type",
  640.                     ['%identifier%' => $fieldDefinitionCreateStruct->fieldTypeIdentifier]
  641.                 );
  642.             }
  643.             $fieldTypeIdentifierSet[$fieldDefinitionCreateStruct->fieldTypeIdentifier] = true;
  644.             $fieldType->applyDefaultSettings($fieldDefinitionCreateStruct->fieldSettings);
  645.             $fieldType->applyDefaultValidatorConfiguration($fieldDefinitionCreateStruct->validatorConfiguration);
  646.             $validationErrors $this->validateFieldDefinitionCreateStruct(
  647.                 $fieldDefinitionCreateStruct,
  648.                 $fieldType
  649.             );
  650.             if (!empty($validationErrors)) {
  651.                 $allValidationErrors[$fieldDefinitionCreateStruct->identifier] = $validationErrors;
  652.             }
  653.             if (!empty($allValidationErrors)) {
  654.                 continue;
  655.             }
  656.             $spiFieldDefinitions[] = $this->contentTypeDomainMapper->buildSPIFieldDefinitionFromCreateStruct(
  657.                 $fieldDefinitionCreateStruct,
  658.                 $fieldType,
  659.                 $contentTypeCreateStruct->mainLanguageCode
  660.             );
  661.         }
  662.         if (!empty($allValidationErrors)) {
  663.             throw new ContentTypeFieldDefinitionValidationException($allValidationErrors);
  664.         }
  665.         $groupIds array_map(
  666.             function (APIContentTypeGroup $contentTypeGroup) {
  667.                 return $contentTypeGroup->id;
  668.             },
  669.             $contentTypeGroups
  670.         );
  671.         if ($contentTypeCreateStruct->creatorId === null) {
  672.             $contentTypeCreateStruct->creatorId $this->permissionResolver->getCurrentUserReference()->getUserId();
  673.         }
  674.         if ($contentTypeCreateStruct->creationDate === null) {
  675.             $timestamp time();
  676.         } else {
  677.             $timestamp $contentTypeCreateStruct->creationDate->getTimestamp();
  678.         }
  679.         if ($contentTypeCreateStruct->remoteId === null) {
  680.             $contentTypeCreateStruct->remoteId $this->contentDomainMapper->getUniqueHash($contentTypeCreateStruct);
  681.         }
  682.         $spiContentTypeCreateStruct = new SPIContentTypeCreateStruct(
  683.             [
  684.                 'identifier' => $contentTypeCreateStruct->identifier,
  685.                 'name' => $contentTypeCreateStruct->names,
  686.                 'status' => APIContentType::STATUS_DRAFT,
  687.                 'description' => $contentTypeCreateStruct->descriptions === null ?
  688.                     [] :
  689.                     $contentTypeCreateStruct->descriptions,
  690.                 'created' => $timestamp,
  691.                 'modified' => $timestamp,
  692.                 'creatorId' => $contentTypeCreateStruct->creatorId,
  693.                 'modifierId' => $contentTypeCreateStruct->creatorId,
  694.                 'remoteId' => $contentTypeCreateStruct->remoteId,
  695.                 'urlAliasSchema' => $contentTypeCreateStruct->urlAliasSchema === null ?
  696.                     '' :
  697.                     $contentTypeCreateStruct->urlAliasSchema,
  698.                 'nameSchema' => $contentTypeCreateStruct->nameSchema === null ?
  699.                     '' :
  700.                     $contentTypeCreateStruct->nameSchema,
  701.                 'isContainer' => $contentTypeCreateStruct->isContainer === null ?
  702.                     false :
  703.                     $contentTypeCreateStruct->isContainer,
  704.                 'initialLanguageId' => $initialLanguageId,
  705.                 'sortField' => $contentTypeCreateStruct->defaultSortField === null ?
  706.                     Location::SORT_FIELD_PUBLISHED :
  707.                     $contentTypeCreateStruct->defaultSortField,
  708.                 'sortOrder' => $contentTypeCreateStruct->defaultSortOrder === null ?
  709.                     Location::SORT_ORDER_DESC :
  710.                     $contentTypeCreateStruct->defaultSortOrder,
  711.                 'groupIds' => $groupIds,
  712.                 'fieldDefinitions' => $spiFieldDefinitions,
  713.                 'defaultAlwaysAvailable' => $contentTypeCreateStruct->defaultAlwaysAvailable,
  714.             ]
  715.         );
  716.         $this->repository->beginTransaction();
  717.         try {
  718.             $spiContentType $this->contentTypeHandler->create(
  719.                 $spiContentTypeCreateStruct
  720.             );
  721.             $this->repository->commit();
  722.         } catch (Exception $e) {
  723.             $this->repository->rollback();
  724.             throw $e;
  725.         }
  726.         return $this->contentTypeDomainMapper->buildContentTypeDraftDomainObject($spiContentType);
  727.     }
  728.     /**
  729.      * Validates FieldDefinitionCreateStruct.
  730.      *
  731.      * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinitionCreateStruct $fieldDefinitionCreateStruct
  732.      * @param \eZ\Publish\SPI\FieldType\FieldType $fieldType
  733.      *
  734.      * @return \eZ\Publish\SPI\FieldType\ValidationError[]
  735.      */
  736.     protected function validateFieldDefinitionCreateStruct(FieldDefinitionCreateStruct $fieldDefinitionCreateStructSPIFieldType $fieldType): array
  737.     {
  738.         $validationErrors = [];
  739.         if ($fieldDefinitionCreateStruct->isSearchable && !$fieldType->isSearchable()) {
  740.             $validationErrors[] = new ValidationError(
  741.                 "FieldType '{$fieldDefinitionCreateStruct->fieldTypeIdentifier}' is not searchable"
  742.             );
  743.         }
  744.         return array_merge(
  745.             $validationErrors,
  746.             $fieldType->validateValidatorConfiguration($fieldDefinitionCreateStruct->validatorConfiguration),
  747.             $fieldType->validateFieldSettings($fieldDefinitionCreateStruct->fieldSettings)
  748.         );
  749.     }
  750.     /**
  751.      * {@inheritdoc}
  752.      */
  753.     public function loadContentType(int $contentTypeId, array $prioritizedLanguages = []): ContentType
  754.     {
  755.         $spiContentType $this->contentTypeHandler->load(
  756.             $contentTypeId,
  757.             SPIContentType::STATUS_DEFINED
  758.         );
  759.         return $this->contentTypeDomainMapper->buildContentTypeDomainObject(
  760.             $spiContentType,
  761.             $prioritizedLanguages
  762.         );
  763.     }
  764.     /**
  765.      * {@inheritdoc}
  766.      */
  767.     public function loadContentTypeByIdentifier(string $identifier, array $prioritizedLanguages = []): ContentType
  768.     {
  769.         $spiContentType $this->contentTypeHandler->loadByIdentifier(
  770.             $identifier
  771.         );
  772.         return $this->contentTypeDomainMapper->buildContentTypeDomainObject(
  773.             $spiContentType,
  774.             $prioritizedLanguages
  775.         );
  776.     }
  777.     /**
  778.      * {@inheritdoc}
  779.      */
  780.     public function loadContentTypeByRemoteId(string $remoteId, array $prioritizedLanguages = []): ContentType
  781.     {
  782.         $spiContentType $this->contentTypeHandler->loadByRemoteId($remoteId);
  783.         return $this->contentTypeDomainMapper->buildContentTypeDomainObject(
  784.             $spiContentType,
  785.             $prioritizedLanguages
  786.         );
  787.     }
  788.     /**
  789.      * Get a Content Type object draft by id.
  790.      *
  791.      * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException If the content type draft owned by the current user can not be found
  792.      *
  793.      * @param int $contentTypeId
  794.      * @param bool $ignoreOwnership if true, method will return draft even if the owner is different than currently logged in user
  795.      *
  796.      * @todo Use another exception when user of draft is someone else
  797.      *
  798.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft
  799.      */
  800.     public function loadContentTypeDraft(int $contentTypeIdbool $ignoreOwnership false): APIContentTypeDraft
  801.     {
  802.         $spiContentType $this->contentTypeHandler->load(
  803.             $contentTypeId,
  804.             SPIContentType::STATUS_DRAFT
  805.         );
  806.         if (!$ignoreOwnership && $spiContentType->modifierId != $this->permissionResolver->getCurrentUserReference()->getUserId()) {
  807.             throw new NotFoundException('The Content Type is owned by someone else'$contentTypeId);
  808.         }
  809.         return $this->contentTypeDomainMapper->buildContentTypeDraftDomainObject($spiContentType);
  810.     }
  811.     /**
  812.      * {@inheritdoc}
  813.      */
  814.     public function loadContentTypeList(array $contentTypeIds, array $prioritizedLanguages = []): iterable
  815.     {
  816.         $spiContentTypes $this->contentTypeHandler->loadContentTypeList($contentTypeIds);
  817.         $contentTypes = [];
  818.         // @todo We could bulk load content type group proxies involved in the future & pass those relevant per type to mapper
  819.         foreach ($spiContentTypes as $spiContentType) {
  820.             $contentTypes[$spiContentType->id] = $this->contentTypeDomainMapper->buildContentTypeDomainObject(
  821.                 $spiContentType,
  822.                 $prioritizedLanguages
  823.             );
  824.         }
  825.         return $contentTypes;
  826.     }
  827.     /**
  828.      * {@inheritdoc}
  829.      */
  830.     public function loadContentTypes(APIContentTypeGroup $contentTypeGroup, array $prioritizedLanguages = []): iterable
  831.     {
  832.         $spiContentTypes $this->contentTypeHandler->loadContentTypes(
  833.             $contentTypeGroup->id,
  834.             SPIContentType::STATUS_DEFINED
  835.         );
  836.         $contentTypes = [];
  837.         foreach ($spiContentTypes as $spiContentType) {
  838.             $contentTypes[] = $this->contentTypeDomainMapper->buildContentTypeDomainObject(
  839.                 $spiContentType,
  840.                 $prioritizedLanguages
  841.             );
  842.         }
  843.         return $contentTypes;
  844.     }
  845.     /**
  846.      * Creates a draft from an existing content type.
  847.      *
  848.      * This is a complete copy of the content
  849.      * type which has the state STATUS_DRAFT.
  850.      *
  851.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit a content type
  852.      * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If there is already a draft assigned to another user
  853.      *
  854.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
  855.      *
  856.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft
  857.      */
  858.     public function createContentTypeDraft(APIContentType $contentType): APIContentTypeDraft
  859.     {
  860.         if (!$this->permissionResolver->canUser('class''create'$contentType)) {
  861.             throw new UnauthorizedException('ContentType''create');
  862.         }
  863.         try {
  864.             $this->contentTypeHandler->load(
  865.                 $contentType->id,
  866.                 SPIContentType::STATUS_DRAFT
  867.             );
  868.             throw new BadStateException(
  869.                 '$contentType',
  870.                 'Draft of the Content Type already exists'
  871.             );
  872.         } catch (APINotFoundException $e) {
  873.             $this->repository->beginTransaction();
  874.             try {
  875.                 $spiContentType $this->contentTypeHandler->createDraft(
  876.                     $this->permissionResolver->getCurrentUserReference()->getUserId(),
  877.                     $contentType->id
  878.                 );
  879.                 $this->repository->commit();
  880.             } catch (Exception $e) {
  881.                 $this->repository->rollback();
  882.                 throw $e;
  883.             }
  884.         }
  885.         return $this->contentTypeDomainMapper->buildContentTypeDraftDomainObject($spiContentType);
  886.     }
  887.     /**
  888.      * Update a Content Type object.
  889.      *
  890.      * Does not update fields (fieldDefinitions), use {@link updateFieldDefinition()} to update them.
  891.      *
  892.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to update a content type
  893.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the given identifier or remoteId already exists
  894.      *         or there is no draft assigned to the authenticated user
  895.      *
  896.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft $contentTypeDraft
  897.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeUpdateStruct $contentTypeUpdateStruct
  898.      */
  899.     public function updateContentTypeDraft(APIContentTypeDraft $contentTypeDraftContentTypeUpdateStruct $contentTypeUpdateStruct): void
  900.     {
  901.         if (!$this->permissionResolver->canUser('class''update'$contentTypeDraft)) {
  902.             throw new UnauthorizedException('ContentType''update');
  903.         }
  904.         try {
  905.             $loadedContentTypeDraft $this->loadContentTypeDraft($contentTypeDraft->id);
  906.         } catch (APINotFoundException $e) {
  907.             throw new InvalidArgumentException(
  908.                 '$contentTypeDraft',
  909.                 'There is no Content Type draft assigned to the authenticated user',
  910.                 $e
  911.             );
  912.         }
  913.         if ($contentTypeUpdateStruct->identifier !== null
  914.             && $contentTypeUpdateStruct->identifier != $loadedContentTypeDraft->identifier) {
  915.             try {
  916.                 $this->loadContentTypeByIdentifier($contentTypeUpdateStruct->identifier);
  917.                 throw new InvalidArgumentException(
  918.                     '$contentTypeUpdateStruct',
  919.                     "Another Content Type with identifier '{$contentTypeUpdateStruct->identifier}' exists"
  920.                 );
  921.             } catch (APINotFoundException $e) {
  922.                 // Do nothing
  923.             }
  924.         }
  925.         if ($contentTypeUpdateStruct->remoteId !== null
  926.             && $contentTypeUpdateStruct->remoteId != $loadedContentTypeDraft->remoteId) {
  927.             try {
  928.                 $this->loadContentTypeByRemoteId($contentTypeUpdateStruct->remoteId);
  929.                 throw new InvalidArgumentException(
  930.                     '$contentTypeUpdateStruct',
  931.                     "Another Content Type with remoteId '{$contentTypeUpdateStruct->remoteId}' exists"
  932.                 );
  933.             } catch (APINotFoundException $e) {
  934.                 // Do nothing
  935.             }
  936.         }
  937.         //Merge new translations into existing before update
  938.         $contentTypeUpdateStruct->names array_merge($contentTypeDraft->getNames(), $contentTypeUpdateStruct->names ?? []);
  939.         $contentTypeUpdateStruct->descriptions array_merge($contentTypeDraft->getDescriptions(), $contentTypeUpdateStruct->descriptions ?? []);
  940.         $this->repository->beginTransaction();
  941.         try {
  942.             $this->contentTypeHandler->update(
  943.                 $contentTypeDraft->id,
  944.                 $contentTypeDraft->status,
  945.                 $this->contentTypeDomainMapper->buildSPIContentTypeUpdateStruct(
  946.                     $loadedContentTypeDraft,
  947.                     $contentTypeUpdateStruct,
  948.                     $this->permissionResolver->getCurrentUserReference()
  949.                 )
  950.             );
  951.             $this->repository->commit();
  952.         } catch (Exception $e) {
  953.             $this->repository->rollback();
  954.             throw $e;
  955.         }
  956.     }
  957.     /**
  958.      * Delete a Content Type object.
  959.      *
  960.      * Deletes a content type if it has no instances. If content type in state STATUS_DRAFT is
  961.      * given, only the draft content type will be deleted. Otherwise, if content type in state
  962.      * STATUS_DEFINED is given, all content type data will be deleted.
  963.      *
  964.      * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If there exist content objects of this type
  965.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to delete a content type
  966.      *
  967.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
  968.      */
  969.     public function deleteContentType(APIContentType $contentType): void
  970.     {
  971.         if (!$this->permissionResolver->canUser('class''delete'$contentType)) {
  972.             throw new UnauthorizedException('ContentType''delete');
  973.         }
  974.         $this->repository->beginTransaction();
  975.         try {
  976.             if (!$contentType instanceof APIContentTypeDraft) {
  977.                 $this->contentTypeHandler->delete(
  978.                     $contentType->id,
  979.                     APIContentTypeDraft::STATUS_DEFINED
  980.                 );
  981.             }
  982.             $this->contentTypeHandler->delete(
  983.                 $contentType->id,
  984.                 APIContentTypeDraft::STATUS_DRAFT
  985.             );
  986.             $this->repository->commit();
  987.         } catch (Exception $e) {
  988.             $this->repository->rollback();
  989.             throw $e;
  990.         }
  991.     }
  992.     /**
  993.      * Copy Type incl fields and groupIds to a new Type object.
  994.      *
  995.      * New Type will have $creator as creator / modifier, created / modified should be updated with current time,
  996.      * updated remoteId and identifier should be appended with '_' + unique string.
  997.      *
  998.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the current-user is not allowed to copy a content type
  999.      *
  1000.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
  1001.      * @param \eZ\Publish\API\Repository\Values\User\User $creator if null the current-user is used
  1002.      *
  1003.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentType
  1004.      */
  1005.     public function copyContentType(APIContentType $contentTypeUser $creator null): ContentType
  1006.     {
  1007.         if (!$this->permissionResolver->canUser('class''create'$contentType)) {
  1008.             throw new UnauthorizedException('ContentType''create');
  1009.         }
  1010.         if (empty($creator)) {
  1011.             $creator $this->permissionResolver->getCurrentUserReference();
  1012.         }
  1013.         $this->repository->beginTransaction();
  1014.         try {
  1015.             $spiContentType $this->contentTypeHandler->copy(
  1016.                 $creator->getUserId(),
  1017.                 $contentType->id,
  1018.                 SPIContentType::STATUS_DEFINED
  1019.             );
  1020.             $this->repository->commit();
  1021.         } catch (Exception $e) {
  1022.             $this->repository->rollback();
  1023.             throw $e;
  1024.         }
  1025.         return $this->loadContentType($spiContentType->id);
  1026.     }
  1027.     /**
  1028.      * Assigns a content type to a content type group.
  1029.      *
  1030.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to unlink a content type
  1031.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the content type is already assigned the given group
  1032.      *
  1033.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
  1034.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup $contentTypeGroup
  1035.      */
  1036.     public function assignContentTypeGroup(APIContentType $contentTypeAPIContentTypeGroup $contentTypeGroup): void
  1037.     {
  1038.         if (!$this->permissionResolver->canUser('class''update'$contentType)) {
  1039.             throw new UnauthorizedException('ContentType''update');
  1040.         }
  1041.         $spiContentType $this->contentTypeHandler->load(
  1042.             $contentType->id,
  1043.             $contentType->status
  1044.         );
  1045.         if (in_array($contentTypeGroup->id$spiContentType->groupIds)) {
  1046.             throw new InvalidArgumentException(
  1047.                 '$contentTypeGroup',
  1048.                 'The provided Content Type is already assigned to the Content Type group'
  1049.             );
  1050.         }
  1051.         $this->repository->beginTransaction();
  1052.         try {
  1053.             $this->contentTypeHandler->link(
  1054.                 $contentTypeGroup->id,
  1055.                 $contentType->id,
  1056.                 $contentType->status
  1057.             );
  1058.             $this->repository->commit();
  1059.         } catch (Exception $e) {
  1060.             $this->repository->rollback();
  1061.             throw $e;
  1062.         }
  1063.     }
  1064.     /**
  1065.      * Unassign a content type from a group.
  1066.      *
  1067.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to link a content type
  1068.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the content type is not assigned this the given group.
  1069.      * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If $contentTypeGroup is the last group assigned to the content type
  1070.      *
  1071.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
  1072.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroup $contentTypeGroup
  1073.      */
  1074.     public function unassignContentTypeGroup(APIContentType $contentTypeAPIContentTypeGroup $contentTypeGroup): void
  1075.     {
  1076.         if (!$this->permissionResolver->canUser('class''update'$contentType, [$contentTypeGroup])) {
  1077.             throw new UnauthorizedException('ContentType''update');
  1078.         }
  1079.         $spiContentType $this->contentTypeHandler->load(
  1080.             $contentType->id,
  1081.             $contentType->status
  1082.         );
  1083.         if (!in_array($contentTypeGroup->id$spiContentType->groupIds)) {
  1084.             throw new InvalidArgumentException(
  1085.                 '$contentTypeGroup',
  1086.                 'The provided Content Type is not assigned the Content Type group'
  1087.             );
  1088.         }
  1089.         $this->repository->beginTransaction();
  1090.         try {
  1091.             $this->contentTypeHandler->unlink(
  1092.                 $contentTypeGroup->id,
  1093.                 $contentType->id,
  1094.                 $contentType->status
  1095.             );
  1096.             $this->repository->commit();
  1097.         } catch (APIBadStateException $e) {
  1098.             $this->repository->rollback();
  1099.             throw new BadStateException(
  1100.                 '$contentType',
  1101.                 'The provided Content Type group is the last group assigned to the Content Type',
  1102.                 $e
  1103.             );
  1104.         } catch (Exception $e) {
  1105.             $this->repository->rollback();
  1106.             throw $e;
  1107.         }
  1108.     }
  1109.     /**
  1110.      * Adds a new field definition to an existing content type.
  1111.      *
  1112.      * The content type must be in state DRAFT.
  1113.      *
  1114.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException if the identifier in already exists in the content type
  1115.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit a content type
  1116.      * @throws \eZ\Publish\API\Repository\Exceptions\ContentTypeFieldDefinitionValidationException
  1117.      *         if a field definition in the $contentTypeCreateStruct is not valid
  1118.      * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If field definition of the same non-repeatable type is being
  1119.      *                                                                 added to the ContentType that already contains one
  1120.      *                                                                 or field definition that can't be added to a ContentType that
  1121.      *                                                                 has Content instances is being added to such ContentType
  1122.      *
  1123.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft $contentTypeDraft
  1124.      * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinitionCreateStruct $fieldDefinitionCreateStruct
  1125.      */
  1126.     public function addFieldDefinition(APIContentTypeDraft $contentTypeDraftFieldDefinitionCreateStruct $fieldDefinitionCreateStruct): void
  1127.     {
  1128.         if (!$this->permissionResolver->canUser('class''update'$contentTypeDraft)) {
  1129.             throw new UnauthorizedException('ContentType''update');
  1130.         }
  1131.         $this->validateInputFieldDefinitionCreateStruct($fieldDefinitionCreateStruct);
  1132.         $loadedContentTypeDraft $this->loadContentTypeDraft($contentTypeDraft->id);
  1133.         if ($loadedContentTypeDraft->hasFieldDefinition($fieldDefinitionCreateStruct->identifier)) {
  1134.             throw new InvalidArgumentException(
  1135.                 '$fieldDefinitionCreateStruct',
  1136.                 "Another Field definition with identifier '{$fieldDefinitionCreateStruct->identifier}' exists in the Content Type"
  1137.             );
  1138.         }
  1139.         //Fill default translations with default value for mainLanguageCode with fallback if no exist
  1140.         if (is_array($fieldDefinitionCreateStruct->names)) {
  1141.             foreach ($contentTypeDraft->languageCodes as $languageCode) {
  1142.                 if (!array_key_exists($languageCode$fieldDefinitionCreateStruct->names)) {
  1143.                     $fieldDefinitionCreateStruct->names[$languageCode] = $fieldDefinitionCreateStruct->names[$contentTypeDraft->mainLanguageCode] ?? reset($fieldDefinitionCreateStruct->names);
  1144.                 }
  1145.             }
  1146.         }
  1147.         /** @var $fieldType \eZ\Publish\SPI\FieldType\FieldType */
  1148.         $fieldType $this->fieldTypeRegistry->getFieldType(
  1149.             $fieldDefinitionCreateStruct->fieldTypeIdentifier
  1150.         );
  1151.         $fieldType->applyDefaultSettings($fieldDefinitionCreateStruct->fieldSettings);
  1152.         $fieldType->applyDefaultValidatorConfiguration($fieldDefinitionCreateStruct->validatorConfiguration);
  1153.         $validationErrors $this->validateFieldDefinitionCreateStruct($fieldDefinitionCreateStruct$fieldType);
  1154.         if (!empty($validationErrors)) {
  1155.             $validationErrors = [$fieldDefinitionCreateStruct->identifier => $validationErrors];
  1156.             throw new ContentTypeFieldDefinitionValidationException($validationErrors);
  1157.         }
  1158.         if ($fieldType->isSingular()) {
  1159.             if ($loadedContentTypeDraft->hasFieldDefinitionOfType($fieldDefinitionCreateStruct->fieldTypeIdentifier)) {
  1160.                 throw new BadStateException(
  1161.                     '$contentTypeDraft',
  1162.                     "The Content Type already contains a Field definition of the singular Field Type '{$fieldDefinitionCreateStruct->fieldTypeIdentifier}'"
  1163.                 );
  1164.             }
  1165.         }
  1166.         if ($fieldType->onlyEmptyInstance() && $this->contentTypeHandler->getContentCount($loadedContentTypeDraft->id)
  1167.         ) {
  1168.             throw new BadStateException(
  1169.                 '$contentTypeDraft',
  1170.                 "A Field definition of the '{$fieldDefinitionCreateStruct->fieldTypeIdentifier}' Field Type cannot be added because the Content Type already has Content items"
  1171.             );
  1172.         }
  1173.         $spiFieldDefinition $this->contentTypeDomainMapper->buildSPIFieldDefinitionFromCreateStruct(
  1174.             $fieldDefinitionCreateStruct,
  1175.             $fieldType,
  1176.             $contentTypeDraft->mainLanguageCode
  1177.         );
  1178.         $this->repository->beginTransaction();
  1179.         try {
  1180.             $this->contentTypeHandler->addFieldDefinition(
  1181.                 $contentTypeDraft->id,
  1182.                 $contentTypeDraft->status,
  1183.                 $spiFieldDefinition
  1184.             );
  1185.             $this->repository->commit();
  1186.         } catch (Exception $e) {
  1187.             $this->repository->rollback();
  1188.             throw $e;
  1189.         }
  1190.     }
  1191.     /**
  1192.      * Remove a field definition from an existing Type.
  1193.      *
  1194.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the given field definition does not belong to the given type
  1195.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit a content type
  1196.      *
  1197.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft $contentTypeDraft
  1198.      * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition $fieldDefinition
  1199.      */
  1200.     public function removeFieldDefinition(APIContentTypeDraft $contentTypeDraftAPIFieldDefinition $fieldDefinition): void
  1201.     {
  1202.         if (!$this->permissionResolver->canUser('class''update'$contentTypeDraft)) {
  1203.             throw new UnauthorizedException('ContentType''update');
  1204.         }
  1205.         $loadedFieldDefinition $this->loadContentTypeDraft(
  1206.             $contentTypeDraft->id
  1207.         )->getFieldDefinition(
  1208.             $fieldDefinition->identifier
  1209.         );
  1210.         if (empty($loadedFieldDefinition) || $loadedFieldDefinition->id != $fieldDefinition->id) {
  1211.             throw new InvalidArgumentException(
  1212.                 '$fieldDefinition',
  1213.                 'The given Field definition does not belong to the Content Type'
  1214.             );
  1215.         }
  1216.         $this->repository->beginTransaction();
  1217.         try {
  1218.             $this->contentTypeHandler->removeFieldDefinition(
  1219.                 $contentTypeDraft->id,
  1220.                 SPIContentType::STATUS_DRAFT,
  1221.                 $fieldDefinition->id
  1222.             );
  1223.             $this->repository->commit();
  1224.         } catch (Exception $e) {
  1225.             $this->repository->rollback();
  1226.             throw $e;
  1227.         }
  1228.     }
  1229.     /**
  1230.      * Update a field definition.
  1231.      *
  1232.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the field id in the update struct is not found or does not belong to the content type
  1233.      *                                                                        If the given identifier is used in an existing field of the given content type
  1234.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to edit a content type
  1235.      *
  1236.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft $contentTypeDraft the content type draft
  1237.      * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition $fieldDefinition the field definition which should be updated
  1238.      * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinitionUpdateStruct $fieldDefinitionUpdateStruct
  1239.      */
  1240.     public function updateFieldDefinition(APIContentTypeDraft $contentTypeDraftAPIFieldDefinition $fieldDefinitionFieldDefinitionUpdateStruct $fieldDefinitionUpdateStruct): void
  1241.     {
  1242.         if (!$this->permissionResolver->canUser('class''update'$contentTypeDraft)) {
  1243.             throw new UnauthorizedException('ContentType''update');
  1244.         }
  1245.         $loadedContentTypeDraft $this->loadContentTypeDraft($contentTypeDraft->id);
  1246.         $foundFieldId false;
  1247.         foreach ($loadedContentTypeDraft->fieldDefinitions as $existingFieldDefinition) {
  1248.             if ($existingFieldDefinition->id == $fieldDefinition->id) {
  1249.                 $foundFieldId true;
  1250.             } elseif ($existingFieldDefinition->identifier == $fieldDefinitionUpdateStruct->identifier) {
  1251.                 throw new InvalidArgumentException(
  1252.                     '$fieldDefinitionUpdateStruct',
  1253.                     "Another Field definition with identifier '{$fieldDefinitionUpdateStruct->identifier}' exists in the Content Type"
  1254.                 );
  1255.             }
  1256.         }
  1257.         if (!$foundFieldId) {
  1258.             throw new InvalidArgumentException(
  1259.                 '$fieldDefinition',
  1260.                 'The given Field definition does not belong to the Content Type'
  1261.             );
  1262.         }
  1263.         $spiFieldDefinition $this->contentTypeDomainMapper->buildSPIFieldDefinitionFromUpdateStruct(
  1264.             $fieldDefinitionUpdateStruct,
  1265.             $fieldDefinition,
  1266.             $contentTypeDraft->mainLanguageCode
  1267.         );
  1268.         $this->repository->beginTransaction();
  1269.         try {
  1270.             $this->contentTypeHandler->updateFieldDefinition(
  1271.                 $contentTypeDraft->id,
  1272.                 SPIContentType::STATUS_DRAFT,
  1273.                 $spiFieldDefinition
  1274.             );
  1275.             $this->repository->commit();
  1276.         } catch (Exception $e) {
  1277.             $this->repository->rollback();
  1278.             throw $e;
  1279.         }
  1280.     }
  1281.     /**
  1282.      * Publish the content type and update content objects.
  1283.      *
  1284.      * This method updates content objects, depending on the changed field definitions.
  1285.      *
  1286.      * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException If the content type has no draft
  1287.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If the content type has no field definitions
  1288.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException if the user is not allowed to publish a content type
  1289.      *
  1290.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft $contentTypeDraft
  1291.      */
  1292.     public function publishContentTypeDraft(APIContentTypeDraft $contentTypeDraft): void
  1293.     {
  1294.         if (!$this->permissionResolver->canUser('class''update'$contentTypeDraft)) {
  1295.             throw new UnauthorizedException('ContentType''update');
  1296.         }
  1297.         try {
  1298.             $loadedContentTypeDraft $this->loadContentTypeDraft($contentTypeDraft->id);
  1299.         } catch (APINotFoundException $e) {
  1300.             throw new BadStateException(
  1301.                 '$contentTypeDraft',
  1302.                 'The Content Type does not have a draft.',
  1303.                 $e
  1304.             );
  1305.         }
  1306.         if ($loadedContentTypeDraft->getFieldDefinitions()->isEmpty()) {
  1307.             throw new InvalidArgumentException(
  1308.                 '$contentTypeDraft',
  1309.                 'The Content Type draft should have at least one Field definition.'
  1310.             );
  1311.         }
  1312.         $this->repository->beginTransaction();
  1313.         try {
  1314.             if (empty($loadedContentTypeDraft->nameSchema)) {
  1315.                 $fieldDefinitions $loadedContentTypeDraft->getFieldDefinitions();
  1316.                 $this->contentTypeHandler->update(
  1317.                     $contentTypeDraft->id,
  1318.                     $contentTypeDraft->status,
  1319.                     $this->contentTypeDomainMapper->buildSPIContentTypeUpdateStruct(
  1320.                         $loadedContentTypeDraft,
  1321.                         new ContentTypeUpdateStruct(
  1322.                             [
  1323.                                 'nameSchema' => '<' $fieldDefinitions[0]->identifier '>',
  1324.                             ]
  1325.                         ),
  1326.                         $this->permissionResolver->getCurrentUserReference()
  1327.                     )
  1328.                 );
  1329.             }
  1330.             $this->contentTypeHandler->publish(
  1331.                 $loadedContentTypeDraft->id
  1332.             );
  1333.             $this->repository->commit();
  1334.         } catch (Exception $e) {
  1335.             $this->repository->rollback();
  1336.             throw $e;
  1337.         }
  1338.     }
  1339.     /**
  1340.      * Instantiates a new content type group create class.
  1341.      *
  1342.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue if given identifier is not a string
  1343.      *
  1344.      * @param string $identifier
  1345.      *
  1346.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroupCreateStruct
  1347.      */
  1348.     public function newContentTypeGroupCreateStruct(string $identifier): ContentTypeGroupCreateStruct
  1349.     {
  1350.         return new ContentTypeGroupCreateStruct(
  1351.             [
  1352.                 'identifier' => $identifier,
  1353.             ]
  1354.         );
  1355.     }
  1356.     /**
  1357.      * Instantiates a new content type create class.
  1358.      *
  1359.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue if given identifier is not a string
  1360.      *
  1361.      * @param string $identifier
  1362.      *
  1363.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeCreateStruct
  1364.      */
  1365.     public function newContentTypeCreateStruct(string $identifier): APIContentTypeCreateStruct
  1366.     {
  1367.         if (!is_string($identifier)) {
  1368.             throw new InvalidArgumentValue('$identifier'$identifier);
  1369.         }
  1370.         return new ContentTypeCreateStruct(
  1371.             [
  1372.                 'identifier' => $identifier,
  1373.             ]
  1374.         );
  1375.     }
  1376.     /**
  1377.      * Instantiates a new content type update struct.
  1378.      *
  1379.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeUpdateStruct
  1380.      */
  1381.     public function newContentTypeUpdateStruct(): ContentTypeUpdateStruct
  1382.     {
  1383.         return new ContentTypeUpdateStruct();
  1384.     }
  1385.     /**
  1386.      * Instantiates a new content type update struct.
  1387.      *
  1388.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeGroupUpdateStruct
  1389.      */
  1390.     public function newContentTypeGroupUpdateStruct(): ContentTypeGroupUpdateStruct
  1391.     {
  1392.         return new ContentTypeGroupUpdateStruct();
  1393.     }
  1394.     /**
  1395.      * Instantiates a field definition create struct.
  1396.      *
  1397.      * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue if given identifier is not a string
  1398.      *          or given fieldTypeIdentifier is not a string
  1399.      *
  1400.      * @param string $fieldTypeIdentifier the required field type identifier
  1401.      * @param string $identifier the required identifier for the field definition
  1402.      *
  1403.      * @return \eZ\Publish\API\Repository\Values\ContentType\FieldDefinitionCreateStruct
  1404.      */
  1405.     public function newFieldDefinitionCreateStruct(string $identifierstring $fieldTypeIdentifier): FieldDefinitionCreateStruct
  1406.     {
  1407.         return new FieldDefinitionCreateStruct(
  1408.             [
  1409.                 'identifier' => $identifier,
  1410.                 'fieldTypeIdentifier' => $fieldTypeIdentifier,
  1411.             ]
  1412.         );
  1413.     }
  1414.     /**
  1415.      * Instantiates a field definition update class.
  1416.      *
  1417.      * @return \eZ\Publish\API\Repository\Values\ContentType\FieldDefinitionUpdateStruct
  1418.      */
  1419.     public function newFieldDefinitionUpdateStruct(): FieldDefinitionUpdateStruct
  1420.     {
  1421.         return new FieldDefinitionUpdateStruct();
  1422.     }
  1423.     /**
  1424.      * Returns true if the given content type $contentType has content instances.
  1425.      *
  1426.      * @since 6.0.1
  1427.      *
  1428.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentType $contentType
  1429.      *
  1430.      * @return bool
  1431.      */
  1432.     public function isContentTypeUsed(APIContentType $contentType): bool
  1433.     {
  1434.         return $this->contentTypeHandler->getContentCount($contentType->id) > 0;
  1435.     }
  1436.     /**
  1437.      * @param \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft $contentTypeDraft
  1438.      * @param string $languageCode
  1439.      *
  1440.      * @return \eZ\Publish\API\Repository\Values\ContentType\ContentTypeDraft
  1441.      *
  1442.      * @throws \eZ\Publish\API\Repository\Exceptions\BadStateException
  1443.      * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException
  1444.      * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException
  1445.      */
  1446.     public function removeContentTypeTranslation(APIContentTypeDraft $contentTypeDraftstring $languageCode): APIContentTypeDraft
  1447.     {
  1448.         if (!$this->permissionResolver->canUser('class''update'$contentTypeDraft)) {
  1449.             throw new UnauthorizedException('ContentType''update');
  1450.         }
  1451.         $this->repository->beginTransaction();
  1452.         try {
  1453.             $contentType $this->contentTypeHandler->removeContentTypeTranslation(
  1454.                 $contentTypeDraft->id,
  1455.                 $languageCode
  1456.             );
  1457.             $this->repository->commit();
  1458.         } catch (Exception $e) {
  1459.             $this->repository->rollback();
  1460.             throw $e;
  1461.         }
  1462.         return $this->contentTypeDomainMapper->buildContentTypeDraftDomainObject($contentType);
  1463.     }
  1464.     public function deleteUserDrafts(int $userId): void
  1465.     {
  1466.         try {
  1467.             $this->userHandler->load($userId);
  1468.         } catch (APINotFoundException $e) {
  1469.             $this->contentTypeHandler->deleteByUserAndStatus($userIdContentType::STATUS_DRAFT);
  1470.             return;
  1471.         }
  1472.         if ($this->repository->getPermissionResolver()->hasAccess('class''delete') !== true) {
  1473.             throw new UnauthorizedException('ContentType''update');
  1474.         }
  1475.         $this->contentTypeHandler->deleteByUserAndStatus($userIdContentType::STATUS_DRAFT);
  1476.     }
  1477. }