<?php namespace SGalinski\SgRest\Service; /*************************************************************** * Copyright notice * * (c) sgalinski Internet Services (http://www.sgalinski.de) * * All rights reserved * * This script is part of the TYPO3 project. The TYPO3 project is * free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * * This script is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ use SGalinski\SgImpulse\Domain\Model\Impulse; use SGalinski\SgRest\Utility\PathUtility; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Domain\Model\FileReference; use TYPO3\CMS\Extbase\DomainObject\AbstractEntity; use TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy; use TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage; use TYPO3\CMS\Extbase\Persistence\ObjectStorage; use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter; /** * The DataResolverService converts the data representations of the REST requests. For instance from objects to array * or the way around. */ class DataResolveService implements SingletonInterface { /** * @inject * @var \TYPO3\CMS\Extbase\Object\ObjectManager */ protected $objectManager; /** * @inject * @var \SGalinski\SgRest\Service\RegistrationService */ protected $registrationService; /** * @var bool */ protected $resolveResourceLinks = FALSE; /** * @var int */ protected $resolveNestingLevel = 1; /** * @var string */ protected $apiKey = ''; /** * @var string */ protected $dateFormat = DateTimeConverter::DEFAULT_DATE_FORMAT; /** * @var array */ protected $objectCache = []; /** * Setter for the nesting level property. * * @param int $resolveNestingLevel * @return void */ public function setResolveNestingLevel($resolveNestingLevel) { $this->resolveNestingLevel = $resolveNestingLevel; } /** * Setter for the resolve resource links flag. * * @param boolean $resolveResourceLinks * @return void */ public function setResolveResourceLinks($resolveResourceLinks) { $this->resolveResourceLinks = $resolveResourceLinks; } /** * Setter for the apiKey. * * @param string $apiKey * @return void */ public function setApiKey($apiKey) { $this->apiKey = $apiKey; } /** * Setter for the date format. * * @param string $format * @return void */ public function setDateFormat($format) { $this->dateFormat = $format; } /** * Method converts a given object to an array. * * @param AbstractEntity $object * @param bool $returnReference * @param int $nestingLevel * @return array */ public function getArrayFromObject(AbstractEntity $object, $returnReference = FALSE, $nestingLevel = -1) { $entityName = $this->getEntityName($object); $identifier = (method_exists($object, 'getUid') ? $object->getUid() : 0); $objectCacheKey = $entityName . '-' . $identifier . '-' . ($returnReference ? 1 : 0); if (isset($this->objectCache[$objectCacheKey])) { return $this->objectCache[$objectCacheKey]; } $properties = $object->_getCleanProperties(); if ($object instanceof FileReference && $identifier) { return [ 'href' => PathUtility::createFileUrl($object), 'uid' => $identifier ]; } if ($returnReference && $identifier) { return [ 'href' => PathUtility::createRestEntityUrl($this->apiKey, $entityName, $identifier), 'uid' => $identifier ]; } // Initialize nesting level if ($this->resolveResourceLinks && $nestingLevel === -1) { $nestingLevel = $this->resolveNestingLevel; } $allowedProperties = []; if ($this->apiKey !== '' && $this->registrationService->isApiKeyAvailable($this->apiKey)) { $accessGroups = $this->registrationService->getAccessGroups(); $allowedProperties = $accessGroups[$this->apiKey]['entities'][$entityName]['read']; } $properties = array_intersect_key($properties, array_flip($allowedProperties)); foreach ($properties as $propertyName => $propertyValues) { if (!is_object($propertyValues)) { continue; } if ($propertyValues instanceof \DateTime) { /** @var \DateTime $date */ $date = $propertyValues; $properties[$propertyName] = $date->format($this->dateFormat); } else { $properties[$propertyName] = $this->convertSubPropertyToArray($object, $propertyName, $nestingLevel); } } $this->objectCache[$objectCacheKey] = $properties; return $properties; } /** * Method returns an array with relations to the sub property as rest urls. * * @param AbstractEntity $parentObject * @param string $subProperty * @param int $nestingLevel * @throws \Exception * @return array */ protected function convertSubPropertyToArray(AbstractEntity $parentObject, $subProperty, $nestingLevel) { $result = []; $getMethod = 'get' . ucfirst($subProperty); if (!method_exists($parentObject, $getMethod)) { $message = 'The sub property ' . $subProperty . ' of the object you requested could not be found.'; throw new \Exception($message, 404); } $subObject = $parentObject->{$getMethod}(); $returnReference = ($this->resolveResourceLinks && $nestingLevel >= 1); if ($subObject instanceof LazyLoadingProxy) { $subObject->_loadRealInstance(); } if ($subObject instanceof LazyObjectStorage) { $subObject->toArray(); } if ($subObject instanceof ObjectStorage || $subObject instanceof \Traversable) { // toArray() is necessary, because an ObjectStorage is losing it's index, if the child object references to // the same storage. So it can happen, that not all objects are iterated. foreach ($subObject->toArray() as $item) { $result[] = $this->getArrayFromObject($item, !$returnReference, $nestingLevel); } } else { $entityName = (($subObject instanceof AbstractEntity) ? $this->getEntityName($subObject) : ''); if (!$this->registrationService->isEntityAvailable($entityName, $this->apiKey)) { $message = 'You are not allowed to access the sub property: ' . $subProperty; throw new \Exception($message, 403); } if ($subObject instanceof AbstractEntity && $returnReference) { $nestingLevel = ($nestingLevel > 0 ? $nestingLevel - 1 : 0); $result = $this->getArrayFromObject($subObject, FALSE, $nestingLevel); } $identifier = (method_exists($subObject, 'getUid') ? $subObject->getUid() : 0); if ($subObject instanceof FileReference && $identifier) { $result['href'] = PathUtility::createFileUrl($subObject); } else { $result['href'] = PathUtility::createRestEntityUrl($this->apiKey, $entityName, $identifier); } $result['uid'] = $identifier; } return $result; } /** * Returns lowercase representation of the entity name. * * @param AbstractEntity $object * @return string */ public function getEntityName(AbstractEntity $object) { return $this->getEntityNameByClassName(get_class($object)); } /** * Returns lowercase representation of the entity name. * * @param string $className * @return string */ public function getEntityNameByClassName($className) { $classPath = GeneralUtility::trimExplode('\\', $className); return lcfirst(array_pop($classPath)); } }