Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions application/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use Laminas\EventManager\Event as LaminasEvent;
use Laminas\EventManager\SharedEventManagerInterface;
use Laminas\Form\Element;
use Laminas\Json\Json;
use Laminas\View\Renderer\PhpRenderer;

/**
Expand Down Expand Up @@ -855,11 +854,6 @@ public function serializeApiOutputJsonLd(LaminasEvent $event)
$output = $args['jsonLd'];
}

if (null !== $model->getOption('pretty_print')) {
// Pretty print the JSON.
$output = Json::prettyPrint($output);
}

$jsonpCallback = (string) $model->getOption('callback');
if (!empty($jsonpCallback)) {
// Wrap the JSON in a JSONP callback. Normally this would be done
Expand Down Expand Up @@ -891,7 +885,7 @@ public function serializeApiOutputRdf(LaminasEvent $event)

$serializeRdf = function ($jsonLd) use ($format) {
$graph = new Graph;
$graph->parse(Json::encode($jsonLd), 'jsonld');
$graph->parse(json_encode($jsonLd), 'jsonld');
return $graph->serialise($format);
};

Expand All @@ -904,7 +898,7 @@ public function serializeApiOutputRdf(LaminasEvent $event)
$eventManager->trigger('api.context', null, $args);
$context = $args['context'];
}
$jsonLd = Json::decode(Json::encode($representation), true);
$jsonLd = json_decode(json_encode($representation), true);
$jsonLd['@context'] = $context;
return $jsonLd;
};
Expand Down
29 changes: 17 additions & 12 deletions application/src/View/Model/ApiJsonModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@

namespace Omeka\View\Model;

use Laminas\View\Model\JsonModel;
use Exception;
use Laminas\View\Model\ViewModel;

/**
* View model for JSON responses from the API.
*
* Carries the API response object and any thrown exception through the
* view layer to the renderer. Set as terminal to prevent layout wrapping.
*/
class ApiJsonModel extends JsonModel
class ApiJsonModel extends ViewModel
{
/**
* Terminate rendering after this model to prevent layout wrapping.
*/
protected $terminate = true;

/**
* Key that stores the API response in the view variables
*/
Expand All @@ -25,7 +34,7 @@ class ApiJsonModel extends JsonModel
* The API response object can be passed here directly as the first
* argument.
*
* @param \Omeka\Api\Response $apiResponse API response object
* @param \Omeka\Api\Response|array|null $apiResponse API response object
* @param array|\Traversable $options
*/
public function __construct($apiResponse = null, $options = null)
Expand All @@ -37,7 +46,7 @@ public function __construct($apiResponse = null, $options = null)
/**
* Get the API response object stored on the model.
*
* @return \Omeka\Api\Response
* @return \Omeka\Api\Response|array|null
*/
public function getApiResponse()
{
Expand All @@ -47,29 +56,25 @@ public function getApiResponse()
/**
* Set the API response object on this model.
*
* @param \Omeka\Api\Response $apiResponse
* @param \Omeka\Api\Response|array|null $apiResponse
*/
public function setApiResponse($apiResponse)
public function setApiResponse($apiResponse): void
{
$this->setVariable(self::API_RESPONSE_KEY, $apiResponse);
}

/**
* Get the exception stored on the model.
*
* @return \Exception|null
*/
public function getException()
public function getException(): ?Exception
{
return $this->getVariable(self::EXCEPTION_KEY);
}

/**
* Set the exception on this model.
*
* @param \Exception $exception
*/
public function setException(\Exception $exception)
public function setException(Exception $exception): void
{
$this->setVariable(self::EXCEPTION_KEY, $exception);
}
Expand Down
53 changes: 41 additions & 12 deletions application/src/View/Renderer/ApiJsonRenderer.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
<?php
namespace Omeka\View\Renderer;

use Exception;
use Omeka\Api\Exception\ValidationException;
use Omeka\Api\Response;
use Laminas\EventManager\EventManager;
use Laminas\Json\Json;
use Laminas\View\Renderer\JsonRenderer;
use Laminas\View\Renderer\RendererInterface;
use Laminas\View\Renderer\TreeRendererInterface;
use Laminas\View\Resolver\ResolverInterface;

/**
* JSON renderer for API responses.
*
* Encodes the API response payload as JSON and fires the
* api.output.serialize event, allowing modules to provide custom output
* for alternate formats such as RDF serializations.
*/
class ApiJsonRenderer extends JsonRenderer
class ApiJsonRenderer implements RendererInterface, TreeRendererInterface
{
/**
* @var bool
Expand All @@ -29,25 +35,45 @@ public function __construct(EventManager $eventManager)
$this->eventManager = $eventManager;
}

public function getEngine()
{
return $this;
}

public function setResolver(ResolverInterface $resolver)
{
}

public function canRenderTrees()
{
return true;
}

/**
* Return whether the response is JSONP
*
* The view strategy checks this to decide what Content-Type to send, and
* we need to provide a different implementation to preserve that signal
* since we're handling JSONP manually here.
*
* @return bool
*/
public function hasJsonpCallback()
public function hasJsonpCallback(): bool
{
return $this->hasJsonpCallback;
}

public function setHasJsonpCallback(bool $hasJsonpCallback)
public function setHasJsonpCallback(bool $hasJsonpCallback): void
{
$this->hasJsonpCallback = $hasJsonpCallback;
}

/**
* Render an API response as JSON.
*
* Returns null for empty (204 No Content) responses.
*
* @param \Omeka\View\Model\ApiJsonModel $model
* @return string|null
*/
public function render($model, $values = null)
{
$response = $model->getApiResponse();
Expand All @@ -58,7 +84,7 @@ public function render($model, $values = null)
} elseif ($exception instanceof ValidationException) {
$errors = $exception->getErrorStore()->getErrors();
$payload = ['errors' => $errors];
} elseif ($exception instanceof \Exception) {
} elseif ($exception instanceof Exception) {
$payload = ['errors' => ['error' => $exception->getMessage()]];
} else {
$payload = $response;
Expand All @@ -68,7 +94,12 @@ public function render($model, $values = null)
return null;
}

$output = parent::render($payload);
// HEX flags preserved from the previous Laminas\Json\Json::encode() path for output compatibility.
$flags = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP;
if ($model->getOption('pretty_print')) {
$flags |= JSON_PRETTY_PRINT;
}
$output = json_encode($payload, $flags);

// Allow modules to return custom output.
$args = $this->eventManager->prepareArgs([
Expand All @@ -83,10 +114,8 @@ public function render($model, $values = null)

/**
* Set an alternate output format.
*
* @param string $format
*/
public function setFormat($format)
public function setFormat(string $format): void
{
$this->format = $format;
}
Expand Down
64 changes: 42 additions & 22 deletions application/src/View/Strategy/ApiJsonStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@
use Omeka\Mvc\Exception as MvcException;
use Omeka\View\Model\ApiJsonModel;
use Omeka\View\Renderer\ApiJsonRenderer;
use Exception;
use Laminas\EventManager\AbstractListenerAggregate;
use Laminas\EventManager\EventManager;
use Laminas\View\Strategy\JsonStrategy;
use Laminas\EventManager\EventManagerInterface;
use Laminas\View\ViewEvent;

/**
* View strategy for returning JSON from the API.
*
* Selects the renderer for ApiJsonModel requests and injects the response
* with the rendered content, HTTP status code, Omeka-S-Version header,
* and the correct Content-Type for the output format.
*/
class ApiJsonStrategy extends JsonStrategy
class ApiJsonStrategy extends AbstractListenerAggregate
{
protected $renderer;
/**
* Output formats and their media types.
*/
Expand All @@ -32,16 +39,25 @@ class ApiJsonStrategy extends JsonStrategy

/**
* Constructor, sets the renderer object
*
* @param ApiJsonRenderer
* @param EventManager
*/
public function __construct(ApiJsonRenderer $renderer, EventManager $eventManager)
{
$this->renderer = $renderer;
$this->eventManager = $eventManager;
}

/**
* Attach listeners for renderer selection and response injection.
*/
public function attach(EventManagerInterface $events, $priority = 1)
{
$this->listeners[] = $events->attach(ViewEvent::EVENT_RENDERER, [$this, 'selectRenderer'], $priority);
$this->listeners[] = $events->attach(ViewEvent::EVENT_RESPONSE, [$this, 'injectResponse'], $priority);
}

/**
* Return our renderer if the model is an ApiJsonModel, otherwise do nothing.
*/
public function selectRenderer(ViewEvent $e)
{
$model = $e->getModel();
Expand All @@ -56,6 +72,9 @@ public function selectRenderer(ViewEvent $e)
return $this->renderer;
}

/**
* Inject the response with content, status code, and headers.
*/
public function injectResponse(ViewEvent $e)
{
// Test this again here to avoid running our extra code for non-API
Expand All @@ -66,23 +85,30 @@ public function injectResponse(ViewEvent $e)
return;
}

parent::injectResponse($e);
$result = $e->getResult();
$response = $e->getResponse();
$headers = $response->getHeaders();

if (is_string($result)) {
$response->setContent($result);
}

$model = $e->getModel();
$e->getResponse()->setStatusCode($this->getResponseStatusCode($model));
$e->getResponse()->getHeaders()->addHeaderLine('Omeka-S-Version', Module::VERSION);
$response->setStatusCode($this->getResponseStatusCode($model));
$headers->addHeaderLine('Omeka-S-Version', Module::VERSION);

// Add the correct Content-Type header for the output format.
$e->getResponse()->getHeaders()->addHeaderLine('Content-Type', $this->formats[$this->getFormat($model)]);
if ($this->renderer->hasJsonpCallback()) {
$headers->addHeaderLine('Content-Type', 'text/javascript');
} else {
$headers->addHeaderLine('Content-Type', $this->formats[$this->getFormat($model)]);
}
}

/**
* Get the HTTP status code for an API response.
*
* @param \Omeka\View\Model\ApiJsonModel $response
* @return int
*/
protected function getResponseStatusCode(ApiJsonModel $model)
protected function getResponseStatusCode(ApiJsonModel $model): int
{
$response = $model->getApiResponse();
$exception = $model->getException();
Expand All @@ -92,7 +118,7 @@ protected function getResponseStatusCode(ApiJsonModel $model)
return 204; // No Content
}
return 200; // OK
} elseif ($exception instanceof \Exception) {
} elseif ($exception instanceof Exception) {
return $this->getStatusCodeForException($exception);
} else {
return 200;
Expand All @@ -101,11 +127,8 @@ protected function getResponseStatusCode(ApiJsonModel $model)

/**
* Get a status code based on the type of an exception (or lack thereof).
*
* @param \Exception|null $exception
* @return int
*/
protected function getStatusCodeForException(?\Exception $exception = null)
protected function getStatusCodeForException(?Exception $exception = null): int
{
if ($exception instanceof MvcException\InvalidJsonException) {
return 400; // Bad Request
Expand All @@ -127,11 +150,8 @@ protected function getStatusCodeForException(?\Exception $exception = null)

/**
* Get the recognized output format.
*
* @param ApiJsonModel $model
* @return string|null
*/
protected function getFormat(ApiJsonModel $model)
protected function getFormat(ApiJsonModel $model): string
{
// Allow modules to register formats.
$args = $this->eventManager->prepareArgs(['formats' => $this->formats]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

use Omeka\Api\Exception\ValidationException;
use Omeka\View\Renderer\ApiJsonRenderer;
use Laminas\Json\Json;
use Omeka\Stdlib\ErrorStore;
use Omeka\Test\TestCase;

Expand Down Expand Up @@ -35,7 +34,7 @@ public function testRendererUsesApiResponse()
->will($this->returnValue($response));

$renderer = new ApiJsonRenderer($this->eventManager);
$this->assertEquals(Json::encode($testValue), $renderer->render($model));
$this->assertEquals(json_encode($testValue, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP), $renderer->render($model));
}

public function testRendererPassesOnNullResponse()
Expand Down Expand Up @@ -70,6 +69,6 @@ public function testRendererShowsErrors()
->will($this->returnValue($exception));

$renderer = new ApiJsonRenderer($this->eventManager);
$this->assertEquals(Json::encode(['errors' => ['foo' => ['bar']]]), $renderer->render($model));
$this->assertEquals(json_encode(['errors' => ['foo' => ['bar']]], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP), $renderer->render($model));
}
}
Loading
Loading