<?php

namespace Silvanite\Brandenburg\Providers;

use Gate;
use Illuminate\Routing\Router;
use Silvanite\Brandenburg\Policy;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\ServiceProvider;
use Illuminate\Config\Repository as Config;

class BrandenburgServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot(Router $router, Kernel $kernel)
    {
        $this->publishes([
            __DIR__.'/../Config/brandenburg.php' => config_path('brandenburg.php'),
        ], 'brandenburg-config');

        if (!$this->app->make(Config::class)->get('brandenburg.ignoreMigrations', false)) {
            $this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
        } else {
            $this->publishes([
                __DIR__.'/../Database/Migrations' => database_path('migrations'),
            ], 'brandenburg-migrations');
        }

        $this->registerPolicy();
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        $this->mergeConfigFrom(
            __DIR__.'/../Config/brandenburg.php', 'brandenburg'
        );
    }

    /**
     * Register the Policies module as a Facade
     *
     * @param string $IoC name of the container
     * @return Silvanite\Brandenburg\Policy
     */
    private function registerPolicy($container = "BrandenburgPolicy")
    {
        $this->app->bind($container, function(){
            return new Policy;
        });
    }
}


<?php

namespace Silvanite\Brandenburg\Traits;

use Silvanite\Brandenburg\Role;

trait HasRoles
{
    /**
     * Get all Roles given to this user
     *
     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
     */
    public function roles()
    {
        return $this->belongsToMany(Role::class)->with('getPermissions');
    }

    /**
     * Scope a query to eager load `roles` relationship
     * to reduce database queries
     *
     * @param \Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopeWithRoles($query)
    {
        return $query->with('roles');
    }

    /**
     * Determine if any of the assigned roles to this user
     * have a specific permission
     *
     * @param string $permission
     * @return boolean
     */
    public function hasRoleWithPermission($permission)
    {
        return $this->roles->contains(function ($role) use ($permission) {
            return $role->getPermissions->contains('permission_slug', $permission);
        });
    }

    /**
     * Assign a role to this user
     *
     * @param string|Role $role
     * @return boolean
     */
    public function assignRole($role)
    {
        if (is_string($role)) {
            return $this->roles()->attach(Role::where('slug', $role)->first());
        }

        return $this->roles()->attach($role);
    }

    /**
     * Remove a role from this user
     *
     * @param string|Role $role
     * @return boolean
     */
    public function removeRole($role)
    {
        if (is_string($role)) {
            return $this->roles()->detach(Role::where('slug', $role)->first());
        }

        return $this->roles()->detach($role);
    }

    /**
     * Reassign roles from an id or an array of role Ids
     *
     * @param int|array $roles
     * @return void
     */
    public function setRolesById($roles)
    {
        $roles = is_array($roles)? $roles : [$roles];
        return $this->roles()->sync($roles);
    }
}


<?php

namespace Silvanite\Brandenburg;

use Silvanite\Brandenburg\Policy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'slug',
        'name',
        'permissions',
    ];

    /**
     * The attributes which should be extended to the model
     *
     * @var array
     */
    protected $appends = [
        'permissions',
    ];

    /**
     * Cast attributes to their correct types
     *
     * @var array
     */
    protected $casts = [
        'permissions' => 'array',
    ];

    /**
     * Get all users which are assigned a specific role
     *
     * @return Illuminate\Support\Collection
     */
    public function users()
    {
        return $this->belongsToMany(config('brandenburg.userModel'));
    }

    /**
     * Returns all Permissions for this Role
     *
     * @return Illuminate\Support\Collection
     */
    public function getPermissions()
    {
        return $this->hasMany(Permission::class);
    }

    /**
     * Replace all existing permissions with a new set of permissions
     *
     * @param array $permissions
     * @return void
     */
    public function setPermissions(array $permissions)
    {
        if (!$this->id) {
            $this->save();
        }

        $this->revokeAll();

        collect($permissions)->map(function ($permission) {
            $this->grant($permission);
        });
    }

    /**
     * Check if a user has a given permission
     *
     * @param string $permission
     * @return boolean
     */
    public function hasPermission($permission)
    {
        return $this->getPermissions->contains('permission_slug', $permission);
    }

    /**
     * Give Permission to a Role
     *
     * @param string $permission
     * @return boolean
     */
    public function grant($permission)
    {
        if ($this->hasPermission($permission)) {
            return true;
        }

        if (!array_key_exists($permission, Gate::abilities())) {
            abort(403, 'Unknown permission');
        }

        return Permission::create([
            'role_id' => $this->id,
            'permission_slug' => $permission,
        ]);

        return false;
    }

    /**
     * Revokes a Permission from a Role
     *
     * @param string $permission
     * @return boolean
     */
    public function revoke($permission)
    {
        if (is_string($permission)) {
            return Permission::findOrFail($permission)->delete();
        }

        return false;
    }

    /**
     * Remove all permissions from this Role
     *
     * @return void
     */
    public function revokeAll()
    {
        return $this->getPermissions()->delete();
    }

    /**
     * Get a list of permissions
     *
     * @return array
     */
    public function getPermissionsAttribute()
    {
        return Permission::where('role_id', $this->id)->get()->pluck('permission_slug')->toArray();
    }

    /**
     * Replace all existing permissions with a new set of permissions
     *
     * @param array $permissions
     * @return void
     */
    public function setPermissionsAttribute(array $permissions)
    {
        if (!$this->id) {
            $this->save();
        }

        $this->revokeAll();

        collect($permissions)->map(function ($permission) {
            if (!in_array($permission, Policy::all())) {
                return;
            }

            $this->grant($permission);
        });
    }
}


<?php

namespace Upyun\Api;

use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use Upyun\Config;
use Upyun\Signature;
use Upyun\Util;

class Rest
{
    /**
     * @var Config
     */
    protected $config;

    protected $endpoint;
    protected $method;
    protected $storagePath;
    public $headers = [];

    /**
     * @var Psr7\Stream
     */
    protected $file;

    public function __construct(Config $config)
    {
        $this->config   = $config;
        $this->endpoint = $config->getProtocol() . Config::$restApiEndPoint . '/' . $config->serviceName;
    }

    public function request($method, $storagePath)
    {
        $this->method = strtoupper($method);
        $this->storagePath = '/' . ltrim($storagePath, '/');
        return $this;
    }

    /**
     * @param string|resource $file
     *
     * @return $this
     */
    public function withFile($file)
    {
        $stream = Psr7\stream_for($file);
        $this->file = $stream;

        return $this;
    }

    /**
     * @return mixed|\Psr\Http\Message\ResponseInterface
     */
    public function send()
    {
        $client = new Client([
            'timeout' => $this->config->timeout,
        ]);

        $url = $this->endpoint . $this->storagePath;
        $body = null;
        if ($this->file && $this->method === 'PUT') {
            $body = $this->file;
        }

        $request = new Psr7\Request(
            $this->method,
            Util::encodeURI($url),
            $this->headers,
            $body
        );
        $authHeader = Signature::getHeaderSign($this->config,
            $this->method,
            $request->getUri()->getPath()
        );
        foreach ($authHeader as $head => $value) {
            $request = $request->withHeader($head, $value);
        }
        $response = $client->send($request, [
            'debug' => $this->config->debug
        ]);

        return $response;
    }

    public function withHeader($header, $value)
    {
        $header = strtolower(trim($header));

        $this->headers[$header] = $value;
        return $this;
    }

    public function withHeaders($headers)
    {
        if (is_array($headers)) {
            foreach ($headers as $header => $value) {
                $this->withHeader($header, $value);
            }
        }
        return $this;
    }

    public function toRequest()
    {
        $url = $this->endpoint . $this->storagePath;
        $body = null;
        if ($this->file && $this->method === 'PUT') {
            $body = $this->file;
        }

        $request = new Psr7\Request(
            $this->method,
            Util::encodeURI($url),
            $this->headers,
            $body
        );
        $authHeader = Signature::getHeaderSign($this->config,
            $this->method,
            $request->getUri()->getPath()
        );
        foreach ($authHeader as $head => $value) {
            $request = $request->withHeader($head, $value);
        }
        return $request;
    }
}


<?php
namespace Upyun;

class Util
{
    public static function trim($str)
    {
        if (is_array($str)) {
            return array_map(array('Util', 'trim'), $str);
        } else {
            return trim($str);
        }
    }

    public static function getHeaderParams($headers, $otherParams = array())
    {
        $params = [];
        $otherParams = array_map('strtolower', $otherParams);
        foreach ($headers as $header => $value) {
            $header = strtolower($header);
            if (strpos($header, 'x-upyun-') !== false) {
                $params[$header] = $value[0];
            } else if (in_array($header, $otherParams)) {
                $params[$header] = $value[0];
            }
        }
        return $params;
    }

    public static function parseDir($body)
    {
        $files = array();
        if (!$body) {
            return array();
        }

        $lines = explode("\n", $body);
        foreach ($lines as $line) {
            $file = [];
            list($file['name'], $file['type'], $file['size'], $file['time']) = explode("\t", $line, 4);
            $files[] = $file;
        }

        return $files;
    }

    public static function base64Json($params)
    {
        return base64_encode(json_encode($params, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    }

    public static function stringifyHeaders($headers)
    {
        $return = array();
        foreach ($headers as $key => $value) {
            $return[] = "$key: $value";
        }
        return $return;
    }

    public static function md5Hash($resource)
    {
        rewind($resource);
        $ctx = hash_init('md5');
        hash_update_stream($ctx, $resource);
        $md5 = hash_final($ctx);
        return $md5;
    }

    /**
     * GuzzleHttp\Psr\Uri use `parse_url`，not good for utf-8,
     * So should `encodeURI` first, before `new Psr7\Request`
     * @see http://stackoverflow.com/questions/4929584/encodeuri-in-php
     */
    public static function encodeURI($url)
    {
        $unescaped = array(
            '%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%7E'=>'~',
            '%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')'
        );
        $reserved = array(
            '%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':',
            '%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$'
        );
        $score = array(
            '%23'=>'#'
        );
        return strtr(rawurlencode($url), array_merge($reserved, $unescaped, $score));
    }
}


<?php
namespace Upyun;

/**
 * Class Signature
 * @package Upyun
 */
class Signature
{
    /**
     * 获取分块上传接口的签名
     */
    const SIGN_MULTIPART     = 1;
    /**
     * 生成视频处理接口的签名
     */
    const SIGN_VIDEO         = 2;
    /**
     * 生成视频处理接口的签名（不需要操作员时使用）
     */
    const SIGN_VIDEO_NO_OPERATOR   = 3;

    /**
     * 获取 Header 签名需要的请求头
     *
     * @param Config $serviceConfig
     * @param $method 请求方法
     * @param $path  请求路径
     * @param $contentMd5 文件内容 md5
     *
     * @return array
     */
    public static function getHeaderSign($serviceConfig, $method, $path, $contentMd5 = null)
    {
        $gmtDate = gmdate('D, d M Y H:i:s \G\M\T');

        $policy = null;
        $sign = self::getBodySignature($serviceConfig, $method, $path, $gmtDate, $policy, $contentMd5);

        $headers = array(
            'Authorization' => $sign,
            'Date' => $gmtDate,
            'User-agent' => 'Php-Sdk/' . $serviceConfig->getVersion()
        );
        return $headers;
    }

