Skip to content
Snippets Groups Projects
VimeoService.php 9.36 KiB
Newer Older
Kevin von Spiczak's avatar
Kevin von Spiczak committed
<?php

namespace SGalinski\SgVimeo\Service;

/***************************************************************
 *  Copyright notice
 *
 *  (c) sgalinski Internet Services (https://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 Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use Vimeo\Exceptions\VimeoRequestException;
use Vimeo\Vimeo;

/**
 * Vimeo Helper Service
 */
class VimeoService implements LoggerAwareInterface {
	use LoggerAwareTrait;

	protected const API_CHANNEL = '/channels/';
	protected const API_VIDEO = '/videos/';
	protected const API_SHOWCASE = '/me/albums/';

Kevin von Spiczak's avatar
Kevin von Spiczak committed
	/**
	 * https://developer.vimeo.com/api/authentication#supported-scopes
	 */
	protected const SCOPE = 'public';

	/**
	 * @var Vimeo
	 */
	protected $vimeoApiClient;

	/**
	 * @var array
	 */
	protected $paginatedResponseData;

	/**
	 * @var int
	 * Used for the `per_page` param, for the vimeo API requests.
	 * Use `per_page` to set the number of representations per page from the default value of 25 up to a maximum value of 100.
	 */
	protected $maxResultsPerPage = 25;

	/**
	 * @var int
	 * The amount of videos fetched within the current pagination request
	 */
	protected $amountOfVideosFetched = 0;

