Example #1
0
 /**
  * Ingest from a URL.
  *
  * Accepts the following non-prefixed keys:
  *
  * + ingest_url: (required) The URL to ingest. The idea is that some URLs
  *   contain sensitive data that should not be saved to the database, such
  *   as private keys. To preserve the URL, remove sensitive data from the
  *   URL and set it to o:source.
  * + store_original: (optional, default true) Whether to store an original
  *   file. This is helpful when you want the media to have thumbnails but do
  *   not need the original file.
  *
  * {@inheritDoc}
  */
 public function ingest(Media $media, Request $request, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     if (!isset($data['ingest_url'])) {
         $errorStore->addError('error', 'No ingest URL specified');
         return;
     }
     $uri = new HttpUri($data['ingest_url']);
     if (!($uri->isValid() && $uri->isAbsolute())) {
         $errorStore->addError('ingest_url', 'Invalid ingest URL');
         return;
     }
     $file = $this->getServiceLocator()->get('Omeka\\File');
     $file->setSourceName($uri->getPath());
     $this->downloadFile($uri, $file->getTempPath());
     $fileManager = $this->getServiceLocator()->get('Omeka\\File\\Manager');
     $hasThumbnails = $fileManager->storeThumbnails($file);
     $media->setHasThumbnails($hasThumbnails);
     if (!isset($data['store_original']) || $data['store_original']) {
         $fileManager->storeOriginal($file);
         $media->setHasOriginal(true);
     }
     $media->setFilename($file->getStorageName());
     $media->setMediaType($file->getMediaType());
     if (!array_key_exists('o:source', $data)) {
         $media->setSource($uri);
     }
 }
Example #2
0
 /**
  * {@inheritDoc}
  */
 public function read(Request $request)
 {
     $manager = $this->getServiceLocator()->get('Omeka\\ModuleManager');
     $response = new Response();
     $representation = $this->getRepresentation($manager->getModule($request->getId()));
     $response->setContent($representation);
     return $response;
 }
Example #3
0
 public function update(Media $media, Request $request, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     $html = $data['o:media']['__index__']['html'];
     $serviceLocator = $this->getServiceLocator();
     $purifier = $serviceLocator->get('Omeka\\HtmlPurifier');
     $html = $purifier->purify($html);
     $media->setData(['html' => $html]);
 }
Example #4
0
 public function ingest(Media $media, Request $request, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     if (!isset($data['o:source'])) {
         $errorStore->addError('o:source', 'No OEmbed URL specified');
         return;
     }
     $config = $this->getServiceLocator()->get('Config');
     $whitelist = $config['oembed']['whitelist'];
     $whitelisted = false;
     foreach ($whitelist as $regex) {
         if (preg_match($regex, $data['o:source']) === 1) {
             $whitelisted = true;
             break;
         }
     }
     if (!$whitelisted) {
         $errorStore->addError('o:source', 'Invalid OEmbed URL');
         return;
     }
     $source = $data['o:source'];
     $response = $this->makeRequest($source, 'OEmbed URL', $errorStore);
     if (!$response) {
         return;
     }
     $document = $response->getBody();
     $dom = new Query($document);
     $oEmbedLinks = $dom->queryXpath('//link[@rel="alternate" or @rel="alternative"][@type="application/json+oembed"]');
     if (!count($oEmbedLinks)) {
         $errorStore->addError('o:source', 'No OEmbed links were found at the given URI');
         return;
     }
     $oEmbedLink = $oEmbedLinks[0];
     $linkResponse = $this->makeRequest($oEmbedLink->getAttribute('href'), 'OEmbed link URL', $errorStore);
     if (!$linkResponse) {
         return;
     }
     $mediaData = json_decode($linkResponse->getBody(), true);
     if (!$mediaData) {
         $errorStore->addError('o:source', 'Error decoding OEmbed JSON');
         return;
     }
     if (isset($mediaData['thumbnail_url'])) {
         $fileManager = $this->getServiceLocator()->get('Omeka\\File\\Manager');
         $file = $this->getServiceLocator()->get('Omeka\\File');
         $this->downloadFile($mediaData['thumbnail_url'], $file->getTempPath());
         $hasThumbnails = $fileManager->storeThumbnails($file);
         if ($hasThumbnails) {
             $media->setFilename($file->getStorageName());
             $media->setHasThumbnails(true);
         }
     }
     $media->setData($mediaData);
     $media->setSource($source);
 }