    /**
     * 获取请求缓存刷新接口需要的签名头
     *
     * @param Config $serviceConfig
     * @param $urlString
     *
     * @return array
     */
    public static function getPurgeSignHeader(Config $serviceConfig, $urlString)
    {
        $gmtDate = gmdate('D, d M Y H:i:s \G\M\T');
        $sign = md5("$urlString&{$serviceConfig->serviceName}&$gmtDate&{$serviceConfig->operatorPassword}");
        return array(
            'Authorization' => "UpYun {$serviceConfig->serviceName}:{$serviceConfig->operatorName}:$sign",
            'Date' => $gmtDate,
            'User-agent' => 'Php-Sdk/' . $serviceConfig->getVersion() . ' (purge api)'
        );
    }

    /**
     * 获取表单 API 需要的签名，依据 body 签名规则计算
     * @param Config $serviceConfig
     * @param $method 请求方法
     * @param $uri 请求路径
     * @param $date 请求时间
     * @param $policy
     * @param $contentMd5 请求 body 的 md5
     *
     * @return array
     */
    public static function getBodySignature(Config $serviceConfig, $method, $uri, $date = null, $policy = null, $contentMd5 = null)
    {
        $data = array(
            $method,
            $uri
        );
        if ($date) {
            $data[] = $date;
        }

        if ($policy) {
            $data[] = $policy;
        }

        if ($contentMd5) {
            $data[] = $contentMd5;
        }
        $signature = base64_encode(hash_hmac('sha1', implode('&', $data), $serviceConfig->operatorPassword, true));
        return 'UPYUN ' . $serviceConfig->operatorName . ':' . $signature;
    }
}


<?php
/**
 * This is project's console commands configuration for Robo task runner.
 *
 * @see http://robo.li/
 */
class RoboFile extends \Robo\Tasks
{
    /**
     * Creates release zip
     *
     * @param string $version
     * @see https://gist.github.com/Rarst/5a8a65478755539770df653c4575219a
     */
    public function release($version)
    {
        $package = 'upyun/sdk';
        $name = 'php-sdk';
        $collection = $this->collectionBuilder();
        $workingPath = __DIR__ . DIRECTORY_SEPARATOR . $collection->workDir("release");
        $collection->taskExec("composer create-project {$package} {$name} {$version}")
             ->dir($workingPath)
             ->arg('--prefer-dist')
             ->arg('--no-dev')
             ->arg('-vvv')
             ->taskExec('composer dump-autoload --optimize')
             ->dir($workingPath . DIRECTORY_SEPARATOR . $name)
             ->arg('-vvv');
        $collection->run();

        $zipFile = "release/{$name}-{$version}.zip";
        $this->_remove($zipFile);
        $this->taskPack($zipFile)
            ->addDir("php-sdk", __DIR__ . "/release/php-sdk")
            ->run();
        $this->_deleteDir("release/$name");
    }
}


<?php
namespace Upyun;

use Upyun\Api\Rest;
use Upyun\Api\Form;
use GuzzleHttp\Psr7;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;

class Uploader
{
    /**
     * @var Config
     */
    protected $config;

    protected $useBlock = false;

    public function __construct(Config $config)
    {
        $this->config = $config;
    }

    public function upload($path, $file, $params, $withAsyncProcess)
    {
        $stream = Psr7\stream_for($file);
        $size = $stream->getSize();
        $useBlock = $this->needUseBlock($size);

        if ($withAsyncProcess) {
            $req = new Form($this->config);
            return $req->upload($path, $stream, $params);
        }

        if (! $useBlock) {
            $req = new Rest($this->config);
            return $req->request('PUT', $path)
                       ->withHeaders($params)
                       ->withFile($stream)
                       ->send();
        } elseif ($this->config->uploadType === 'BLOCK_PARALLEL') {
            return $this->concurrentPointUpload($path, $stream, $params);
        } else {
            return $this->pointUpload($path, $stream, $params);
        }
    }

    /**
     * 串行式断点续传
     * @param $path
     * @param $stream
     * @param $params
     *
     * @return mixed|\Psr\Http\Message\ResponseInterface
     * @throws \Exception
     */
    private function pointUpload($path, $stream, $params)
    {
        $req = new Rest($this->config);
        $headers = array();
        if (is_array($params)) {
            foreach ($params as $key => $val) {
                $headers['X-Upyun-Meta-' . $key] = $val;
            }
        }
        $res = $req->request('PUT', $path)
            ->withHeaders(array_merge(array(
                'X-Upyun-Multi-Stage' => 'initiate',
                'X-Upyun-Multi-Type' => Psr7\mimetype_from_filename($path),
                'X-Upyun-Multi-Length' => $stream->getSize(),
            ), $headers))
            ->send();
        if ($res->getStatusCode() !== 204) {
            throw new \Exception('init request failed when poinit upload!');
        }

        $init      = Util::getHeaderParams($res->getHeaders());
        $uuid      = $init['x-upyun-multi-uuid'];
        $blockSize = 1024 * 1024;
        $partId    = 0;
        do {
            $fileBlock = $stream->read($blockSize);
            $res = $req->request('PUT', $path)
                ->withHeaders(array(
                    'X-Upyun-Multi-Stage' => 'upload',
                    'X-Upyun-Multi-Uuid' => $uuid,
                    'X-Upyun-Part-Id' => $partId
                ))
                ->withFile(Psr7\stream_for($fileBlock))
                ->send();

            if ($res->getStatusCode() !== 204) {
                throw new \Exception('upload request failed when poinit upload!');
            }
            $data   = Util::getHeaderParams($res->getHeaders());
            $partId = $data['x-upyun-next-part-id'];
        } while ($partId != -1);

        $res = $req->request('PUT', $path)
            ->withHeaders(array(
                'X-Upyun-Multi-Uuid' => $uuid,
                'X-Upyun-Multi-Stage' => 'complete'
            ))
            ->send();

        if ($res->getStatusCode() != 204 && $res->getStatusCode() != 201) {
            throw new \Exception('end request failed when poinit upload!');
        }
        return $res;
    }

    private function needUseBlock($fileSize)
    {
        if ($this->config->uploadType === 'BLOCK' ||
            $this->config->uploadType === 'BLOCK_PARALLEL') {
            return true;
        } elseif ($this->config->uploadType === 'AUTO' &&
                  $fileSize >= $this->config->sizeBoundary) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 并行式断点续传
     * @param $path
     * @param $stream
     * @param $params
     *
     * @return mixed|\Psr\Http\Message\ResponseInterface
     * @throws \Exception
     */
    private function concurrentPointUpload($path, $stream, $params)
    {
        $req = new Rest($this->config);

        $headers = array();
        if (is_array($params)) {
            foreach ($params as $key => $val) {
                $headers['X-Upyun-Meta-' . $key] = $val;
            }
        }
        $res = $req->request('PUT', $path)
            ->withHeaders(array_merge(array(
                'X-Upyun-Multi-Disorder' => 'true',
                'X-Upyun-Multi-Stage' => 'initiate',
                'X-Upyun-Multi-Type' => Psr7\mimetype_from_filename($path),
                'X-Upyun-Multi-Length' => $stream->getSize(),
            ), $headers))
            ->send();
        if ($res->getStatusCode() !== 204) {
            throw new \Exception('init request failed when poinit upload!');
        }

        $init = Util::getHeaderParams($res->getHeaders());
        $uuid = $init['x-upyun-multi-uuid'];
        $requests = function ($req, $path, $stream, $uuid) {
            $blockSize = 1024 * 1024;
            $total = ceil($stream->getSize() / $blockSize);
            for ($i = 0; $i < $total; $i++) {
                $fileBlock = $stream->read($blockSize);
                yield $req->request('PUT', $path)
                    ->withHeaders(array(
                        'X-Upyun-Multi-Stage' => 'upload',
                        'X-Upyun-Multi-Uuid' => $uuid,
                        'X-Upyun-Part-Id' => $i
                    ))
                    ->withFile(Psr7\stream_for($fileBlock))
                    ->toRequest();
            }
        };
        $client = new Client([
            'timeout' => $this->config->timeout,
        ]);
        $pool = new Pool($client, $requests($req, $path, $stream, $uuid), [
            'concurrency' => $this->config->concurrency,
            'fulfilled' => function ($res) {
                if ($res->getStatusCode() !== 204) {
                    throw new \Exception('upload request failed when poinit upload!');
                }
            },
            'rejected' => function () {
                throw new \Exception('upload request failed when poinit upload!');
            },
        ]);
        $promise = $pool->promise();
        $promise->wait();

        $res = $req->request('PUT', $path)
            ->withHeaders(array(
                'X-Upyun-Multi-Uuid' => $uuid,
                'X-Upyun-Multi-Stage' => 'complete'
            ))
            ->send();
        if ($res->getStatusCode() != 204 && $res->getStatusCode() != 201) {
            throw new \Exception('end request failed when poinit upload!');
        }
        return $res;
    }
}


<?php
/**
 * 又拍云 PHP-SDK
 */
namespace Upyun;

use Upyun\Api\Rest;

use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use GuzzleHttp;

/**
 * Class Upyun
 *
 * 又拍云云存储、云处理接口
 *
 * Upyun 类实现了又拍云云存储和云处理的所有接口，通过该类可以实现文件上传、下载；图片视频等多媒体资源云处理。
 * 本文档中，提到的"服务"是指又拍云文件加速回又拍云源类型的服务（即原先的存储类空间）。
 *
 * @package Upyun
 */
class Upyun
{

    /**
     * @var Config: 服务配置
     */
    protected $config;

    // 异步云处理任务类型
    /**
     * @var string 异步音视频处理
     */
    public static $PROCESS_TYPE_MEDIA = 'media';
    /**
     * @var string 文件压缩
     */
    public static $PROCESS_TYPE_ZIP = 'zip-file';
    /**
     * @var string 解压缩
     */
    public static $PROCESS_TYPE_UNZIP = 'unzip-file';
    /**
     * @var string 文件拉取
     */
    public static $PROCESS_TYPE_SYNC_FILE = 'sync-remote-file-to-upyun';
    /**
     * @var string 文档转换
     */
    public static $PROCESS_TYPE_CONVERT = 'document-type-convert';
    /**
     * @var string 异步图片拼接
     */
    public static $PROCESS_TYPE_STITCH = 'picture-stitch';

    /**
     * Upyun constructor.
     *
     * @param Config $config 服务配置
     */
    public function __construct(Config $config)
    {
        $this->setConfig($config);
    }

    /**
     * 配置服务信息
     *
     * 当需要操作的新的服务时，使用该方法传入新的服务配置即可
     *
     * @param Config $config 服务配置
     *
     * @return $this
     */
    public function setConfig(Config $config)
    {
        $this->config = $config;
        return $this;
    }