	/**
	 * VimeoService constructor.
	 *
	 * @param string $clientId
	 * @param string $clientSecret
	 * @param string $personalAccessToken
Kevin von Spiczak's avatar
Kevin von Spiczak committed
	 */
	public function __construct(string $clientId, string $clientSecret, string $personalAccessToken) {
		$this->vimeoApiClient = new Vimeo($clientId, $clientSecret, $personalAccessToken);
		// We only need to request an unauthenticated token, if there is no personal access token provided already.
		// An authenticated access token with the public scope is identical to an unauthenticated access token,
		// except that you can use the /me endpoint to refer to the currently logged-in user.
		// Accessing /me with an unauthenticated access token generates an error.
		// See also: https://developer.vimeo.com/api/authentication
		if ($personalAccessToken === '') {
			$this->requestAccessToken();
		}
	 * @param string $vimeoId can be a video id, showcase id or a channel name
Kevin von Spiczak's avatar
Kevin von Spiczak committed
	 * @param int $maxResults
	 * @return array|null
	 */
	public function getVimeoData(string $vimeoId, int $maxResults): ?array {
Kevin von Spiczak's avatar
Kevin von Spiczak committed
		$this->maxResultsPerPage = $maxResults;
		/** @var Registry $registry */
Kevin von Spiczak's avatar
Kevin von Spiczak committed
		$registry = GeneralUtility::makeInstance(Registry::class);
		$currentDay = date('Y-m-d', $GLOBALS['EXEC_TIME']);
		$cacheKey = sha1($vimeoId . $maxResults);
		$disableVimeoCache = (bool) GeneralUtility::_GP('disableVimeoCache');

		if (!$disableVimeoCache) {
			$cachedResult = $registry->get('sg_vimeo', $cacheKey);
			if ($cachedResult) {
				if ($cachedResult['CACHE_DATE'] === $currentDay) {
					return $cachedResult;
				}

				$registry->remove('sg_vimeo', $cacheKey);
			}
		}

		if (strpos($vimeoId, 'showcase') === 0) {
			$showcaseId = explode("\/", $vimeoId)[1];
			$response['items'] = $this->addVideoIdsToResponse($this->getShowcaseVideos((int) $showcaseId));
			$response['kind'] = 'showcase';
		} else if (strpos($vimeoId, 'channel') === 0) {
			$channelId = explode("\/", $vimeoId)[1];
			$response['items'] = $this->addVideoIdsToResponse($this->getChannelVideos($channelId));
			$response['kind'] = 'channel';
		} else {
			$response['items'] = $this->addVideoIdsToResponse($this->getVideo((int) $vimeoId));
			$response['kind'] = 'video';
Kevin von Spiczak's avatar
Kevin von Spiczak committed
		}

		if (!$disableVimeoCache) {
			$response['CACHE_DATE'] = $currentDay;
			$registry->set('sg_vimeo', $cacheKey, $response);
		}

		return $response;
	}

	/**
	 * Extracts the video id from the video's canonical relative URI and adds it to each entry with the key 'videoId'
	 *
	 * @param array|null $response
	 * @return array|null
	 */
	protected function addVideoIdsToResponse(?array $response): ?array {
		if (!is_array($response)) {
Kevin von Spiczak's avatar
Kevin von Spiczak committed
			return NULL;
		}

		foreach ($response as $index => $item) {
			if (array_key_exists('uri', $item)) {
				$uri = $item['uri'];
				$videoId = (int) str_replace('/videos/', '', $uri);
				$response[$index]['videoId'] = $videoId;
			}
		}

		return $response;
	}

	/**
	 * Unauthenticated API requests must generate an access token.
	 * (Access tokens without a user. These tokens can view only public data.)
	 * You should not generate a new access token for each request.
	 * Instead, request an access token once and use it forever.
	 */
	protected function requestAccessToken(): void {
		$token = $this->vimeoApiClient->clientCredentials(self::SCOPE);
		if ($token['body']['access_token']) {
			$this->vimeoApiClient->setToken($token['body']['access_token']);
		}
Kevin von Spiczak's avatar
Kevin von Spiczak committed
	}

	/**
	 * Returns the response body, wrapped in an array if the response contains a single item.
	 * If the response is a paginated response, all items are fetched until the maxResultsPerPage is reached,
	 * or no $nextUrl is available anymore (last page reached). Since the flexform allows a max value of 100 currently,
	 * this function will never go past the first page, since the vimeo API allows a value of 100 as max value for the `per_page` parameter.
	 *
	 * @param array|null $response
	 * @return array|null
	 */
	protected function preprocessApiResponse(?array $response): ?array {
		if (!is_array($response)) {
Kevin von Spiczak's avatar
Kevin von Spiczak committed
			return NULL;
		}

		// log error & return here, since the response was not OK
		if ($response['status'] !== 200) {
			$this->logger->error('sg_vimeo API Request failed, got the following response:', $response);
			return NULL;
		}

		// @TODO: we could check $response['headers‘]['X-RateLimit-Remaining'] here for remaining quota

		if (array_key_exists('paging', $response['body'])) {
			$amountOfVideosInResponse = count($response['body']['data']);
			$this->amountOfVideosFetched += $amountOfVideosInResponse;
			$this->paginatedResponseData[] = $response['body']['data'];
			$nextUrl = $response['body']['paging']['next'];
			if ($this->amountOfVideosFetched === $this->maxResultsPerPage || $nextUrl === NULL) {
				// return flattened array here, so that we don't end up with one sub array per pagination page
				return array_merge(...$this->paginatedResponseData);
			}

			$this->fetchPaginatedResult($nextUrl);
		}

		// wrap response body in an array, so that we can treat all return values the same in the template
		return [$response['body']];
	}

	/**
	 * @param string $nextUrl
	 * @return array|null
	 */
	protected function fetchPaginatedResult(string $nextUrl): ?array {
		try {
			$response = $this->vimeoApiClient->request($nextUrl);
		} catch (VimeoRequestException $e) {
			return NULL;
		}

		return $this->preprocessApiResponse($response);
	}

	/**
	 * Returns a single video for the given $videoId
	 *
	 * @see https://developer.vimeo.com/api/reference/videos#get_video
	 * @param int $videoId
	 * @return array|null
	 */
	public function getVideo(int $videoId): ?array {
		// use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting
		$fieldsToSelect = 'uri,name,description,link,embed,pictures';
Kevin von Spiczak's avatar
Kevin von Spiczak committed
		try {
			$response = $this->vimeoApiClient->request(self::API_VIDEO . $videoId . '?fields=' . $fieldsToSelect);
Kevin von Spiczak's avatar
Kevin von Spiczak committed
		} catch (VimeoRequestException $e) {
			$response = NULL;
		}

		return $this->preprocessApiResponse($response);
	}

	/**
	 * Returns all videos for the given $channelIdentifier
	 *
	 * @see https://developer.vimeo.com/api/reference/channels#get_channel_videos
	 * @param string $channelIdentifier
	 * @return array|null
	 */
	public function getChannelVideos(string $channelIdentifier): ?array {
		// use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting
		$fieldsToSelect = 'uri,name,description,link,embed,pictures';
Kevin von Spiczak's avatar
Kevin von Spiczak committed
		try {
			$response = $this->vimeoApiClient->request(
				self::API_CHANNEL . $channelIdentifier . self::API_VIDEO . '?fields=' . $fieldsToSelect . '&per_page=' . $this->maxResultsPerPage
			);
		} catch (VimeoRequestException $e) {
			$response = NULL;
		}

		return $this->preprocessApiResponse($response);
	}

	/**
	 * Returns all videos for the given $showcaseId
	 *
	 * @see https://developer.vimeo.com/api/reference/showcases#get_showcase
	 * @param string $showcaseId
	 * @return array|null
	 */
	public function getShowcaseVideos(string $showcaseId): ?array {
		// use field filtering, to save on quota, see: https://developer.vimeo.com/guidelines/rate-limiting
		$fieldsToSelect = 'uri,name,description,link,embed,pictures';
		try {
			$response = $this->vimeoApiClient->request(
				self::API_SHOWCASE . $showcaseId . self::API_VIDEO . '?fields=' . $fieldsToSelect . '&per_page=' . $this->maxResultsPerPage
			);
		} catch (VimeoRequestException $e) {
			$response = NULL;
		}

		return $this->preprocessApiResponse($response);
	}