Example #5
0
 public function ingest(Media $media, Request $request, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     if (!isset($data['o:source'])) {
         $errorStore->addError('o:source', 'No IIIF Image URL specified');
         return;
     }
     $source = $data['o:source'];
     //Make a request and handle any errors that might occur.
     $uri = new HttpUri($source);
     if (!($uri->isValid() && $uri->isAbsolute())) {
         $errorStore->addError('o:source', "Invalid url specified");
         return false;
     }
     $client = $this->getServiceLocator()->get('Omeka\\HttpClient');
     $client->setUri($uri);
     $response = $client->send();
     if (!$response->isOk()) {
         $errorStore->addError('o:source', sprintf("Error reading %s: %s (%s)", $type, $response->getReasonPhrase(), $response->getStatusCode()));
         return false;
     }
     $IIIFData = json_decode($response->getBody(), true);
     if (!$IIIFData) {
         $errorStore->addError('o:source', 'Error decoding IIIF JSON');
         return;
     }
     //Check if valid IIIF data
     if ($this->validate($IIIFData)) {
         $media->setData($IIIFData);
         // Not IIIF
     } else {
         $errorStore->addError('o:source', 'URL does not link to IIIF JSON');
         return;
     }
     //Check API version and generate a thumbnail
     //Version 2.0
     if (isset($IIIFData['@context']) && $IIIFData['@context'] == 'http://iiif.io/api/image/2/context.json') {
         $URLString = '/full/full/0/default.jpg';
         // Earlier versions
     } else {
         $URLString = '/full/full/0/native.jpg';
     }
     if (isset($IIIFData['@id'])) {
         $fileManager = $this->getServiceLocator()->get('Omeka\\File\\Manager');
         $file = $this->getServiceLocator()->get('Omeka\\File');
         $this->downloadFile($IIIFData['@id'] . $URLString, $file->getTempPath());
         $hasThumbnails = $fileManager->storeThumbnails($file);
         if ($hasThumbnails) {
             $media->setFilename($file->getStorageName());
             $media->setHasThumbnails(true);
         }
     }
 }
Example #6
0
 /**
  * {@inheritDoc}
  */
 public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     $this->hydrateOwner($request, $entity);
     if ($this->shouldHydrate($request, 'o:local_name')) {
         $entity->setLocalName($request->getValue('o:local_name'));
     }
     if ($this->shouldHydrate($request, 'o:label')) {
         $entity->setLabel($request->getValue('o:label'));
     }
     if ($this->shouldHydrate($request, 'o:comment')) {
         $entity->setComment($request->getValue('o:comment'));
     }
 }
Example #7
0
 /**
  * {@inheritDoc}
  */
 public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     if (Request::CREATE === $request->getOperation() && isset($data['o:site']['o:id'])) {
         $site = $this->getAdapter('sites')->findEntity($data['o:site']['o:id']);
         $this->authorize($site, 'add-page');
         $entity->setSite($site);
     }
     if ($this->shouldHydrate($request, 'o:slug')) {
         $entity->setSlug($request->getValue('o:slug'));
     }
     if ($this->shouldHydrate($request, 'o:title')) {
         $entity->setTitle($request->getValue('o:title'));
     }
     $appendBlocks = $request->getOperation() === Request::UPDATE && $request->isPartial();
     $this->hydrateBlocks($request->getValue('o:block', []), $entity, $errorStore, $appendBlocks);
 }
 /**
  * {@inheritDoc}
  */
 public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     if ($this->shouldHydrate($request, 'o:is_public')) {
         $entity->setIsPublic($request->getValue('o:is_public', true));
     }
     // Hydrate this resource's values.
     $append = $request->getOperation() === Request::UPDATE && $request->isPartial();
     $valueHydrator = new ValueHydrator($this);
     $valueHydrator->hydrate($data, $entity, $append);
     // o:owner
     $this->hydrateOwner($request, $entity);
     // o:resource_class
     $this->hydrateResourceClass($request, $entity);
     // o:resource_template
     $this->hydrateResourceTemplate($request, $entity);
 }