    /**
     * 上传一个文件到又拍云存储
     *
     * 上传的文件格式支持文件流或者字符串方式上传。除简单的文件上传外，针对多媒体资源（图片、音视频），还可以设置同步/异步预处理多媒体资源，例如：图片的裁剪缩放，音视频的转码截图等等众多又拍云强大的云处理功能
     *
     * @param string $path 被上传的文件在又拍云存储服务中保存的路径
     * @param string|resource $content 被上传的文件内容（字符串），或者打开该文件获得的文件句柄（文件流）。当上传本地大文件时，推荐使用文件流的方式上传
     * @param array $params 上传文件时，附加的自定义参数。支持 Content-MD5 Content-Type Content-Secret 等，详见 [上传参数](http://docs.upyun.com/api/rest_api/#_2)，例如：
     * - 设置文件[保护秘钥](http://docs.upyun.com/api/rest_api/#Content-Secret) `write($path, $content, array('Content-Secret' => 'my-secret'))`；
     * - 添加[文件元信息](http://docs.upyun.com/api/rest_api/#metadata) `write($path, $content, array('X-Upyun-Meta-Foo' =>
     * 'bar'))`
     * - [图片同步预处理](http://docs.upyun.com/cloud/image/#_5) `write($path, $content, array('x-gmkerl-thumb' => '/format/png'))`
     * @param bool $withAsyncProcess  默认为 `false`，当上传图片或者音视频资源时，可以设置该参数为 `true`，开启图片音视频的[异步处理功能](http://docs.upyun.com/api/form_api/#_6) ，例如：
     *```
     * // 以下参数会将新上传的图片，再异步生成另一份 png 格式的图片，原图不受影响
     * write($path, $content, array(
     *    'apps' => array(
     *        array(
     *            'name' => 'thumb',         //异步图片处理任务
     *            'x-gmkerl-thumb' => '/format/png', // 格式化图片为 png 格式
     *            'save_as': '/iamge/png/new.png',   // 处理成功后的图片保存路径
     *            'notify_url': 'http://your.notify.url'  // 异步任务完成后的回调地址
     *        )
     *    )
     * ), true);
     *```
     *
     *
     *
     * @return array|bool 若文件是图片则返回图片基本信息，如：`array('x-upyun-width' => 123, 'x-upyun-height' => 50, 'x-upyun-frames'
     * => 1, 'x-upyun-file-type' => 'JPEG')`，否则返回空数组。当使用异步预处理功能时，返回结果为布尔值，成功为 `true`。
     *
     * @throws \Exception 上传失败时，抛出异常
     */
    public function write($path, $content, $params = array(), $withAsyncProcess = false)
    {
        if (!$content) {
            throw new \Exception('write content can not be empty.');
        }

        $upload = new Uploader($this->config);
        $response = $upload->upload($path, $content, $params, $withAsyncProcess);
        if ($withAsyncProcess) {
            return $response;
        }
        return Util::getHeaderParams($response->getHeaders());
    }

    /**
     * 读取云存储文件/目录内容
     *
     * @param string $path 又拍云存储中的文件或者目录路径
     * @param resource $saveHandler 文件内容写入本地文件流。例如 `$saveHandler = fopen('/local/file', 'w')
     * `。当设置该参数时，将以文件流的方式，直接将又拍云中的文件写入本地的文件流，或其他可以写入的流
     * @param array $params  可选参数，读取目录内容时，需要设置三个参数： `X-List-Iter` 分页开始位置（第一页不需要设置），`X-List-Limit` 获取的文件数量（默认 100，最大
     * 10000），`X-List-Order` 结果以时间正序或者倒序
     *
     * @return mixed $return 当读取文件且没有设置 `$saveHandler` 参数时，返回一个字符串类型，表示文件内容；设置了 `$saveHandler` 参数时，返回布尔值
     * `true`。当读取目录时，返回一个数组，表示目录下的文件列表。目录下文件内容过多时，需要通过判断返回数组中的 `is_end` 属性，进行分页读取内容
     *
     * @throws \Exception
     */
    public function read($path, $saveHandler = null, $params = array())
    {
        $req = new Rest($this->config);
        $response = $req->request('GET', $path)
            ->withHeaders($params)
            ->send();

        $params = Util::getHeaderParams($response->getHeaders());

        if (! isset($params['x-upyun-list-iter'])) {
            if (is_resource($saveHandler)) {
                Psr7\copy_to_stream($response->getBody(), Psr7\stream_for($saveHandler));
                return true;
            } else {
                return $response->getBody()->getContents();
            }
        } else {
            $files = Util::parseDir($response->getBody()->getContents());
            return array('files' => $files, 'is_end' => $params['x-upyun-list-iter'] === 'g2gCZAAEbmV4dGQAA2VvZg', 'iter' => $params['x-upyun-list-iter']);
        }
    }

    /**
     * 判断文件是否存在于又拍云存储
     *
     * 注意: 对刚删除的文件, 立即调用该方法可能会返回 true, 因为服务端执行删除操作后可能会有很短暂的延迟.
     *
     * @param string $path 云存储的文件路径
     *
     * @return bool 存在时返回 `true`，否则返回 `false`
     * @throws \Exception
     */
    public function has($path)
    {
        $req = new Rest($this->config);
        try {
            $req->request('HEAD', $path)
                            ->send();
        } catch (GuzzleHttp\Exception\BadResponseException $e) {
            $statusCode = $e->getResponse()->getStatusCode();
            if ($statusCode === 404) {
                return false;
            } else {
                throw $e;
            }
        }

        return true;
    }

    /**
     * 获取云存储文件/目录的基本信息
     *
     * @param string $path 云存储的文件路径
     * @param array $otherHeaders 设置了后，方法将返回其他 http header 中的信息，默认为空
     *
     * @return array 返回一个数组，默认包含以下 key
     * - `x-upyun-file-type` 当 $path 是目录时，值为 *folder*，当 $path 是文件时，值为 *file*，
     * - `x-upyun-file-size` 文件大小
     * - `x-upyun-file-date` 文件的创建时间
     */
    public function info($path, $otherHeaders = array())
    {
        $req = new Rest($this->config);
        $response = $req->request('HEAD', $path)
                        ->send();
        return Util::getHeaderParams($response->getHeaders(), $otherHeaders);
    }

    /**
     * 获取文件的文档类型
     * @param string $path 云存储文件路径
     * @return string 文档类型，e.g: `appcation/json`，获取失败返回空字符串
     */
    public function getMimetype($path)
    {
        $params = $this->info($path, array('content-type'));
        if (isset($params['content-type'])) {
            return explode(';', $params['content-type'])[0];
        }
        return '';
    }

    /**
     * 删除文件或者目录
     *
     * @param string $path 文件或目录在又拍云存储的路径
     * @param bool $async 是否异步删除，默认为 false，表示同步删除。当需要批量删除大量文件时，必须选择异步删除
     *
     * @return bool 删除成功返回 true，否则 false
     * @throws \Exception 删除不存在的文件将会抛出异常
     */
    public function delete($path, $async = false)
    {
        $req = new Rest($this->config);
        $req->request('DELETE', $path);
        if ($async) {
            $req->withHeader('x-upyun-async', 'true');
        }
        $res = $req->send();
        return $res->getStatusCode() === 200;
    }

    /**
     * 创建目录
     *
     * @param string $path 需要在又拍云存储创建的目录路径
     *
     * @return bool 创建成功返回 true，否则返回 false
     * @throws \Exception
     */
    public function createDir($path)
    {
        $path = rtrim($path, '/') . '/';
        $req = new Rest($this->config);
        $res = $req->request('POST', $path)
            ->withHeader('folder', 'true')
            ->send();
        return $res->getStatusCode() === 200;
    }

    /**
     * 删除文件或者目录
     *
     * @param string $path 需要被删除的云存储文件或目录路径
     *
     * @return bool 成功返回 true，否则 false
     * @throws \Exception
     */
    public function deleteDir($path)
    {
        return $this->delete($path);
    }

    /**
     * 获取目录下存储使用量
     *
     * @param string $path 云存储目录路径，默认为根目录，表示整个云存储服务使用的空间大小
     * @return string 存储使用量，单位字节
     * @throws \Exception
     */
    public function usage($path = '/')
    {
        $path = rtrim($path, '/') . '/';
        $req = new Rest($this->config);
        $response = $req->request('GET', $path . '?usage')
            ->send();

        return $response->getBody()->getContents();
    }

    /**
     * 刷新缓存
     *
     * @param array|string $urls 需要刷新的文件 url 列表
     *
     * @return array 刷新失败的 url 列表，若全部刷新成功则为空数组
     */
    public function purge($urls)
    {
        $urlString = $urls;
        if (is_array($urls)) {
            $urlString = implode("\n", $urls);
        }

        $client = new Client([
            'timeout' => $this->config->timeout
        ]);
        $response = $client->request('POST', Config::ED_PURGE, [
            'headers' =>  Signature::getPurgeSignHeader($this->config, $urlString),
            'form_params' => ['purge' => $urlString]
        ]);
        $result = json_decode($response->getBody()->getContents(), true);
        return $result['invalid_domain_of_url'];
    }

    /**
     * 异步云处理
     *
     * 该方法是基于[又拍云云处理](http://docs.upyun.com/cloud/) 服务实现，可以实现音视频的转码、切片、剪辑；文件的压缩解压缩；文件拉取功能
     *
     * 注意：
     * - 所有需要调用该方法处理的资源，必须已经上传到云存储服务
     * - 使用 `process` 之前，必须配置 `config->processNotifyUrl`，否则会提交任务失败
     *
     * 例如视频转码：
     * ```
     *  process(array(
     *    array(
     *        'type' => 'video',  // video 表示视频任务, audio 表示音频任务
     *        'avopts' => '/s/240p(4:3)/as/1/r/30', // 处理参数，`s` 表示输出的分辨率，`r` 表示视频帧率，`as` 表示是否自动调整分辨率
     *        'save_as' => '/video/240/new.mp4', // 新视频在又拍云存储的保存路径
     *    ),
     *    ... // 同时还可以添加其他任务
     * ), Upyun::$PROCESS_TYPE_MEDIA, $source)
     * ```
     *
     * @param array $tasks 需要处理的任务
     * @param string $type 异步云处理任务类型，可选值:
     * - `Upyun::$PROCESS_TYPE_MEDIA` 异步音视频处理
     * - `Upyun::$PROCESS_TYPE_ZIP` 文件压缩
     * - `Upyun::$PROCESS_TYPE_UNZIP` 文件解压
     * - `Upyun::$PROCESS_TYPE_SYNC_FILE` 文件拉取
     * - `Upyun::$PROCESS_TYPE_STITCH` 图片拼接
     * @param string $source 可选参数，处理异步音视频任务时，需要传递该参数，表示需要处理的文件路径
     *
     * @return array 任务 ID，提交了多少任务，便会返回多少任务 ID，与提交任务的顺序保持一致。可以通过任务 ID 查询处理进度。格式如下：
     * ```
     * array(
     * '35f0148d414a688a275bf915ba7cebb2',
     * '98adbaa52b2f63d6d7f327a0ff223348',
     * )
     * ```
     * @throws \Exception
     */
    public function process($tasks, $type, $source = '')
    {
        $video = new Api\Pretreat($this->config);

        $options = array();
        switch($type) {
            case self::$PROCESS_TYPE_MEDIA:
                $options['accept'] = 'json';
                $options['source'] = $source;
                break;
            case self::$PROCESS_TYPE_ZIP:
                $options['app_name'] = 'compress';
                break;
            case self::$PROCESS_TYPE_UNZIP:
                $options['app_name'] = 'depress';
                break;
            case self::$PROCESS_TYPE_SYNC_FILE:
                $options['app_name'] = 'spiderman';
                break;
            case self::$PROCESS_TYPE_CONVERT:
                $options['app_name'] = 'uconvert';
                break;
            case self::$PROCESS_TYPE_STITCH:
                $options['app_name'] = 'jigsaw';
                break;
            default:
                throw new \Exception('upyun - not support process type.');

        }
        return $video->process($tasks, $options);
    }

