superdesk/contentapi-sdk-php

View on GitHub
src/Superdesk/ContentApiSdk/ContentApiSdk.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

/**
 * This file is part of the PHP SDK library for the Superdesk Content API.
 *
 * Copyright 2015 Sourcefabric z.u. and contributors.
 *
 * For the full copyright and license information, please see the
 * AUTHORS and LICENSE files distributed with this source code.
 *
 * @copyright 2015 Sourcefabric z.รบ.
 * @license http://www.superdesk.org/license
 */

namespace Superdesk\ContentApiSdk;

use Superdesk\ContentApiSdk\Api\Request;
use Superdesk\ContentApiSdk\Api\Request\RequestParameters;
use Superdesk\ContentApiSdk\Api\Response;
use Superdesk\ContentApiSdk\Api\Pagerfanta\ItemAdapter;
use Superdesk\ContentApiSdk\Api\Pagerfanta\PackageAdapter;
use Superdesk\ContentApiSdk\Api\Pagerfanta\ResourceAdapter;
use Superdesk\ContentApiSdk\Api\Pagerfanta\ResourceCollection;
use Superdesk\ContentApiSdk\Client\ApiClientInterface;
use Superdesk\ContentApiSdk\Data\Item;
use Superdesk\ContentApiSdk\Data\Package;
use Superdesk\ContentApiSdk\Exception\ClientException;
use Superdesk\ContentApiSdk\Exception\ContentApiException;
use Superdesk\ContentApiSdk\Exception\InvalidArgumentException;
use Superdesk\ContentApiSdk\Exception\InvalidDataException;
use Exception;
use stdClass;

/**
 * Superdesk ContentApi class.
 */
class ContentApiSdk
{
    /**
     * Items endpoint
     */
    const SUPERDESK_ENDPOINT_ITEMS = '/items';

    /**
     * Package endpoint
     */
    const SUPERDESK_ENDPOINT_PACKAGES = '/packages';

    /**
     * Type indication for packages
     */
    const PACKAGE_TYPE_COMPOSITE = 'composite';

    /**
     * Supported API version by this SDK version
     */
    const API_VERSION = 1;

    /**
     * Useragent string sent to the API when making requests.
     */
    const USERAGENT = 'Content API SDK v1';

    /**
     * Any (http) client that implements ClientInterface.
     *
     * @var ApiClientInterface
     */
    protected $client;

    /**
     * Protocol to reach the api instance.
     *
     * @var string|null
     */
    protected $protocol = null;

    /**
     * Hostname of the api instance.
     *
     * @var string|null
     */
    protected $host = null;

    /**
     * Port of the api instance.
     *
     * @var int|null
     */
    protected $port = null;

    /**
     * Authentication object.
     *
     * @var AuthenticationInterface
     */
    protected $authentication = null;

    /**
     * Construct method for class.
     *
     * @param ApiClientInterface $client
     * @param string|null $host
     * @param int|null $port
     * @param string|null $protocol
     */
    public function __construct(
        ApiClientInterface $client,
        $host = null,
        $port = null,
        $protocol = null
    ) {
        $this->client = $client;

        if (!is_null($host)) {
            $this->setHost($host);
        }

        if (!is_null($port)) {
            $this->setPort($port);
        }

        if (!is_null($protocol)) {
            $this->setProtocol($protocol);
        }
    }

    /**
     * Gets the value of client.
     *
     * @return ApiClientInterface
     */
    public function getClient()
    {
        return $this->client;
    }

    /**
     * Sets the value of client.
     *
     * @param ApiClientInterface $client Value to set
     *
     * @return self
     */
    public function setClient(ApiClientInterface $client)
    {
        $this->client = $client;

        return $this;
    }

    /**
     * Gets the value of apiHost.
     *
     * @return string|null
     */
    public function getHost()
    {
        return $this->host;
    }

    /**
     * Sets the value of host.
     *
     * @param string|null $host Value to set
     *
     * @return self
     */
    public function setHost($host)
    {
        $this->host = $host;

        return $this;
    }

    /**
     * Gets the value of port.
     *
     * @return int|null
     */
    public function getPort()
    {
        return $this->port;
    }

    /**
     * Sets the value of port.
     *
     * @param int|null $port Value to set
     *
     * @return self
     */
    public function setPort($port)
    {
        $this->port = $port;

        return $this;
    }

    /**
     * Gets the value of protocol.
     *
     * @return string|null
     */
    public function getProtocol()
    {
        return $this->protocol;
    }

    /**
     * Sets the value of protocol.
     *
     * @param string|null $protocol Value to set
     *
     * @return self
     */
    public function setProtocol($protocol)
    {
        $this->protocol = $protocol;

        return $this;
    }

    /**
     * Get a single item via id.
     *
     * @param string $itemId Identifier for item
     *
     * @return Item
     */
    public function getItem($itemId)
    {
        $request = $this->getNewRequest(sprintf('%s/%s', self::SUPERDESK_ENDPOINT_ITEMS, $itemId));

        try {
            $response = $this->client->makeApiCall($request);
            $item = new Item($response->getResources());

            return $item;
        } catch (ClientException $e) {
            throw new ContentApiException($e->getMessage(), $e->getCode(), $e);
        }
    }

    /**
     * Get multiple items based on a filter.
     *
     * @param RequestParameters $paramObj Filter parameters
     *
     * @return ResourceCollection
     */
    public function getItems(RequestParameters $paramObj)
    {
        return $this->getNewResourceCollection(
            new ItemAdapter(
                $this->client,
                $this->getNewRequest(self::SUPERDESK_ENDPOINT_ITEMS, $paramObj)
            )
        );
    }