Example #9
0
 public function ingest(Media $media, Request $request, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     if (!isset($data['o:source'])) {
         $errorStore->addError('o:source', 'No YouTube URL specified');
         return;
     }
     $uri = new HttpUri($data['o:source']);
     if (!($uri->isValid() && $uri->isAbsolute())) {
         $errorStore->addError('o:source', 'Invalid YouTube URL specified');
         return;
     }
     switch ($uri->getHost()) {
         case 'www.youtube.com':
             if ('/watch' !== $uri->getPath()) {
                 $errorStore->addError('o:source', 'Invalid YouTube URL specified, missing "/watch" path');
                 return;
             }
             $query = $uri->getQueryAsArray();
             if (!isset($query['v'])) {
                 $errorStore->addError('o:source', 'Invalid YouTube URL specified, missing "v" parameter');
                 return;
             }
             $youtubeId = $query['v'];
             break;
         case 'youtu.be':
             $youtubeId = substr($uri->getPath(), 1);
             break;
         default:
             $errorStore->addError('o:source', 'Invalid YouTube URL specified, not a YouTube URL');
             return;
     }
     $fileManager = $this->getServiceLocator()->get('Omeka\\File\\Manager');
     $file = $this->getServiceLocator()->get('Omeka\\File');
     $url = sprintf('http://img.youtube.com/vi/%s/0.jpg', $youtubeId);
     $this->downloadFile($url, $file->getTempPath());
     $hasThumbnails = $fileManager->storeThumbnails($file);
     $media->setData(['id' => $youtubeId, 'start' => $request->getValue('start'), 'end' => $request->getValue('end')]);
     if ($hasThumbnails) {
         $media->setFilename($file->getStorageName());
         $media->setHasThumbnails(true);
     }
 }
Example #10
0
 /**
  * {@inheritDoc}
  */
 public function ingest(Media $media, Request $request, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     $fileData = $request->getFileData();
     if (!isset($fileData['file'])) {
         $errorStore->addError('error', 'No files were uploaded');
         return;
     }
     if (!isset($data['file_index'])) {
         $errorStore->addError('error', 'No file index was specified');
         return;
     }
     $index = $data['file_index'];
     if (!isset($fileData['file'][$index])) {
         $errorStore->addError('error', 'No file uploaded for the specified index');
         return;
     }
     $fileManager = $this->getServiceLocator()->get('Omeka\\File\\Manager');
     $file = $this->getServiceLocator()->get('Omeka\\File');
     $fileInput = new FileInput('file');
     $fileInput->getFilterChain()->attach(new RenameUpload(['target' => $file->getTempPath(), 'overwrite' => true]));
     $fileData = $fileData['file'][$index];
     $fileInput->setValue($fileData);
     if (!$fileInput->isValid()) {
         foreach ($fileInput->getMessages() as $message) {
             $errorStore->addError('upload', $message);
         }
         return;
     }
     // Actually process and move the upload
     $fileInput->getValue();
     $file->setSourceName($fileData['name']);
     $hasThumbnails = $fileManager->storeThumbnails($file);
     $fileManager->storeOriginal($file);
     $media->setFilename($file->getStorageName());
     $media->setMediaType($file->getMediaType());
     $media->setHasThumbnails($hasThumbnails);
     $media->setHasOriginal(true);
     if (!array_key_exists('o:source', $data)) {
         $media->setSource($fileData['name']);
     }
 }
