Skip to content
Snippets Groups Projects
TsrefRestService.php 15.6 KiB
Newer Older
<?php

namespace SGalinski\TypoScriptReferenceFrontend\Service;

damjan's avatar
damjan committed
use SGalinski\TypoScriptReferenceFrontend\Domain\Model\Attribute;
use SGalinski\TypoScriptReferenceFrontend\Domain\Model\Property;
use SGalinski\TypoScriptReferenceFrontend\Domain\Model\Type;
use SGalinski\TypoScriptReferenceFrontend\Utilities\Conversion;
use TYPO3\Flow\Annotations as Flow;

/**
 * The Class TsrefRestService contains methods for communication with TypoScript REST API.
 *
 * @package SGalinski\TypoScriptReferenceFrontend\Service
 * @Flow\Scope("singleton")
 */
class TsrefRestService {
	/**
	 * Default typo3 version of typoScript types and properties.
	 * Filtering by this version (very high number) will fetch properties and types of current version.
	const TYPO3_DEFAULT_VERSION = '99999';
	/**
	 * A label to be used in URL instead of TYPO3_DEFAULT_VERSION.
	 * In REST requests, TYPO3_DEFAULT_VERSION is sent instead of this label.
	 */
	const TYPO3_CURRENT_VERSION_LABEL = 'current';

	 * @var string
	 * @Flow\Inject(setting="backendWriteAccessToken")
	 */
	protected $backendWriteAccessToken;

	/**
	 * URL of REST API resource.
	 *
	 * @var string
	 * @Flow\Inject(setting="resourceUrl")
	protected $resourceUrl;
	/**
	 * TypoScriptGroup string to int mapping.
	 *
	 * @var array
	 */
	protected $typoScriptGroupMapping = [
		'typoscript' => Attribute::NORMAL_GROUP,
		'user-tsconfig' => Attribute::USER_GROUP,
		'page-tsconfig' => Attribute::PAGE_GROUP
	];

	/**
	 * Makes curl handle, sets some general options and returns the handle.
	 * It also checks if cURL is installed.
	 *
	 * @return resource
	 */
	protected function initialiseCurl($suffix) {
		// is cURL installed yet?
		if (!function_exists('curl_init')) {
			throw new \RuntimeException('Sorry cURL is not installed. Install cURL.');
		}

		$curlHandle = curl_init();
		// Set URL to download
		curl_setopt($curlHandle, CURLOPT_URL, $this->resourceUrl . '/api' . $suffix);

		// Should cURL return or print out the data? (true = return, false = print)
		curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, TRUE);

		// Timeout in seconds
		curl_setopt($curlHandle, CURLOPT_TIMEOUT, 30);
		return $curlHandle;
	}

	 * Fetches types from web resource: RESOURCE_URL.
	 *
	 * @param boolean|null $isType - filters by isType
	 * @param bool $namesOnly - get urlNames and names only of types.
	 * @param string $typo3Version - filters by typo3Version
	 * @param string $typoScriptGroup - filters by typo3Group
	 * @return mixed
	 */
	public function getAttributesJson(
		$isType = NULL, $namesOnly = TRUE, $typo3Version = self::TYPO3_DEFAULT_VERSION,
		$typoScriptGroup
		$resourceName = 'properties';
			'namesonly: ' . ($namesOnly ? '1' : '0'),
			'version: ' . $typo3Version,
		];

		if ($isType !== NULL) {
			$resourceName = 'types';
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/' . $resourceName);

		curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $headerProperties);

		// Download the given URL, and return output
		$output = curl_exec($curlHandle);
damjan's avatar
damjan committed
		$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);

		// Close the cURL resource, and free system resources
		curl_close($curlHandle);
damjan's avatar
damjan committed
		$this->checkForErrors($statusCode, $output);
	 * Returns a property\type by its ID from RESOURCE_URL.
	 * @param string $resourceName ('properties' or 'types')
	 * @param int $attributeId
	 * @param string $typoScriptGroup
	 * @return mixed JSON
	public function getAttributeByIdJson($resourceName, $attributeId, $typoScriptGroup) {
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/' . $resourceName . '/' . $attributeId);

		// Download the given URL, and return output
		$output = curl_exec($curlHandle);
damjan's avatar
damjan committed
		$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);

		// Close the cURL resource, and free system resources
		curl_close($curlHandle);