    /**
     * Get package by identifier.
     *
     * @param string $packageId Package identifier
     * @param bool   $resolveAssociations Inject full associations recursively
     *                                    instead of references by uri.
     *
     * @return Package
     */
    public function getPackage($packageId, $resolveAssociations = false)
    {
        $request = $this->getNewRequest(sprintf('%s/%s', self::SUPERDESK_ENDPOINT_PACKAGES, $packageId));
        $response = $this->client->makeApiCall($request);

        $package = new Package($response->getResources());

        // This can be removed once the API fully supports retrieving package associations
        if ($resolveAssociations) {
            $associations = $this->getAssociationsFromPackage($package);
            $package = $this->injectAssociations($package, $associations);
        }

        return $package;
    }

    /**
     * Get multiple packages based on a filter.
     *
     * @param RequestParameters $paramObj Filter parameters
     * @param bool $resolveAssociations Inject full associations recursively
     *                                  instead of references by uri.
     *
     * @return ResourceCollection
     */
    public function getPackages(
        RequestParameters $paramObj,
        $resolveAssociations = false
    ) {
        return $this->getNewResourceCollection(
            new PackageAdapter(
                $this->client,
                $this->getNewRequest(self::SUPERDESK_ENDPOINT_PACKAGES, $paramObj),
                $this,
                $resolveAssociations
            )
        );
    }

    /**
     * Gets full objects for all associations for a package.
     *
     * @param Package $package A package
     *
     * @return stdClass List of associations
     */
    public function getAssociationsFromPackage(Package $package)
    {
        $associations = new stdClass();

        if (property_exists($package, 'associations')) {

            foreach ($package->associations as $associationGroupName => $associationGroupItems) {

                $groupAssociations = new stdClass();

                foreach ($associationGroupItems as $associatedName => $associatedItem) {
                    $associatedId = $this->getIdFromUri($associatedItem->uri);

                    try {
                        if ($associatedItem->type == self::PACKAGE_TYPE_COMPOSITE) {
                            $associatedObj = $this->getPackage($associatedId, true);
                        } else {
                            $associatedObj = $this->getItem($associatedId);
                            $associatedObj->type = $associatedItem->type;
                        }
                    } catch (ContentApiException $e) {
                        // If subrequests fail, dont fail main request
                    }

                    $groupAssociations->$associatedName = $associatedObj;
                }

                $associations->$associationGroupName = $groupAssociations;
            }
        }

        return $associations;
    }

    /**
     * Overwrite the associations links in a packages with the actual association
     * data.
     *
     * @param Package  $package      Package
     * @param stdClass $associations Multiple items or packages
     *
     * @return Package Package with data injected
     */
    public function injectAssociations(Package $package, stdClass $associations)
    {
        if (count($package->associations) > 0 && count($associations) > 0) {
            $package->associations = $associations;
        }

        return $package;
    }

    /**
     * Shortcut method to create new class.
     *
     * @param  string $uri Uri of the request
     * @param  RequestParameters|null $parameters Parameters for the request
     *                                            object
     *
     * @return Request
     */
    public function getNewRequest($uri, RequestParameters $parameters = null)
    {
        try {
            $request = new Request($this->host, $uri, $parameters, $this->port, $this->protocol);
        } catch (ContentApiException $e) {
            throw new ContentApiException($e->getMessage(), $e->getCode(), $e);
        }

        return $request;
    }

    /**
     * Shortcut to get a new ResourceCollection object.
     *
     * @param  ResourceAdapter $resourceAdapter
     *
     * @return ResourceCollection
     */
    private function getNewResourceCollection(ResourceAdapter $resourceAdapter)
    {
        return new ResourceCollection($resourceAdapter);
    }

    /**
     * Tries to find a valid id in an uri, both item as package uris. The id
     * is returned urldecoded.
     *
     * @param string $uri Item or package uri
     *
     * @return string Urldecoded id
     */
    public static function getIdFromUri($uri)
    {
        /*
         * Works for package and item uris
         *   http://publicapi:5050/packages/tag%3Ademodata.org%2C0012%3Aninjs_XYZ123
         *   http://publicapi:5050/items/tag%3Ademodata.org%2C0003%3Aninjs_XYZ123
         */

        $uriPath = parse_url($uri, PHP_URL_PATH);
        $objectId = str_replace(self::getAvailableEndpoints(), '', $uriPath);
        // Remove possible slashes and spaces, since we're working with urls
        $objectId = trim($objectId, '/ ');
        $objectId = urldecode($objectId);

        return $objectId;
    }

    /**
     * Returns a list of all supported endpoints for the Superdesk Content API.
     *
     * @return string[]
     */
    public static function getAvailableEndpoints()
    {
        return array(
            self::SUPERDESK_ENDPOINT_ITEMS,
            self::SUPERDESK_ENDPOINT_PACKAGES,
        );
    }

    /**
     * Converts json string into StdClass object. Throws an InvalidDataException
     * when string could not be converted to object.
     *
     * @param string $jsonString JSON string
     *
     * @return object
     * @throws Exception|InvalidDataException
     */
    public static function getValidJsonObj($jsonString)
    {
        $jsonObj = json_decode($jsonString);
        if (is_null($jsonObj) || json_last_error() !== JSON_ERROR_NONE) {
            throw new InvalidDataException('Response body is not (valid) json.', json_last_error());
        }

        return $jsonObj;
    }

    /**
     * Returns version of api for creating verioned url.
     *
     * @return string
     */
    public static function getVersionURL()
    {
        return sprintf('v%d', self::API_VERSION);
    }
}