Example #11
0
 /**
  * {@inheritDoc}
  */
 public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     if (Request::CREATE === $request->getOperation()) {
         // Accept the passed ingester only on CREATE to prevent overwriting
         // on subsequent UPDATE requests.
         $ingesterName = $request->getValue('o:ingester');
     } else {
         $ingesterName = $entity->getIngester();
     }
     $ingester = $this->getServiceLocator()->get('Omeka\\MediaIngesterManager')->get($ingesterName);
     if (Request::CREATE === $request->getOperation()) {
         if ($ingester instanceof Fallback) {
             $errorStore->addError('o:ingester', 'Media must set a valid ingester.');
             return;
         }
         $entity->setIngester($ingesterName);
         $entity->setRenderer($ingester->getRenderer());
         if (isset($data['o:item']['o:id'])) {
             $item = $this->getAdapter('items')->findEntity($data['o:item']['o:id']);
             $entity->setItem($item);
         }
         if (isset($data['data'])) {
             $entity->setData($data['data']);
         }
         if (isset($data['o:source'])) {
             $entity->setSource($data['o:source']);
         }
     }
     parent::hydrate($request, $entity, $errorStore);
     if ($this->shouldHydrate($request, 'o:lang')) {
         $entity->setLang($request->getValue('o:lang', null));
     }
     if (Request::CREATE === $request->getOperation()) {
         $ingester->ingest($entity, $request, $errorStore);
     } elseif ($ingester instanceof MutableIngesterInterface) {
         $ingester->update($entity, $request, $errorStore);
     }
 }
Example #12
0
 public function testConstructorSetsProperties()
 {
     $request = new Request('search', 'foo');
     $this->assertEquals('search', $request->getOperation());
     $this->assertEquals('foo', $request->getResource());
 }
Example #13
0
 /**
  * {@inheritDoc}
  */
 public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore)
 {
     parent::hydrate($request, $entity, $errorStore);
     if ($this->shouldHydrate($request, 'o:item_set')) {
         $itemSetsData = $request->getValue('o:item_set', []);
         $itemSetAdapter = $this->getAdapter('item_sets');
         $itemSets = $entity->getItemSets();
         $itemSetsToRetain = [];
         foreach ($itemSetsData as $itemSetData) {
             if (is_array($itemSetData) && array_key_exists('o:id', $itemSetData) && is_numeric($itemSetData['o:id'])) {
                 $itemSetId = $itemSetData['o:id'];
             } elseif (is_numeric($itemSetData)) {
                 $itemSetId = $itemSetData;
             } else {
                 continue;
             }
             if (!($itemSet = $itemSets->get($itemSetId))) {
                 // Item set not already assigned. Assign it.
                 $itemSet = $itemSetAdapter->findEntity($itemSetId);
                 $itemSets->add($itemSet);
             }
             $itemSetsToRetain[] = $itemSet;
         }
         // Unassign item sets that were not included in the passed data.
         foreach ($itemSets as $itemSet) {
             if (!in_array($itemSet, $itemSetsToRetain)) {
                 $itemSets->removeElement($itemSet);
             }
         }
     }
     if ($this->shouldHydrate($request, 'o:media')) {
         $mediasData = $request->getValue('o:media', []);
         $adapter = $this->getAdapter('media');
         $class = $adapter->getEntityClass();
         $retainMedia = [];
         $position = 1;
         foreach ($mediasData as $mediaData) {
             $subErrorStore = new ErrorStore();
             if (isset($mediaData['o:id'])) {
                 $media = $adapter->findEntity($mediaData['o:id']);
                 $media->setPosition($position);
                 if (isset($mediaData['o:is_public'])) {
                     $media->setIsPublic($mediaData['o:is_public']);
                 }
                 $retainMedia[] = $media;
             } else {
                 // Create a new media.
                 $media = new $class();
                 $media->setItem($entity);
                 $media->setPosition($position);
                 $subrequest = new Request(Request::CREATE, 'media');
                 $subrequest->setContent($mediaData);
                 $subrequest->setFileData($request->getFileData());
                 try {
                     $adapter->hydrateEntity($subrequest, $media, $subErrorStore);
                 } catch (Exception\ValidationException $e) {
                     $errorStore->mergeErrors($e->getErrorStore(), 'o:media');
                 }
                 $entity->getMedia()->add($media);
                 $retainMedia[] = $media;
             }
             $position++;
         }
         // Remove media not included in request.
         foreach ($entity->getMedia() as $media) {
             if (!in_array($media, $retainMedia, true)) {
                 $entity->getMedia()->removeElement($media);
             }
         }
     }
 }