damjan's avatar
damjan committed
		$this->checkForErrors($statusCode, $output);
	/**
	 * Returns a type by its name from RESOURCE_URL.
	 *
	 * @param string $typeUrlName
	 * @param string $typoScriptGroup
	public function getTypeByUrlNameJson($typeUrlName, $typoScriptGroup) {
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/types/' . $typeUrlName . '/byurlname');

		// Download the given URL, and return output
		$output = curl_exec($curlHandle);
damjan's avatar
damjan committed
		$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);

		// Close the cURL resource, and free system resources
		curl_close($curlHandle);
damjan's avatar
damjan committed
		$this->checkForErrors($statusCode, $output);
	 * @param string $typeUrlName
	 * @param string $typoScriptGroup
	public function getTypeByUrlName($typeUrlName, $typoScriptGroup) {
		$typeJson = $this->getTypeByUrlNameJson($typeUrlName, $typoScriptGroup);
		$typePhp = json_decode($typeJson);
		return $typePhp;
	}

	 * Returns properties of a type by its ID from RESOURCE_URL/propertiesID/byparenttype.
	 * @param int $typeId
	 * @param string $typo3Version
	 * @param string $typoScriptGroup
	public function getPropertiesByParentTypeIdJson(
		$typeId, $typo3Version = self::TYPO3_DEFAULT_VERSION, $typoScriptGroup
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/properties/' . $typeId . '/byparenttype');
		$headerProperties = [
			'version: ' . $typo3Version,
		];

		curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $headerProperties);

		// Download the given URL, and return output
		$output = curl_exec($curlHandle);
damjan's avatar
damjan committed
		$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);

		// Close the cURL resource, and free system resources
		curl_close($curlHandle);
damjan's avatar
damjan committed
		$this->checkForErrors($statusCode, $output);
	 * Gets a type by id.
	 * @param int $typeId
	 * @param string $typoScriptGroup
damjan's avatar
damjan committed
	 * @return \stdClass
	public function getTypeById($typeId, $typoScriptGroup) {
		$typeJson = $this->getAttributeByIdJson('types', $typeId, $typoScriptGroup);
		$typePhp = json_decode($typeJson);
		return $typePhp;
	 * Gets a property by id.
	 * @param int $propertyId
	 * @param string $typoScriptGroup
	public function getPropertyById($propertyId, $typoScriptGroup) {
		$propertyJson = $this->getAttributeByIdJson('properties', $propertyId, $typoScriptGroup);
		$propertyPhp = json_decode($propertyJson);
		return $propertyPhp;
	}

	/**
	 * Gets all properties by parent type id, $typo3Version and $typoScriptGroup from RESOURCE_URL
	 *
	 * @param int $parentTypeId
	 * @param string $typo3Version
	 * @param string $typoScriptGroup
	public function getPropertiesByParentTypeId(
		$parentTypeId, $typo3Version = self::TYPO3_DEFAULT_VERSION, $typoScriptGroup
		$propertiesJson = $this->getPropertiesByParentTypeIdJson($parentTypeId, $typo3Version, $typoScriptGroup);
		$propertiesPhp = json_decode($propertiesJson);
		return $propertiesPhp;
	}

	 * Gets all types by $typo3Version and $typoScriptGroup from RESOURCE_URL
	 * @param bool $namesOnly
	 * @param string $typo3Version
	 * @param string $typoScriptGroup
	 * @return mixed
	 */
	public function getTypes(
		$namesOnly = TRUE, $typo3Version = self::TYPO3_DEFAULT_VERSION, $typoScriptGroup
		$typesJson = $this->getAttributesJson(TRUE, $namesOnly, $typo3Version, $typoScriptGroup);
		$typesPhp = json_decode($typesJson);
		return $typesPhp;
	}

