<?php
namespace DreamFactory\Core\Aws\Resources;

use DreamFactory\Core\Aws\Services\Sns;
use DreamFactory\Core\Exceptions\BadRequestException;
use DreamFactory\Core\Exceptions\InternalServerErrorException;
use DreamFactory\Core\Exceptions\NotFoundException;
use DreamFactory\Core\Contracts\ServiceResponseInterface;

/**
 * Class SnsSubscription
 *
 * @package DreamFactory\Core\Aws\Resources
 */
class SnsSubscription extends BaseSnsResource
{
    //*************************************************************************
    //	Constants
    //*************************************************************************

    /**
     * Resource tag for dealing with subscription
     */
    const RESOURCE_NAME = 'subscription';

    //*************************************************************************
    //	Members
    //*************************************************************************

    //*************************************************************************
    //	Methods
    //*************************************************************************

    /**
     * {@inheritdoc}
     */
    protected static function getResourceIdentifier()
    {
        return 'SubscriptionArn';
    }

    /**
     * {@inheritdoc}
     */
    public function getResources()
    {
//        $refresh = $this->request->queryBool('refresh');

        $out = [];
        $token = null;
        try {
            do {
                if (empty($this->parentResource)) {
                    $result = $this->service->getConnection()->listSubscriptions(
                        [
                            'NextToken' => $token
                        ]
                    );
                } else {
                    $result = $this->service->getConnection()->listSubscriptionsByTopic(
                        [
                            'TopicArn'  => $this->parentResource,
                            'NextToken' => $token
                        ]
                    );
                }
                $topics = $result['Subscriptions'];
                $token = $result['NextToken'];

                if (!empty($topics)) {
                    $out = array_merge($out, $topics);
                }
            } while ($token);
        } catch (\Exception $ex) {
            if (null !== $newEx = Sns::translateException($ex)) {
                throw $newEx;
            }

            throw new InternalServerErrorException("Failed to retrieve subscriptions.\n{$ex->getMessage()}",
                $ex->getCode());
        }

        return $out;
    }

    /**
     * @param mixed $fields Use boolean, comma-delimited string, or array of properties
     *
     * @return ServiceResponseInterface
     */
    public function listResources($fields = null)
    {
        $resources = [];
        $result = $this->getResources();
        foreach ($result as $sub) {
            switch ($fields) {
                case false:
                case Sns::FORMAT_SIMPLE:
                    $resources[] = $this->service->stripArnPrefix(array_get($sub, 'SubscriptionArn'));
                    break;
                case Sns::FORMAT_ARN:
                    $resources[] = array_get($sub, 'SubscriptionArn');
                    break;
                case true:
                case Sns::FORMAT_FULL:
                default:
                    $sub['Subscription'] = $this->service->stripArnPrefix(array_get($sub, 'SubscriptionArn'));
                    $resources[] = $sub;
                    break;
            }
        }

        return $resources;
    }

    protected function handleGET()
    {
        if (empty($this->resource)) {
            return parent::handleGET();
        } else {
            return $this->retrieveSubscription($this->resource);
        }
    }

    protected function handlePOST()
    {
        $payload = $this->request->getPayloadData();
        if (empty($payload)) {
            throw new BadRequestException('No data in subscription post request.');
        }

        if (empty($this->resource)) {
            if ($this->parentResource) {
                $payload['Topic'] = $this->parentResource;
            }

            return $this->createSubscription($payload);
        } else {
            return false;
        }
    }

    protected function handlePUT()
    {
        $payload = $this->request->getPayloadData();
        if (empty($payload)) {
            throw new BadRequestException('No data in subscription update request.');
        }

        if (!empty($this->resource)) {
            $payload['Subscription'] = $this->resource;
        }

        return $this->updateSubscription($payload);
    }

    protected function handlePATCH()
    {
        return $this->handlePUT();
    }

    protected function handleDELETE()
    {
        $payload = $this->request->getPayloadData();
        if (empty($this->resource)) {
            if (empty($payload)) {
                throw new BadRequestException('No data in subscription delete request.');
            }

            $this->deleteSubscription($payload);
        } else {
            $this->deleteSubscription($this->resource);
        }

        return ['success' => true];
    }

    /**
     * @param $resource
     *
     * @return array
     * @throws InternalServerErrorException
     * @throws NotFoundException
     */
    public function retrieveSubscription($resource)
    {
        $request = ['SubscriptionArn' => $this->service->addArnPrefix($resource)];

        try {
            if (null !== $result = $this->service->getConnection()->getSubscriptionAttributes($request)) {
                $out = array_merge($request, array_get($result->toArray(), 'Attributes', []));
                $out['Subscription'] = $this->service->stripArnPrefix($resource);

                return $out;
            }
        } catch (\Exception $ex) {
            if (null !== $newEx = Sns::translateException($ex)) {
                throw $newEx;
            }

            throw new InternalServerErrorException("Failed to retrieve properties for '$resource'.\n{$ex->getMessage()}",
                $ex->getCode());
        }

        return [];
    }

    public function createSubscription($request)
    {
        if (is_array($request)) {
            $name = array_get($request, 'Topic', array_get($request, 'TopicArn'));
            if (empty($name)) {
                throw new BadRequestException("Create Subscription request contains no 'Topic' field.");
            }

            $request['TopicArn'] = $this->service->addArnPrefix($name);
        } else {
            throw new BadRequestException("Create Subscription request contains no fields.");
        }

        try {
            if (null !== $result = $this->service->getConnection()->subscribe($request)) {
                $arn = array_get($result->toArray(), 'SubscriptionArn', '');

                return ['Subscription' => $this->service->stripArnPrefix($arn), 'SubscriptionArn' => $arn];
            }
        } catch (\Exception $ex) {
            if (null !== $newEx = Sns::translateException($ex)) {
                throw $newEx;
            }

            throw new InternalServerErrorException("Failed to create subscription to  '{$request['TopicArn']}'.\n{$ex->getMessage()}",
                $ex->getCode());
        }

        return [];
    }

    public function updateSubscription($request)
    {
        if (is_array($request)) {
            $name = array_get($request, 'Subscription', array_get($request, 'SubscriptionArn'));
            if (empty($name)) {
                throw new BadRequestException("Update subscription request contains no 'Subscription' field.");
            }

            $request['SubscriptionArn'] = $this->service->addArnPrefix($name);
        } else {
            throw new BadRequestException("Update subscription request contains no fields.");
        }

        try {
            if (null !== $result = $this->service->getConnection()->setSubscriptionAttributes($request)) {
                return ['success' => true];
            }
        } catch (\Exception $ex) {
            if (null !== $newEx = Sns::translateException($ex)) {
                throw $newEx;
            }

            throw new InternalServerErrorException("Failed to update subscription '{$request['SubscriptionArn']}'.\n{$ex->getMessage()}",
                $ex->getCode());
        }

        return [];
    }

    public function deleteSubscription($request)
    {
        $data = [];
        if (is_array($request)) {
            $name = array_get($request, 'Subscription', array_get($request, 'SubscriptionArn'));
            if (empty($name)) {
                throw new BadRequestException("Delete subscription request contains no 'Subscription' field.");
            }

            $data['SubscriptionArn'] = $this->service->addArnPrefix($name);
        } else {
            $data['SubscriptionArn'] = $this->service->addArnPrefix($request);
        }

        try {
            if (null !== $result = $this->service->getConnection()->unsubscribe($data)) {
                return ['success' => true];
            }
        } catch (\Exception $ex) {
            if (null !== $newEx = Sns::translateException($ex)) {
                throw $newEx;
            }

            throw new InternalServerErrorException("Failed to delete subscription '{$data['SubscriptionArn']}'.\n{$ex->getMessage()}",
                $ex->getCode());
        }

        return [];
    }
}


<?php

namespace DreamFactory\Core\Aws\Components;

use Aws\S3\Exception\S3Exception;
use DreamFactory\Core\Enums\HttpStatusCodes;
use DreamFactory\Core\Exceptions\InternalServerErrorException;
use DreamFactory\Core\Exceptions\NotFoundException;
use DreamFactory\Core\Utility\Session;
use DreamFactory\Core\File\Components\RemoteFileSystem;
use DreamFactory\Core\Exceptions\DfException;
use DreamFactory\Core\Exceptions\BadRequestException;
use Aws\S3\S3Client;

/**
 * Class S3FileSystem
 *
 * @package DreamFactory\Core\Aws\Components
 */
class S3FileSystem extends RemoteFileSystem
{
    //*************************************************************************
    //	Members
    //*************************************************************************

    /**
     * @var S3Client
     */
    protected $blobConn = null;

    //*************************************************************************
    //	Methods
    //*************************************************************************

    /**
     * @throws DfException
     */
    protected function checkConnection()
    {
        if (empty($this->blobConn)) {
            throw new DfException('No valid connection to blob file storage.');
        }
    }

    /**
     * @param array $config
     *
     * @throws InternalServerErrorException
     */
    public function __construct($config)
    {
        //  Replace any private lookups
        Session::replaceLookups($config, true);
        // statically assign the our supported version
        $config['version'] = '2006-03-01';
        if (isset($config['key'])) {
            $config['credentials']['key'] = $config['key'];
        }
        if (isset($config['secret'])) {
            $config['credentials']['secret'] = $config['secret'];
        }

        try {
            $this->blobConn = new S3Client($config);
        } catch (\Exception $ex) {
            throw new InternalServerErrorException("AWS DynamoDb Service Exception:\n{$ex->getMessage()}",
                $ex->getCode());
        }

        $this->container = array_get($config, 'container');

        if (!$this->containerExists($this->container)) {
            $this->createContainer(['name' => $this->container]);
        }
    }

    /**
     * Object destructor
     */
    public function __destruct()
    {
        unset($this->blobConn);
    }

    /**
     * List all containers, just names if noted
     *
     * @param bool $include_properties If true, additional properties are retrieved
     *
     * @throws \Exception
     * @return array
     */
    public function listContainers($include_properties = false)
    {
        $this->checkConnection();

        if (!empty($this->container)) {
            return $this->listResource($include_properties);
        }

        /** @noinspection PhpUndefinedMethodInspection */
        $buckets = $this->blobConn->listBuckets()->get('Buckets');

        $out = [];
        foreach ($buckets as $bucket) {
            $name = rtrim($bucket['Name']);
            $out[] = ['name' => $name, 'path' => $name];
        }

        return $out;
    }