    /**
     * 查询异步云处理任务进度
     *
     * 根据 `process` 方法返回的任务 ID，通过该访问查询处理进度
     *
     * @param array $taskIds 任务 ID
     *
     * @return bool|array 查询失败返回布尔值 `false`，否则返回每个任务的百分比进度信息，格式如下：
     * ```
     * array(
     *     '35f0148d414a688a275bf915ba7cebb2' => 100,  // 100 表示任务完成
     *     'c3103189fa906a5354d29bd807e8dc51' => 35,
     *     '98adbaa52b2f63d6d7f327a0ff223348' => null, // null 表示任务未开始，或异常
     * )
     * ```
     */
    public function queryProcessStatus($taskIds)
    {
        $video = new Api\Pretreat($this->config);
        return $video->query($taskIds, '/status/');
    }

    /**
     *  查询异步云处理任务结果
     *
     * 根据 `process` 方法返回的任务 ID，通过该访问查询处理结果，会包含每个任务详细信息
     * @param array $taskIds 任务 ID
     *
     * @return bool|mixed 查询失败返回 `false`，否则返回每个任务的处理结果，格式如下：
     * ```
     * array(
     *    '9d9c32b63a1034834e77672c6f51f661' => array(
     *         'path' => array('/v2.mp4'),
     *         'signature' => '4042c1f07f546d28',
     *         'status_code' => 200,
     *         'service_name' => 'your_storage_service',
     *         'description' => 'OK',
     *         'task_id' => '9d9c32b63a1034834e77672c6f51f661',
     *         'timestamp' => 1472010684
     *    )
     * )
     * ```
     */
    public function queryProcessResult($taskIds)
    {
        $video = new Api\Pretreat($this->config);
        return $video->query($taskIds, '/result/');
    }

    /**
     * 多个 m3u8 文件拼接
     * @param array $files  保存在又拍云云存储中的多个 m3u8 文件路径
     * @param string $saveAs 拼接生成的新 m3u8 文件保存路径
     *
     * @return array 见 [m3u8 拼接 - 响应](http://docs.upyun.com/cloud/sync_video/#_3)
     */
    public function m3u8Concat($files, $saveAs)
    {
        $p = new Api\SyncVideo($this->config);
        return $p->process([
            'm3u8s' => $files,
            'save_as' => $saveAs,
        ], '/m3u8er/concat');
    }

    /**
     * 单个 m3u8 文件剪辑
     * @param string $file 需要剪辑的又拍云云存储中的 m3u8 文件路径
     * @param string $saveAs 剪辑完成后新的 m3u8 文件保存路径
     * @param array $slice 需要被保留或删除的片段。
     * @param bool $isInclude 默认为 `true` 表示 `$slice` 参数描述的片段被保留，否则表示 `$slice` 参数描述的片段被删除
     * @param bool $index 指定 `$slice` 参数的格式，默认为 `false` 表示使用时间范围描述片段，单位秒：`[<开始时间>, <结束时间>]`；`true` 表示使用 `m3u8` 文件的分片序号，从 0 开始，这种方式可以一次对多个片段操作
     *
     * @return array 见 [m3u8 剪辑 - 响应](http://docs.upyun.com/cloud/sync_video/#_6)
     */
    public function m3u8Clip($file, $saveAs, $slice = array(), $isInclude = true, $index = false)
    {
        $p = new Api\SyncVideo($this->config);
        $params = [
            'm3u8' => $file,
            'save_as' => $saveAs,
            'index' => $index,
        ];
        if ($isInclude) {
            $params['include'] = $slice;
        } else {
            $params['exclude'] = $slice;
        }
        return $p->process($params, '/m3u8er/clip');
    }

    /**
     * 获取单个 m3u8 文件描述信息
     * @param string $file 又拍云云存储的中的 m3u8 文件路径
     *
     * @return array 见 [获取 m3u8 信息 - 响应](http://docs.upyun.com/cloud/sync_video/#_6)
     */
    public function m3u8Meta($file)
    {
        $p = new Api\SyncVideo($this->config);
        return $p->process([
            'm3u8' => $file,
        ], '/m3u8er/get_meta');
    }

    /**
     * 视频截图，可以对 mp4、m3u8 等视频文件进行截图
     * @param string $file 需要截图的又拍云云存储中的视频文件路径
     * @param string $saveAs 截图保存路径
     * @param string $point 截图时间点，`HH:MM:SS` 格式
     * @param string $size 截图尺寸 `宽x高` 格式的字符串。默认和视频尺寸一致
     * @param string $format 截图保存的格式，默认根据 `$saveAs` 参数的后缀生成，可以指定 `jpg | png | webp` 三种格式
     *
     * @return array 见 [视频截图 - 响应](http://docs.upyun.com/cloud/sync_video/#m3u8_2)
     */
    public function snapshot($file, $saveAs, $point, $size = '', $format = '')
    {
        $p = new Api\SyncVideo($this->config);
        $params = [
            'source' => $file,
            'save_as' => $saveAs,
            'point' => $point,
        ];
        if ($size) {
            $params['size'] = $size;
        }
        if ($format) {
            $params['format'] = $format;
        }
        return $p->process($params, '/snapshot');
    }

    /**
     * 获取音视频文件元信息
     * @param string $file 又拍云云存储的中的音视频文件路径
     *
     * @return array 见 [获取音视频文件信息 - 响应](http://docs.upyun.com/cloud/sync_video/#_16)
     */
    public function avMeta($file)
    {
        $p = new Api\SyncVideo($this->config);
        return $p->process([
            'source' => $file,
        ], '/avmeta/get_meta');
    }
}


<?php

namespace Davaxi\Sparkline;

/**
 * Class PictureTrait.
 */
class Picture
{
    const DOT_RADIUS_TO_WIDTH = 2;

    /**
     * @var \resource
     */
    protected $resource;

    /**
     * Picture constructor.
     *
     * @param $width
     * @param $height
     */
    public function __construct($width, $height)
    {
        $this->width = $width;
        $this->height = $height;
        $this->resource = imagecreatetruecolor($width, $height);
    }

    /**
     * @param array $setColor
     *
     * @return int
     */
    protected function getBackground(array $setColor = [])
    {
        if ($setColor) {
            return imagecolorallocate(
                $this->resource,
                $setColor[0],
                $setColor[1],
                $setColor[2]
            );
        }

        return imagecolorallocatealpha(
            $this->resource,
            0,
            0,
            0,
            127
        );
    }

    /**
     * @param $lineColor
     *
     * @return int
     */
    public function getLineColor($lineColor)
    {
        return imagecolorallocate(
            $this->resource,
            $lineColor[0],
            $lineColor[1],
            $lineColor[2]
        );
    }

    /**
     * @param array $backgroundColor
     */
    public function applyBackground(array $backgroundColor)
    {
        imagesavealpha($this->resource, true);
        imagefill(
            $this->resource,
            0,
            0,
            $this->getBackground($backgroundColor)
        );
    }

    /**
     * @param $lineThickness
     */
    public function applyThickness($lineThickness)
    {
        imagesetthickness($this->resource, $lineThickness);
    }

    /**
     * @param array $polygon
     * @param array $fillColor
     * @param $count
     */
    public function applyPolygon(array $polygon, array $fillColor, $count)
    {
        if (!$fillColor) {
            return;
        }
        $fillColor = imagecolorallocate($this->resource, $fillColor[0], $fillColor[1], $fillColor[2]);
        imagefilledpolygon($this->resource, $polygon, $count + 2, $fillColor);
    }

    /**
     * @param array $line
     * @param array $lineColor
     */
    public function applyLine(array $line, array $lineColor)
    {
        $lineColor = $this->getLineColor($lineColor);
        foreach ($line as $coordinates) {
            list($pictureX1, $pictureY1, $pictureX2, $pictureY2) = $coordinates;
            imageline($this->resource, $pictureX1, $pictureY1, $pictureX2, $pictureY2, $lineColor);
        }
    }

    /**
     * @param $positionX
     * @param $positionY
     * @param $radius
     * @param array $color
     */
    public function applyDot($positionX, $positionY, $radius, $color)
    {
        if (!$color || !$radius) {
            return;
        }

        $minimumColor = imagecolorallocate(
            $this->resource,
            $color[0],
            $color[1],
            $color[2]
        );
        imagefilledellipse(
            $this->resource,
            $positionX,
            $positionY,
            $radius * static::DOT_RADIUS_TO_WIDTH,
            $radius * static::DOT_RADIUS_TO_WIDTH,
            $minimumColor
        );
    }

    /**
     * @param $width
     * @param $height
     *
     * @return resource
     */
    public function generate($width, $height)
    {
        $sparkline = imagecreatetruecolor($width, $height);
        imagealphablending($sparkline, false);
        imagecopyresampled(
            $sparkline,
            $this->resource,
            0,
            0,
            0,
            0,
            $width,
            $height,
            $this->width,
            $this->height
        );
        imagesavealpha($sparkline, true);
        imagedestroy($this->resource);

        return $sparkline;
    }
}


<?php

namespace Davaxi\Sparkline;

/**
 * Trait StyleTrait.
 */
trait StyleTrait
{
    /**
     * @var array (rgb)
     *            Default: #ffffff
     */
    protected $backgroundColor = [255, 255, 255];

    /**
     * @var array (rgb)
     *            Default: #1388db
     */
    protected $lineColor = [19, 136, 219];

    /**
     * @var array (rgb)
     *            Default: #e6f2fa
     */
    protected $fillColor = [230, 242, 250];

    /**
     * @var float (px)
     *            Default: 1.75px
     */
    protected $lineThickness = 1.75;

    /**
     * Set background to transparent.
     */
    public function deactivateBackgroundColor()
    {
        $this->backgroundColor = [];
    }

    /**
     * @param string $color (hexadecimal)
     */
    public function setBackgroundColorHex($color)
    {
        list($red, $green, $blue) = $this->colorHexToRGB($color);
        $this->setBackgroundColorRGB($red, $green, $blue);
    }

    /**
     * @param int $red
     * @param int $green
     * @param int $blue
     */
    public function setBackgroundColorRGB($red, $green, $blue)
    {
        $this->backgroundColor = [$red, $green, $blue];
    }

    /**
     * @param string $color (hexadecimal)
     */
    public function setLineColorHex($color)
    {
        list($red, $green, $blue) = $this->colorHexToRGB($color);
        $this->setLineColorRGB($red, $green, $blue);
    }

    /**
     * @param int $red
     * @param int $green
     * @param int $blue
     */
    public function setLineColorRGB($red, $green, $blue)
    {
        $this->lineColor = [$red, $green, $blue];
    }

    /**
     * @param float $thickness (in px)
     */
    public function setLineThickness($thickness)
    {
        $this->lineThickness = (float)$thickness;
    }

    /**
     * Set fill color to transparent.
     */
    public function deactivateFillColor()
    {
        $this->fillColor = [];
    }

    /**
     * @param string $color (hexadecimal)
     */
    public function setFillColorHex($color)
    {
        list($red, $green, $blue) = $this->colorHexToRGB($color);
        $this->setFillColorRGB($red, $green, $blue);
    }

    /**
     * @param int $red
     * @param int $green
     * @param int $blue
     */
    public function setFillColorRGB($red, $green, $blue)
    {
        $this->fillColor = [$red, $green, $blue];
    }

    /**
     * @param string $color (hexadecimal)
     * @exceptions \InvalidArgumentException
     *
     * @return array (r,g,b)
     */
    protected function colorHexToRGB($color)
    {
        if (!$this->checkColorHex($color)) {
            throw new \InvalidArgumentException('Invalid hexadecimal value ' . $color);
        }

        $color = mb_strtolower($color);
        $color = ltrim($color, '#');
        if (mb_strlen($color) === static::HEXADECIMAL_ALIAS_LENGTH) {
            $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2];
        }
        $color = hexdec($color);