Example #14
0
 /**
  * Hydrate the entity's resource template.
  *
  * Assumes the resource template can be set to NULL.
  *
  * @param Request $request
  * @param EntityInterface $entity
  */
 public function hydrateResourceTemplate(Request $request, EntityInterface $entity)
 {
     $data = $request->getContent();
     $resourceTemplate = $entity->getResourceTemplate();
     if ($this->shouldHydrate($request, 'o:resource_template')) {
         if (isset($data['o:resource_template']['o:id']) && is_numeric($data['o:resource_template']['o:id'])) {
             $resourceTemplate = $this->getAdapter('resource_templates')->findEntity($data['o:resource_template']['o:id']);
         } else {
             $resourceTemplate = null;
         }
     }
     $entity->setResourceTemplate($resourceTemplate);
 }
Example #15
0
 /**
  * Execute a batch create operation.
  *
  * @param Request $request
  * @param null|AdapterInterface $adapter Custom adapter
  * @return Response
  */
 protected function executeBatchCreate(Request $request, AdapterInterface $adapter)
 {
     $t = $this->getTranslator();
     if (!is_array($request->getContent())) {
         throw new Exception\BadRequestException($t->translate('Invalid batch operation request data.'));
     }
     // Create a simulated request for individual create events.
     $createRequest = new Request(Request::CREATE, $request->getResource());
     // Trigger the create.pre event for every resource.
     foreach ($request->getContent() as $content) {
         $createRequest->setContent($content);
         $createEvent = new Event(Event::API_CREATE_PRE, $adapter, ['request' => $createRequest]);
         $adapter->getEventManager()->trigger($createEvent);
     }
     $response = $adapter->batchCreate($request);
     // Do not trigger create.post events if an error has occured or if the
     // response does not return valid content.
     if ($response->isError() || !is_array($response->getContent())) {
         return $response;
     }
     // Trigger the create.post event for every created resource.
     foreach ($response->getContent() as $resource) {
         $createRequest->setContent($resource);
         $createEvent = new Event(Event::API_CREATE_POST, $adapter, ['request' => $createRequest, 'response' => new Response($resource)]);
         $adapter->getEventManager()->trigger($createEvent);
     }
     return $response;
 }
 /**
  * {@inheritDoc}
  */
 public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore)
 {
     $data = $request->getContent();
     $this->hydrateOwner($request, $entity);
     $this->hydrateResourceClass($request, $entity);
     if ($this->shouldHydrate($request, 'o:label')) {
         $entity->setLabel($request->getValue('o:label'));
     }
     if ($this->shouldHydrate($request, 'o:resource_template_property') && isset($data['o:resource_template_property']) && is_array($data['o:resource_template_property'])) {
         // Get a resource template property by property ID.
         $getResTemProp = function ($propertyId, $resTemProps) {
             foreach ($resTemProps as $resTemProp) {
                 if ($propertyId == $resTemProp->getProperty()->getId()) {
                     return $resTemProp;
                 }
             }
             return null;
         };
         $propertyAdapter = $this->getAdapter('properties');
         $resTemProps = $entity->getResourceTemplateProperties();
         $resTemPropsToRetain = [];
         $position = 1;
         foreach ($data['o:resource_template_property'] as $resTemPropData) {
             if (!isset($resTemPropData['o:property']['o:id'])) {
                 continue;
                 // skip when no property ID
             }
             $propertyId = $resTemPropData['o:property']['o:id'];
             $altLabel = null;
             if (isset($resTemPropData['o:alternate_label']) && '' !== trim($resTemPropData['o:alternate_label'])) {
                 $altLabel = $resTemPropData['o:alternate_label'];
             }
             $altComment = null;
             if (isset($resTemPropData['o:alternate_comment']) && '' !== trim($resTemPropData['o:alternate_comment'])) {
                 $altComment = $resTemPropData['o:alternate_comment'];
             }
             // Check whether a passed property is already assigned to this
             // resource template.
             $resTemProp = $getResTemProp($propertyId, $resTemProps);
             if ($resTemProp) {
                 // It is already assigned. Modify the existing entity.
                 $resTemProp->setAlternateLabel($altLabel);
                 $resTemProp->setAlternateComment($altComment);
             } else {
                 // It is not assigned. Add a new resource template property.
                 // No need to explicitly add it to the collection since it
                 // is added implicitly when setting the resource template.
                 $property = $propertyAdapter->findEntity($propertyId);
                 $resTemProp = new ResourceTemplateProperty();
                 $resTemProp->setResourceTemplate($entity);
                 $resTemProp->setProperty($property);
                 $resTemProp->setAlternateLabel($altLabel);
                 $resTemProp->setAlternateComment($altComment);
                 $entity->getResourceTemplateProperties()->add($resTemProp);
             }
             // Set the position of the property to its intrinsic order
             // within the passed array.
             $resTemProp->setPosition($position++);
             $resTemPropsToRetain[] = $resTemProp;
         }
         // Remove resource template properties that were not included in the
         // passed data.
         foreach ($resTemProps as $resTemPropId => $resTemProp) {
             if (!in_array($resTemProp, $resTemPropsToRetain)) {
                 $resTemProps->remove($resTemPropId);
             }
         }
     }
 }
