Seditio Source
Root |
./othercms/croogo-4.0.7/vendor/friendsofcake/crud-json-api/src/View/JsonApiView.php
<?php
namespace CrudJsonApi\View;

use
Cake\Core\App;
use
Cake\Core\Configure;
use
Cake\Datasource\RepositoryInterface;
use
Cake\Event\EventManager;
use
Cake\Http\Response;
use
Cake\Http\ServerRequest;
use
Cake\ORM\Entity;
use
Cake\Utility\Hash;
use
Cake\Utility\Inflector;
use
Cake\View\View;
use
Crud\Error\Exception\CrudException;
use
Neomerx\JsonApi\Contracts\Schema\LinkInterface;
use
Neomerx\JsonApi\Encoder\Encoder;
use
Neomerx\JsonApi\Encoder\EncoderOptions;
use
Neomerx\JsonApi\Encoder\Parameters\EncodingParameters;
use
Neomerx\JsonApi\Schema\Link;

class
JsonApiView extends View
{
   
/**
     * Constructor
     *
     * @param \Cake\Http\ServerRequest $request ServerRequest
     * @param \Cake\Http\Response $response Response
     * @param \Cake\Event\EventManager $eventManager EventManager
     * @param array $viewOptions An array of view options
     */
   
public function __construct(
       
ServerRequest $request = null,
       
Response $response = null,
       
EventManager $eventManager = null,
        array
$viewOptions = []
    ) {
       
parent::__construct($request, $response, $eventManager, $viewOptions);

        if (
$response && $response instanceof Response) {
           
$this->response = $response->withType('jsonapi');
        }
    }

   
/**
     * Returns an array of special viewVars (names starting with an underscore).
     *
     * We need to dynamically generate this array to prevent special vars
     * from the app or other plugins being passed to NeoMerx for processing
     * as data (and thus effectively breaking this view).
     *
     * @return array
     */
   
protected function _getSpecialVars(): array
    {
       
$result = [];

       
$viewVarKeys = $this->getVars();
        foreach (
$viewVarKeys as $viewVarKey) {
            if (
$viewVarKey[0] === '_') {
               
$result[] = $viewVarKey;
            }
        }

        return
$result;
    }

   
/**
     * Renders one of the three supported JSON API responses:
     *
     * - with body containing an entity based resource (data)
     * - with empty body
     * - with body containing only the meta node
     *
     * @param string|null $view Name of view file to use
     * @param string|null $layout Layout to use.
     * @return string
     */
   
public function render($view = null, $layout = null)
    {
        if (
$this->get('_repositories')) {
           
$json = $this->_encodeWithSchemas();
        } else {
           
$json = $this->_encodeWithoutSchemas();
        }

       
// Add query logs node if ApiQueryLogListener is loaded
       
if (Configure::read('debug') && $this->get('queryLog')) {
           
$json = json_decode($json, true);
           
$json['query'] = $this->get('queryLog');
           
$json = json_encode($json, $this->_jsonOptions());
        }

        return
$json;
    }

   
/**
     * Generates a JSON API string without resource(s).
     *
     * @return null|string
     */
   
protected function _encodeWithoutSchemas(): ?string
   
{
        if (empty(
$this->get('_meta'))) {
            return
null;
        }

       
$encoder = Encoder::instance()
            ->
withEncodeOptions($this->_jsonOptions());

       
// Add optional top-level `link` node to the response if enabled by
        // user using listener config option.
       
if ($this->get('_links')) {
           
$encoder->withLinks($this->get('_links'));
        }

        return
$encoder->encodeMeta($this->get('_meta'));
    }

   
/**
     * Generates a JSON API string with resource(s).
     *
     * @return string
     */
   
protected function _encodeWithSchemas(): string
   
{
        if (
$this->get('_inflect') === 'dasherize') {
           
$this->_dasherizeIncludesViewVar();
        }

       
// All "Schema is not registered for a resource at path 'xyz'" errors
        // originate from the line below and are caused by the mentioned Cake Table
        // object not being present in the  `_repositories` viewVar array.
       
$schemas = $this->_entitiesToNeoMerxSchema($this->get('_repositories'));

       
// Please note that a third NeoMerx EncoderOptions argument `depth`
        // exists but has not been implemented in this plugin.
       
$encoder = Encoder::instance($schemas)
            ->
withEncodeOptions($this->_jsonOptions());

       
$serialize = $this->get('_serialize');

        if (
$this->get('_serialize') !== false) {
           
$serialize = $this->_getDataToSerializeFromViewVars($this->get('_serialize'));
        }

       
// By default the listener will automatically add all associated data
        // (as produced by containable and thus present in the entity) to the
        // '_include' viewVar which is used to produce the top-level `included`
        // node UNLESS user specified an array with associations using the
        // listener config option `include`.
        //
        // Please be aware that `include` will take precedence over the
        //`getIncludePaths()` method that MIGHT exist in custom NeoMerx schemas.
        //
        // Lastly, listener config option `fieldSets` may be used to limit
        // the fields shown in the result.
       
$include = $this->get('_include');
       
$fieldSets = $this->get('_fieldSets');

       
$encoder
           
->withIncludedPaths($include)
            ->
withFieldSets($fieldSets);

       
// Add optional top-level `version` node to the response if enabled
        // by user using listener config option.
       
if ($this->get('_withJsonApiVersion')) {
           
$encoder->withJsonApiVersion('1.1');
            if (!
is_bool($this->get('_withJsonApiVersion'))) {
               
$encoder->withJsonApiMeta($this->get('_withJsonApiVersion'));
            }
        }

       
// Add top-level `links` node with pagination information (requires
        // ApiPaginationListener which will have set/filled viewVar).
       
if ($this->get('_pagination')) {
           
$pagination = $this->get('_pagination');

           
$links = $this->get('_links', []);
           
$paginationLinks = $this->_getPaginationLinks($pagination);
           
$this->set('_links', $links + $paginationLinks);

           
// Additional pagination information has to be in top-level node `meta`
           
$meta = $this->get('_meta');
           
$meta['record_count'] = $pagination['record_count'];
           
$meta['page_count'] = $pagination['page_count'];
           
$meta['page_limit'] = $pagination['page_limit'];
           
$this->set('_meta', $meta);
        }

       
// Add optional top-level `link` node to the response if enabled by
        // user using listener config option.
       
if ($this->get('_links')) {
           
$links = $this->get('_links');
           
$encoder->withLinks(array_map(function ($link) {
                if (
$link instanceof Link) {
                    return
$link;
                }

                return new
Link(false, $link, false);
            },
$links));
        }

       
// Add optional top-level `meta` node to the response if enabled by
        // user using listener config option.
       
if ($this->get('_meta')) {
            if (empty(
$serialize)) {
                return
$encoder->encodeMeta($this->get('_meta'));
            }

             
$encoder->withMeta($this->get('_meta'));
        }

       
// JSON API as generated by NeoMerx. When things look off,  start debugging here
       
return $encoder->encodeData($serialize);
    }

   
/**
     * Maps each entity to the first schema match in this order:
     * 1. custom entity schema
     * 2. custom dynamic schema
     * 3. Crud's dynamic schema
     *
     * @param \Cake\ORM\Table[] $repositories List holding repositories used to map entities to schema classes
     * @throws \Crud\Error\Exception\CrudException
     * @return array A list with Entity class names as key holding NeoMerx Closure object
     */
   
protected function _entitiesToNeoMerxSchema(array $repositories): array
    {
       
$schemas = [];
        foreach (
$repositories as $repositoryName => $repository) {
           
$entityClass = $repository->getEntityClass();

            if (isset(
$schemas[$entityClass])) {
                continue;
            }

            if (
$entityClass === Entity::class) {
                throw new
CrudException(sprintf('Entity classes must not be the generic "%s" class for repository "%s"', $entityClass, $repositoryName));
            }

           
// Turn full class name back into plugin split format
            // Not including /Entity in the type makes sure its compatible with other types
           
$entityName = App::shortName($entityClass, 'Model');

           
// Take plugin name and entity name off
           
[$pluginName, $entityName] = pluginSplit($entityName, true);

           
// Find the first namespace separator to take everything after the entity type.
           
$firstNamespaceSeparator = strpos($entityName, '/');
            if (
$firstNamespaceSeparator === false) {
                throw new
CrudException('Invalid entity name specified');
            }
           
$entityName = substr($entityName, $firstNamespaceSeparator + 1);

           
$entityName = $pluginName . $entityName;

           
// If user created a custom entity schema... use it
           
$schemaClass = App::className($entityName, 'Schema\JsonApi', 'Schema');

           
// If user created a custom dynamic schema... use it
           
if (!$schemaClass) {
               
$schemaClass = App::className('DynamicEntity', 'Schema\JsonApi', 'Schema');
            }

           
// Otherwise use the dynamic schema provided by Crud
           
if (!$schemaClass) {
               
$schemaClass = App::className('CrudJsonApi.DynamicEntity', 'Schema\JsonApi', 'Schema');
            }

           
// Uses NeoMerx createSchemaFromClosure()` to generate Closure
            // object with schema information.
           
$schema = function ($factory) use ($schemaClass, $repository) {
                return new
$schemaClass($factory, $this, $repository);
            };

           
// Add generated schema to the collection before processing next
           
$schemas[$repository->getEntityClass()] = $schema;
        }

        return
$schemas;
    }

   
/**
     * Returns an array with NeoMerx Link objects to be used for pagination.
     *
     * @param array $pagination ApiPaginationListener pagination response
     * @return array
     */
   
protected function _getPaginationLinks($pagination): array
    {
       
$links = [];

        if (isset(
$pagination['self'])) {
           
$links[LinkInterface::SELF] = new Link(false, $pagination['self'], false);
        }

        if (isset(
$pagination['first'])) {
           
$links[LinkInterface::FIRST] = new Link(false, $pagination['first'], false);
        }

        if (isset(
$pagination['last'])) {
           
$links[LinkInterface::LAST] = new Link(false, $pagination['last'], false);
        }

        if (isset(
$pagination['prev'])) {
           
$links[LinkInterface::PREV] = new Link(false, $pagination['prev'], false);
        }

        if (isset(
$pagination['next'])) {
           
$links[LinkInterface::NEXT] = new Link(false, $pagination['next'], false);
        }

        return
$links;
    }

   
/**
     * Returns data to be serialized.
     *
     * @param array|string|bool|object $serialize The name(s) of the view variable(s) that
     *   need(s) to be serialized. If true all available view variables will be used.
     * @return mixed The data to serialize.
     */
   
protected function _getDataToSerializeFromViewVars($serialize = true)
    {
        if (
is_object($serialize)) {
            throw new
CrudException('Assigning an object to JsonApiListener "_serialize" is deprecated, assign the object to its own variable and assign "_serialize" = true instead.');
        }

        if (
$serialize === true) {
           
$viewVars = array_diff(
               
$this->getVars(),
               
$this->_getSpecialVars()
            );

            if (empty(
$viewVars)) {
                return
null;
            }

           
$serialize = current($viewVars);
        }

        if (
is_array($serialize)) {
           
$serialize = current($serialize);
        }

        return
$this->get($serialize);
    }

   
/**
     * Returns an integer flag holding any combination of php predefined json
     * option constants as found at http://php.net/manual/en/json.constants.php.
     *
     * @return int Flag holding json options
     */
   
protected function _jsonOptions()
    {
       
$jsonOptions = 0;

        if (!empty(
$this->get('_jsonOptions'))) {
            foreach (
$this->get('_jsonOptions') as $jsonOption) {
               
$jsonOptions |= $jsonOption;
            }

            if (isset(
$jsonOption)) {
               
$jsonOptions |= $jsonOption;
            }
        }

        if (
Configure::read('debug') === false) {
            return
$jsonOptions;
        }

        if (
$this->get('_debugPrettyPrint')) {
           
$jsonOptions |= JSON_PRETTY_PRINT;
        }

        return
$jsonOptions;
    }

   
/**
     * Dasherizes all values in the '_includes` viewVar array.
     *
     * @return void
     */
   
protected function _dasherizeIncludesViewVar()
    {
        foreach (
$this->get('_include') as $key => $value) {
           
$this->get('_include')[$key] = Inflector::dasherize($value);
        }
    }
}