        return [
            0xFF & ($color >> 0x10), // Red
            0xFF & ($color >> 0x8), // Green
            0xFF & $color, // Blue
        ];
    }

    /**
     * Formats:
     *      all
     *      vertical horizontal
     *      top horizontal bottom
     *      top right bottom left.
     *
     * @param string $padding
     *
     * @return array
     */
    protected function paddingStringToArray($padding)
    {
        $parts = explode(' ', $padding);
        switch (count($parts)) {
            case static::CSS_PADDING_ONE:
                $value = (float)$parts[0];

                return [$value, $value, $value, $value];
                break;
            case static::CSS_PADDING_TWO:
                $verticalValue = (float)$parts[0];
                $horizontalValue = (float)$parts[1];

                return [$verticalValue, $horizontalValue, $verticalValue, $horizontalValue];
            case static::CSS_PADDING_THREE:
                $parts[3] = $parts[1];

                return $parts;
            case static::CSS_PADDING:
                return $parts;
            default:
                throw new \InvalidArgumentException('Invalid padding format');
        }
    }

    /**
     * @param $color
     *
     * @return int
     */
    protected static function checkColorHex($color)
    {
        return preg_match('/^#?+[0-9a-f]{3}(?:[0-9a-f]{3})?$/i', $color);
    }
}


<?php

namespace Davaxi\Sparkline;

/**
 * Trait FormatTrait.
 */
trait FormatTrait
{
    /**
     * @var int
     *          Recommended: 50 < 800
     */
    protected $width = 80;

    /**
     * @var int
     *          Recommended: 20 < 800
     */
    protected $height = 20;

    /**
     * @var int
     */
    protected $ratioComputing = 4;

    /**
     * @var array
     */
    protected $padding = [
        'top' => 0,
        'right' => 0,
        'bottom' => 0,
        'left' => 0,
    ];

    /**
     * @param string $format (Width x Height)
     */
    public function setFormat($format)
    {
        $values = explode('x', $format);
        if (count($values) !== static::FORMAT_DIMENSION) {
            throw new \InvalidArgumentException('Invalid format params. Expected string Width x Height');
        }
        $this->setWidth($values[0]);
        $this->setHeight($values[1]);
    }

    /**
     * @param int $width
     */
    public function setWidth($width)
    {
        $this->width = (int)$width;
    }

    /**
     * @param int $height
     */
    public function setHeight($height)
    {
        $this->height = (int)$height;
    }

    /**
     * Set padding : format top right bottom left
     * ex: 0 10 0 10.
     *
     * @param string $padding
     */
    public function setPadding($padding)
    {
        list($top, $right, $bottom, $left) = $this->paddingStringToArray($padding);
        $this->padding['top'] = $top;
        $this->padding['right'] = $right;
        $this->padding['bottom'] = $bottom;
        $this->padding['left'] = $left;
    }

    /**
     * @return int
     */
    protected function getNormalizedHeight()
    {
        return $this->height * $this->ratioComputing;
    }

    /**
     * @return int
     */
    protected function getInnerHeight()
    {
        return $this->height - $this->padding['top'] - $this->padding['bottom'];
    }

    /**
     * @return array
     */
    protected function getNormalizedPadding()
    {
        return array_map(
            function ($value) {
                return $value * $this->ratioComputing;
            },
            $this->padding
        );
    }

    /**
     * @return int
     */
    protected function getInnerNormalizedHeight()
    {
        return $this->getInnerHeight() * $this->ratioComputing;
    }

    /**
     * @return int
     */
    protected function getNormalizedWidth()
    {
        return $this->width * $this->ratioComputing;
    }

    /**
     * @return int
     */
    protected function getInnerWidth()
    {
        return $this->width - ($this->padding['left'] + $this->padding['right']);
    }

    /**
     * @return int
     */
    protected function getInnerNormalizedWidth()
    {
        return $this->getInnerWidth() * $this->ratioComputing;
    }

    /**
     * @return array
     */
    protected function getNormalizedSize()
    {
        return [
            $this->getNormalizedWidth(),
            $this->getNormalizedHeight(),
        ];
    }

    /**
     * @return array
     */
    protected function getInnerNormalizedSize()
    {
        return [
            $this->getInnerNormalizedWidth(),
            $this->getInnerNormalizedHeight(),
        ];
    }

    /**
     * @param $count
     *
     * @return float|int
     */
    protected function getStepWidth($count)
    {
        $innerWidth = $this->getInnerNormalizedWidth();

        return $innerWidth / ($count - 1);
    }

    /**
     * @param array $data
     * @param $height
     *
     * @return array
     */
    protected function getDataForChartElements(array $data, $height)
    {
        $max = $this->getMaxValue();
        $minHeight = 1 * $this->ratioComputing;
        $maxHeight = $height - $minHeight;
        foreach ($data as $i => $value) {
            $value = (int)$value;
            if ($value <= 0) {
                $value = 0;
            }
            if ($value > 0) {
                $value = round(($value / $max) * $height);
            }
            $data[$i] = max($minHeight, min($value, $maxHeight));
        }

        return $data;
    }

    /**
     * @param array $data
     *
     * @return array
     */
    protected function getChartElements(array $data)
    {
        $count = count($data);
        $step = $this->getStepWidth($count);
        $height = $this->getInnerNormalizedHeight();
        $normalizedPadding = $this->getNormalizedPadding();
        $data = $this->getDataForChartElements($data, $height);

        $pictureX1 = $pictureX2 = $normalizedPadding['left'];
        $pictureY1 = $normalizedPadding['top'] + $height - $data[0];

        $polygon = [];
        $line = [];

        // Initialize
        $polygon[] = $normalizedPadding['left'];
        $polygon[] = $normalizedPadding['top'] + $height;
        // First element
        $polygon[] = $pictureX1;
        $polygon[] = $pictureY1;
        for ($i = 1; $i < $count; ++$i) {
            $pictureX2 = $pictureX1 + $step;
            $pictureY2 = $normalizedPadding['top'] + $height - $data[$i];

            $line[] = [$pictureX1, $pictureY1, $pictureX2, $pictureY2];

            $polygon[] = $pictureX2;
            $polygon[] = $pictureY2;

            $pictureX1 = $pictureX2;
            $pictureY1 = $pictureY2;
        }
        // Last
        $polygon[] = $pictureX2;
        $polygon[] = $normalizedPadding['top'] + $height;

        return [$polygon, $line];
    }
}


<?php

namespace Davaxi;

use Davaxi\Sparkline\DataTrait;
use Davaxi\Sparkline\FormatTrait;
use Davaxi\Sparkline\Picture;
use Davaxi\Sparkline\PointTrait;
use Davaxi\Sparkline\StyleTrait;

/**
 * Class Sparkline.
 */
class Sparkline
{
    use StyleTrait;
    use DataTrait;
    use FormatTrait;
    use PointTrait;

    const MIN_DATA_LENGTH = 2;
    const FORMAT_DIMENSION = 2;
    const HEXADECIMAL_ALIAS_LENGTH = 3;
    const CSS_PADDING_ONE = 1;
    const CSS_PADDING_TWO = 2;
    const CSS_PADDING_THREE = 3;
    const CSS_PADDING = 4;

    /**
     * @var string
     *             ex: QUERY_STRING if dedicated url
     */
    protected $eTag;

    /**
     * @var int
     */
    protected $expire;

    /**
     * @var string
     */
    protected $filename = 'sparkline';

    /**
     * @var resource
     */
    protected $file;

    /**
     * @var array
     */
    protected $server = [];

    /**
     * Sparkline constructor.
     *
     * @codeCoverageIgnore
     */
    public function __construct()
    {
        if (!extension_loaded('gd')) {
            throw new \InvalidArgumentException('GD extension is not installed');
        }
    }

    /**
     * @param string $eTag
     */
    public function setETag($eTag)
    {
        if (null === $eTag) {
            $this->eTag = null;

            return;
        }
        $this->eTag = md5($eTag);
    }

    /**
     * @param string $filename
     *                         Without extension
     */
    public function setFilename($filename)
    {
        $this->filename = $filename;
    }

    /**
     * @param string|int $expire
     *                           time format or string format
     */
    public function setExpire($expire)
    {
        if (null === $expire) {
            $this->expire = null;

            return;
        }
        if (is_numeric($expire)) {
            $this->expire = $expire;

            return;
        }
        $this->expire = strtotime($expire);
    }

    public function generate()
    {
        list($width, $height) = $this->getNormalizedSize();

        $count = $this->getCount();
        list($polygon, $line) = $this->getChartElements($this->data);

        $picture = new Picture($width, $height);
        $picture->applyBackground($this->backgroundColor);
        $picture->applyThickness($this->lineThickness * $this->ratioComputing);
        $picture->applyPolygon($polygon, $this->fillColor, $count);
        $picture->applyLine($line, $this->lineColor);

        foreach ($this->points as $point) {
            $isFirst = $point['index'] === 0;
            $lineIndex = $isFirst ? 0 : $point['index'] - 1;
            $picture->applyDot(
                $line[$lineIndex][$isFirst ? 0 : 2],
                $line[$lineIndex][$isFirst ? 1 : 3],
                $point['radius'] * $this->ratioComputing,
                $point['color']
            );
        }

        $this->file = $picture->generate($this->width, $this->height);
    }

    /**
     * @param array $server
     */
    public function setServer(array $server)
    {
        $this->server = $server;
    }

    /**
     * @param $key
     *
     * @return mixed|null
     */
    public function getServerValue($key)
    {
        if (isset($this->server[$key])) {
            return $this->server[$key];
        }

        return null;
    }

    /**
     * @return bool
     */
    protected function checkNoModified()
    {
        $httpIfNoneMatch = $this->getServerValue('HTTP_IF_NONE_MATCH');
        if ($this->eTag && $httpIfNoneMatch) {
            if ($httpIfNoneMatch === $this->eTag) {
                $serverProtocol = $this->getServerValue('SERVER_PROTOCOL');
                header($serverProtocol . ' 304 Not Modified', true, 304);

                return true;
            }
        }

        return false;
    }

    public function display()
    {
        if (!$this->file) {
            $this->generate();
        }

        if ($this->checkNoModified()) {
            return;
        }

        header('Content-Type: image/png');
        header('Content-Disposition: inline; filename="' . $this->filename . '.png"');
        header('Accept-Ranges: none');
        if ($this->eTag) {
            header('ETag: ' . $this->eTag);
        }
        if (null !== $this->expire) {
            header('Expires: ' . gmdate('D, d M Y H:i:s T', $this->expire));
        }
        imagepng($this->file);
    }

    public function save($savePath)
    {
        if (!$this->file) {
            $this->generate();
        }
        imagepng($this->file, $savePath);
    }

    /**
     * @return string
     */
    public function toBase64()
    {
        if (!$this->file) {
            $this->generate();
        }
        ob_start();
        imagepng($this->file);
        $buffer = ob_get_contents();
        if (ob_get_length()) {
            ob_end_clean();
        }

        return base64_encode($buffer);
    }