Example #17
0
 /**
  * {@inheritDoc}
  */
 public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore)
 {
     $this->hydrateOwner($request, $entity);
     if ($this->shouldHydrate($request, 'o:namespace_uri')) {
         $entity->setNamespaceUri($request->getValue('o:namespace_uri'));
     }
     if ($this->shouldHydrate($request, 'o:prefix')) {
         $entity->setPrefix($request->getValue('o:prefix'));
     }
     if ($this->shouldHydrate($request, 'o:label')) {
         $entity->setLabel($request->getValue('o:label'));
     }
     if ($this->shouldHydrate($request, 'o:comment')) {
         $entity->setComment($request->getValue('o:comment'));
     }
     if ($this->shouldHydrate($request, 'o:class')) {
         $classesData = $request->getValue('o:class', []);
         $adapter = $this->getAdapter('resource_classes');
         $class = $adapter->getEntityClass();
         $retainResourceClasses = [];
         $retainResourceClassIds = [];
         foreach ($classesData as $classData) {
             if (isset($classData['o:id'])) {
                 // Do not update existing resource classes.
                 $retainResourceClassIds[] = $classData['o:id'];
             } else {
                 // Create a new resource class.
                 $resourceClass = new $class();
                 $resourceClass->setVocabulary($entity);
                 $subrequest = new Request(Request::CREATE, 'resource_classes');
                 $subrequest->setContent($classData);
                 $adapter->hydrateEntity($subrequest, $resourceClass, $errorStore);
                 $entity->getResourceClasses()->add($resourceClass);
                 $retainResourceClasses[] = $resourceClass;
             }
         }
         // Remove resource classes not included in request.
         foreach ($entity->getResourceClasses() as $resourceClass) {
             if (!in_array($resourceClass, $retainResourceClasses, true) && !in_array($resourceClass->getId(), $retainResourceClassIds)) {
                 $entity->getResourceClasses()->removeElement($resourceClass);
             }
         }
     }
     if ($this->shouldHydrate($request, 'o:property')) {
         $propertiesData = $request->getValue('o:property', []);
         $adapter = $this->getAdapter('properties');
         $class = $adapter->getEntityClass();
         $retainProperties = [];
         $retainPropertyIds = [];
         foreach ($propertiesData as $propertyData) {
             if (isset($propertyData['o:id'])) {
                 // Do not update existing properties.
                 $retainPropertyIds[] = $propertyData['o:id'];
             } else {
                 // Create a new property.
                 $property = new $class();
                 $property->setVocabulary($entity);
                 $subrequest = new Request(Request::CREATE, 'properties');
                 $subrequest->setContent($propertyData);
                 $adapter->hydrateEntity($subrequest, $property, $errorStore);
                 $entity->getProperties()->add($property);
                 $retainProperties[] = $property;
             }
         }
         // Remove resource classes not included in request.
         foreach ($entity->getProperties() as $property) {
             if (!in_array($property, $retainProperties, true) && !in_array($property->getId(), $retainPropertyIds)) {
                 $entity->getProperties()->removeElement($property);
             }
         }
     }
 }