    /**
     * Gets all properties of a particular container, if options are false,
     * otherwise include content from the container
     *
     * @param  string $container Container name
     * @param  bool   $include_files
     * @param  bool   $include_folders
     * @param  bool   $full_tree
     *
     * @throws \Exception
     * @return array
     */
    public function getContainer($container, $include_files = true, $include_folders = true, $full_tree = false)
    {
        $result = [];
        if ($this->containerExists($container)) {
            $result = $this->getFolder($container, '', $include_files, $include_folders, $full_tree);
        }

        return $result;
    }

    public function getContainerProperties($container)
    {
        $result = [];

        if ($this->containerExists($container)) {
        }

        return $result;
    }

    /**
     * Check if a container exists
     *
     * @param  string $container Container name
     *
     * @return boolean
     */
    public function containerExists($container = '')
    {
        $this->checkConnection();

        return $this->blobConn->doesBucketExist($container);
    }

    /**
     * @param array $properties
     * @param array $metadata
     *
     * @throws BadRequestException
     * @throws DfException
     * @internal param array $properties
     * @return array
     */
    public function createContainer($properties, $metadata = [])
    {
        $name = array_get($properties, 'name', array_get($properties, 'path'));
        if (empty($name)) {
            throw new BadRequestException('No name found for container in create request.');
        }
        try {
            $this->checkConnection();
            $this->blobConn->createBucket(
                [
                    'Bucket' => $name
                ]
            );

            return ['name' => $name, 'path' => $name];
        } catch (\Exception $ex) {
            throw new DfException("Failed to create container '$name': " . $ex->getMessage());
        }
    }

    /**
     * Update a container with some properties
     *
     * @param string $container
     * @param array  $properties
     *
     * @throws DfException
     * @return void
     */
    public function updateContainerProperties($container, $properties = [])
    {
        $this->checkConnection();
        try {
            if ($this->blobConn->doesBucketExist($container)) {
                throw new \Exception("No container named '$container'");
            }
        } catch (\Exception $ex) {
            throw new DfException("Failed to update container '$container': " . $ex->getMessage());
        }
    }

    /**
     * @param string $container
     * @param bool   $force
     *
     * @throws DfException
     */
    public function deleteContainer($container = '', $force = false)
    {
        try {
            $this->checkConnection();
            $this->blobConn->deleteBucket(
                [
                    'Bucket' => $container
                ]
            );
        } catch (\Exception $ex) {
            throw new DfException('Failed to delete container "' . $container . '": ' . $ex->getMessage());
        }
    }

    /**
     * Check if a blob exists
     *
     * @param  string $container Container name
     * @param  string $name      Blob name
     *
     * @return boolean
     */
    public function blobExists($container = '', $name = '')
    {
        try {
            $this->checkConnection();

            return $this->blobConn->doesObjectExist($container, $name);
        } catch (\Exception $ex) {
            return false;
        }
    }

    /**
     * @param string $container
     * @param string $name
     * @param string $blob
     * @param string $type
     *
     * @throws DfException
     */
    public function putBlobData($container = '', $name = '', $blob = '', $type = '')
    {
        try {
            $this->checkConnection();

            $options = [
                'Bucket' => $container,
                'Key'    => $name,
                'Body'   => $blob
            ];

            if (!empty($type)) {
                $options['ContentType'] = $type;
            }

            $this->blobConn->putObject($options);
        } catch (\Exception $ex) {
            throw new DfException('Failed to create blob "' . $name . '": ' . $ex->getMessage());
        }
    }

    /**
     * @param string $container
     * @param string $name
     * @param string $localFileName
     * @param string $type
     *
     * @throws DfException
     */
    public function putBlobFromFile($container = '', $name = '', $localFileName = '', $type = '')
    {
        try {
            $this->checkConnection();

            $options = [
                'Bucket'     => $container,
                'Key'        => $name,
                'SourceFile' => $localFileName
            ];

            if (!empty($type)) {
                $options['ContentType'] = $type;
            }

            $this->blobConn->putObject($options);
        } catch (\Exception $ex) {
            throw new DfException('Failed to create blob "' . $name . '": ' . $ex->getMessage());
        }
    }

    /**
     * @param string $container
     * @param string $name
     * @param string $src_container
     * @param string $src_name
     * @param array  $properties
     *
     * @throws DfException
     *
     * @param array  $properties
     */
    public function copyBlob($container = '', $name = '', $src_container = '', $src_name = '', $properties = [])
    {
        try {
            $this->checkConnection();

            $options = [
                'Bucket'     => $container,
                'Key'        => $name,
                'CopySource' => urlencode($src_container . '/' . $src_name)
            ];

            $this->blobConn->copyObject($options);
        } catch (\Exception $ex) {
            throw new DfException('Failed to copy blob "' . $name . '": ' . $ex->getMessage());
        }
    }

    /**
     * Get blob
     *
     * @param  string $container     Container name
     * @param  string $name          Blob name
     * @param  string $localFileName Local file name to store downloaded blob
     *
     * @throws DfException
     */
    public function getBlobAsFile($container = '', $name = '', $localFileName = '')
    {
        try {
            $this->checkConnection();

            $options = [
                'Bucket' => $container,
                'Key'    => $name,
                'SaveAs' => $localFileName
            ];

            $this->blobConn->getObject($options);
        } catch (\Exception $ex) {
            throw new DfException('Failed to retrieve blob "' . $name . '": ' . $ex->getMessage());
        }
    }

    /**
     * @param string $container
     * @param string $name
     *
     * @return string
     * @throws DfException
     */
    public function getBlobData($container = '', $name = '')
    {
        try {
            $this->checkConnection();

            $options = [
                'Bucket' => $container,
                'Key'    => $name
            ];

            $result = $this->blobConn->getObject($options);

            return $result['Body'];
        } catch (\Exception $ex) {
            throw new DfException('Failed to retrieve blob "' . $name . '": ' . $ex->getMessage());
        }
    }

    /**
     * @param string $container
     * @param string $name
     * @param bool   $noCheck
     *
     * @throws DfException
     */
    public function deleteBlob($container = '', $name = '', $noCheck = false)
    {
        try {
            $this->checkConnection();

            if (!$noCheck) {
                $this->blobConn->getObject(
                    [
                        'Bucket' => $container,
                        'Key'    => $name
                    ]
                );
            }

            $this->blobConn->deleteObject(
                [
                    'Bucket' => $container,
                    'Key'    => $name
                ]
            );
        } catch (\Exception $ex) {
            if ($ex instanceof S3Exception) {
                if ($ex->getStatusCode() === HttpStatusCodes::HTTP_NOT_FOUND) {
                    throw new NotFoundException("File '$name' was not found.'");
                }
            }
            throw new DfException('Failed to delete blob "' . $name . '": ' . $ex->getMessage());
        }
    }

    /**
     * List blobs
     *
     * @param  string $container Container name
     * @param  string $prefix    Optional. Filters the results to return only blobs whose name begins with the
     *                           specified prefix.
     * @param  string $delimiter Optional. Delimiter, i.e. '/', for specifying folder hierarchy
     *
     * @return array
     * @throws DfException
     */
    public function listBlobs($container = '', $prefix = '', $delimiter = '')
    {
        $options = [
            'Bucket'  => $container,
            'Prefix'  => $prefix,
            'MaxKeys' => 1000
        ];

        if (!empty($delimiter)) {
            $options['Delimiter'] = $delimiter;
        }

        //	No max-keys specified. Get everything.
        $keys = [];

        do {
            /** @var \Aws\Result $list */
            $list = $this->blobConn->listObjects($options);

            $objects = $list->get('Contents');

            if (!empty($objects)) {
                foreach ($objects as $object) {
                    if (0 != strcasecmp($prefix, $object['Key'])) {
                        $keys[$object['Key']] = true;
                    }
                }
            }

            $objects = $list->get('CommonPrefixes');

            if (!empty($objects)) {
                foreach ($objects as $object) {
                    if (0 != strcasecmp($prefix, $object['Prefix'])) {
                        if (!isset($keys[$object['Prefix']])) {
                            $keys[$object['Prefix']] = false;
                        }
                    }
                }
            }

            $options['Marker'] = $list->get('NextMarker');
        } while ($list->get('IsTruncated'));

        $options = [
            'Bucket' => $container,
            'Key'    => ''
        ];

        $out = [];
        foreach ($keys as $key => $isObject) {
            $options['Key'] = $key;

            if ($isObject) {
                /** @var \Aws\Result $meta */
                $meta = $this->blobConn->headObject($options);

                $out[] = [
                    'name'           => $key,
                    'content_type'   => $meta->get('ContentType'),
                    'content_length' => intval($meta->get('ContentLength')),
                    'last_modified'  => $meta->get('LastModified')
                ];
            } else {
                $out[] = [
                    'name'           => $key,
                    'content_type'   => null,
                    'content_length' => 0,
                    'last_modified'  => null
                ];
            }
        }

        return $out;
    }

    /**
     * List blob
     *
     * @param  string $container Container name
     * @param  string $name      Blob name
     *
     * @return array instance
     * @throws DfException
     */
    public function getBlobProperties($container, $name)
    {
        try {
            $this->checkConnection();

            /** @var \Aws\Result $result */
            $result = $this->blobConn->headObject(
                [
                    'Bucket' => $container,
                    'Key'    => $name
                ]
            );

            $out = [
                'name'           => $name,
                'content_type'   => $result->get('ContentType'),
                'content_length' => intval($result->get('ContentLength')),
                'last_modified'  => $result->get('LastModified')
            ];

            return $out;
        } catch (\Exception $ex) {
            throw new DfException('Failed to list blob metadata: ' . $ex->getMessage());
        }
    }