    public function destroy()
    {
        if ($this->file) {
            imagedestroy($this->file);
        }
        $this->file = null;
    }
}


<?php

namespace Rede;

use stdClass;

trait CreateTrait
{
    /**
     * @param stdClass $data
     *
     * @return mixed
     */
    static public function create(stdClass $data)
    {
        $obj = new self();
        $vars = get_object_vars($obj);

        foreach ($data as $property => $value) {
            if (array_key_exists($property, $vars)) {
                $obj->$property = $value;
            }
        }

        return $obj;
    }
}


<?php

namespace Rede;

use Psr\Log\LoggerInterface;
use Rede\Service\CancelTransactionService;
use Rede\Service\CaptureTransactionService;
use Rede\Service\CreateTransactionService;
use Rede\Service\GetTransactionService;

class eRede
{
    const VERSION = '4.2.1';
    const USER_AGENT = 'eRede/' . eRede::VERSION . ' (PHP %s; Store %s; %s %s)';

    /**
     * @var Store
     */
    private $store;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var string
     */
    private $platform;

    /**
     * @var string
     */
    private $platformVersion;

    /**
     * eRede constructor.
     *
     * @param Store $store
     * @param LoggerInterface $logger
     */
    public function __construct(Store $store, LoggerInterface $logger = null)
    {
        $this->store = $store;
        $this->logger = $logger;
    }

    /**
     * @param string $platform
     * @return eRede
     */
    public function platform($platform, $platformVersion)
    {
        $this->platform = $platform;
        $this->platformVersion = $platformVersion;

        return $this;
    }

    /**
     * @param Transaction $transaction
     *
     * @return Transaction
     * @see    eRede::create()
     */
    public function authorize(Transaction $transaction)
    {
        return $this->create($transaction);
    }

    /**
     * @param Transaction $transaction
     *
     * @return Transaction
     */
    public function create(Transaction $transaction)
    {
        $createTransactionService = new CreateTransactionService($this->store, $transaction, $this->logger);
        $createTransactionService->platform($this->platform, $this->platformVersion);

        return $createTransactionService->execute();
    }

    /**
     * @param Transaction $transaction
     *
     * @return Transaction
     */
    public function cancel(Transaction $transaction)
    {
        $cancelTransactionService = new CancelTransactionService($this->store, $transaction, $this->logger);
        $cancelTransactionService->platform($this->platform, $this->platformVersion);

        return $cancelTransactionService->execute();
    }

    /**
     * @param Transaction $transaction
     *
     * @return Transaction
     */
    public function capture(Transaction $transaction)
    {
        $captureTransactionService = new CaptureTransactionService($this->store, $transaction, $this->logger);
        $captureTransactionService->platform($this->platform, $this->platformVersion);

        return $captureTransactionService->execute();
    }

    /**
     * @param $tid
     *
     * @return Transaction
     * @see    eRede::get()
     */
    public function getById($tid)
    {
        return $this->get($tid);
    }

    /**
     * @param string $tid
     *
     * @return Transaction
     */
    public function get($tid)
    {
        $getTransactionService = new GetTransactionService($this->store, null, $this->logger);
        $getTransactionService->platform($this->platform, $this->platformVersion);
        $getTransactionService->setTid($tid);

        return $getTransactionService->execute();
    }

    /**
     * @param $reference
     *
     * @return Transaction
     */
    public function getByReference($reference)
    {
        $getTransactionService = new GetTransactionService($this->store, null, $this->logger);
        $getTransactionService->platform($this->platform, $this->platformVersion);
        $getTransactionService->setReference($reference);

        return $getTransactionService->execute();
    }

    /**
     * @param $tid
     *
     * @return Transaction
     */
    public function getRefunds($tid)
    {
        $getTransactionService = new GetTransactionService($this->store, null, $this->logger);
        $getTransactionService->platform($this->platform, $this->platformVersion);
        $getTransactionService->setTid($tid);
        $getTransactionService->setRefund(true);

        return $getTransactionService->execute();
    }

    /**
     * @param Transaction $transaction
     *
     * @return Transaction
     */
    public function zero(Transaction $transaction)
    {
        $amount = (int)$transaction->getAmount();
        $capture = (bool)$transaction->getCapture();

        $transaction->setAmount(0);
        $transaction->capture();

        $transaction = $this->create($transaction);

        $transaction->setAmount($amount);
        $transaction->capture($capture);

        return $transaction;
    }
}


<?php

namespace Rede;

use ArrayIterator;

class Iata implements RedeSerializable
{
    use SerializeTrait;

    /**
     * @var string
     */
    private $code;

    /**
     * @var string
     */
    private $departureTax;

    /**
     * @var array[Flight]
     */
    private $flight;

    /**
     * @return string
     */
    public function getCode()
    {
        return $this->code;
    }

    /**
     * @param string $code
     *
     * @return Iata
     */
    public function setCode($code)
    {
        $this->code = $code;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getDepartureTax()
    {
        return $this->departureTax;
    }

    /**
     * @param string $departureTax
     *
     * @return Iata
     */
    public function setDepartureTax($departureTax)
    {
        $this->departureTax = $departureTax;
        return $this;
    }

    /**
     *
     * @return ArrayIterator
     */
    public function getFlightIterator()
    {
        return new ArrayIterator($this->flight);
    }

    /**
     * @param Flight $flight
     *
     * @return Iata
     */
    public function setFlight($flight)
    {
        $this->flight = [];
        $this->addFlight($flight);

        return $this;
    }

    /**
     * @param Flight $flight
     *
     * @return Iata
     */
    public function addFlight(Flight $flight)
    {
        if ($this->flight === null) {
            $this->flight = [];
        }

        $this->flight[] = $flight;

        return $this;
    }
}


<?php

namespace Rede;

use ArrayIterator;
use stdClass;

class Consumer implements RedeSerializable
{
    use SerializeTrait;

    const MALE = 'M';
    const FEMALE = 'F';

    /**
     * @var string
     */
    private $cpf;

    /**
     * @var array
     */
    private $documents;

    /**
     * @var string
     */
    private $email;

    /**
     * @var string
     */
    private $gender;

    /**
     *
     * @var string
     */
    private $name;

    /**
     *
     * @var Phone
     */
    private $phone;

    /**
     * Consumer constructor.
     *
     * @param string $name
     * @param string $email
     * @param string $cpf
     */
    public function __construct($name, $email, $cpf)
    {
        $this->setName($name);
        $this->setEmail($email);
        $this->setCpf($cpf);
    }

    /**
     * @param string $type
     * @param string $number
     *
     * @return Consumer
     */
    public function addDocument($type, $number)
    {
        if ($this->documents === null) {
            $this->documents = [];
        }

        $document = new stdClass();
        $document->type = $type;
        $document->number = $number;

        $this->documents[] = $document;

        return $this;
    }

    /**
     *
     * @return string
     */
    public function getCpf()
    {
        return $this->cpf;
    }

    /**
     * @param string $cpf
     *
     * @return Consumer
     */
    public function setCpf($cpf)
    {
        $this->cpf = $cpf;
        return $this;
    }

    /**
     * @return \ArrayIterator
     */
    public function getDocumentsIterator()
    {
        if ($this->documents === null) {
            $this->documents = [];
        }

        return new ArrayIterator($this->documents);
    }