damjan's avatar
damjan committed
	/**
	 * Adds new type to REST API resource by use of POST method.
	 * @param Type $type
	 * @param string $typoScriptGroup
damjan's avatar
damjan committed
	 */
	public function addNewType(Type $type, $typoScriptGroup) {
		$typeJson = json_encode($type->toArray());
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/types');

		curl_setopt($curlHandle, CURLOPT_POST, 1);
		$outputJson = $this->submit($curlHandle, $typeJson);
	/**
	 * Adds new property to REST API resource by use of POST method.
	 *
	 * @param Property $property
	 * @param string $typoScriptGroup
	public function addNewProperty(Property $property, $typoScriptGroup) {
		$propertyJson = json_encode($property->toArray());
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/properties');

		curl_setopt($curlHandle, CURLOPT_POST, 1);
		$outputJson = $this->submit($curlHandle, $propertyJson);
damjan's avatar
damjan committed
		return $outputJson;
	 * Sends edited type to REST API resource by use of PUT method.
	 * @param Type $type
	 * @param string $typoScriptGroup
	public function editType(Type $type, $typoScriptGroup) {
		if ($type->getId() === NULL) {
				'Edited type: ' . ($type->getName() ? $type->getName() : '...') . ' doesnt have ID.'
		$typeJson = json_encode($type->toArray(TRUE));
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/types/' . $type->getId());

		curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PUT');
		$outputJson = $this->submit($curlHandle, $typeJson);
	/**
	 * Sends edited property to REST API resource by use of PUT method.
	 *
	 * @param Property $property
	 * @param string $typoScriptGroup
	public function editProperty(Property $property, $typoScriptGroup) {
		if ($property->getId() === NULL) {
			throw new \RuntimeException(
				'Edited property: ' . ($property->getName() ? $property->getName() : '...') . ' doesnt have ID.'
			);
		}
		$dataJson = json_encode($property->toArray(TRUE));
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/properties/' . $property->getId());
		curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PUT');
		$outputJson = $this->submit($curlHandle, $dataJson);

		return $outputJson;
	 * Changes only those fields specified in the array $data. Type to change is specified with $id.
	 *
	 * @param int $id
	 * @param array $data
	 * @param string $typoScriptGroup
	 * @return mixed
	 */
	public function patchType($id, array $data, $typoScriptGroup) {
		if ($id === NULL) {
			throw new \RuntimeException(
				'$id must not be NULL.'
			);
		}
		$dataJson = json_encode($data);
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/types/' . $id);

		curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PATCH');
		$outputJson = $this->submit($curlHandle, $dataJson);
	/**
	 * Changes only those fields specified in the array $data. Property to change is specified with $id.
	 *
	 * @param int $id
	 * @param array $data
	 * @param string $typoScriptGroup
	public function patchProperty($id, array $data, $typoScriptGroup) {
		if ($id === NULL) {
			throw new \RuntimeException(
				'$id must not be NULL.'
			);
		}
		$dataJson = json_encode($data);
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/properties/' . $id);
		curl_setopt($curlHandle, CURLOPT_CUSTOMREQUEST, 'PATCH');
		$outputJson = $this->submit($curlHandle, $dataJson);

		return $outputJson;
	}

	/**
	 * Downloads typo script reference xml from web resource.
	 *
	 * @param string $typo3Version
	 * @param string $typoScriptGroup
	 * @return mixed
	 */
	public function getTsrefXml($typo3Version = self::TYPO3_DEFAULT_VERSION, $typoScriptGroup) {
		$curlHandle = $this->initialiseCurl('/' . $typoScriptGroup . '/tsref');

		$headerProperties = [
			'version: ' . $typo3Version,
		];

		curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $headerProperties);

		curl_setopt($curlHandle, CURLOPT_FAILONERROR, 1);
		curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, 1);

		// Download the given URL, and return output
		$output = curl_exec($curlHandle);
damjan's avatar
damjan committed
		$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);

		// Close the cURL resource, and free system resources
		curl_close($curlHandle);
damjan's avatar
damjan committed
		$this->checkForErrors($statusCode, $output);
	/**
	 * Gets all categories from web resource and returns them as array.
	 *
	 * @return mixed
	 */
	public function getCategoriesJson() {
		$curlHandle = $this->initialiseCurl('/categories');

		// Download the given URL, and return output
		$output = curl_exec($curlHandle);
damjan's avatar
damjan committed
		$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);

		// Close the cURL resource, and free system resources
		curl_close($curlHandle);