    /**
     * @param string $container
     * @param string $name
     * @param array  $params
     *
     * @throws DfException
     */
    public function streamBlob($container, $name, $params = [])
    {
        try {
            $this->checkConnection();

            /** @var \Aws\Result $result */
            $result = $this->blobConn->getObject(
                [
                    'Bucket' => $container,
                    'Key'    => $name
                ]
            );

            header('Last-Modified: ' . $result->get('LastModified'));
            header('Content-Type: ' . $result->get('ContentType'));
            header('Content-Length:' . intval($result->get('ContentLength')));

            $disposition =
                (isset($params['disposition']) && !empty($params['disposition'])) ? $params['disposition'] : 'inline';

            header('Content-Disposition: ' . $disposition . '; filename="' . $name . '";');
            //echo $result->get('Body');
            $result['Body']->rewind();
            $chunk = \Config::get('df.file_chunk_size');
            while ($data = $result['Body']->read($chunk)) {
                echo $data;
            }
        } catch (\Exception $ex) {
            if ($ex instanceof S3Exception) {
                $code = $ex->getStatusCode();
                $status_header = "HTTP/1.1 $code";
                header($status_header);
                header('Content-Type: text/html');
                echo 'Failed to stream/download file. File ' . $name . ' was not found. ' . $ex->getMessage();
            } else {
                throw new DfException('Failed to stream blob: ' . $ex->getMessage());
            }
        }
    }
}


<?php

namespace Controlabs\Helper;

use Exception;
use DateTimeImmutable;
use Firebase\JWT\JWT as FirebaseJWT;

class JWT implements JWTInterface
{
    private const ALGO = 'RS256';

    private $privateKey;
    private $publicKey;

    public function __construct(string $privateKey = null, string $publicKey = null)
    {
        $this->privateKey = $privateKey;
        $this->publicKey = $publicKey;
    }

    /**
     * Generate JWT Token using private key
     *
     * @param string $iss Issuer
     * @param string $aud Audition
     * @param string $sub Subject
     * @param string|null $expires Expiration constraint (example: + 10 days)
     * @param array $claims Public claims (example: ['user_id' => 10])
     * @return string Returns JWT token as string
     * @throws Exception
     */
    public function encode(string $iss, string $aud, string $sub, string $expires = null, array $claims = []): string
    {
        $now = new DateTimeImmutable();

        $claims = array_merge($claims, [
            'iss' => $iss,
            'aud' => $aud,
            'sub' => $sub,
            'iat' => $now->getTimestamp(),
            'exp' => $this->expires($now, $expires)
        ]);

        return FirebaseJWT::encode($claims, $this->privateKey, self::ALGO);
    }

    /**
     * Decode JWT Token using public key
     *
     * @param string $token JWT Token as string
     * @param bool $ignoreExceptions If true will ignore any kind of exception and return null
     * @return array|null Returns payload as array or null if exceptions are ignored
     * @throws Exception
     */
    public function decode(string $token, bool $ignoreExceptions = false): ?array
    {
        try {
            $payload = FirebaseJWT::decode($token, $this->publicKey, [self::ALGO]);

            return json_decode(json_encode($payload), true);
        } catch (Exception $exception) {
            $this->exceptionHandler($exception, $ignoreExceptions);

            return null;
        }
    }

    /**
     * Extract payload of jwt token without validations
     *
     * @param string $token JWT Token as string
     * @return array Returns payload as array
     * @throws Exception
     */
    public function payload(string $token): array
    {
        $segments = $this->segments($token);

        return json_decode(
            FirebaseJWT::urlsafeB64Decode($segments[1]), true
        );
    }

    /**
     * Verify the number of segments to assert a valid token
     *
     * @param string $token
     * @return array
     * @throws Exception
     */
    protected function segments(string $token): array
    {
        $segments = explode('.', $token);

        if (3 !== count($segments)) {
            throw new Exception("Malformed jwt received.");
        }

        return $segments;
    }

    /**
     * Verify expiration contraint and return the timestamp of expiration date.
     * If null will return null too and token will never expire
     *
     * @param DateTimeImmutable $now
     * @param string|null $expires
     * @return int|null
     */
    protected function expires(DateTimeImmutable $now, string $expires = null)
    {
        return !$expires ? null : $now->modify($expires)->getTimestamp();
    }

    /**
     * Handle exception if $ignoreException is false
     *
     * @param Exception $exception
     * @param bool $ignoreException
     * @throws Exception
     */
    protected function exceptionHandler(Exception $exception, $ignoreException = false)
    {
        if (!$ignoreException) {
            throw $exception;
        }
    }
}


<?php
namespace DreamFactory\Core\Aws\Services;

use Aws\Ses\SesClient;
use DreamFactory\Core\Exceptions\InternalServerErrorException;
use Illuminate\Mail\Transport\SesTransport;
use DreamFactory\Core\Email\Services\BaseService;
use Illuminate\Support\Arr;

class Ses extends BaseService
{
    /**
     * {@inheritdoc}
     */
    protected function setTransport($config)
    {
        $key = array_get($config, 'key');
        $secret = array_get($config, 'secret');
        $region = array_get($config, 'region', 'us-east-1');

        $this->transport = static::getTransport($key, $secret, $region);
    }

    /**
     * @param $key
     * @param $secret
     * @param $region
     *
     * @return \Illuminate\Mail\Transport\SesTransport
     * @throws \DreamFactory\Core\Exceptions\InternalServerErrorException
     */
    public static function getTransport($key, $secret, $region)
    {
        if (empty($key) || empty($secret) || empty($region)) {
            throw new InternalServerErrorException('Missing one or more configuration for SES service.');
        }

        $config = [
            'key'     => $key,
            'secret'  => $secret,
            'region'  => $region,
            'version' => 'latest',
            'service' => 'email'
        ];

        if ($config['key'] && $config['secret']) {
            $config['credentials'] = Arr::only($config, ['key', 'secret']);
        }

        return new SesTransport(new SesClient($config));
    }
}


<?php
namespace DreamFactory\Core\Aws\Database\Schema;

use DreamFactory\Core\Database\Schema\ColumnSchema;
use DreamFactory\Core\Database\Schema\TableSchema;
use DreamFactory\Core\Enums\DbResourceTypes;
use DreamFactory\Core\Enums\DbSimpleTypes;
use DreamFactory\Core\Exceptions\BadRequestException;
use DreamFactory\Core\SqlDb\Database\Schema\SqlSchema;

/**
 * Schema is the class for retrieving metadata information from a AWS Redshift database.
 */
class RedshiftSchema extends SqlSchema
{
    const DEFAULT_SCHEMA = 'public';

    /**
     * @inheritdoc
     */
    public function getDefaultSchema()
    {
        return static::DEFAULT_SCHEMA;
    }

    /**
     * @inheritdoc
     */
    public function getSupportedResourceTypes()
    {
        return [
            DbResourceTypes::TYPE_TABLE,
            DbResourceTypes::TYPE_TABLE_FIELD,
            DbResourceTypes::TYPE_TABLE_CONSTRAINT,
            DbResourceTypes::TYPE_TABLE_RELATIONSHIP,
            DbResourceTypes::TYPE_VIEW,
        ];
    }

    protected function translateSimpleColumnTypes(array &$info)
    {
        // override this in each schema class
        $type = (isset($info['type'])) ? $info['type'] : null;
        switch ($type) {
            // some types need massaging, some need other required properties
            case 'pk':
            case DbSimpleTypes::TYPE_ID:
                $info['type'] = 'integer';
                $info['allow_null'] = false;
                $info['auto_increment'] = true;
                $info['is_primary_key'] = true;
                break;

            case 'fk':
            case DbSimpleTypes::TYPE_REF:
                $info['type'] = 'integer';
                $info['is_foreign_key'] = true;
                // check foreign tables
                break;

            case DbSimpleTypes::TYPE_DATETIME:
                $info['type'] = 'timestamp';
                break;

            case DbSimpleTypes::TYPE_TIMESTAMP_ON_CREATE:
            case DbSimpleTypes::TYPE_TIMESTAMP_ON_UPDATE:
                $info['type'] = 'timestamp';
                $default = (isset($info['default'])) ? $info['default'] : null;
                if (!isset($default)) {
                    $default = 'CURRENT_TIMESTAMP';
                    // ON UPDATE CURRENT_TIMESTAMP not supported by PostgreSQL, use triggers
                    $info['default'] = $default;
                }
                break;

            case DbSimpleTypes::TYPE_USER_ID:
            case DbSimpleTypes::TYPE_USER_ID_ON_CREATE:
            case DbSimpleTypes::TYPE_USER_ID_ON_UPDATE:
                $info['type'] = 'integer';
                break;

            case 'int':
                $info['type'] = 'integer';
                break;

            case DbSimpleTypes::TYPE_FLOAT:
                $info['type'] = 'real';
                break;

            case DbSimpleTypes::TYPE_DOUBLE:
                $info['type'] = 'double precision';
                break;

            case DbSimpleTypes::TYPE_MONEY: // not supported, use decimal
                $info['type'] = 'decimal';
                break;

            case DbSimpleTypes::TYPE_STRING:
                $fixed =
                    (isset($info['fixed_length'])) ? filter_var($info['fixed_length'], FILTER_VALIDATE_BOOLEAN) : false;
                $national =
                    (isset($info['supports_multibyte'])) ? filter_var($info['supports_multibyte'],
                        FILTER_VALIDATE_BOOLEAN) : false;
                if ($fixed) {
                    $info['type'] = ($national) ? 'national char' : 'char';
                } elseif ($national) {
                    $info['type'] = 'national varchar';
                } else {
                    $info['type'] = 'varchar';
                }
                break;

            case DbSimpleTypes::TYPE_BINARY:
                $info['type'] = 'varchar';
                break;
        }
    }