    /**
     *
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * @param string $email
     *
     * @return Consumer
     */
    public function setEmail($email)
    {
        $this->email = $email;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getGender()
    {
        return $this->gender;
    }

    /**
     * @param string $gender
     *
     * @return Consumer
     */
    public function setGender($gender)
    {
        $this->gender = $gender;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     *
     * @param string $name
     *
     * @return Consumer
     */
    public function setName($name)
    {
        $this->name = $name;
        return $this;
    }

    /**
     *
     * @return Phone
     */
    public function getPhone()
    {
        return $this->phone;
    }

    /**
     *
     * @param Phone $phone
     *
     * @return Consumer
     */
    public function setPhone($phone)
    {
        $this->phone = $phone;
        return $this;
    }

    /**
     * @param string $ddd
     * @param string $number
     * @param int $type
     *
     * @return Consumer
     */
    public function phone($ddd, $number, $type = Phone::CELLPHONE)
    {
        return $this->setPhone(new Phone($ddd, $number, $type));
    }
}


<?php /**
 * @noinspection MessDetectorValidationInspection
 */

namespace Rede;

use ArrayIterator;
use DateTime;
use InvalidArgumentException;

class Transaction implements RedeSerializable, RedeUnserializable
{
    const CREDIT = 'credit';
    const DEBIT = 'debit';

    const ORIGIN_EREDE = 1;
    const ORIGIN_VISA_CHECKOUT = 4;
    const ORIGIN_MASTERPASS = 6;

    /**
     * @var int
     */
    private $amount;

    /**
     * @var Additional
     */
    private $additional;

    /**
     * @var Antifraud
     */
    private $antifraud;

    /**
     * @var bool
     */
    private $antifraudRequired;

    /**
     * @var Authorization
     */
    private $authorization;

    /**
     *
     * @var string
     */
    private $authorizationCode;

    /**
     *
     * @var string
     */
    private $cancelId;

    /**
     *
     * @var bool|Capture
     */
    private $capture;

    /**
     *
     * @var string
     */
    private $cardBin;

    /**
     *
     * @var string
     */
    private $cardHolderName;

    /**
     *
     * @var string
     */
    private $cardNumber;

    /**
     *
     * @var Cart
     */
    private $cart;

    /**
     *
     * @var \DateTime
     */
    private $dateTime;

    /**
     *
     * @var int
     */
    private $distributorAffiliation;

    /**
     *
     * @var int
     */
    private $expirationMonth;

    /**
     *
     * @var int
     */
    private $expirationYear;

    /**
     *
     * @var Iata
     */
    private $iata;

    /**
     *
     * @var int
     */
    private $installments;

    /**
     *
     * @var string
     */
    private $kind;

    /**
     *
     * @var string
     */
    private $last4;

    /**
     *
     * @var string
     */
    private $nsu;

    /**
     *
     * @var int
     */
    private $origin;

    /**
     *
     * @var string
     */
    private $reference;

    /**
     *
     * @var string
     */
    private $refundDateTime;

    /**
     *
     * @var string
     */
    private $refundId;

    /**
     *
     * @var array[Refund]
     */
    private $refunds;

    /**
     *
     * @var DateTime
     */
    private $requestDateTime;

    /**
     *
     * @var string
     */
    private $returnCode;

    /**
     *
     * @var string
     */
    private $returnMessage;

    /**
     *
     * @var string
     */
    private $securityCode;

    /**
     *
     * @var string
     */
    private $softDescriptor;

    /**
     *
     * @var int
     */
    private $storageCard;

    /**
     *
     * @var bool
     */
    private $subscription;

    /**
     *
     * @var ThreeDSecure
     */
    private $threeDSecure;

    /**
     *
     * @var string
     */
    private $tid;

    /**
     *
     * @var array
     */
    private $urls;

    /**
     * @var SubMerchant
     */
    private $subMerchant;

    /**
     * @var string
     */
    private $paymentFacilitatorID;

    /**
     * Transaction constructor.
     *
     * @param int $amount
     * @param string $reference
     */
    public function __construct($amount = null, $reference = null)
    {
        $this->setAmount($amount);
        $this->setReference($reference);
    }

    /**
     * @param string $url
     * @param string $kind
     *
     * @return Transaction
     */
    public function addUrl($url, $kind = Url::CALLBACK)
    {
        if ($this->urls == null) {
            $this->urls = [];
        }

        $this->urls[] = new Url($url, $kind);

        return $this;
    }

    /**
     * @param Environment $environment
     *
     * @return Cart
     */
    public function antifraud(Environment $environment)
    {
        $cart = new Cart();
        $cart->setEnvironment($environment);

        $this->setAntifraudRequired(true);
        $this->setCart($cart);

        return $cart;
    }

    /**
     * @param integer $gateway
     * @param integer $module
     *
     * @return Transaction
     */
    public function additional($gateway = null, $module = null)
    {
        $this->additional = new Additional();
        $this->additional->setGateway($gateway);
        $this->additional->setModule($module);

        return $this;
    }

    /**
     * @param $cardNumber
     * @param $cardCvv
     * @param $expirationYear
     * @param $expirationMonth
     * @param $holderName
     *
     * @return Transaction this transaction
     */
    public function creditCard($cardNumber, $cardCvv, $expirationMonth, $expirationYear, $holderName)
    {
        return $this->setCard(
            $cardNumber,
            $cardCvv,
            $expirationMonth,
            $expirationYear,
            $holderName,
            Transaction::CREDIT
        );
    }

    /**
     * @param $cardNumber
     * @param $securityCode
     * @param $expirationMonth
     * @param $expirationYear
     * @param $cardHolderName
     * @param $kind
     *
     * @return Transaction this transaction
     */
    public function setCard($cardNumber, $securityCode, $expirationMonth, $expirationYear, $cardHolderName, $kind)
    {
        $this->setCardNumber($cardNumber);
        $this->setSecurityCode($securityCode);
        $this->setExpirationMonth($expirationMonth);
        $this->setExpirationYear($expirationYear);
        $this->setCardHolderName($cardHolderName);
        $this->setKind($kind);

        return $this;
    }

    /**
     * @param $cardNumber
     * @param $cardCvv
     * @param $expirationYear
     * @param $expirationMonth
     * @param $holderName
     *
     * @return Transaction this transaction
     */
    public function debitCard($cardNumber, $cardCvv, $expirationMonth, $expirationYear, $holderName)
    {
        $this->capture(true);

        return $this->setCard(
            $cardNumber,
            $cardCvv,
            $expirationMonth,
            $expirationYear,
            $holderName,
            Transaction::DEBIT
        );
    }

    /**
     * @param bool $capture
     *
     * @return Transaction
     */
    public function capture($capture = true)
    {
        if (!$capture && $this->kind === Transaction::DEBIT) {
            throw new InvalidArgumentException('Debit transactions will always be captured');
        }

        $this->capture = $capture;
        return $this;
    }

    /**
     * @return array
     * @see    \JsonSerializable::jsonSerialize()
     */
    public function jsonSerialize()
    {
        $capture = null;

        if (is_bool($this->capture)) {
            $capture = $this->capture ? 'true' : 'false';
        }

        return array_filter(
            [
                'capture' => $capture,
                'antifraudRequired' => $this->antifraudRequired,
                'cart' => $this->cart,
                'kind' => $this->kind,
                'threeDSecure' => $this->threeDSecure,
                'reference' => $this->reference,
                'amount' => $this->amount,
                'installments' => $this->installments,
                'cardHolderName' => $this->cardHolderName,
                'cardNumber' => $this->cardNumber,
                'expirationMonth' => $this->expirationMonth,
                'expirationYear' => $this->expirationYear,
                'securityCode' => $this->securityCode,
                'softDescriptor' => $this->softDescriptor,
                'subscription' => $this->subscription,
                'origin' => $this->origin,
                'distributorAffiliation' => $this->distributorAffiliation,
                'storageCard' => $this->storageCard,
                'urls' => $this->urls,
                'iata' => $this->iata,
                'additional' => $this->additional
            ], function ($value) {
            return !is_null($value);
        }
        );
    }

    /**
     *
     * @param string $serialized
     *
     * @return Transaction
     * @throws \Exception
     */
    public function jsonUnserialize($serialized)
    {
        $properties = json_decode($serialized);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new InvalidArgumentException(sprintf('JSON: %s', json_last_error_msg()));
        }

        foreach (get_object_vars($properties) as $property => $value) {
            if ($property == 'links') {
                continue;
            }

            if ($property == 'refunds' && is_array($value)) {
                $this->refunds = [];

                foreach ($value as $refundValue) {
                    $this->refunds[] = Refund::create($refundValue);
                }

                continue;
            }

            if ($property == 'urls' && is_array($value)) {
                $this->urls = [];

                foreach ($value as $urlValue) {
                    $this->urls[] = new Url($urlValue->url, $urlValue->kind);
                }

                continue;
            }

            if ($property == 'capture' && is_object($value)) {
                $this->capture = Capture::create($value);

                continue;
            }

            if ($property == 'authorization' && is_object($value)) {
                $this->authorization = Authorization::create($value);

                continue;
            }

            if ($property == 'additional' && is_object($value)) {
                $this->additional = Additional::create($value);

                continue;
            }

            if ($property == 'threeDSecure' && is_object($value)) {
                $this->threeDSecure = ThreeDSecure::create($value);

                continue;
            }

            if ($property == 'antifraud' && is_object($value)) {
                $this->antifraud = Antifraud::create($value);

                continue;
            }

            if ($property == 'requestDateTime' || $property == 'dateTime' || $property == 'refundDateTime') {
                $value = new DateTime($value);
            }

            $this->$property = $value;
        }

        return $this;
    }

    /**
     *
     * @return int
     */
    public function getAmount()
    {
        return $this->amount;
    }

    /**
     *
     * @param int $amount
     *
     * @return Transaction
     */
    public function setAmount($amount)
    {
        $this->amount = (int)($amount * 100);
        return $this;
    }

    /**
     *
     * @return Antifraud
     */
    public function getAntifraud()
    {
        $antifraud = $this->antifraud;

        if ($antifraud === null) {
            $antifraud = new Antifraud();
        }

        return $antifraud;
    }

    /**
     *
     * @return Authorization
     */
    public function getAuthorization()
    {
        return $this->authorization;
    }

    /**
     *
     * @return string
     */
    public function getAuthorizationCode()
    {
        return $this->authorizationCode;
    }

    /**
     *
     * @return string
     */
    public function getCancelId()
    {
        return $this->cancelId;
    }

    /**
     *
     * @return bool|Capture
     */
    public function getCapture()
    {
        return $this->capture;
    }

    /**
     *
     * @return string
     */
    public function getCardBin()
    {
        return $this->cardBin;
    }

    /**
     *
     * @return string
     */
    public function getCardHolderName()
    {
        return $this->cardHolderName;
    }

    /**
     *
     * @param string $cardHolderName
     *
     * @return Transaction
     */
    public function setCardHolderName($cardHolderName)
    {
        $this->cardHolderName = $cardHolderName;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getCardNumber()
    {
        return $this->cardNumber;
    }

    /**
     *
     * @param string $cardNumber
     *
     * @return Transaction
     */
    public function setCardNumber($cardNumber)
    {
        $this->cardNumber = $cardNumber;
        return $this;
    }

    /**
     *
     * @return Cart
     */
    public function getCart()
    {
        return $this->cart;
    }

    /**
     *
     * @param Cart $cart
     *
     * @return Transaction
     */
    public function setCart(Cart $cart)
    {
        $this->cart = $cart;
        return $this;
    }

    /**
     *
     * @return DateTime
     */
    public function getDateTime()
    {
        return $this->dateTime;
    }

    /**
     *
     * @return int
     */
    public function getDistributorAffiliation()
    {
        return $this->distributorAffiliation;
    }

    /**
     *
     * @param int $distributorAffiliation
     *
     * @return Transaction
     */
    public function setDistributorAffiliation($distributorAffiliation)
    {
        $this->distributorAffiliation = $distributorAffiliation;
        return $this;
    }

    /**
     *
     * @return int
     */
    public function getExpirationMonth()
    {
        return $this->expirationMonth;
    }

    /**
     *
     * @param int $expirationMonth
     *
     * @return Transaction
     */
    public function setExpirationMonth($expirationMonth)
    {
        $this->expirationMonth = $expirationMonth;
        return $this;
    }

    /**
     *
     * @return int
     */
    public function getExpirationYear()
    {
        return $this->expirationYear;
    }

    /**
     *
     * @param int $expirationYear
     *
     * @return Transaction
     */
    public function setExpirationYear($expirationYear)
    {
        $this->expirationYear = $expirationYear;
        return $this;
    }

    /**
     *
     * @return Iata
     */
    public function getIata()
    {
        return $this->iata;
    }

    /**
     * @param string $code
     * @param string $departureTax
     *
     * @return Transaction
     */
    public function setIata($code, $departureTax)
    {
        $this->iata = new Iata();
        $this->iata->setCode($code);
        $this->iata->setDepartureTax($departureTax);

        return $this;
    }

    /**
     *
     * @return int
     */
    public function getInstallments()
    {
        return $this->installments;
    }

    /**
     *
     * @param int $installments
     *
     * @return Transaction
     */
    public function setInstallments($installments)
    {
        $this->installments = $installments;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getKind()
    {
        return $this->kind;
    }

    /**
     *
     * @param string $kind
     *
     * @return Transaction
     */
    public function setKind($kind)
    {
        $this->kind = $kind;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getLast4()
    {
        return $this->last4;
    }

    /**
     *
     * @return string
     */
    public function getNsu()
    {
        return $this->nsu;
    }

    /**
     *
     * @return int
     */
    public function getOrigin()
    {
        return $this->origin;
    }

    /**
     *
     * @param int $origin
     *
     * @return Transaction
     */
    public function setOrigin($origin)
    {
        $this->origin = $origin;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getReference()
    {
        return $this->reference;
    }

    /**
     *
     * @param string $reference
     *
     * @return Transaction
     */
    public function setReference($reference)
    {
        $this->reference = $reference;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getRefundDateTime()
    {
        return $this->refundDateTime;
    }

    /**
     *
     * @return string
     */
    public function getRefundId()
    {
        return $this->refundId;
    }

    /**
     *
     * @return array
     */
    public function getRefunds()
    {
        return $this->refunds;
    }

    /**
     *
     * @return DateTime
     */
    public function getRequestDateTime()
    {
        return $this->requestDateTime;
    }

    /**
     *
     * @return string
     */
    public function getReturnCode()
    {
        return $this->returnCode;
    }

    /**
     *
     * @return string
     */
    public function getReturnMessage()
    {
        return $this->returnMessage;
    }

    /**
     *
     * @return string
     */
    public function getSecurityCode()
    {
        return $this->securityCode;
    }

    /**
     *
     * @param string $securityCode
     *
     * @return Transaction
     */
    public function setSecurityCode($securityCode)
    {
        $this->securityCode = $securityCode;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getSoftDescriptor()
    {
        return $this->softDescriptor;
    }

    /**
     *
     * @param string $softDescriptor
     *
     * @return Transaction
     */
    public function setSoftDescriptor($softDescriptor)
    {
        $this->softDescriptor = $softDescriptor;
        return $this;
    }

    /**
     *
     * @return int
     */
    public function getStorageCard()
    {
        return $this->storageCard;
    }

    /**
     *
     * @param int $storageCard
     *
     * @return Transaction
     */
    public function setStorageCard($storageCard)
    {
        $this->storageCard = $storageCard;
        return $this;
    }

    /**
     *
     * @return bool
     */
    public function isAntifraudRequired()
    {
        return $this->antifraudRequired;
    }

    /**
     *
     * @param bool $antifraudRequired
     *
     * @return Transaction
     */
    public function setAntifraudRequired($antifraudRequired)
    {
        $this->antifraudRequired = $antifraudRequired;
        return $this;
    }

    /**
     * @param $code
     * @param $departureTax
     *
     * @return Transaction
     */
    public function iata($code, $departureTax)
    {
        return $this->setIata($code, $departureTax);
    }

    /**
     *
     * @return bool
     */
    public function isSubscription()
    {
        return $this->subscription;
    }

    /**
     *
     * @param bool $subscription
     *
     * @return Transaction
     */
    public function setSubscription($subscription)
    {
        $this->subscription = $subscription;
        return $this;
    }

    /**
     *
     * @return ThreeDSecure
     */
    public function getThreeDSecure()
    {
        $threeDSecure = $this->threeDSecure;

        if ($threeDSecure == null) {
            $threeDSecure = new ThreeDSecure();
        }

        return $threeDSecure;
    }

    /**
     *
     * @return string
     */
    public function getTid()
    {
        return $this->tid;
    }

    /**
     *
     * @param string $tid
     *
     * @return Transaction
     */
    public function setTid($tid)
    {
        $this->tid = $tid;
        return $this;
    }

    /**
     *
     * @return \ArrayIterator
     */
    public function getUrlsIterator()
    {
        return new ArrayIterator($this->urls);
    }

    /**
     * @param $softDescriptor
     * @param $paymentFacilitatorID
     * @param SubMerchant $subMerchant
     *
     * @return $this
     */
    public function mcc($softDescriptor, $paymentFacilitatorID, SubMerchant $subMerchant)
    {
        $this->setSoftDescriptor($softDescriptor);
        $this->setPaymentFacilitatorID($paymentFacilitatorID);
        $this->setSubMerchant($subMerchant);

        return $this;
    }

    /**
     * @return SubMerchant
     */
    public function getSubMerchant()
    {
        return $this->subMerchant;
    }

    /**
     * @param SubMerchant $subMerchant
     * @return Transaction
     */
    public function setSubMerchant($subMerchant)
    {
        $this->subMerchant = $subMerchant;
        return $this;
    }

    /**
     * @return string
     */
    public function getPaymentFacilitatorID()
    {
        return $this->paymentFacilitatorID;
    }

    /**
     * @param string $paymentFacilitatorID
     * @return Transaction
     */
    public function setPaymentFacilitatorID($paymentFacilitatorID)
    {
        $this->paymentFacilitatorID = $paymentFacilitatorID;
        return $this;
    }

    /**
     * @param string $onFailure
     * @param bool $embed
     * @param string $directoryServerTransactionId
     * @param string $threeDIndicator
     *
     * @return Transaction
     */
    public function threeDSecure($onFailure = ThreeDSecure::DECLINE_ON_FAILURE, $embed = true, $directoryServerTransactionId = "", $threeDIndicator = "1")
    {
        $threeDSecure = new ThreeDSecure();
        $threeDSecure->setOnFailure($onFailure);
        $threeDSecure->setEmbedded($embed);
        $threeDSecure->setThreeDIndicator($threeDIndicator);
        $threeDSecure->setDirectoryServerTransactionId($directoryServerTransactionId);

        $this->threeDSecure = $threeDSecure;

        return $this;
    }
}


<?php

namespace Rede;

class Flight implements RedeSerializable
{
    use SerializeTrait;

    /**
     * @var string
     */
    private $date;

    /**
     * @var string
     */
    private $from;

    /**
     * @var string
     */
    private $number;

    /**
     * @var array[Passenger]
     */
    private $passenger;

    /**
     *
     * @var string
     */
    private $to;

    public function __construct($number, $from, $to, $date)
    {
        $this->setNumber($number);
        $this->setFrom($from);
        $this->setTo($to);
        $this->setDate($date);
    }

    /**
     * @return string
     */
    public function getDate()
    {
        return $this->date;
    }

    /**
     * @param string $date
     *
     * @return Flight
     */
    public function setDate($date)
    {
        $this->date = $date;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getFrom()
    {
        return $this->from;
    }

    /**
     * @param string $from
     *
     * @return Flight
     */
    public function setFrom($from)
    {
        $this->from = $from;
        return $this;
    }

    /**
     *
     * @return string
     */
    public function getNumber()
    {
        return $this->number;
    }

    /**
     * @param string $number
     *
     * @return Flight
     */
    public function setNumber($number)
    {
        $this->number = $number;
        return $this;
    }

    /**
     *
     * @return array
     */
    public function getPassenger()
    {
        return $this->passenger;
    }

    /**
     * @param Passenger $passenger
     *
     * @return Flight
     */
    public function setPassenger(Passenger $passenger)
    {
        $this->passenger = [];
        $this->addPassenger($passenger);
        return $this;
    }

    /**
     * @param Passenger $passenger
     *
     * @return Flight
     */
    public function addPassenger(Passenger $passenger)
    {
        if ($this->passenger === null) {
            $this->passenger = [];
        }

        $this->passenger[] = $passenger;

        return $this;
    }

    /**
     *
     * @return string
     */
    public function getTo()
    {
        return $this->to;
    }

    /**
     *
     * @param string $to
     *
     * @return Flight
     */
    public function setTo($to)
    {
        $this->to = $to;
        return $this;
    }
}


<?php

namespace Rede\Service;

use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Rede\Exception\RedeException;
use Rede\Store;
use Rede\Transaction;

abstract class AbstractTransactionsService extends AbstractService
{
    /**
     * @var Transaction
     */
    protected $transaction;

    /**
     * @var string
     */
    private $tid;

    /**
     * AbstractTransactionsService constructor.
     *
     * @param Store $store
     * @param Transaction $transaction
     * @param LoggerInterface $logger
     */
    public function __construct(Store $store, Transaction $transaction = null, LoggerInterface $logger = null)
    {
        parent::__construct($store, $logger);

        $this->transaction = $transaction;
    }

    /**
     * @return Transaction
     * @throws \InvalidArgumentException
     * @throws \RuntimeException
     * @throws \Rede\Exception\RedeException
     */
    public function execute()
    {
        return $this->sendRequest(json_encode($this->transaction), AbstractService::POST);
    }

    /**
     * @return string
     */
    public function getTid()
    {
        return $this->tid;
    }

    /**
     *
     * @param string $tid
     */
    public function setTid($tid)
    {
        $this->tid = $tid;
    }

    /**
     * @see    AbstractService::getService()
     * @return string
     */
    protected function getService()
    {
        return 'transactions';
    }

    /**
     * @param string $response
     * @param string $statusCode
     *
     * @return Transaction
     * @see    AbstractService::parseResponse()
     * @throws \Rede\Exception\RedeException
     * @throws \InvalidArgumentException
     * @throws \Exception
     */
    protected function parseResponse($response, $statusCode)
    {
        $previous = null;

        if ($this->transaction === null) {
            $this->transaction = new Transaction();
        }

        try {
            $this->transaction->jsonUnserialize($response);
        } catch (InvalidArgumentException $e) {
            $previous = $e;
        }

        if ((int)$statusCode >= 400) {
            throw new RedeException(
                $this->transaction->getReturnMessage(),
                $this->transaction->getReturnCode(),
                $previous
            );
        }

        return $this->transaction;
    }
}


<?php

namespace Rede\Service;

use Psr\Log\LoggerInterface;
use Rede\eRede;
use Rede\Store;
use Rede\Transaction;
use RuntimeException;
use function Sodium\version_string;

abstract class AbstractService
{
    const GET = 'GET';
    const POST = 'POST';
    const PUT = 'PUT';

    /**
     * @var resource
     */
    protected $curl;

    /**
     * @var Store
     */
    protected $store;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var string
     */
    private $platform;

    /**
     * @var string
     */
    private $platformVersion;

    /**
     * AbstractService constructor.
     *
     * @param Store $store
     * @param LoggerInterface $logger
     */
    public function __construct(Store $store, LoggerInterface $logger = null)
    {
        $this->store = $store;
        $this->logger = $logger;
    }

    /**
     * @param string $platform
     * @param string $platformVersion
     * @return $this
     */
    public function platform($platform, $platformVersion)
    {
        $this->platform = $platform;
        $this->platformVersion = $platformVersion;

        return $this;
    }

    /**
     * @return Transaction
     * @throws \InvalidArgumentException
     * @throws \RuntimeException
     * @throws \Rede\Exception\RedeException
     */
    abstract public function execute();

    /**
     * @param string $body
     * @param string $method
     *
     * @return mixed
     * @throws \RuntimeException
     */
    protected function sendRequest($body = null, $method = 'GET')
    {
        $userAgent = sprintf('User-Agent: %s',
            sprintf(eRede::USER_AGENT, phpversion(), $this->store->getFiliation(), php_uname('s'), php_uname('r'), php_uname('m'))
        );

        if (!empty($this->platform) && !empty($this->platformVersion)) {
            $userAgent .= sprintf(' %s/%s', $this->platform, $this->platformVersion);
        }

        if (is_resource($this->curl)) {
            curl_close($this->curl);
        }

        $curlVersion = curl_version();

        if (is_array($curlVersion)) {
            $userAgent .= sprintf(' curl/%s %s',
                isset($curlVersion['version']) ? $curlVersion['version'] : '',
                isset($curlVersion['ssl_version']) ? $curlVersion['ssl_version'] : ''
            );
        }

        $headers = [
            str_replace('  ', ' ',
                $userAgent
            ),
            'Accept: application/json'
        ];

        $this->curl = curl_init($this->store->getEnvironment()->getEndpoint($this->getService()));

        curl_setopt(
            $this->curl,
            CURLOPT_USERPWD,
            sprintf('%s:%s', $this->store->getFiliation(), $this->store->getToken())
        );

        if (!defined('CURL_SSLVERSION_TLSv1_2')) {
            define('CURL_SSLVERSION_TLSv1_2', 6);

            if ($this->logger !== null) {
                $this->logger->alert('Atenção, por motivos de segurança, recomendamos fortemente que você atualize a versão do seu PHP.');
            }
        }

        curl_setopt($this->curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
        curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, true);

        switch ($method) {
            case 'GET':
                break;
            case 'POST':
                curl_setopt($this->curl, CURLOPT_POST, true);
                break;
            default:
                curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method);
        }

        if ($body !== null) {
            curl_setopt($this->curl, CURLOPT_POSTFIELDS, $body);

            $headers[] = 'Content-Type: application/json; charset=utf8';
        } else {
            $headers[] = 'Content-Length: 0';
        }

        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers);

        if ($this->logger !== null) {
            $this->logger->debug(
                trim(
                    sprintf("Request Rede\n%s %s\n%s\n\n%s",
                        $method,
                        $this->store->getEnvironment()->getEndpoint($this->getService()),
                        implode("\n", $headers),
                        preg_replace('/"(cardnumber|securitycode)":"[^"]+"/i', '"\1":"***"', $body)
                    )
                )
            );
        }

        $response = curl_exec($this->curl);
        $statusCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);

        if ($this->logger !== null) {
            $this->logger->debug(
                sprintf("Response Rede\nStatus Code: %s\n\n%s",
                    $statusCode,
                    $response
                )
            );
        }

        if (curl_errno($this->curl)) {
            throw new RuntimeException('Curl error: ' . curl_error($this->curl));
        }

        curl_close($this->curl);

        $this->curl = null;

        return $this->parseResponse($response, $statusCode);
    }

    /**
     * @return string Gets the service that will be used on the request
     */
    abstract protected function getService();

    /**
     * @param string $response Parses the HTTP response from Rede
     * @param string $statusCode The HTTP status code
     *
     * @return mixed
     */
    abstract protected function parseResponse($response, $statusCode);
}