damjan's avatar
damjan committed
		$this->checkForErrors($statusCode, $output);

		return $output;
	}

	/**
	 * Gets all categories from web resource and returns them as stdClass.
	 *
	 * @return mixed
	 */
	public function getCategories() {
		$categoriesJson = $this->getCategoriesJson();
		$categoriesPhp = json_decode($categoriesJson);
		return $categoriesPhp;
	}

damjan's avatar
damjan committed
	/**
	 * Returns associative array of all typo3 groups.
	 *
	 * @return array
	 */
	public function getAllTypo3Groups() {
		return [
			Attribute::NORMAL_GROUP => 'Normal TypoScript',
			Attribute::PAGE_GROUP => 'Page TypoScript',
			Attribute::USER_GROUP => 'User TypoScript',
damjan's avatar
damjan committed

	/**
	 * Returns associative array of available typo3 versions.
	 *
	 * @return array
	 */
	public function getTypo3Versions() {
		return [
			TsrefRestService::TYPO3_CURRENT_VERSION_LABEL => 'Current',
damjan's avatar
damjan committed
			'7.4' => '7.4',
			'6.2' => '6.2',
			'4.5' => '4.5',
			'all' => 'All',

	/**
	 * Gets en array of types for given TYPO3 version. The types are fetched with name and id only.
	 *
	 * @param string $typo3Version
	 * @param string $typoScriptGroup
	public function getTypesWithNull($typo3Version, $typoScriptGroup) {
		$typesAssociative = Conversion::typesToAssociativeIdArray(
			$this->getTypes(TRUE, $typo3Version, $typoScriptGroup)
		);
		$typesAssociative = [-1 => 'No type (NULL)'] + $typesAssociative;
		return $typesAssociative;
	}
damjan's avatar
damjan committed

	/**
	 * Checks if there was en error in response and trows exception.
	 *
	 * @param $statusCode
	 * @param $outputJson
	 */
	protected function checkForErrors($statusCode, $outputJson) {
		if (gettype($statusCode) === 'integer' && $statusCode >= 400) {
			$outputObject = json_decode($outputJson);
			$message = isset($outputObject->message) ? $outputObject->message : NULL;
			$errors = isset($outputObject->errors) ? '. Errors:' . json_encode($outputObject->errors) : '';
			throw new \RuntimeException($message . $errors, $statusCode);
damjan's avatar
damjan committed
		}
	}

	/**
	 * Submits by CURL json data and validates response.
	 *
	 * @param $curlHandle
	 * @param $dataJson
	 * @return mixed
	 */
	protected function submit($curlHandle, $dataJson) {
		curl_setopt(
			$curlHandle, CURLOPT_HTTPHEADER, [
				'Content-Type: application/json',
				'accesstoken: ' . $this->backendWriteAccessToken
			]
		);
		curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $dataJson);

		// Download the given URL, and return output
		$outputJson = curl_exec($curlHandle);
		$statusCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE);

		// Close the cURL resource, and free system resources
		curl_close($curlHandle);
		$this->checkForErrors($statusCode, $outputJson);
		return $outputJson;
	}

	/**
	 * Returns ID (integer representation) of given tpoScript group.
	 *
	 * @param string $typoScriptGroupName
	 * @return int
	 */
	public function getTypoScriptGroupId($typoScriptGroupName) {
		if (!array_key_exists($typoScriptGroupName, $this->typoScriptGroupMapping)) {
			throw new \RuntimeException('TypoScript group \'' . $typoScriptGroupName . '\' doesn\'t exist.');
		}

		return $this->typoScriptGroupMapping[$typoScriptGroupName];
	}

	/**
	 * Returns typoScript group name for given ID.
	 *
	 * @param int $typoScriptGroupId
	 * @return string
	 */
	public function getTypoScriptGroupName($typoScriptGroupId) {
		if (!in_array($typoScriptGroupId, $this->typoScriptGroupMapping)) {
			throw new \RuntimeException('Invalid tpoScript group ID: \'' . $typoScriptGroupId . '\'.');
		}

		$typoScriptGroupInverseMapping = array_flip($this->typoScriptGroupMapping);
		return $typoScriptGroupInverseMapping[$typoScriptGroupId];
	}