    protected function validateColumnSettings(array &$info)
    {
        // override this in each schema class
        $type = (isset($info['type'])) ? $info['type'] : null;
        switch ($type) {
            // some types need massaging, some need other required properties
            case 'boolean':
                $default = (isset($info['default'])) ? $info['default'] : null;
                if (isset($default)) {
                    // convert to bit 0 or 1, where necessary
                    $info['default'] = (filter_var($default, FILTER_VALIDATE_BOOLEAN)) ? 'TRUE' : 'FALSE';
                }
                break;

            case 'smallint':
            case 'integer':
            case 'int':
            case 'bigint':
                if (!isset($info['type_extras'])) {
                    $length =
                        (isset($info['length']))
                            ? $info['length']
                            : ((isset($info['precision'])) ? $info['precision']
                            : null);
                    if (!empty($length)) {
                        $info['type_extras'] = "($length)"; // sets the viewable length
                    }
                }

                $default = (isset($info['default'])) ? $info['default'] : null;
                if (isset($default) && is_numeric($default)) {
                    $info['default'] = intval($default);
                }
                break;

            case 'decimal':
            case 'numeric':
            case 'real':
            case 'double precision':
                if (!isset($info['type_extras'])) {
                    $length =
                        (isset($info['length']))
                            ? $info['length']
                            : ((isset($info['precision'])) ? $info['precision']
                            : null);
                    if (!empty($length)) {
                        $scale =
                            (isset($info['decimals']))
                                ? $info['decimals']
                                : ((isset($info['scale'])) ? $info['scale']
                                : null);
                        $info['type_extras'] = (!empty($scale)) ? "($length,$scale)" : "($length)";
                    }
                }

                $default = (isset($info['default'])) ? $info['default'] : null;
                if (isset($default) && is_numeric($default)) {
                    $info['default'] = floatval($default);
                }
                break;

            case 'char':
            case 'national char':
                $length = (isset($info['length'])) ? $info['length'] : ((isset($info['size'])) ? $info['size'] : null);
                if (isset($length)) {
                    $info['type_extras'] = "($length)";
                }
                break;

            case 'varchar':
            case 'national varchar':
                $length = (isset($info['length'])) ? $info['length'] : ((isset($info['size'])) ? $info['size'] : null);
                if (isset($length)) {
                    $info['type_extras'] = "($length)";
                } else // requires a max length
                {
                    $info['type_extras'] = '(' . static::DEFAULT_STRING_MAX_SIZE . ')';
                }
                break;

            case 'time':
            case 'timestamp':
                $length = (isset($info['length'])) ? $info['length'] : ((isset($info['size'])) ? $info['size'] : null);
                if (isset($length)) {
                    $info['type_extras'] = "($length)";
                }
                break;
        }
    }

    /**
     * @param array $info
     *
     * @return string
     * @throws \Exception
     */
    protected function buildColumnDefinition(array $info)
    {
        $type = (isset($info['type'])) ? $info['type'] : null;
        $typeExtras = (isset($info['type_extras'])) ? $info['type_extras'] : null;

        $definition = $type . $typeExtras;

        $allowNull = (isset($info['allow_null'])) ? filter_var($info['allow_null'], FILTER_VALIDATE_BOOLEAN) : false;
        $definition .= ($allowNull) ? ' NULL' : ' NOT NULL';

        $default = (isset($info['default'])) ? $info['default'] : null;
        if (isset($default)) {
            $quoteDefault =
                (isset($info['quote_default'])) ? filter_var($info['quote_default'], FILTER_VALIDATE_BOOLEAN) : false;
            if ($quoteDefault) {
                $default = "'" . $default . "'";
            }

            $definition .= ' DEFAULT ' . $default;
        }

        $auto = (isset($info['auto_increment'])) ? filter_var($info['auto_increment'], FILTER_VALIDATE_BOOLEAN) : false;
        if ($auto) {
            $definition .= ' IDENTITY(1,1)';
        }

        if (isset($info['is_primary_key']) && filter_var($info['is_primary_key'], FILTER_VALIDATE_BOOLEAN)) {
            $definition .= ' PRIMARY KEY';
        } elseif (isset($info['is_unique']) && filter_var($info['is_unique'], FILTER_VALIDATE_BOOLEAN)) {
            $definition .= ' UNIQUE';
        }

        return $definition;
    }

    /**
     * Resets the sequence value of a table's primary key.
     * The sequence will be reset such that the primary key of the next new row inserted
     * will have the specified value or max value of a primary key plus one (i.e. sequence trimming).
     *
     * @param TableSchema  $table   the table schema whose primary key sequence will be reset
     * @param integer|null $value   the value for the primary key of the next new row inserted.
     *                              If this is not set, the next new row's primary key will have the max value of a
     *                              primary key plus one (i.e. sequence trimming).
     *
     */
    public function resetSequence($table, $value = null)
    {
        if ($table->sequenceName === null) {
            return;
        }
        $sequence = '"' . $table->sequenceName . '"';
        if (strpos($sequence, '.') !== false) {
            $sequence = str_replace('.', '"."', $sequence);
        }
        if ($value !== null) {
            $value = (int)$value;
        } else {
            $value = "(SELECT COALESCE(MAX(\"{$table->primaryKey}\"),0) FROM {$table->quotedName})+1";
        }
        $this->connection->statement("SELECT SETVAL('$sequence',$value,false)");
    }

    /**
     * @inheritdoc
     */
    protected function loadTableColumns(TableSchema $table)
    {
        $sql = <<<EOD
SELECT a.attname AS "name", LOWER(format_type(a.atttypid, a.atttypmod)) AS db_type, a.attnotnull, a.atthasdef,
    pg_catalog.pg_get_expr(d.adbin,d.adrelid) as default,
	pg_catalog.col_description(a.attrelid, a.attnum) AS comment,
	CASE c.contype WHEN 'p' THEN true ELSE false END AS is_primary_key,
	CASE c.contype WHEN 'u' THEN true ELSE false END AS is_unique,
	CASE c.contype WHEN 'f' THEN true ELSE false END AS is_foreign_key,
	c.confupdtype AS ref_on_update, c.confdeltype AS ref_on_update, f.relname AS ref_table,
    (SELECT a2.attname FROM pg_attribute a2 WHERE a2.attrelid = f.oid AND a2.attnum = c.confkey[1] AND a2.attisdropped = false) AS ref_field
FROM pg_attribute a
LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
LEFT JOIN pg_constraint c ON c.conrelid = a.attrelid AND (a.attnum = ANY (c.conkey))
LEFT JOIN pg_class f ON f.oid = c.confrelid
WHERE a.attnum > 0 AND NOT a.attisdropped
	AND a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname=:table
		AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = :schema))
ORDER BY a.attnum
EOD;

        $columns = $this->connection->select($sql, [':table' => $table->resourceName, ':schema' => $table->schemaName]);
        foreach ($columns as $column) {
            $column = array_change_key_case((array)$column, CASE_LOWER);
            $c = new ColumnSchema(array_except($column, ['atthasdef', 'default', 'attnotnull']));
            $c->quotedName = $this->quoteColumnName($c->name);
            $c->allowNull = !boolval($column['attnotnull']);
            $this->extractLimit($c, $c->dbType);
            $c->fixedLength = $this->extractFixedLength($c->dbType);
            $c->supportsMultibyte = $this->extractMultiByteSupport($c->dbType);
            $this->extractType($c, $c->dbType);
            if ($column['atthasdef']) {
                $this->extractDefault($c, $column['default']);
            }

            if ($c->isPrimaryKey) {
                if ($c->autoIncrement) {
                    $table->sequenceName = array_get($column, 'sequence', $c->name);
                    if ((DbSimpleTypes::TYPE_INTEGER === $c->type)) {
                        $c->type = DbSimpleTypes::TYPE_ID;
                    }
                }
                $table->addPrimaryKey($c->name);
            }
            $table->addColumn($c);
        }
    }

    /**
     * @inheritdoc
     */
    protected function getTableConstraints($schema = '')
    {
        $sql = <<<EOD
SELECT
  o.conname AS constraint_name,
  (SELECT nspname FROM pg_namespace WHERE oid=m.relnamespace) AS table_schema,
  m.relname AS table_name,
  (SELECT a.attname FROM pg_attribute a WHERE a.attrelid = m.oid AND a.attnum = o.conkey[1] AND a.attisdropped = false) AS column_name,
  (SELECT nspname FROM pg_namespace WHERE oid=f.relnamespace) AS referenced_table_schema,
  f.relname AS referenced_table_name,
  (SELECT a.attname FROM pg_attribute a WHERE a.attrelid = f.oid AND a.attnum = o.confkey[1] AND a.attisdropped = false) AS referenced_column_name,
  cc.contype AS constraint_type
FROM
  pg_constraint o LEFT JOIN pg_class c ON c.oid = o.conrelid
  LEFT JOIN pg_class f ON f.oid = o.confrelid
  LEFT JOIN pg_class m ON m.oid = o.conrelid
  LEFT JOIN pg_constraint cc ON cc.conrelid = o.conrelid AND (cc.conkey[1] = o.conkey[1]) AND (cc.contype IN ('p', 'u'))
WHERE
  o.contype = 'f' AND o.conrelid IN (SELECT oid FROM pg_class c WHERE c.relkind = 'r');
EOD;

        return $this->connection->select($sql);
    }

    public function getSchemas()
    {
        $sql = <<<MYSQL
SELECT nspname FROM pg_namespace WHERE nspname NOT IN ('information_schema','pg_catalog','pg_internal')
MYSQL;
        $rows = $this->selectColumn($sql);

        return $rows;
    }

    /**
     * @inheritdoc
     */
    protected function getTableNames($schema = '')
    {
        $sql = <<<EOD
SELECT table_name, table_schema, table_type FROM information_schema.tables WHERE table_type = 'BASE TABLE'
EOD;

        if (!empty($schema)) {
            $sql .= " AND table_schema = '$schema'";
        }

        $rows = $this->connection->select($sql);

        $names = [];
        foreach ($rows as $row) {
            $row = (array)$row;
            $schemaName = isset($row['table_schema']) ? $row['table_schema'] : '';
            $resourceName = isset($row['table_name']) ? $row['table_name'] : '';
            $internalName = $schemaName . '.' . $resourceName;
            $name = $resourceName;
            $quotedName = $this->quoteTableName($schemaName) . '.' . $this->quoteTableName($resourceName);
            $settings = compact('schemaName', 'resourceName', 'name', 'internalName','quotedName');
            $names[strtolower($name)] = new TableSchema($settings);
        }

        return $names;
    }

    /**
     * @inheritdoc
     */
    protected function getViewNames($schema = '')
    {
        $sql = <<<EOD
SELECT table_name, table_schema, table_type FROM information_schema.tables WHERE table_type = 'VIEW'
EOD;

        if (!empty($schema)) {
            $sql .= " AND table_schema = '$schema'";
        }

        $rows = $this->connection->select($sql);

        $names = [];
        foreach ($rows as $row) {
            $row = (array)$row;
            $schemaName = isset($row['table_schema']) ? $row['table_schema'] : '';
            $resourceName = isset($row['table_name']) ? $row['table_name'] : '';
            $internalName = $schemaName . '.' . $resourceName;
            $name = $resourceName;
            $quotedName = $this->quoteTableName($schemaName) . '.' . $this->quoteTableName($resourceName);
            $settings = compact('schemaName', 'resourceName', 'name', 'internalName','quotedName');
            $settings['isView'] = true;
            $names[strtolower($name)] = new TableSchema($settings);
        }

        return $names;
    }

    /**
     * Builds a SQL statement for renaming a DB table.
     *
     * @param string $table   the table to be renamed. The name will be properly quoted by the method.
     * @param string $newName the new table name. The name will be properly quoted by the method.
     *
     * @return string the SQL statement for renaming a DB table.
     * @since 1.1.6
     */
    public function renameTable($table, $newName)
    {
        return 'ALTER TABLE ' . $this->quoteTableName($table) . ' RENAME TO ' . $this->quoteTableName($newName);
    }

    /**
     * @inheritdoc
     */
    public function alterColumn($table, $column, $definition)
    {
        /** @noinspection SqlNoDataSourceInspection */
        $sql = "ALTER TABLE $table ALTER COLUMN " . $this->quoteColumnName($column);
        if (false !== $pos = strpos($definition, ' ')) {
            $sql .= ' TYPE ' . $this->getColumnType(substr($definition, 0, $pos));
            switch (substr($definition, $pos + 1)) {
                case 'NULL':
                    $sql .= ', ALTER COLUMN ' . $this->quoteColumnName($column) . ' DROP NOT NULL';
                    break;
                case 'NOT NULL':
                    $sql .= ', ALTER COLUMN ' . $this->quoteColumnName($column) . ' SET NOT NULL';
                    break;
            }
        } else {
            $sql .= ' TYPE ' . $this->getColumnType($definition);
        }

        return $sql;
    }

    public function requiresCreateIndex($unique = false, $on_create_table = false)
    {
        return $unique; // regular indexes are not supported by redshift
    }

    /**
     * {@InheritDoc}
     */
    public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
    {
        // ON DELETE and ON UPDATE not supported by Redshift
        return parent::addForeignKey($name, $table, $columns, $refTable, $refColumns, null, null);
    }

    /**
     * Builds a SQL statement for creating a new index.
     *
     * @param string  $name    the name of the index. The name will be properly quoted by the method.
     * @param string  $table   the table that the new index will be created for. The table name will be properly quoted
     *                         by the method.
     * @param string  $columns the column(s) that should be included in the index. If there are multiple columns,
     *                         please separate them by commas. Each column name will be properly quoted by the method,
     *                         unless a parenthesis is found in the name.
     * @param boolean $unique  whether to add UNIQUE constraint on the created index.
     * @return string the SQL statement for creating a new index.
     * @throws BadRequestException
     */
    public function createIndex($name, $table, $columns, $unique = false)
    {
        $cols = [];
        if (is_string($columns)) {
            $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
        }
        foreach ($columns as $col) {
            if (strpos($col, '(') !== false) {
                $cols[] = $col;
            } else {
                $cols[] = $this->quoteColumnName($col);
            }
        }
        if ($unique) {
            return
                'ALTER TABLE ' .
                $this->quoteTableName($table) .
                ' ADD CONSTRAINT ' .
                $this->quoteTableName($name) .
                ' UNIQUE (' .
                implode(', ', $cols) .
                ')';
        } else {
            throw new BadRequestException('Indexes not supported.');
        }
    }

    /**
     * Builds a SQL statement for dropping an index.
     *
     * @param string $name  the name of the index to be dropped. The name will be properly quoted by the method.
     * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
     *
     * @return string the SQL statement for dropping an index.
     * @since 1.1.6
     */
    public function dropIndex($name, $table)
    {
        /** @noinspection SqlNoDataSourceInspection */
        return "ALTER TABLE {$this->quoteTableName($table)} DROP CONSTRAINT {$this->quoteTableName($name)} )";
    }

    public function typecastToNative($value, $field_info, $allow_null = true)
    {
        $value = parent::typecastToNative($value, $field_info, $allow_null);

        switch ($field_info->type) {
            case DbSimpleTypes::TYPE_BOOLEAN:
                $value = ($value ? 'TRUE' : 'FALSE');
                break;
        }

        return $value;
    }

    protected function formatValueToPhpType($value, $type, $allow_null = true)
    {
        if (!is_null($value)) {
            switch (strtolower(strval($type))) {
                case 'int':
                case 'integer':
                    if ('' === $value) {
                        // Postgresql strangely returns "" for null integers
                        return null;
                    }
            }
        }

        return parent::formatValueToPhpType($value, $type, $allow_null);
    }

    /**
     * @inheritdoc
     */
    public function extractType(ColumnSchema $column, $dbType)
    {
        parent::extractType($column, $dbType);
        if (strpos($dbType, '[') !== false || strpos($dbType, 'char') !== false || strpos($dbType, 'text') !== false) {
            $column->type = DbSimpleTypes::TYPE_STRING;
        } elseif (preg_match('/(real|float|double)/', $dbType)) {
            $column->type = DbSimpleTypes::TYPE_DOUBLE;
        } elseif (preg_match('/(integer|oid|serial|smallint)/', $dbType)) {
            $column->type = DbSimpleTypes::TYPE_INTEGER;
        }
    }

    /**
     * Extracts size, precision and scale information from column's DB type.
     *
     * @param ColumnSchema $field
     * @param string       $dbType the column's DB type
     */
    public function extractLimit(ColumnSchema $field, $dbType)
    {
        if (strpos($dbType, '(')) {
            if (preg_match('/^time.*\((.*)\)/', $dbType, $matches)) {
                $field->precision = (int)$matches[1];
            } elseif (preg_match('/\((.*)\)/', $dbType, $matches)) {
                $values = explode(',', $matches[1]);
                $field->size = $field->precision = (int)$values[0];
                if (isset($values[1])) {
                    $field->scale = (int)$values[1];
                }
            }
        }
    }

    /**
     * Extracts the default value for the column.
     * The value is typecasted to correct PHP type.
     *
     * @param ColumnSchema $field
     * @param mixed        $defaultValue the default value obtained from metadata
     */
    public function extractDefault(ColumnSchema $field, $defaultValue)
    {
        if ($defaultValue === 'true') {
            $field->defaultValue = true;
        } elseif ($defaultValue === 'false') {
            $field->defaultValue = false;
        } elseif (0 === stripos($defaultValue, '"identity"')) {
            $field->autoIncrement = true;
        } elseif (preg_match('/^\'(.*)\'::/', $defaultValue, $matches)) {
            parent::extractDefault($field, str_replace("''", "'", $matches[1]));
        } elseif (preg_match('/^(-?\d+(\.\d*)?)(::.*)?$/', $defaultValue, $matches)) {
            parent::extractDefault($field, $matches[1]);
        } else {
            // could be a internal function call like setting uuids
            $field->defaultValue = $defaultValue;
        }
    }

    /**
     * @return mixed
     */
    public function getTimestampForSet()
    {
        return $this->connection->raw('(GETDATE())');
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the RollerworksDatagrid package.
 *
 * (c) Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Rollerworks\Component\Datagrid\Extension\Core\Type;

use Rollerworks\Component\Datagrid\Column\AbstractType;
use Rollerworks\Component\Datagrid\Column\CellView;
use Rollerworks\Component\Datagrid\Column\ColumnInterface;
use Rollerworks\Component\Datagrid\Exception\InvalidConfigurationException;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
class ActionType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildCellView(CellView $view, ColumnInterface $column, array $options)
    {
        $mappingValues = $view->value;

        if (is_object($options['content'])) {
            $options['content'] = $options['content']($mappingValues);
        }

        $view->vars['url'] = $this->createUrl($options, $mappingValues);
        $view->vars['content'] = $options['content'];
        $view->vars['attr'] = $options['attr'];
        $view->vars['url_attr'] = $options['url_attr'];
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(
            [
                'redirect_uri' => null,
                'content' => null,
                'attr' => [],
                'url_attr' => [],
                'url' => null,
                'uri_scheme' => null,

                // Remove requirement for label
                'label' => function (Options $options) {
                    return $options['content'];
                },
            ]
        );

        $resolver->setAllowedTypes('redirect_uri', ['string', 'null', 'callable']);
        $resolver->setAllowedTypes('url', ['string', 'null']);
        $resolver->setAllowedTypes('uri_scheme', ['string', 'callable', 'null']);
        $resolver->setAllowedTypes('content', ['null', 'string', 'callable']);
        $resolver->setAllowedTypes('attr', ['array']);
        $resolver->setAllowedTypes('url_attr', ['array']);
    }

    private static function wrapValues(array $values)
    {
        $return = [];

        foreach ($values as $key => $value) {
            $return['{'.$key.'}'] = $value;
        }

        return $return;
    }

    private function createUrl(array $options, $mappingValues): string
    {
        if (null !== $options['url']) {
            return $options['url'];
        }

        if (null === $options['uri_scheme']) {
            throw new InvalidConfigurationException('Action needs an "url" or "uri_scheme" but none is provided.');
        }

        if (is_object($options['uri_scheme'])) {
            $url = $options['uri_scheme']($mappingValues);
        } else {
            $url = strtr($options['uri_scheme'], $this->wrapValues($mappingValues));
        }

        return $url.$this->getRedirectUrl($options, $url, $mappingValues);
    }

    private function getRedirectUrl(array $options, $url, $mappingValues): string
    {
        if (null === $options['redirect_uri']) {
            return '';
        }

        if (is_object($options['redirect_uri'])) {
            $options['redirect_uri'] = $options['redirect_uri']($mappingValues);
        }

        return (false !== strpos($url, '?') ? '&' : '?').'redirect_uri='.urlencode($options['redirect_uri']);
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the RollerworksDatagrid package.
 *
 * (c) Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Rollerworks\Component\Datagrid;

use Rollerworks\Component\Datagrid\Column\ColumnTypeExtensionInterface;
use Rollerworks\Component\Datagrid\Column\ColumnTypeInterface;
use Rollerworks\Component\Datagrid\Exception\InvalidArgumentException;
use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException;

/**
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
abstract class AbstractDatagridExtension implements DatagridExtensionInterface
{
    /**
     * All column types extensions provided by Datagrid extension.
     *
     * @var array
     */
    private $typesExtensions;

    /**
     * All column types provided by extension.
     *
     * @var array
     */
    private $types;

    /**
     * {@inheritdoc}
     */
    public function getType(string $name): ColumnTypeInterface
    {
        if (null === $this->types) {
            $this->initColumnTypes();
        }

        if (!isset($this->types[$name])) {
            throw new InvalidArgumentException(sprintf('Column type "%s" can not be loaded by this extension.', $name));
        }

        return $this->types[$name];
    }

    /**
     * {@inheritdoc}
     */
    public function hasType(string $name): bool
    {
        if (null === $this->types) {
            $this->initColumnTypes();
        }

        return isset($this->types[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function hasTypeExtensions(string $type): bool
    {
        if (null === $this->typesExtensions) {
            $this->initTypesExtensions();
        }

        return isset($this->typesExtensions[$type]);
    }

    /**
     * {@inheritdoc}
     */
    public function getTypeExtensions(string $type): array
    {
        if (null === $this->typesExtensions) {
            $this->initTypesExtensions();
        }

        return isset($this->typesExtensions[$type]) ? $this->typesExtensions[$type] : [];
    }

    /**
     * If extension needs to provide new column types this function
     * should be overloaded in child class and return an array of ColumnTypeInterface
     * instances.
     *
     * @return ColumnTypeInterface[]
     *
     * @codeCoverageIgnore
     */
    protected function loadTypes(): array
    {
        return [];
    }

    /**
     * If extension needs to provide new column types this function
     * should be overloaded in child class and return array of ColumnTypeInterface
     * instances.
     *
     * @return array
     *
     * @codeCoverageIgnore
     */
    protected function loadTypesExtensions(): array
    {
        return [];
    }

    /**
     * @throws UnexpectedTypeException
     */
    private function initColumnTypes()
    {
        $this->types = [];

        foreach ($this->loadTypes() as $type) {
            if (!$type instanceof ColumnTypeInterface) {
                throw new UnexpectedTypeException($type, ColumnTypeInterface::class);
            }

            $this->types[get_class($type)] = $type;
        }
    }

    /**
     * @throws UnexpectedTypeException
     */
    private function initTypesExtensions()
    {
        $this->typesExtensions = [];

        foreach ($this->loadTypesExtensions() as $extension) {
            if (!$extension instanceof ColumnTypeExtensionInterface) {
                throw new UnexpectedTypeException($extension, ColumnTypeExtensionInterface::class);
            }

            $type = $extension->getExtendedType();

            $this->typesExtensions[$type][] = $extension;
        }
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the RollerworksDatagrid package.
 *
 * (c) Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Rollerworks\Component\Datagrid\Extension\Core\Type;

use Rollerworks\Component\Datagrid\Column\AbstractType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 * @author FSi sp. z o.o. <info@fsi.pl>
 */
class BooleanType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return TextType::class;
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'true_value' => 'true',
            'false_value' => 'false',
            'value_format' => function (Options $options) {
                $trueValue = $options['true_value'];
                $falseValue = $options['false_value'];

                // Return a closure for later execution (the actual formatter)
                return function ($value) use ($trueValue, $falseValue) {
                    $value = (array) $value;

                    $boolValue = true;
                    foreach ($value as $val) {
                        $boolValue = (bool) ($boolValue & (bool) $val);
                        if (!$boolValue) {
                            break;
                        }
                    }

                    return $boolValue ? $trueValue : $falseValue;
                };
            },
        ]);
    }
}


<?php namespace Kharanenka\Helper;

/**
 * Class PaginationHelper
 * @package Kharanenka\Helper
 * @author Andrey Kharanenka, kharanenka@gmail.com
 */
class PaginationHelper {

    const FIRST_BUTTON_CODE = 'first';
    const FIRST_MORE_BUTTON_CODE = 'first-more';
    const PREV_BUTTON_CODE = 'prev';
    const PREV_MORE_BUTTON_CODE = 'prev-more';
    const NEXT_BUTTON_CODE = 'next';
    const NEXT_MORE_BUTTON_CODE = 'next-more';
    const LAST_BUTTON_CODE = 'last';
    const LAST_MORE_BUTTON_CODE = 'last-more';
    const MAIN_BUTTON_CODE = 'main';
    const ACT_BUTTON_CODE = 'act';

    protected static $arResult = [];
    protected static $iCurrentPage;
    protected static $iTotalCountPages;
    protected static $iPosition;

    protected static $arSettings = [
        //Common settings
        'count_per_page' => 10,
        'pagination_limit' => 5,
        'active_class' => '_act',
        'button_list' => [self::MAIN_BUTTON_CODE],

        //Button "First"
        'first_button_name' => 'First',                 // Button name
        'first_button_limit' => 1,                      // Show button if current page > this value
        'first_button_number' => false,                 // true - button name = page number
        'first_button_class' => null,                   // Button class

        //Button "First-More"
        'first-more_button_name' => '...',              // Button name
        'first-more_button_limit' => 1,                 // Show button if current page > this value
        'first-more_button_class' => null,              // Button class

        //Button "Prev"
        'prev_button_name' => 'Prev',                   // Button name
        'prev_button_limit' => 1,                       // Show button if current page > this value
        'prev_button_number' => false,                  // true - button name = page number
        'prev_button_class' => null,                    // Button class

        //Button "Prev-More"
        'prev-more_button_name' => '...',               // Button name
        'prev-more_button_limit' => 1,                  // Show button if current page > this value
        'prev-more_button_class' => null,               // Button class

        //Main buttons
        'main_button_on' => true,                       // Switch on/off button
        'main_button_class' => null,                    // Button class

        //Button "Next-More"
        'next-more_button_name' => '...',               // Button name
        'next-more_button_limit' => 1,                  // Show button if current page + this value <= total page count
        'next-more_button_class' => null,               // Button class

        //Button "Next"
        'next_button_name' => 'Next',                   // Button name
        'next_button_limit' => 1,                       // Show button if current page + this value <= total page count
        'next_button_number' => false,                  // true - button name = page number
        'next_button_class' => null,                    // Button class

        //Button "Last-More"
        'last-more_button_name' => '...',               // Button name
        'last-more_button_limit' => 1,                  // Show button if current page + this value <= total page count
        'last-more_button_class' => null,               // Button class

        //Button "Last"
        'last_button_name' => 'Last',                   // Button name
        'last_button_limit' => 1,                       // Show button if current page + this value <= total page count
        'last_button_number' => false,                  // true - button name = page number
        'last_button_class' => null,                    // Button class
    ];

    /**
     * Get pagination elements
     * @param int $iCurrentPage - current page number
     * @param int $iTotalCount - total count elements
     * @param array $arSettings - settings array
     * @return array
     */
    public static function get($iCurrentPage, $iTotalCount, $arSettings = []) {

        self::$arResult = [];

        self::$iCurrentPage = $iCurrentPage;
        self::initSettings($arSettings);
        if(empty(self::$arSettings['button_list'])) {
            self::$arResult;
        }

        //Get count per page elements
        $iCountPerPage = self::$arSettings['count_per_page'];

        //Get total page count
        self::$iTotalCountPages = ceil($iTotalCount/$iCountPerPage);

        //get pagination button limit
        $iElementLimit = self::$arSettings['pagination_limit'];

        //init start position
        self::$iPosition = $iCurrentPage - floor($iElementLimit/2);

        //Update position
        if((self::$iPosition + $iElementLimit) > self::$iTotalCountPages) {
            self::$iPosition = self::$iTotalCountPages - $iElementLimit + 1;
        }

        if(self::$iPosition < 1) {
            self::$iPosition = 1;
        }

        $arMainPageList = [];

        foreach(self::$arSettings['button_list'] as $sButtonCode) {

            switch($sButtonCode) {
                case self::FIRST_BUTTON_CODE:
                    self::addPrevButton(self::FIRST_BUTTON_CODE);
                    break;
                case self::FIRST_MORE_BUTTON_CODE:
                    self::addPrevButton(self::FIRST_MORE_BUTTON_CODE);
                    break;
                case self::PREV_BUTTON_CODE:
                    self::addPrevButton(self::PREV_BUTTON_CODE);
                    break;
                case self::PREV_MORE_BUTTON_CODE:
                    self::addPrevButton(self::PREV_MORE_BUTTON_CODE);
                    break;
                case self::MAIN_BUTTON_CODE:

                    if(self::$arSettings['main_button_on']) {
                        //Get pagination buttons
                        $i = 0;
                        while($i < $iElementLimit) {

                            //Set active page button
                            $sCode = null;
                            $sElementClass = self::$arSettings['main_button_class'];
                            if(self::$iPosition == self::$iCurrentPage) {
                                $sElementClass = ' '.self::$arSettings['active_class'];
                                $sCode = self::ACT_BUTTON_CODE;
                            }

                            if(self::$iPosition > 0) {
                                self::$arResult[] = [
                                    'name' => self::$iPosition,
                                    'value' => self::$iPosition,
                                    'class' => $sElementClass,
                                    'code' => $sCode,
                                ];

                                $arMainPageList[] = self::$iPosition;

                                $i++;
                            }

                            self::$iPosition++;
                            if(self::$iPosition > self::$iTotalCountPages) {
                                break;
                            }
                        }
                    }
                    break;
                case self::NEXT_MORE_BUTTON_CODE:
                    self::addNextButton(self::NEXT_MORE_BUTTON_CODE);
                    break;
                case self::NEXT_BUTTON_CODE:
                    self::addNextButton(self::NEXT_BUTTON_CODE);
                    break;
                case self::LAST_MORE_BUTTON_CODE:
                    self::addNextButton(self::LAST_MORE_BUTTON_CODE);
                    break;
                case self::LAST_BUTTON_CODE:
                    self::addNextButton(self::LAST_BUTTON_CODE);
                    break;
            }
        }

        $arUnsetCodeList = [];
        foreach (self::$arResult as $iKey => $arButtonData) {
            if ($arButtonData['code'] == self::ACT_BUTTON_CODE || empty($arButtonData['code'])) {
                continue;
            }

            if (!in_array($arButtonData['code'], [self::FIRST_BUTTON_CODE, self::FIRST_MORE_BUTTON_CODE, self::LAST_BUTTON_CODE, self::LAST_MORE_BUTTON_CODE])) {
                continue;
            }

            if (!in_array($arButtonData['value'], $arMainPageList)) {
                continue;
            }

            $arUnsetCodeList[] = $arButtonData['code'];
            $arUnsetCodeList[] = $arButtonData['code'].'-more';
        }

        if (!empty($arUnsetCodeList)) {
            foreach (self::$arResult as $iKey => $arButtonData) {
                if (!in_array($arButtonData['code'], $arUnsetCodeList)) {
                    continue;
                }

                unset(self::$arResult[$iKey]);
            }
        }

        return self::$arResult;
    }

    /**
     * Init settings
     * @param array $arSettings
     */
    protected static function initSettings($arSettings) {

        if(empty($arSettings)) {
            return;
        }

        foreach($arSettings as $sKey => $sValue) {
            self::$arSettings[$sKey] = $sValue;
        }
    }

    /**
     * Add previous button
     * @param string $sCode
     */
    protected static function addPrevButton($sCode) {

        if(self::$iCurrentPage <= self::$arSettings[$sCode.'_button_limit']) {
            return;
        }

        //Get button value
        $sValue = self::getValue($sCode);

        //Get button name
        $sName = self::$arSettings[$sCode.'_button_name'];
        if(isset(self::$arSettings[$sCode.'_button_number']) && self::$arSettings[$sCode.'_button_number']) {
            $sName = $sValue;
        }

        self::$arResult[] = [
            'name' => $sName,
            'value' => $sValue,
            'class' => self::$arSettings[$sCode.'_button_class'],
            'code' => $sCode,
        ];
    }

    /**
     * Add next button
     * @param string $sCode
     */
    protected static function addNextButton($sCode) {

        if(self::$iCurrentPage + self::$arSettings[$sCode.'_button_limit'] > self::$iTotalCountPages) {
            return;
        }

        //Get button value
        $sValue = self::getValue($sCode);

        //Get button name
        $sName = self::$arSettings[$sCode.'_button_name'];
        if(isset(self::$arSettings[$sCode.'_button_number']) && self::$arSettings[$sCode.'_button_number']) {
            $sName = $sValue;
        }

        self::$arResult[] = [
            'name' => $sName,
            'value' => $sValue,
            'class' => self::$arSettings[$sCode.'_button_class'],
            'code' => $sCode,
        ];
    }

    /**
     * Get page button value
     * @param string $sCode
     * @return int|null
     */
    protected static function getValue($sCode) {

        switch($sCode) {
            case self::FIRST_BUTTON_CODE:
                return 1;
            case self::FIRST_MORE_BUTTON_CODE:
                return null;
            case self::PREV_BUTTON_CODE:
                $iValue = self::$iCurrentPage - 1;
                if($iValue  < 1) {
                    $iValue = 1;
                }

                return $iValue;
            case self::PREV_MORE_BUTTON_CODE:
                return null;
            case self::NEXT_MORE_BUTTON_CODE:
                return null;
            case self::NEXT_BUTTON_CODE:
                $iValue = self::$iCurrentPage + 1;
                if($iValue > self::$iTotalCountPages) {
                    $iValue = self::$iTotalCountPages;
                }

                return $iValue;
            case self::LAST_MORE_BUTTON_CODE:
                return null;
            case self::LAST_BUTTON_CODE:
                return self::$iTotalCountPages;
        }

        return null;
    }
}


<?php
/*
 * PSX is a open source PHP framework to develop RESTful APIs.
 * For the current version and informations visit <http://phpsx.org>
 *
 * Copyright 2010-2017 Christoph Kappestein <christoph.kappestein@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace PSX\Data;

use ArrayObject;
use InvalidArgumentException;
use JsonSerializable;
use PSX\Data\Util\CurveArray;
use PSX\Record\RecordInterface;
use Traversable;

/**
 * GraphTraverser
 *
 * @author  Christoph Kappestein <christoph.kappestein@gmail.com>
 * @license http://www.apache.org/licenses/LICENSE-2.0
 * @link    http://phpsx.org
 */
class GraphTraverser
{
    /**
     * @param mixed $record
     * @param \PSX\Data\VisitorInterface $visitor
     */
    public function traverse($record, VisitorInterface $visitor)
    {
        $this->traverseValue(self::reveal($record), $visitor);
    }

    /**
     * @param object $object
     * @param \PSX\Data\VisitorInterface $visitor
     */
    protected function traverseObject($object, VisitorInterface $visitor)
    {
        $name = null;
        if ($object instanceof RecordInterface) {
            $properties = $object->getProperties();
            $name       = $object->getDisplayName();
        } else {
            $properties = (array) $object;
        }

        if (empty($name)) {
            $name = 'record';
        }

        $visitor->visitObjectStart($name);

        foreach ($properties as $key => $value) {
            $value = self::reveal($value);

            $visitor->visitObjectValueStart($key, $value);

            $this->traverseValue($value, $visitor);

            $visitor->visitObjectValueEnd();
        }

        $visitor->visitObjectEnd();
    }

    /**
     * @param array $values
     * @param \PSX\Data\VisitorInterface $visitor
     */
    protected function traverseArray($values, VisitorInterface $visitor)
    {
        $visitor->visitArrayStart();

        foreach ($values as $value) {
            $value = self::reveal($value);

            $visitor->visitArrayValueStart($value);

            $this->traverseValue($value, $visitor);

            $visitor->visitArrayValueEnd();
        }

        $visitor->visitArrayEnd();
    }

    /**
     * @param mixed $value
     * @param \PSX\Data\VisitorInterface $visitor
     */
    protected function traverseValue($value, VisitorInterface $visitor)
    {
        if (self::isObject($value)) {
            $this->traverseObject($value, $visitor);
        } elseif (self::isArray($value)) {
            $this->traverseArray($value, $visitor);
        } else {
            $visitor->visitValue($value);
        }
    }

    /**
     * Method which reveals the true value of an object if it has a known
     * interface. Note this resolves also all Traversable instances to an array
     *
     * @param mixed $object
     * @return mixed
     */
    public static function reveal($object)
    {
        if ($object instanceof RecordInterface) {
            return $object;
        } elseif ($object instanceof JsonSerializable) {
            return $object->jsonSerialize();
        } elseif ($object instanceof ArrayObject) {
            return $object->getArrayCopy();
        } elseif ($object instanceof Traversable) {
            return iterator_to_array($object);
        }

        return $object;
    }

    /**
     * Checks whether a value is an object type
     *
     * @param mixed $value
     * @return boolean
     */
    public static function isObject($value)
    {
        return $value instanceof RecordInterface || $value instanceof \stdClass || (is_array($value) && CurveArray::isAssoc($value));
    }

    /**
     * Checks whether a value is an array type
     *
     * @param mixed $value
     * @return boolean
     */
    public static function isArray($value)
    {
        return is_array($value);
    }

    /**
     * Checks whether a value is empty
     *
     * @param mixed $value
     * @return boolean
     */
    public static function isEmpty($value)
    {
        if (empty($value)) {
            return true;
        } elseif ($value instanceof \stdClass) {
            return count((array) $value) === 0;
        } elseif ($value instanceof RecordInterface) {
            return count($value->getProperties()) === 0;
        }

        return false;
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the RollerworksDatagrid package.
 *
 * (c) Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Rollerworks\Component\Datagrid\Extension\Core\DataTransformer;

use Rollerworks\Component\Datagrid\DataTransformerInterface;
use Rollerworks\Component\Datagrid\Exception\TransformationFailedException;
use Rollerworks\Component\Datagrid\Exception\UnexpectedTypeException;

/**
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
final class EmptyValueTransformer implements DataTransformerInterface
{
    /**
     * @var string|array
     */
    private $emptyValue;

    /**
     * @param string|array $emptyValue
     *
     * @throws UnexpectedTypeException
     */
    public function __construct($emptyValue = '')
    {
        if (!is_string($emptyValue) && !is_array($emptyValue)) {
            throw new UnexpectedTypeException($emptyValue, ['string', 'array']);
        }

        $this->emptyValue = $emptyValue;
    }

    /**
     * {@inheritdoc}
     */
    public function transform($value)
    {
        if (is_array($this->emptyValue) && !is_array($value)) {
            throw new TransformationFailedException('Empty value is an array, but provided value is not an array.');
        }

        // Note. empty is not used here as 0 is a none-empty value.
        // Only false, null and an empty string is considered empty.

        if (!is_array($value)) {
            return '' === (string) $value ? $this->emptyValue : $value;
        }

        $emptyIsString = is_string($this->emptyValue);

        foreach ($value as $field => &$val) {
            if (isset($val) && '' !== $val) {
                continue;
            }

            if ($emptyIsString) {
                $val = $this->emptyValue;
            } elseif (!array_key_exists($field, $this->emptyValue)) {
                throw new TransformationFailedException(
                    sprintf('No empty value-replacement for field "%s" set.', $field)
                );
            } else {
                $emptyValue = $this->emptyValue[$field];

                if (!is_string($emptyValue)) {
                    throw new TransformationFailedException(
                        sprintf('Empty value of field "%s" must be a string value.', $field)
                    );
                }

                $val = $emptyValue;
            }
        }

        return $value;
    }
}


<?php

namespace Omnipay\Agms\Message;

/**
 * Agms Authorize Request
 */
class VoidRequest extends AbstractRequest
{
    /**
     * Transaction type
     *
     * @return string
     */
    protected $transactionType = 'void';

    /**
     * Get the request data
     *
     * @return array
     */
    public function getData()
    {
        $this->validate('transactionId');
        $data = $this->getBaseData();

        // Add auth data
        $data = array_merge($data, $this->getAuthData());

        return $data;
    }
}


<?php
/*
 * PSX is a open source PHP framework to develop RESTful APIs.
 * For the current version and informations visit <http://phpsx.org>
 *
 * Copyright 2010-2017 Christoph Kappestein <christoph.kappestein@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace PSX\Data\Record;

use PSX\Data\GraphTraverser;
use PSX\Data\Visitor\ArraySerializeVisitor;
use PSX\Data\Visitor\RecordSerializeVisitor;
use PSX\Data\Visitor\StdClassSerializeVisitor;
use PSX\Record\RecordInterface;

/**
 * Transformer
 *
 * @author  Christoph Kappestein <christoph.kappestein@gmail.com>
 * @license http://www.apache.org/licenses/LICENSE-2.0
 * @link    http://phpsx.org
 */
class Transformer
{
    /**
     * Transforms an arbitrary data structure into a record graph
     *
     * @param mixed $data
     * @param \PSX\Record\RecordInterface $root
     * @return \PSX\Record\RecordInterface
     */
    public static function toRecord($data, RecordInterface $root = null)
    {
        $visitor   = new RecordSerializeVisitor($root);
        $traverser = new GraphTraverser();
        $traverser->traverse($data, $visitor);

        return $visitor->getObject();
    }

    /**
     * Transforms an arbitrary data structure into a stdClass graph
     *
     * @param mixed $data
     * @return \stdClass
     */
    public static function toStdClass($data)
    {
        $visitor   = new StdClassSerializeVisitor();
        $traverser = new GraphTraverser();
        $traverser->traverse($data, $visitor);

        return $visitor->getObject();
    }

    /**
     * Transforms an arbitrary data structure into a stdClass graph
     *
     * @param mixed $data
     * @return array
     */
    public static function toArray($data)
    {
        $visitor   = new ArraySerializeVisitor();
        $traverser = new GraphTraverser();
        $traverser->traverse($data, $visitor);

        return $visitor->getObject();
    }
}


<?php

namespace Omnipay\Agms\Message;

use Omnipay\Common\Message\AbstractResponse;

/**
 * Agms Response
 */
class Response extends AbstractResponse
{
    public function __construct($request, $data, $op = 'ProcessTransaction')
    {
        $this->request = $request;
        $this->data = $this->parseResponse($data, $op);
    }

    /**
     * Get the request status
     *
     * @return boolean
     */
    public function isSuccessful()
    {
        return isset($this->data['STATUS_CODE']) && $this->data['STATUS_CODE'] == 1;
    }

    /**
     * Get the transaction id
     *
     * @return string
     */
    public function getTransactionId()
    {
        return isset($this->data['TRANS_ID']) ? $this->data['TRANS_ID'] : null;
    }

    /**
     * Get the transaction reference
     *
     * @return string
     */
    public function getTransactionReference()
    {
        return isset($this->data['TRANS_ID']) ? $this->data['TRANS_ID'] : null;
    }

    /**
     * Get the status message
     *
     * @return string
     */
    public function getMessage()
    {
        return isset($this->data['STATUS_MSG']) ? $this->data['STATUS_MSG'] : null;
    }

    /**
     * Get the card reference or safe id
     *
     * @return string
     */
    public function getCardReference()
    {
        return isset($this->data['SAFE_ID']) ? $this->data['SAFE_ID'] : null;
    }

    /**
     * Parse the transaction response
     *
     * @return array
     */
    private function parseResponse($data, $op)
    {
        $arr = array();
        $xml = new \SimpleXMLElement($data);
        $xml = $xml->xpath('/soap:Envelope/soap:Body');
        $xml = $xml[0];
        $data = json_decode(json_encode($xml));
        $opResponse = $op . 'Response';
        $opResult = $op . 'Result';
        $arr = $this->object2array($data->$opResponse->$opResult);
        return $arr;
    }

    /**
     * Convert object to array
     *
     * @return array
     */
    private function object2array($data)
    {
        if (is_array($data) || is_object($data)) {
            $result = array();
            foreach ($data as $key => $value) {
                $result[$key] = $this->object2array($value);
            }
            return $result;
        }
        return $data;
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the RollerworksDatagrid package.
 *
 * (c) Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Rollerworks\Component\Datagrid\Extension\Core\Type;

use Rollerworks\Component\Datagrid\Column\AbstractType;
use Rollerworks\Component\Datagrid\Column\ColumnInterface;
use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\ChainTransformer;
use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\EmptyValueTransformer;
use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\ValueFormatTransformer;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TextType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildColumn(ColumnInterface $column, array $options)
    {
        $transformer = new ChainTransformer();

        if (null !== $options['empty_value']) {
            $transformer->append(new EmptyValueTransformer($options['empty_value']));
        }

        if (null !== $options['value_format'] || null !== $options['value_glue']) {
            $transformer->append(new ValueFormatTransformer($options['value_glue'], $options['value_format']));
        }

        $column->setViewTransformer($transformer);
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'value_glue' => null,
            'value_format' => null,
            'empty_value' => null,
        ]);

        $resolver->setAllowedTypes('value_glue', ['string', 'null']);
        $resolver->setAllowedTypes('value_format', ['string', 'callable', 'null']);
        $resolver->setAllowedTypes('empty_value', ['string', 'array', 'null']);
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the RollerworksDatagrid package.
 *
 * (c) Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Rollerworks\Component\Datagrid\Extension\Core\Type;

use Rollerworks\Component\Datagrid\Column\CellView;
use Rollerworks\Component\Datagrid\Column\ColumnInterface;
use Rollerworks\Component\Datagrid\Column\CompoundColumn;
use Rollerworks\Component\Datagrid\Column\HeaderView;

/**
 * CompoundColumn allows multiple sub-columns for advanced view building.
 *
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
class CompoundColumnType extends BaseType
{
    /**
     * {@inheritdoc}
     */
    public function buildColumn(ColumnInterface $column, array $options)
    {
        // Simple pass all $data to the sub-columns.
        $column->setDataProvider(
            function ($data) {
                return $data;
            }
        );
    }

    /**
     * @param HeaderView                     $view
     * @param ColumnInterface|CompoundColumn $column
     * @param array                          $options
     */
    public function buildHeaderView(HeaderView $view, ColumnInterface $column, array $options)
    {
        parent::buildHeaderView($view, $column, $options);

        // The header information contains the actual block information (and cache key)
        $datagrid = $view->datagrid;

        $headers = [];

        foreach ($column->getColumns() as $subColumn) {
            $headers[$subColumn->getName()] = $subColumn->createHeaderView($datagrid);
        }

        $view->vars['_sub_headers'] = $headers;
    }

    /**
     * {@inheritdoc}
     */
    public function buildCellView(CellView $view, ColumnInterface $column, array $options)
    {
        $parent = $view->column;

        // Set shared information from the header.
        // This information is not recomputed for better performance.
        // Each header is created once, but this method will be called
        // 5000 times for a grid with 500 rows!

        $view->vars = array_replace($view->vars, [
            'cell_attr' => $options['cell_attr'],
            'unique_block_prefix' => $parent->vars['unique_block_prefix'],
            'block_prefixes' => $parent->vars['block_prefixes'],
            'cache_key' => $parent->vars['cache_key'],
        ]);

        $cells = [];

        $headers = $view->column->vars['_sub_headers'];

        /** @var CompoundColumn $column */
        foreach ($column->getColumns() as $subColumn) {
            $name = $subColumn->getName();

            $subView = $subColumn->createCellView($headers[$name], $view->source, $view->vars['row']);
            $subView->vars['compound'] = true;

            $cells[$name] = $subView;
        }

        return $view->value = $cells;
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the RollerworksDatagrid package.
 *
 * (c) Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Rollerworks\Component\Datagrid\Extension\Core\Type;

use Rollerworks\Component\Datagrid\Column\AbstractType;
use Rollerworks\Component\Datagrid\Column\ColumnInterface;
use Rollerworks\Component\Datagrid\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer;
use Symfony\Component\OptionsResolver\OptionsResolver;

class NumberType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildColumn(ColumnInterface $column, array $options)
    {
        $column->setViewTransformer(new NumberToLocalizedStringTransformer(
            $options['precision'],
            $options['grouping'],
            $options['rounding_mode']
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            // default precision is locale specific (usually around 3)
            'precision' => null,
            'grouping' => false,
            'rounding_mode' => \NumberFormatter::ROUND_HALFUP,
        ]);

        $resolver->setAllowedValues(
            'rounding_mode',
            [
                \NumberFormatter::ROUND_FLOOR,
                \NumberFormatter::ROUND_DOWN,
                \NumberFormatter::ROUND_HALFDOWN,
                \NumberFormatter::ROUND_HALFEVEN,
                \NumberFormatter::ROUND_HALFUP,
                \NumberFormatter::ROUND_UP,
                \NumberFormatter::ROUND_CEILING,
            ]
        );
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the RollerworksDatagrid package.
 *
 * (c) Sebastiaan Stok <s.stok@rollerscapes.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Rollerworks\Component\Datagrid;

use Rollerworks\Component\Datagrid\Column\ColumnInterface;
use Rollerworks\Component\Datagrid\Column\ColumnTypeRegistryInterface;
use Rollerworks\Component\Datagrid\Util\StringUtil;

/**
 * @author Sebastiaan Stok <s.stok@rollerscapes.net>
 */
class DatagridFactory implements DatagridFactoryInterface
{
    private $typeRegistry;
    private $datagridRegistry;

    public function __construct(ColumnTypeRegistryInterface $typeRegistry, DatagridRegistryInterface $datagridRegistry)
    {
        $this->typeRegistry = $typeRegistry;
        $this->datagridRegistry = $datagridRegistry;
    }

    /**
     * {@inheritdoc}
     */
    public function createDatagrid($configurator, string $name = null, array $options = []): DatagridInterface
    {
        if (!$configurator instanceof DatagridConfiguratorInterface) {
            $configurator = $this->datagridRegistry->getConfigurator($configurator);
        }

        if (null === $name) {
            $name = StringUtil::fqcnToBlockPrefix(get_class($configurator));
        }

        $builder = $this->createDatagridBuilder();
        $configurator->buildDatagrid($builder, $options);

        return $builder->getDatagrid($name);
    }

    /**
     * {@inheritdoc}
     */
    public function createDatagridBuilder(): DatagridBuilderInterface
    {
        return new DatagridBuilder($this);
    }

    /**
     * {@inheritdoc}
     */
    public function createColumn(string $name, string $type, array $options = []): ColumnInterface
    {
        $type = $this->typeRegistry->getType($type);

        $column = $type->createColumn($name, $options);

        // Explicitly call buildType() in order to be able to override either
        // createColumn() or buildType() in the resolved column type.
        $type->buildType($column, $column->getOptions());

        return $column;
    }
}