first commit

This commit is contained in:
2024-11-11 18:46:54 +01:00
commit a630d17338
25634 changed files with 4923715 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
# Payment Core Library
It is a simple PHP library that allow implement payment gateway methods out of the box.
## Requirements
* [PHP](http://php.net/) needs to be a minimum version of 5.4.0
* [cURL](http://php.net/manual/en/book.curl.php) needs to be enabled
### Installation
1. `git clone`
2. `composer install`
## How to use:
Library provides few already created methods like generate form order or just generate form fields.
If you want to generate full form:
1. First you need to initialize Payment instance and provide environment, service key and service id. The last parameter `language` is optional, if if won't be provided the default value will be used (which is 'en')
2. Then simply run `$payment->buildOrderForm($orderData)` to build full form or `$payment->buildFormFields($orderData)` to build form fields.
## Examples:
##### Build full form:
Order data must have fields:
`amount, currency, orderId, customerFirstName, customerLastName`
Optional data:
`customerEmail, urlSuccess, urlFailure, urlReturn, urlNotification, twistoData, version`
Example `$orderData`:
```php
$orderData = [
'amount' => (int) (1.33 * 100), //INT, price should be multipled by 100 so for example 1.33 will be 133
'currency' => 'PLN', // see suported currences in 'Supported variables' sectiom
'orderId' => '123456790',
'customerFirstName' => 'John',
'customerLastName' => 'Doe',
'customerEmail' => 'john@doe.com',
'urlSuccess' => 'https://example.com/success.php',
'urlFailure' => 'https://example.com/failure.php',
'urlReturn' => 'https://example.com/return.php',
];
```
```php
use Imoje\Payment\Payment;
public function yourFunction()
{
// ...
$payment = new Payment(\Payment\Util::ENVIRONMENT_PRODUCTION, 'b1c8bd6c7411455aab3295c48fcf26a7', 'e692d208484b4d5887755362d0587a00', 'pl');
$form = $payment->buildOrderForm($orderData); // You can also pass second parameter `language` to generate form that will redirect to this language
}
```
The `$form` must contain $orderData and additional keys (signature must have which one hash method was use (for example sha256)), like:
`serviceId, merchantId, signature`
```php
<input type="hidden" value="133" name="serviceId">
<input type="hidden" value="133" name="merchantId">
<input type="hidden" value="133" name="amount">
<input type="hidden" value="PLN" name="currency">
<input type="hidden" value="123456790" name="orderId">
<input type="hidden" value="John" name="customerFirstName">
<input type="hidden" value="Doe" name="customerLastName">
<input type="hidden" value="john@doe.com" name="customerEmail">
<input type="hidden" value="https://example.com/success.php" name="urlSuccess">
<input type="hidden" value="https://example.com/failure.php" name="urlFailure">
<input type="hidden" value="https://example.com/return.php" name="urlReturn">
<input type="hidden" value="7c46b5c9836520a9a0552d31136a705d3f7a7ea3db13de87f41fb3ee31f6dbff;sha256" name="signature">
```
and then you can wrap it to your own `form` HTML tag
```php
<form method="POST" action="http://example.com/pl/payment">
<input type="hidden" value="133" name="serviceId">
<input type="hidden" value="133" name="merchantId">
<input type="hidden" value="133" name="amount">
<input type="hidden" value="PLN" name="currency">
<input type="hidden" value="123456790" name="orderId">
<input type="hidden" value="John" name="customerFirstName">
<input type="hidden" value="Doe" name="customerLastName">
<input type="hidden" value="john@doe.com" name="customerEmail">
<input type="hidden" value="https://example.com/success.php" name="urlSuccess">
<input type="hidden" value="https://example.com/failure.php" name="urlFailure">
<input type="hidden" value="https://example.com/return.php" name="urlReturn">
<input type="hidden" value="7c46b5c9836520a9a0552d31136a705d3f7a7ea3db13de87f41fb3ee31f6dbff;sha256" name="signature">
<input class="button" type="submit" value="Continue" id="submit-payment-form">
</form>
```
## Additional features:
Library provides few additional methods that you can use.
First you need to add `use Imoje\Payment\Util;` then you can:
* `Util::getSupportedCurrencies()` - it will return an array with available currencies.
* `Util::getSupportedLanguages()` - it will return an array with available languages.
* `Util::canUseForCurrency($currencyCode)` - `$currencyCode` should be in format ISO4217. The method will return bool.
## Supported variables:
The supported currencies are:
`PLN`
The supported languages are:
`Polish, English`
The format you should use for it is ISO 639-1 (Alpha-2 code)

View File

@@ -0,0 +1,18 @@
{
"name": "payment/payment-core",
"description": "Payment core library, contains basic functions to implement Payment Method",
"type": "library",
"version": "1.0.4",
"require": {
"justinrainbow/json-schema": "^5.2",
"ext-curl": "*",
"ext-json": "*",
"php": ">=5.4.0"
},
"license": "MIT",
"autoload": {
"psr-4": {
"Imoje\\Payment\\": "src"
}
}
}

View File

@@ -0,0 +1,85 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "d1aab2dbb3df569ba385f43311ced0cf",
"content-hash": "e93a1a7684db00ce76986e3fba5d3178",
"packages": [
{
"name": "justinrainbow/json-schema",
"version": "5.2.7",
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
"reference": "8560d4314577199ba51bf2032f02cd1315587c23"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23",
"reference": "8560d4314577199ba51bf2032f02cd1315587c23",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.1",
"json-schema/json-schema-test-suite": "1.2.0",
"phpunit/phpunit": "^4.8.35"
},
"bin": [
"bin/validate-json"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
},
"autoload": {
"psr-4": {
"JsonSchema\\": "src/JsonSchema/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bruno Prieto Reis",
"email": "bruno.p.reis@gmail.com"
},
{
"name": "Justin Rainbow",
"email": "justin.rainbow@gmail.com"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
}
],
"description": "A library to validate a json schema.",
"homepage": "https://github.com/justinrainbow/json-schema",
"keywords": [
"json",
"schema"
],
"time": "2018-02-14 22:26:30"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,679 @@
<?php
namespace Imoje\Payment;
use Exception;
/**
* Class Api
*
* @package Imoje\Payment
*/
class Api
{
const TRANSACTION = 'transaction';
const PROFILE = 'profile';
const MERCHANT = 'merchant';
const SERVICE = 'service';
const TRANSACTION_TYPE_SALE = 'sale';
const TRANSACTION_TYPE_REFUND = 'refund';
/**
* @var array
*/
private static $serviceUrls = [
Util::ENVIRONMENT_PRODUCTION => 'https://api.imoje.pl/v1',
Util::ENVIRONMENT_SANDBOX => 'https://sandbox.api.imoje.pl/v1',
];
/**
* @var string
*/
private $authorizationToken;
/**
* @var string
*/
private $merchantId;
/**
* @var string
*/
private $serviceId;
/**
* @var string
*/
private $environment;
/**
* Api constructor.
*
* @param string $authorizationToken
* @param string $merchantId
* @param string $serviceId
* @param string $environment
*/
public function __construct($authorizationToken, $merchantId, $serviceId, $environment = '')
{
$this->authorizationToken = $authorizationToken;
$this->merchantId = $merchantId;
$this->serviceId = $serviceId;
if (!$environment) {
$environment = Util::ENVIRONMENT_PRODUCTION;
}
$this->environment = $environment;
}
/**
* @param string $firstName
* @param string $lastName
* @param string $street
* @param string $city
* @param string $region
* @param string $postalCode
* @param string $countryCodeAlpha2
*
* @return array
*/
public static function prepareAddressData(
$firstName,
$lastName,
$street,
$city,
$region,
$postalCode,
$countryCodeAlpha2
) {
$array = [
'firstName' => $firstName,
'lastName' => $lastName,
'street' => $street,
'city' => $city,
'postalCode' => $postalCode,
'countryCodeAlpha2' => $countryCodeAlpha2,
];
if ($region) {
$array['region'] = $region;
}
return $array;
}
/**
* @param array $transaction
*
* @return array
*/
public static function parseStringToArray($transaction)
{
$array = [];
if ($transaction['action']['method'] === Util::METHOD_REQUEST_POST) {
parse_str($transaction['action']['contentBodyRaw'], $array);
}
if ($transaction['action']['method'] === Util::METHOD_REQUEST_GET) {
$urlParsed = parse_url($transaction['action']['url']);
if (isset($urlParsed['query']) && $urlParsed['query']) {
parse_str($urlParsed['query'], $array);
}
}
return $array;
}
/**
* @param string $blikProfileId
* @param int $amount
* @param string $currency
* @param string $orderId
* @param string $title
* @param string $clientIp
* @param string $blikKey
* @param string $blikCode
*
* @return string
*/
public function prepareBlikOneclickData(
$blikProfileId,
$amount,
$currency,
$orderId,
$title,
$clientIp,
$blikKey = '',
$blikCode = ''
) {
$array = [
'serviceId' => $this->serviceId,
'blikProfileId' => $blikProfileId,
'amount' => Util::convertAmountToFractional($amount),
'currency' => $currency,
'orderId' => $orderId,
'title' => $title,
'clientIp' => $clientIp,
];
if ($blikKey) {
$array['blikKey'] = $blikKey;
}
if ($blikCode) {
$array['blikCode'] = $blikCode;
}
return json_encode($array);
}
/**
* @param string $id
*
* @return array
*/
public function getTransaction($id)
{
return $this->call(
$this->getTransactionUrl($id),
Util::METHOD_REQUEST_GET
);
}
/**
* @param int $amount
* @param string $currency
* @param string $orderId
* @param string $paymentMethod
* @param string $paymentMethodCode
* @param string $successReturnUrl
* @param string $failureReturnUrl
* @param string $customerFirstName
* @param string $customerLastName
* @param string $customerEmail
* @param string $type
* @param string $clientIp
* @param string $blikCode
* @param array $address
* @param string $cid
*
* @return string
*/
public function prepareData(
$amount,
$currency,
$orderId,
$paymentMethod,
$paymentMethodCode,
$successReturnUrl,
$failureReturnUrl,
$customerFirstName,
$customerLastName,
$customerEmail,
$type = 'sale',
$clientIp = '',
$blikCode = '',
$address = [],
$cid = ''
) {
if (!$clientIp) {
$clientIp = $_SERVER['REMOTE_ADDR'];
}
$array = [
'type' => $type,
'serviceId' => $this->serviceId,
'amount' => Util::convertAmountToFractional($amount),
'currency' => $currency,
'orderId' => (string) $orderId,
'title' => (string) $orderId,
'paymentMethod' => $paymentMethod,
'paymentMethodCode' => $paymentMethodCode,
'successReturnUrl' => $successReturnUrl,
'failureReturnUrl' => $failureReturnUrl,
'clientIp' => $clientIp,
'customer' => [
'firstName' => $customerFirstName,
'lastName' => $customerLastName,
'email' => $customerEmail,
],
];
if ($blikCode) {
$array['blikCode'] = $blikCode;
}
if (isset($address['billing']) && $address['billing']) {
$array['billing'] = $address['billing'];
}
if (isset($address['shipping']) && $address['shipping']) {
$array['shipping'] = $address['shipping'];
}
if ($cid) {
$array['customer']['cid'] = (string) $cid;
}
return json_encode($array);
}
/**
* @param int $amount
*
* @return string
*/
public function prepareRefundData(
$amount
) {
return json_encode([
'amount' => $amount,
'serviceId' => $this->serviceId,
'type' => self::TRANSACTION_TYPE_REFUND,
]);
}
// endregion
/**
* @param string $body
*
* @return array
*/
public function createTransaction($body, $url = '', $method = '')
{
if (!$url) {
$url = $this->getTransactionCreateUrl();
}
if (!$method) {
$method = Util::METHOD_REQUEST_POST;
}
return $this->call(
$url,
$method,
$body
);
}
/**
* @return array
*/
public function getServiceInfo()
{
return $this->call(
$this->getServiceInfoUrl(),
Util::METHOD_REQUEST_GET
);
}
/**
* @param string $cid
*
* @return array
*/
public function getBlikProfileList($cid)
{
return $this->call(
$this->getProfileBlikUrl($cid),
Util::METHOD_REQUEST_GET
);
}
/**
* @param string $body
*
* @return array
*/
public function createRefund($body, $transactionUuid)
{
return $this->call(
$this->getRefundCreateUrl($transactionUuid),
Util::METHOD_REQUEST_POST,
$body
);
}
/**
* Creates full form with order data.
*
* @param array $transaction
* @param string $submitValue
* @param string $submitClass
* @param string $submitStyle
*
* @return string
* @throws Exception
*/
public function buildOrderForm($transaction, $submitValue = '', $submitClass = '', $submitStyle = '')
{
if (!isset($transaction['body']['action'])) {
throw new Exception(json_encode([
'action' => 'apiBuildOrderForm',
'error' => 'Doesnt action exist',
]
));
}
return Util::createOrderForm(
self::parseStringToArray($transaction['body']),
$transaction['body']['action']['url'],
$transaction['body']['action']['method'],
$submitValue,
$submitClass,
$submitStyle
);
}
/**
* @return string
*/
public function getDebitBlikProfileUrl()
{
$baseUrl = self::getServiceUrl();
if ($baseUrl) {
return $baseUrl
. '/'
. self::MERCHANT
. '/'
. $this->merchantId
. '/'
. self::TRANSACTION
. '/'
. self::PROFILE
. '/blik';
}
return '';
}
/**
* @return string
*/
public function getDeactivateBlikProfileUrl()
{
$baseUrl = self::getServiceUrl();
if ($baseUrl) {
return $baseUrl
. '/'
. self::MERCHANT
. '/'
. $this->merchantId
. '/'
. self::PROFILE
. '/deactivate/blik';
}
return '';
}
/**
* @param string $url
* @param string $methodRequest
* @param string $body
*
* @return array
*/
private function call($url, $methodRequest, $body = '')
{
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $methodRequest);
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->authorizationToken,
]);
$resultCurl = json_decode(curl_exec($curl), true);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if (($httpCode !== 200) || !$resultCurl) {
$array = [
'success' => false,
'data' => [
'httpCode' => $httpCode,
'error' => curl_error($curl),
'body' => '',
],
];
if (isset($resultCurl['apiErrorResponse']['message']) && $resultCurl['apiErrorResponse']['message']) {
$array['data']['body'] = $resultCurl['apiErrorResponse']['message'];
}
return $array;
}
if (isset($resultCurl['transaction']['statusCode']) && $resultCurl['transaction']['statusCode']) {
if ($this->check_retype_code($resultCurl['transaction']['statusCode'])) {
$resultCurl['code'] = 1;
}
if ($this->check_new_param_alias($resultCurl['transaction']['statusCode'])) {
$resultCurl['newParamAlias'] = 1;
}
}
return [
'success' => true,
'body' => $resultCurl,
];
}
/**
* Verify that error code needs to retype BLIK code in frontend
*
* @param string $code
*
* @return bool
*/
private function check_retype_code($code)
{
$array = [
'BLK-ERROR-210002',
'BLK-ERROR-210003',
'BLK-ERROR-210004',
'BLK-ERROR-210005',
'BLK-ERROR-210006',
'BLK-ERROR-210007',
'BLK-ERROR-210008',
'BLK-ERROR-210009',
'BLK-ERROR-210010',
'BLK-ERROR-210011',
'BLK-ERROR-210012',
'BLK-ERROR-210013',
'BLK-ERROR-210014',
'BLK-ERROR-210015',
'BLK-ERROR-210016',
'BLK-ERROR-210017',
'BLK-ERROR-210018',
'BLK-ERROR-210019',
'BLK-ERROR-210020',
'BLK-ERROR-210021',
'BLK-ERROR-210022',
];
return in_array($code, $array);
}
/**
* Verify that error code needs to create new paramAlias
*
* @param string $code
*
* @return bool
*/
private function check_new_param_alias($code)
{
$array = [
'BLK-ERROR-210002',
'BLK-ERROR-210003',
'BLK-ERROR-210004',
];
return in_array($code, $array);
}
/**
* @return string
*/
private function getTransactionUrl($id)
{
$baseUrl = self::getServiceUrl();
if ($baseUrl) {
return $baseUrl
. '/'
. self::MERCHANT
. '/'
. $this->merchantId
. '/'
. self::TRANSACTION
. '/'
. $id;
}
return '';
}
/**
* @return string
*/
private function getServiceUrl()
{
if (isset(self::$serviceUrls[$this->environment])) {
return self::$serviceUrls[$this->environment];
}
return '';
}
/**
* @return string
*/
private function getTransactionCreateUrl()
{
$baseUrl = self::getServiceUrl();
if ($baseUrl) {
return $baseUrl
. '/'
. self::MERCHANT
. '/'
. $this->merchantId
. '/'
. self::TRANSACTION;
}
return '';
}
/**
* @return string
*/
private function getServiceInfoUrl()
{
$baseUrl = self::getServiceUrl();
if ($baseUrl) {
return $baseUrl
. '/'
. self::MERCHANT
. '/'
. $this->merchantId
. '/'
. self::SERVICE
. '/'
. $this->serviceId;
}
return '';
}
/**
* @param string $cid
*
* @return string
*/
private function getProfileBlikUrl($cid)
{
$baseUrl = $this->getServiceUrl();
if ($baseUrl) {
return $baseUrl
. '/'
. self::MERCHANT
. '/'
. $this->merchantId
. '/'
. self::PROFILE
. '/'
. self::SERVICE
. '/'
. $this->serviceId
. '/cid/'
. $cid
. '/blik';
}
return '';
}
/**
* @param string $transactionUuid
*
* @return string
*/
private function getRefundCreateUrl($transactionUuid)
{
$baseUrl = $this->getServiceUrl();
if ($baseUrl) {
return $baseUrl
. '/'
. self::MERCHANT
. '/'
. $this->merchantId
. '/'
. self::TRANSACTION
. '/'
. $transactionUuid
. '/refund';
}
return '';
}
}

View File

@@ -0,0 +1,215 @@
<?php
namespace Imoje\Payment;
/**
* Class CartData
*
* @package Imoje\Payment
*/
class CartData
{
const TYPE_SCHEMA_ITEMS = 'cartDataItems';
const TYPE_SCHEMA_ADDRESS = 'cartDataAddress';
const TYPE_SCHEMA_DISCOUNT = 'cartDataDiscount';
const TYPE_SCHEMA_SHIPPING = 'cartDataShipping';
/**
* @var array
*/
private $items;
/**
* @var array
*/
private $addressBilling;
/**
* @var int
*/
private $createdAt;
/**
* @var int
*/
private $amount;
/**
* @var array
*/
private $addressDelivery;
/**
* @var array
*/
private $shipping = [];
/**
* @var array
*/
private $discount = [];
/**
* @param $id
* @param $vat
* @param $name
* @param $amount
* @param $quantity
*
* @return void
*/
public function addItem($id, $vat, $name, $amount, $quantity)
{
$this->items[] = [
'id' => $id,
'vat' => $vat,
'name' => $name,
'amount' => $amount,
'quantity' => $quantity,
];
}
/**
* @param int $vat
* @param string $name
* @param int $amount
*
* @return void
*/
public function setDiscount($vat, $name, $amount)
{
$this->discount = [
'vat' => $vat,
'name' => $name,
'amount' => $amount,
];
}
/**
* @param int $vat
* @param string $name
* @param int $amount
*
* @return void
*/
public function setShipping($vat, $name, $amount)
{
$this->shipping = [
'vat' => $vat,
'name' => $name,
'amount' => $amount,
];
}
/**
* @param int $amount
*
* @return void
*/
public function setAmount($amount)
{
$this->amount = $amount;
}
/**
* @param int $createdAt
*
* @return void
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
}
/**
* @param string $city
* @param string $name
* @param string $phone
* @param string $street
* @param string $country
* @param string $postalCode
*
* @return void
*/
public function setAddressBilling($city, $name, $phone, $street, $country, $postalCode)
{
$this->addressBilling = [
'city' => $city,
'name' => $name,
'phone' => (string) $phone,
'street' => $street,
'country' => $country,
'postalCode' => (string) $postalCode,
];
}
/**
* @param string $city
* @param string $name
* @param string $phone
* @param string $street
* @param string $country
* @param string $postalCode
*
* @return void
*/
public function setAddressDelivery($city, $name, $phone, $street, $country, $postalCode)
{
$this->addressDelivery = [
'city' => $city,
'name' => $name,
'phone' => (string) $phone,
'street' => $street,
'country' => $country,
'postalCode' => (string) $postalCode,
];
}
/**
* @return string
*/
public function prepareCartData()
{
return base64_encode(gzencode(json_encode($this->prepareCartDataArray()), 5));
}
/**
* @return array
*/
public function prepareCartDataArray()
{
$data = [
'address' => [
'billing' => $this->addressBilling,
'delivery' => $this->addressDelivery,
],
'items' => $this->items,
];
if (!empty($this->discount)) {
$data['discount'] = $this->discount;
}
if (!empty($this->shipping)) {
$data['shipping'] = $this->shipping;
}
if (!empty($this->createdAt)) {
$data['createdAt'] = $this->createdAt;
}
if (!empty($this->amount)) {
$data['amount'] = $this->amount;
}
return $data;
}
}

View File

@@ -0,0 +1,218 @@
<?php
namespace Imoje\Payment;
/**
* Class Configuration
*
* @package Imoje\Payment
*/
class Configuration
{
const ROUTE_PAY = '/payment';
const ROUTE_CHECK_PAYMENT_METHODS = '/check/methods';
const METHOD_REQUEST_POST = 'POST';
/**
* @var array
*/
private static $serviceUrls = [
Util::ENVIRONMENT_PRODUCTION => 'https://paywall.imoje.pl',
Util::ENVIRONMENT_SANDBOX => 'https://sandbox.paywall.imoje.pl',
];
/**
* @var string
*/
private static $headerSignatureName = 'HTTP_X_IMOJE_SIGNATURE';
/**
* @var string
*/
private static $serviceKey;
/**
* @var string
*/
private static $serviceId;
/**
* @var string
*/
private static $merchantId;
/**
* @var bool
*/
private static $apiMode = false;
/**
* @var string
*/
private static $environment;
/**
* @var string
*/
private static $authorizationToken;
/**
* @var array
*/
private static $serviceApiUrls = [
Util::ENVIRONMENT_PRODUCTION => 'https://api.imoje.pl/v1',
//Util::ENVIRONMENT_SANDBOX => 'https://sandbox.api.imoje.pl', sandbox doesnt exist at this moment
];
/**
* @return string
*/
public static function getServiceKey()
{
return self::$serviceKey;
}
/**
* @param string $serviceKey
*/
public static function setServiceKey($serviceKey)
{
self::$serviceKey = trim($serviceKey);
}
/**
* @return string
*/
public static function getServiceId()
{
return self::$serviceId;
}
/**
* @param string $serviceId
*/
public static function setServiceId($serviceId)
{
self::$serviceId = trim($serviceId);
}
/**
* @return string
*/
public static function getMerchantId()
{
return self::$merchantId;
}
/**
* @param string $merchantId
*/
public static function setMerchantId($merchantId)
{
self::$merchantId = trim($merchantId);
}
/**
* @return string
*/
public static function getEnvironment()
{
return self::$environment;
}
/**
* @param string $environment
*/
public static function setEnvironment($environment)
{
self::$environment = $environment;
}
/**
* @return string
*/
public static function getApiMode()
{
return self::$apiMode;
}
/**
* @param bool $apiMode
*/
public static function setApiMode($apiMode)
{
self::$apiMode = $apiMode;
}
/**
* @return string
*/
public static function getAuthorizationToken()
{
return self::$authorizationToken;
}
/**
* @param string $authorizationToken
*/
public static function setAuthorizationToken($authorizationToken)
{
self::$authorizationToken = $authorizationToken;
}
/**
* @param string $environment
*
* @return string|bool
*/
public static function getServiceUrl($environment)
{
if(isset(self::$serviceUrls[$environment])) {
return self::$serviceUrls[$environment];
}
return false;
}
/**
* @return string
*/
public static function getHeaderSignatureName()
{
return self::$headerSignatureName;
}
/**
* @param string $environment
*
* @return string|bool
*/
public static function getServiceApiUrl($environment)
{
if(isset(self::$serviceApiUrls[$environment])) {
return self::$serviceApiUrls[$environment];
}
return false;
}
/**
* @param string $environment
* @param string $mid
*
* @return string|bool
*/
public static function getTransactionCreateUrlApi($environment, $mid)
{
if(!($baseApiUrl = self::getServiceApiUrl($environment))) {
return false;
};
return $baseApiUrl . '/merchant/' . $mid . '/transaction';
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Imoje\Payment;
/**
* Class Payment
*
* @package Imoje\Payment
*/
class Payment
{
/**
* Payment constructor.
*
* @param string $environment
* @param string $serviceKey
* @param string $serviceId
* @param string $merchantId
* @param bool $apiMode
* @param string $authorizationToken
*/
public function __construct($environment, $serviceKey, $serviceId, $merchantId, $apiMode = false, $authorizationToken = '')
{
Configuration::setEnvironment($environment);
Configuration::setServiceKey($serviceKey);
Configuration::setServiceId($serviceId);
Configuration::setMerchantId($merchantId);
if($apiMode) {
Configuration::setApiMode($apiMode);
Configuration::setAuthorizationToken($authorizationToken);
}
}
/**
* Creates full form with order data.
*
* @param array $orderData
* @param string $submitValue
* @param string $url
*
* @return string
*/
public function buildOrderForm($orderData, $submitValue = '', $url = '')
{
return Util::createOrderForm(Util::prepareOrderData($orderData), $submitValue, $url);
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace Imoje\Payment;
use DateTime;
use Twisto\Address;
use Twisto\Customer;
use Twisto\Item;
use Twisto\Order;
/**
* Class Twisto
*
* @package Imoje\Payment
*/
class Twisto
{
/**
* @var string[]
*/
private static $urlsScript = [
Util::ENVIRONMENT_PRODUCTION => 'https://api.twisto.pl/v2/lib/twisto.js',
Util::ENVIRONMENT_SANDBOX => 'https://static.test.twisto.pl/api/v2/twisto.js',
];
/**
* @param string $environment
*
* @return string
*/
public static function getUrlScript($environment)
{
return self::$urlsScript[$environment];
}
/**
* @return string
*/
public static function getDataSandbox()
{
return json_encode([
'status' => 'accepted-verification-required',
'transaction_id' => 'sandbox',
]);
}
/**
* @param string $itemName
*
* @return bool|string
*/
public static function getItemName($itemName
) {
return substr($itemName, 0, 255);
}
/**
* @param array $paymentMethods
* @param bool $twistoEnabled
*
* @return bool
*/
public static function isActive($paymentMethods, $twistoEnabled)
{
return isset(
$paymentMethods['twisto'],
$paymentMethods['twisto']['pk'],
$paymentMethods['twisto']['sk']
)
&& $paymentMethods['twisto']
&& $paymentMethods['twisto']['pk']
&& $paymentMethods['twisto']['sk']
&& $twistoEnabled;
}
/**
* @param string $secretKey
* @param string $total
* @param array $cartData
* @param string $email
* @param DateTime $dateCreated
* @param array $previousOrders
*
* @return string
*/
public static function getPayload($secretKey, $total, $cartData, $email, DateTime $dateCreated, $previousOrders)
{
if(empty($cartData)) {
return '';
}
require __DIR__ . '/../vendor/twistopayments/twisto.php/src/Twisto.php';
$twistoItems = [];
$twistoItems[] = new Item(
Item::TYPE_SHIPMENT,
self::getItemName($cartData['shipping']['name']),
'shipment',
1,
round($cartData['shipping']['amount'], 2),
$cartData['shipping']['vatRate']);
if(isset($cartData['discount'])) {
$twistoItems[] = new Item(
Item::TYPE_DISCOUNT,
self::getItemName($cartData['discount']['name']),
'discount',
1,
-$cartData['discount']['amount'],
$cartData['discount']['vatRate']);
}
foreach($cartData['items'] as $item) {
$twistoItems[] = new Item(
Item::TYPE_DEFAULT,
self::getItemName($item['name']),
$item['productId'],
$item['quantity'],
Util::multiplyValues($item['amount'], $item['quantity'], 2),
$item['vatRate']
);
}
$cartTotal = 0;
foreach($twistoItems as $k => $v) {
if(isset($v->price_vat)) {
$cartTotal += $v->price_vat;
}
}
if($cartTotal !== $total) {
$twistoItems[] = new Item(
Item::TYPE_ROUND,
'round',
'round',
1,
round($total - $cartTotal, 2),
0
);
}
$addressBilling = $cartData['addressBilling'];
$addressShipping = $cartData['addressDelivery'];
$data = new Order($dateCreated,
new Address($addressBilling['name'],
$addressBilling['street'],
$addressBilling['city'],
str_replace('-', '', $addressBilling['postalCode']),
$addressBilling['countryCode'],
$addressBilling['phone']),
new Address($addressShipping['name'],
$addressShipping['street'],
$addressShipping['city'],
str_replace('-', '', $addressShipping['postalCode']),
$addressShipping['countryCode'],
$addressShipping['phone']),
$total, $twistoItems);
$twisto = new \Twisto\Twisto();
$twisto->setSecretKey($secretKey);
return $twisto->getCheckPayload(new Customer($email), $data, $previousOrders);
}
}

View File

@@ -0,0 +1,795 @@
<?php
namespace Imoje\Payment;
use JsonSchema\Validator;
/**
* Class Util
*
* @package Imoje\Payment
*/
class Util
{
// region notification codes
const NC_OK = 0;
const NC_INVALID_SIGNATURE = 1;
const NC_SERVICE_ID_NOT_MATCH = 2;
const NC_ORDER_NOT_FOUND = 3;
const NC_INVALID_SIGNATURE_HEADERS = 4;
const NC_EMPTY_NOTIFICATION = 5;
const NC_NOTIFICATION_IS_NOT_JSON = 6;
const NC_INVALID_JSON_STRUCTURE = 7;
const NC_INVALID_ORDER_STATUS = 8;
const NC_AMOUNT_NOT_MATCH = 9;
const NC_UNHANDLED_STATUS = 10;
const NC_ORDER_STATUS_NOT_CHANGED = 11;
const NC_CART_NOT_FOUND = 12;
const NC_ORDER_STATUS_IS_NOT_SETTLED_ORDER_ARRANGEMENT_AFTER_IPN = 13;
const NC_ORDER_EXISTS_ORDER_ARRANGEMENT_AFTER_IPN = 14;
const NC_REQUEST_IS_NOT_POST = 15;
const NC_MISSING_TWISTO_RESPONSE_IN_POST = 16;
const NC_MISSING_ORDER_ID_IN_POST = 17;
const NC_CURL_IS_NOT_INSTALLED = 18;
const NC_MISSING_SIGNATURE_IN_POST = 19;
const NC_COULD_NOT_INSERT_TRANSACTION_ID_TO_DB = 20;
const NC_UNKNOWN = 100;
// endregion
// region notification status
const NS_OK = 'ok';
const NS_ERROR = 'error';
// endregion
const METHOD_REQUEST_POST = 'POST';
const METHOD_REQUEST_GET = 'GET';
const ENVIRONMENT_PRODUCTION = 'production';
const ENVIRONMENT_SANDBOX = 'sandbox';
private static $cdnUrl = 'https://data.imoje.pl';
/**
* @var array
*/
private static $supportedCurrencies = [
'pln' => 'PLN',
'eur' => 'EUR',
'czk' => 'CZK',
'gbp' => 'GBP',
'usd' => 'USD',
'uah' => 'UAH',
'hrk' => 'HRK',
'huf' => 'HUF',
'sek' => 'SEK',
'ron' => 'RON',
'chf' => 'CHF',
'bgn' => 'BGN',
];
/**
* @var array
*/
private static $paymentMethods = [
'blik' => 'blik',
'paylater' => 'imoje_paylater',
'pbl' => 'pbl',
'card' => 'card',
'ing' => 'ing',
];
/**
* @var array
*/
private static $paymentMethodCodeList = [
'blik' => 'blik',
'alior' => 'alior',
'bnpparibas' => 'bnpparibas',
'bos' => 'bos',
'bs' => 'bs',
'bspb' => 'bspb',
'bzwbk' => 'bzwbk',
'citi' => 'citi',
'creditagricole' => 'creditagricole',
'envelo' => 'envelo',
'getin' => 'getin',
'ideabank' => 'ideabank',
'ing' => 'ing',
'inteligo' => 'inteligo',
'ipko' => 'ipko',
'millennium' => 'millennium',
'mtransfer' => 'mtransfer',
'nest' => 'nest',
'noble' => 'noble',
'pbs' => 'pbs',
'pekao24' => 'pekao24',
'plusbank' => 'plusbank',
'pocztowy' => 'pocztowy',
'tmobile' => 'tmobile',
'paylater' => 'imoje_twisto',
'blik_oneclick' => 'blik_oneclick',
];
/**
* @var array
*/
private static $paymentMethodCodeLogoExt = [
'alior' => 'alior.svg',
'bnpparibas' => 'bnpparibas.png',
'bos' => 'bos.png',
'bs' => 'bs.png',
'bspb' => 'bspb.png',
'bzwbk' => 'bzwbk.png',
'citi' => 'citi.png',
'creditagricole' => 'creditagricole.svg',
'envelo' => 'envelo.png',
'getin' => 'getin.svg',
'ideabank' => 'ideabank.png',
'ing' => 'ing.png',
'inteligo' => 'inteligo.png',
'ipko' => 'ipko.png',
'millennium' => 'millennium.svg',
'mtransfer' => 'mtransfer.png',
'nest' => 'nest.svg',
'noble' => 'noble.png',
'pbs' => 'pbs.png',
'pekao24' => 'pekao24.svg',
'plusbank' => 'plusbank.png',
'pocztowy' => 'pocztowy.svg',
'tmobile' => 'tmobile.svg',
];
/**
* @var array
*/
private static $hashMethods = [
'sha224' => 'sha224',
'sha256' => 'sha256',
'sha384' => 'sha384',
'sha512' => 'sha512',
];
/**
* @var array
*/
private static $transactionStatuses = [
'new' => 'new',
'authorized' => 'authorized',
'pending' => 'pending',
'submitted_for_settlement'
=> 'submitted_for_settlement',
'rejected' => 'rejected',
'settled' => 'settled',
'error' => 'error',
'cancelled' => 'cancelled',
];
/**
* Functions that return true when passed currency is on supported currencies list.
*
* @param string $currencyCode ISO4217
*
* @return bool
*/
public static function canUseForCurrency($currencyCode)
{
return isset(self::$supportedCurrencies[strtolower($currencyCode)]);
}
/**
* @param array $order
* @param string $url
* @param string $method
* @param string $submitValue
*
* @return string
*/
public static function createOrderForm($order, $url = '', $method = '', $submitValue = '')
{
if (!$submitValue) {
$submitValue = 'Continue';
}
if (!$url) {
$url = self::getServiceUrl();
}
if (!$method) {
$method = 'POST';
}
$form = '<form method="' . $method . '" action="' . $url . '">';
if (is_array($order)) {
foreach ($order as $key => $value) {
$form .= '<input type="hidden" value="' . htmlentities($value) . '" name="' . $key . '" id="imoje_' . $key . '">';
}
}
$form .= '<button type="submit" id="submit-payment-form">' . $submitValue . '</button>';
$form .= '</form>';
return $form;
}
/**
* @return string
*/
public static function getServiceUrl()
{
$url = Configuration::getServiceUrl(Configuration::getEnvironment());
if ($url === false) {
return '';
}
return $url . Configuration::ROUTE_PAY;
}
/**
* @param int $amount
* @param string $currency
* @param string $orderId
* @param string $orderDescription
* @param string $customerFirstName
* @param string $customerLastName
* @param null|string $customerEmail
* @param null|string $customerPhone
* @param null|string $urlSuccess
* @param null|string $urlFailure
* @param null|string $urlReturn
* @param null|string $version
* @param null|string $cartData
* @param null|string $visibleMethod
*
* @return array
*/
public static function prepareData(
$amount, $currency, $orderId, $orderDescription = null, $customerFirstName, $customerLastName,
$customerEmail = null, $customerPhone = null, $urlSuccess = null,
$urlFailure = null, $urlReturn = null, $version = null, $cartData = null, $visibleMethod = null
) {
$data = [];
$data['amount'] = Util::convertAmountToFractional($amount);
$data['currency'] = $currency;
$data['orderId'] = $orderId;
if ($orderDescription) {
$data['orderDescription'] = $orderDescription;
}
$data['customerFirstName'] = $customerFirstName;
$data['customerLastName'] = $customerLastName;
if ($customerPhone) {
$data['customerPhone'] = $customerPhone;
}
if ($customerEmail) {
$data['customerEmail'] = $customerEmail;
}
if ($urlSuccess) {
$data['urlSuccess'] = $urlSuccess;
}
if ($urlFailure) {
$data['urlFailure'] = $urlFailure;
}
if ($urlReturn) {
$data['urlReturn'] = $urlReturn;
}
if ($version) {
$data['version'] = $version;
}
if ($cartData) {
$data['cartData'] = $cartData;
}
if ($visibleMethod) {
$data['visibleMethod'] = $visibleMethod;
}
return $data;
}
/**
* @param float $amount
*
* @return int
*/
public static function convertAmountToFractional($amount)
{
return (int) round( (float)$amount * 100, 2 );// self::multiplyValues(round($amount, 2), 100, 0);
}
/**
* @param number $firstValue
* @param number $secondValue
* @param number $precision
*
* @return float
*/
public static function multiplyValues($firstValue, $secondValue, $precision)
{
return round($firstValue * $secondValue, $precision);
}
/**
* @param string $serviceId
* @param int $amount
* @param string $currency
* @param string $orderId
* @param string $paymentMethod
* @param string $paymentMethodCode
* @param string $successReturnUrl
* @param string $failureReturnUrl
* @param string $customerFirstName
* @param string $customerLastName
* @param string $customerEmail
* @param string $clientIp
* @param string $type
*
* @return string
*/
public static function prepareDataApi(
$serviceId,
$amount,
$currency,
$orderId,
$paymentMethod,
$paymentMethodCode,
$successReturnUrl,
$failureReturnUrl,
$customerFirstName,
$customerLastName,
$customerEmail,
$type = 'sale',
$clientIp = ''
) {
if (!$clientIp) {
$clientIp = $_SERVER['REMOTE_ADDR'];
}
return json_encode([
'type' => $type,
'serviceId' => $serviceId,
'amount' => Util::convertAmountToFractional($amount),
'currency' => $currency,
'orderId' => (string) $orderId,
'paymentMethod' => $paymentMethod,
'paymentMethodCode' => $paymentMethodCode,
'successReturnUrl' => $successReturnUrl,
'failureReturnUrl' => $failureReturnUrl,
'clientIp' => $clientIp,
'customer' => [
'firstName' => $customerFirstName,
'lastName' => $customerLastName,
'email' => $customerEmail,
],
]);
}
/**
* @param string $string
*
* @return array
*/
public static function parseStringToArray($string)
{
$array = [];
parse_str($string, $array);
return $array;
}
/**
* Simple functions that adds signature and service_id to order array depends on Configuration::$apiMode.
*
* @param array $order
* @param string $hashMethod
*
* @return array
*/
public static function prepareOrderData($order, $hashMethod = 'sha256')
{
if (Configuration::getApiMode()) {
return $order;
}
$order['serviceId'] = Configuration::getServiceId();
$order['merchantId'] = Configuration::getMerchantId();
$order['signature'] = self::createSignature($order, Configuration::getServiceKey(), $hashMethod);
return $order;
}
/**
* @param array $orderData
* @param string $serviceKey
* @param string $hashMethod
*
* @return string|bool
*/
private static function createSignature($orderData, $serviceKey, $hashMethod = 'sha256')
{
if (!isset(self::$hashMethods[$hashMethod])
|| !is_array($orderData)) {
return false;
}
ksort($orderData);
$data = [];
foreach ($orderData as $key => $value) {
$data[] = $key . '=' . $value;
}
return self::hashSignature($hashMethod, implode('&', $data), $serviceKey) . ';' . $hashMethod;
}
/**
* @param string $hashMethod
* @param string $data
* @param string $serviceKey
*
* @return string
*/
public static function hashSignature($hashMethod, $data, $serviceKey)
{
return hash($hashMethod, $data . $serviceKey);
}
/**
* @return array
*/
public static function getSupportedCurrencies()
{
return self::$supportedCurrencies;
}
/**
* @param string $paymentMethod
*
* @return string
*/
public static function getPaymentMethod($paymentMethod)
{
if (isset(self::$paymentMethods[$paymentMethod])) {
return self::$paymentMethods[$paymentMethod];
}
return '';
}
/**
* @param string $paymentMethodCode
*
* @return string
*/
public static function getPaymentMethodCodeLogo($paymentMethodCode)
{
if (isset(self::$paymentMethodCodeLogoExt[$paymentMethodCode])) {
return self::$cdnUrl . '/img/pay/' . self::$paymentMethodCodeLogoExt[$paymentMethodCode];
}
return '';
}
/**
* @param string $paymentMethodCode
*
* @return string
*/
public static function getPaymentMethodCode($paymentMethodCode)
{
if (isset(self::$paymentMethodCodeList[$paymentMethodCode])) {
return self::$paymentMethodCodeList[$paymentMethodCode];
}
return '';
}
/**
* @param string $status
* @param string $code
* @param bool|string $debugMode
* @param string|int|null $statusBefore
* @param string|int|null $statusAfter
* @param bool|string $arrangementCreateOrder
**
*
* @return string
*/
public static function notificationResponse($status, $code = '', $debugMode = false, $statusBefore = null, $statusAfter = null, $arrangementCreateOrder = false)
{
$response = [
'status' => $status,
];
if ($debugMode && $code) {
$response['data'] = [
'code' => $code,
];
}
if ($arrangementCreateOrder) {
$response['data']['creatingOrderMode'] = $arrangementCreateOrder;
}
if ($statusBefore) {
$response['data']['statusBefore'] = $statusBefore;
}
if ($statusAfter) {
$response['data']['statusAfter'] = $statusAfter;
}
// region add additional data for some cases and set proper header
switch ($status) {
case self::NS_OK:
header('HTTP/1.1 200 OK');
break;
case self::NS_ERROR:
header('HTTP/1.1 404 Not Found');
break;
default:
break;
}
// endregion
header('Content-Type: application/json');
return json_encode($response);
}
/**
* @param $transaction
*
* @return string|void
*/
public static function calculateAmountToRefund($transaction)
{
$refundAmount = 0;
if (isset($transaction['body']['transaction']['refunds']) && $transaction['body']['transaction']['refunds']) {
foreach ($transaction['body']['transaction']['refunds'] as $refund) {
if ($refund['transaction']['status'] === 'settled') {
$refundAmount += $refund['transaction']['amount'];
}
}
}
return $transaction['body']['transaction']['amount'] - $refundAmount;
}
/**
* Verify body and signature of notification
*
* @param string $serviceKey
* @param string $serviceId
*
* @return bool|array
*/
public static function checkRequestNotification($serviceKey, $serviceId)
{
if (!isset($_SERVER['CONTENT_TYPE'], $_SERVER[Configuration::getHeaderSignatureName()]) || strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== 0) {
return self::NC_INVALID_SIGNATURE_HEADERS;
}
$payload = file_get_contents('php://input', true);
if (!$payload) {
return self::NC_EMPTY_NOTIFICATION;
}
if (!self::isJson($payload)) {
return self::NC_NOTIFICATION_IS_NOT_JSON;
}
$header = $_SERVER[Configuration::getHeaderSignatureName()];
$header = (explode(';', $header));
$algoFromNotification = explode('=', $header[3]);
$algoFromNotification = $algoFromNotification[1];
$headerSignature = explode('=', $header[2]);
if ($headerSignature[1] !== self::hashSignature($algoFromNotification, $payload, $serviceKey)) {
return self::NC_INVALID_SIGNATURE;
}
if (!self::validateNotificationJson($payload)['success']) {
return self::NC_INVALID_JSON_STRUCTURE;
}
$payloadDecoded = json_decode($payload, true);
if ($payloadDecoded['transaction']['serviceId'] !== $serviceId) {
return self::NC_SERVICE_ID_NOT_MATCH;
}
return $payloadDecoded;
}
/**
* @param string $variable
*
* @return bool
*/
public static function isJson($variable)
{
json_decode($variable);
return (json_last_error() === JSON_ERROR_NONE);
}
/**
* @param string $notification
*
* @return array
*/
private static function validateNotificationJson($notification)
{
$notification = json_decode($notification);
$schema = [
'title' => 'order',
'type' => 'object',
'properties' => [
'transaction' => [
'type' => 'object',
'properties' => [
'amount' => [
'type' => 'integer',
'minimum' => 0,
'exclusiveMinimum' => true,
],
'currency' => [
'type' => 'string',
'enum' => array_values(self::$supportedCurrencies),
],
'status' => [
'type' => 'string',
'enum' => array_values(self::getTransactionStatuses()),
],
'orderId' => [
'type' => 'string',
],
'serviceId' => [
'type' => 'string',
],
'type' => [
'type' => 'string',
'enum' => [
'sale',
'refund',
],
],
],
'required' => [
'amount',
'currency',
'status',
'orderId',
'serviceId',
'type',
],
],
],
'required' => [
'transaction',
],
];
$validator = new Validator();
$validator->validate($notification, json_decode(json_encode($schema)));
if ($validator->isValid()) {
return [
'success' => true,
'errors' => [],
];
}
$errors = [];
foreach ($validator->getErrors() as $error) {
$errors[$error['property']] = $error['message'];
}
return [
'success' => false,
'errors' => $errors,
];
}
/**
* @return array
*/
public static function getTransactionStatuses()
{
return self::$transactionStatuses;
}
/**
* @param string|array $json
*/
public static function doResponseJson($json)
{
header('Content-Type: application/json');
echo $json;
die();
}
/**
* @param string|int| $data
*
* @return false|string
*/
public static function getCid($data)
{
return hash('md5', $data);
}
/**
* @param array $payloadDecoded
* @param int $amount
* @param string $currency
*
* @return bool
*/
public static function checkRequestAmount(array $payloadDecoded, $amount, $currency)
{
return $payloadDecoded['transaction']['amount'] === $amount && $payloadDecoded['transaction']['currency'] === $currency;
}
/**
* @param string $name
* @param string $street
* @param string $city
* @param string $postalCode
* @param string $countryCode
* @param string $phone
* @param string $region
*
* @return array
*/
public static function formatAddress($name, $street, $city, $postalCode, $countryCode, $phone, $region = '')
{
return [
'name' => $name,
'street' => $street,
'city' => $city,
'postalCode' => $postalCode,
'countryCode' => $countryCode,
'phone' => $phone,
'region' => $region,
];
}
/**
* @param int $amount
*
* @return float
*/
public static function convertAmountToMain($amount)
{
return (float) round($amount / 100, 2);
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit20238b54c7f11e178375fb4176f96fe0::getLoader();

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,251 @@
#!/usr/bin/env php
<?php
/**
* JSON schema validator
*
* @author Christian Weiske <christian.weiske@netresearch.de>
*/
/**
* Dead simple autoloader
*
* @param string $className Name of class to load
*
* @return void
*/
function __autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
if (stream_resolve_include_path($fileName)) {
require_once $fileName;
}
}
// support running this tool from git checkout
if (is_dir(__DIR__ . '/../src/JsonSchema')) {
set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path());
}
$arOptions = array();
$arArgs = array();
array_shift($argv);//script itself
foreach ($argv as $arg) {
if ($arg{0} == '-') {
$arOptions[$arg] = true;
} else {
$arArgs[] = $arg;
}
}
if (count($arArgs) == 0
|| isset($arOptions['--help']) || isset($arOptions['-h'])
) {
echo <<<HLP
Validate schema
Usage: validate-json data.json
or: validate-json data.json schema.json
Options:
--dump-schema Output full schema and exit
--dump-schema-url Output URL of schema
--verbose Show additional output
--quiet Suppress all output
-h --help Show this help
HLP;
exit(1);
}
if (count($arArgs) == 1) {
$pathData = $arArgs[0];
$pathSchema = null;
} else {
$pathData = $arArgs[0];
$pathSchema = getUrlFromPath($arArgs[1]);
}
/**
* Show the json parse error that happened last
*
* @return void
*/
function showJsonError()
{
$constants = get_defined_constants(true);
$json_errors = array();
foreach ($constants['json'] as $name => $value) {
if (!strncmp($name, 'JSON_ERROR_', 11)) {
$json_errors[$value] = $name;
}
}
output('JSON parse error: ' . $json_errors[json_last_error()] . "\n");
}
function getUrlFromPath($path)
{
if (parse_url($path, PHP_URL_SCHEME) !== null) {
//already an URL
return $path;
}
if ($path{0} == '/') {
//absolute path
return 'file://' . $path;
}
//relative path: make absolute
return 'file://' . getcwd() . '/' . $path;
}
/**
* Take a HTTP header value and split it up into parts.
*
* @param $headerValue
* @return array Key "_value" contains the main value, all others
* as given in the header value
*/
function parseHeaderValue($headerValue)
{
if (strpos($headerValue, ';') === false) {
return array('_value' => $headerValue);
}
$parts = explode(';', $headerValue);
$arData = array('_value' => array_shift($parts));
foreach ($parts as $part) {
list($name, $value) = explode('=', $part);
$arData[$name] = trim($value, ' "\'');
}
return $arData;
}
/**
* Send a string to the output stream, but only if --quiet is not enabled
*
* @param $str string A string output
*/
function output($str) {
global $arOptions;
if (!isset($arOptions['--quiet'])) {
echo $str;
}
}
$urlData = getUrlFromPath($pathData);
$context = stream_context_create(
array(
'http' => array(
'header' => array(
'Accept: */*',
'Connection: Close'
),
'max_redirects' => 5
)
)
);
$dataString = file_get_contents($pathData, false, $context);
if ($dataString == '') {
output("Data file is not readable or empty.\n");
exit(3);
}
$data = json_decode($dataString);
unset($dataString);
if ($data === null) {
output("Error loading JSON data file\n");
showJsonError();
exit(5);
}
if ($pathSchema === null) {
if (isset($http_response_header)) {
array_shift($http_response_header);//HTTP/1.0 line
foreach ($http_response_header as $headerLine) {
list($hName, $hValue) = explode(':', $headerLine, 2);
$hName = strtolower($hName);
if ($hName == 'link') {
//Link: <http://example.org/schema#>; rel="describedBy"
$hParts = parseHeaderValue($hValue);
if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') {
$pathSchema = trim($hParts['_value'], ' <>');
}
} else if ($hName == 'content-type') {
//Content-Type: application/my-media-type+json;
// profile=http://example.org/schema#
$hParts = parseHeaderValue($hValue);
if (isset($hParts['profile'])) {
$pathSchema = $hParts['profile'];
}
}
}
}
if (is_object($data) && property_exists($data, '$schema')) {
$pathSchema = $data->{'$schema'};
}
//autodetect schema
if ($pathSchema === null) {
output("JSON data must be an object and have a \$schema property.\n");
output("You can pass the schema file on the command line as well.\n");
output("Schema autodetection failed.\n");
exit(6);
}
}
if ($pathSchema{0} == '/') {
$pathSchema = 'file://' . $pathSchema;
}
$resolver = new JsonSchema\Uri\UriResolver();
$retriever = new JsonSchema\Uri\UriRetriever();
try {
$urlSchema = $resolver->resolve($pathSchema, $urlData);
if (isset($arOptions['--dump-schema-url'])) {
echo $urlSchema . "\n";
exit();
}
} catch (Exception $e) {
output("Error loading JSON schema file\n");
output($urlSchema . "\n");
output($e->getMessage() . "\n");
exit(2);
}
$refResolver = new JsonSchema\SchemaStorage($retriever, $resolver);
$schema = $refResolver->resolveRef($urlSchema);
if (isset($arOptions['--dump-schema'])) {
$options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
echo json_encode($schema, $options) . "\n";
exit();
}
try {
$validator = new JsonSchema\Validator();
$validator->check($data, $schema);
if ($validator->isValid()) {
if(isset($arOptions['--verbose'])) {
output("OK. The supplied JSON validates against the schema.\n");
}
} else {
output("JSON does not validate. Violations:\n");
foreach ($validator->getErrors() as $error) {
output(sprintf("[%s] %s\n", $error['property'], $error['message']));
}
exit(23);
}
} catch (Exception $e) {
output("JSON does not validate. Error:\n");
output($e->getMessage() . "\n");
output("Error code: " . $e->getCode() . "\n");
exit(24);
}

View File

@@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
);

View File

@@ -0,0 +1,11 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'),
'Imoje\\Payment\\' => array($baseDir . '/src'),
);

View File

@@ -0,0 +1,52 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit20238b54c7f11e178375fb4176f96fe0
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit20238b54c7f11e178375fb4176f96fe0', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit20238b54c7f11e178375fb4176f96fe0', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit20238b54c7f11e178375fb4176f96fe0::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
return $loader;
}
}

View File

@@ -0,0 +1,39 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit20238b54c7f11e178375fb4176f96fe0
{
public static $prefixLengthsPsr4 = array (
'J' =>
array (
'JsonSchema\\' => 11,
),
'I' =>
array (
'Imoje\\Payment\\' => 14,
),
);
public static $prefixDirsPsr4 = array (
'JsonSchema\\' =>
array (
0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema',
),
'Imoje\\Payment\\' =>
array (
0 => __DIR__ . '/../..',
),
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit20238b54c7f11e178375fb4176f96fe0::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit20238b54c7f11e178375fb4176f96fe0::$prefixDirsPsr4;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,70 @@
[
{
"name": "justinrainbow/json-schema",
"version": "5.2.8",
"version_normalized": "5.2.8.0",
"source": {
"type": "git",
"url": "https://github.com/justinrainbow/json-schema.git",
"reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4",
"reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.2.20",
"json-schema/json-schema-test-suite": "1.2.0",
"phpunit/phpunit": "^4.8.35"
},
"time": "2019-01-14T23:55:14+00:00",
"bin": [
"bin/validate-json"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"JsonSchema\\": "src/JsonSchema/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bruno Prieto Reis",
"email": "bruno.p.reis@gmail.com"
},
{
"name": "Justin Rainbow",
"email": "justin.rainbow@gmail.com"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
}
],
"description": "A library to validate a json schema.",
"homepage": "https://github.com/justinrainbow/json-schema",
"keywords": [
"json",
"schema"
]
}
]

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,5 @@
/docs export-ignore
/tests export-ignore
.gitignore export-ignore
.travis.yml export-ignore
phpunit.dist.xml export-ignore

View File

@@ -0,0 +1,30 @@
<?php
$finder = new PhpCsFixer\Finder();
$config = new PhpCsFixer\Config('json-schema');
$finder->in(__DIR__);
/* Based on ^2.1 of php-cs-fixer */
$config
->setRules(array(
// default
'@PSR2' => true,
'@Symfony' => true,
// additionally
'array_syntax' => array('syntax' => 'long'),
'binary_operator_spaces' => false,
'concat_space' => array('spacing' => 'one'),
'no_useless_else' => true,
'no_useless_return' => true,
'ordered_imports' => true,
'phpdoc_no_package' => false,
'phpdoc_order' => true,
'phpdoc_summary' => false,
'pre_increment' => false,
'simplified_null_return' => false,
'trailing_comma_in_multiline_array' => false,
))
->setFinder($finder)
;
return $config;

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,206 @@
# JSON Schema for PHP
[![Build Status](https://travis-ci.org/justinrainbow/json-schema.svg?branch=master)](https://travis-ci.org/justinrainbow/json-schema)
[![Latest Stable Version](https://poser.pugx.org/justinrainbow/json-schema/v/stable.png)](https://packagist.org/packages/justinrainbow/json-schema)
[![Total Downloads](https://poser.pugx.org/justinrainbow/json-schema/downloads.png)](https://packagist.org/packages/justinrainbow/json-schema)
A PHP Implementation for validating `JSON` Structures against a given `Schema`.
See [json-schema](http://json-schema.org/) for more details.
## Installation
### Library
```bash
git clone https://github.com/justinrainbow/json-schema.git
```
### Composer
[Install PHP Composer](https://getcomposer.org/doc/00-intro.md)
```bash
composer require justinrainbow/json-schema
```
## Usage
```php
<?php
$data = json_decode(file_get_contents('data.json'));
// Validate
$validator = new JsonSchema\Validator;
$validator->validate($data, (object)['$ref' => 'file://' . realpath('schema.json')]);
if ($validator->isValid()) {
echo "The supplied JSON validates against the schema.\n";
} else {
echo "JSON does not validate. Violations:\n";
foreach ($validator->getErrors() as $error) {
echo sprintf("[%s] %s\n", $error['property'], $error['message']);
}
}
```
### Type coercion
If you're validating data passed to your application via HTTP, you can cast strings and booleans to
the expected types defined by your schema:
```php
<?php
use JsonSchema\SchemaStorage;
use JsonSchema\Validator;
use JsonSchema\Constraints\Factory;
use JsonSchema\Constraints\Constraint;
$request = (object)[
'processRefund'=>"true",
'refundAmount'=>"17"
];
$validator->validate(
$request, (object) [
"type"=>"object",
"properties"=>(object)[
"processRefund"=>(object)[
"type"=>"boolean"
],
"refundAmount"=>(object)[
"type"=>"number"
]
]
],
Constraint::CHECK_MODE_COERCE_TYPES
); // validates!
is_bool($request->processRefund); // true
is_int($request->refundAmount); // true
```
A shorthand method is also available:
```PHP
$validator->coerce($request, $schema);
// equivalent to $validator->validate($data, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
```
### Default values
If your schema contains default values, you can have these automatically applied during validation:
```php
<?php
use JsonSchema\Validator;
use JsonSchema\Constraints\Constraint;
$request = (object)[
'refundAmount'=>17
];
$validator = new Validator();
$validator->validate(
$request,
(object)[
"type"=>"object",
"properties"=>(object)[
"processRefund"=>(object)[
"type"=>"boolean",
"default"=>true
]
]
],
Constraint::CHECK_MODE_APPLY_DEFAULTS
); //validates, and sets defaults for missing properties
is_bool($request->processRefund); // true
$request->processRefund; // true
```
### With inline references
```php
<?php
use JsonSchema\SchemaStorage;
use JsonSchema\Validator;
use JsonSchema\Constraints\Factory;
$jsonSchema = <<<'JSON'
{
"type": "object",
"properties": {
"data": {
"oneOf": [
{ "$ref": "#/definitions/integerData" },
{ "$ref": "#/definitions/stringData" }
]
}
},
"required": ["data"],
"definitions": {
"integerData" : {
"type": "integer",
"minimum" : 0
},
"stringData" : {
"type": "string"
}
}
}
JSON;
// Schema must be decoded before it can be used for validation
$jsonSchemaObject = json_decode($jsonSchema);
// The SchemaStorage can resolve references, loading additional schemas from file as needed, etc.
$schemaStorage = new SchemaStorage();
// This does two things:
// 1) Mutates $jsonSchemaObject to normalize the references (to file://mySchema#/definitions/integerData, etc)
// 2) Tells $schemaStorage that references to file://mySchema... should be resolved by looking in $jsonSchemaObject
$schemaStorage->addSchema('file://mySchema', $jsonSchemaObject);
// Provide $schemaStorage to the Validator so that references can be resolved during validation
$jsonValidator = new Validator( new Factory($schemaStorage));
// JSON must be decoded before it can be validated
$jsonToValidateObject = json_decode('{"data":123}');
// Do validation (use isValid() and getErrors() to check the result)
$jsonValidator->validate($jsonToValidateObject, $jsonSchemaObject);
```
### Configuration Options
A number of flags are available to alter the behavior of the validator. These can be passed as the
third argument to `Validator::validate()`, or can be provided as the third argument to
`Factory::__construct()` if you wish to persist them across multiple `validate()` calls.
| Flag | Description |
|------|-------------|
| `Constraint::CHECK_MODE_NORMAL` | Validate in 'normal' mode - this is the default |
| `Constraint::CHECK_MODE_TYPE_CAST` | Enable fuzzy type checking for associative arrays and objects |
| `Constraint::CHECK_MODE_COERCE_TYPES` | Convert data types to match the schema where possible |
| `Constraint::CHECK_MODE_APPLY_DEFAULTS` | Apply default values from the schema if not set |
| `Constraint::CHECK_MODE_ONLY_REQUIRED_DEFAULTS` | When applying defaults, only set values that are required |
| `Constraint::CHECK_MODE_EXCEPTIONS` | Throw an exception immediately if validation fails |
| `Constraint::CHECK_MODE_DISABLE_FORMAT` | Do not validate "format" constraints |
| `Constraint::CHECK_MODE_VALIDATE_SCHEMA` | Validate the schema as well as the provided document |
Please note that using `Constraint::CHECK_MODE_COERCE_TYPES` or `Constraint::CHECK_MODE_APPLY_DEFAULTS`
will modify your original data.
## Running the tests
```bash
composer test # run all unit tests
composer testOnly TestClass # run specific unit test class
composer testOnly TestClass::testMethod # run specific unit test method
composer style-check # check code style for errors
composer style-fix # automatically fix code style errors
```

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,251 @@
#!/usr/bin/env php
<?php
/**
* JSON schema validator
*
* @author Christian Weiske <christian.weiske@netresearch.de>
*/
/**
* Dead simple autoloader
*
* @param string $className Name of class to load
*
* @return void
*/
function __autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
if (stream_resolve_include_path($fileName)) {
require_once $fileName;
}
}
// support running this tool from git checkout
if (is_dir(__DIR__ . '/../src/JsonSchema')) {
set_include_path(__DIR__ . '/../src' . PATH_SEPARATOR . get_include_path());
}
$arOptions = array();
$arArgs = array();
array_shift($argv);//script itself
foreach ($argv as $arg) {
if ($arg{0} == '-') {
$arOptions[$arg] = true;
} else {
$arArgs[] = $arg;
}
}
if (count($arArgs) == 0
|| isset($arOptions['--help']) || isset($arOptions['-h'])
) {
echo <<<HLP
Validate schema
Usage: validate-json data.json
or: validate-json data.json schema.json
Options:
--dump-schema Output full schema and exit
--dump-schema-url Output URL of schema
--verbose Show additional output
--quiet Suppress all output
-h --help Show this help
HLP;
exit(1);
}
if (count($arArgs) == 1) {
$pathData = $arArgs[0];
$pathSchema = null;
} else {
$pathData = $arArgs[0];
$pathSchema = getUrlFromPath($arArgs[1]);
}
/**
* Show the json parse error that happened last
*
* @return void
*/
function showJsonError()
{
$constants = get_defined_constants(true);
$json_errors = array();
foreach ($constants['json'] as $name => $value) {
if (!strncmp($name, 'JSON_ERROR_', 11)) {
$json_errors[$value] = $name;
}
}
output('JSON parse error: ' . $json_errors[json_last_error()] . "\n");
}
function getUrlFromPath($path)
{
if (parse_url($path, PHP_URL_SCHEME) !== null) {
//already an URL
return $path;
}
if ($path{0} == '/') {
//absolute path
return 'file://' . $path;
}
//relative path: make absolute
return 'file://' . getcwd() . '/' . $path;
}
/**
* Take a HTTP header value and split it up into parts.
*
* @param $headerValue
* @return array Key "_value" contains the main value, all others
* as given in the header value
*/
function parseHeaderValue($headerValue)
{
if (strpos($headerValue, ';') === false) {
return array('_value' => $headerValue);
}
$parts = explode(';', $headerValue);
$arData = array('_value' => array_shift($parts));
foreach ($parts as $part) {
list($name, $value) = explode('=', $part);
$arData[$name] = trim($value, ' "\'');
}
return $arData;
}
/**
* Send a string to the output stream, but only if --quiet is not enabled
*
* @param $str string A string output
*/
function output($str) {
global $arOptions;
if (!isset($arOptions['--quiet'])) {
echo $str;
}
}
$urlData = getUrlFromPath($pathData);
$context = stream_context_create(
array(
'http' => array(
'header' => array(
'Accept: */*',
'Connection: Close'
),
'max_redirects' => 5
)
)
);
$dataString = file_get_contents($pathData, false, $context);
if ($dataString == '') {
output("Data file is not readable or empty.\n");
exit(3);
}
$data = json_decode($dataString);
unset($dataString);
if ($data === null) {
output("Error loading JSON data file\n");
showJsonError();
exit(5);
}
if ($pathSchema === null) {
if (isset($http_response_header)) {
array_shift($http_response_header);//HTTP/1.0 line
foreach ($http_response_header as $headerLine) {
list($hName, $hValue) = explode(':', $headerLine, 2);
$hName = strtolower($hName);
if ($hName == 'link') {
//Link: <http://example.org/schema#>; rel="describedBy"
$hParts = parseHeaderValue($hValue);
if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') {
$pathSchema = trim($hParts['_value'], ' <>');
}
} else if ($hName == 'content-type') {
//Content-Type: application/my-media-type+json;
// profile=http://example.org/schema#
$hParts = parseHeaderValue($hValue);
if (isset($hParts['profile'])) {
$pathSchema = $hParts['profile'];
}
}
}
}
if (is_object($data) && property_exists($data, '$schema')) {
$pathSchema = $data->{'$schema'};
}
//autodetect schema
if ($pathSchema === null) {
output("JSON data must be an object and have a \$schema property.\n");
output("You can pass the schema file on the command line as well.\n");
output("Schema autodetection failed.\n");
exit(6);
}
}
if ($pathSchema{0} == '/') {
$pathSchema = 'file://' . $pathSchema;
}
$resolver = new JsonSchema\Uri\UriResolver();
$retriever = new JsonSchema\Uri\UriRetriever();
try {
$urlSchema = $resolver->resolve($pathSchema, $urlData);
if (isset($arOptions['--dump-schema-url'])) {
echo $urlSchema . "\n";
exit();
}
} catch (Exception $e) {
output("Error loading JSON schema file\n");
output($urlSchema . "\n");
output($e->getMessage() . "\n");
exit(2);
}
$refResolver = new JsonSchema\SchemaStorage($retriever, $resolver);
$schema = $refResolver->resolveRef($urlSchema);
if (isset($arOptions['--dump-schema'])) {
$options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
echo json_encode($schema, $options) . "\n";
exit();
}
try {
$validator = new JsonSchema\Validator();
$validator->check($data, $schema);
if ($validator->isValid()) {
if(isset($arOptions['--verbose'])) {
output("OK. The supplied JSON validates against the schema.\n");
}
} else {
output("JSON does not validate. Violations:\n");
foreach ($validator->getErrors() as $error) {
output(sprintf("[%s] %s\n", $error['property'], $error['message']));
}
exit(23);
}
} catch (Exception $e) {
output("JSON does not validate. Error:\n");
output($e->getMessage() . "\n");
output("Error code: " . $e->getCode() . "\n");
exit(24);
}

View File

@@ -0,0 +1,76 @@
{
"name": "justinrainbow/json-schema",
"type": "library",
"description": "A library to validate a json schema.",
"keywords": [
"json",
"schema"
],
"homepage": "https://github.com/justinrainbow/json-schema",
"license": "MIT",
"authors": [
{
"name": "Bruno Prieto Reis",
"email": "bruno.p.reis@gmail.com"
},
{
"name": "Justin Rainbow",
"email": "justin.rainbow@gmail.com"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Robert Schönthal",
"email": "seroscho@googlemail.com"
}
],
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.2.20",
"json-schema/JSON-Schema-Test-Suite": "1.2.0",
"phpunit/phpunit": "^4.8.35"
},
"extra": {
"branch-alias": {
"dev-master": "5.0.x-dev"
}
},
"autoload": {
"psr-4": {
"JsonSchema\\": "src/JsonSchema/"
}
},
"autoload-dev": {
"psr-4": {
"JsonSchema\\Tests\\": "tests/"
}
},
"repositories": [
{
"type": "package",
"package": {
"name": "json-schema/JSON-Schema-Test-Suite",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/json-schema/JSON-Schema-Test-Suite",
"reference": "1.2.0"
}
}
}
],
"bin": [
"bin/validate-json"
],
"scripts": {
"coverage": "phpunit --coverage-text",
"style-check": "php-cs-fixer fix --dry-run --verbose --diff",
"style-fix": "php-cs-fixer fix --verbose",
"test": "phpunit",
"testOnly": "phpunit --colors --filter"
}
}

View File

@@ -0,0 +1,14 @@
## JsonSchema\Validator Demo
This demo script uses the example from the root README.md and provides sample JSON files for testing `JsonSchema\Validator`.
To change or replace the JSON schema document, please edit `/path/to/json-schema/demo/schema.json`.
To change or replace the JSON document that is validated, please edit `/path/to/json-schema/demo/data.json`.
To run the demo, change the path in the following example and run it in your terminal.
```
cd /path/to/json-schema/demo
php demo.php // The supplied JSON validates against the schema.
```

View File

@@ -0,0 +1,3 @@
{
"foo":"bar"
}

View File

@@ -0,0 +1,18 @@
<?php
require __DIR__ . '/../vendor/autoload.php';
$data = json_decode(file_get_contents('data.json'));
// Validate
$validator = new JsonSchema\Validator();
$validator->check($data, (object) array('$ref' => 'file://' . realpath('schema.json')));
if ($validator->isValid()) {
echo "The supplied JSON validates against the schema.\n";
} else {
echo "JSON does not validate. Violations:\n";
foreach ($validator->getErrors() as $error) {
echo sprintf("[%s] %s\n", $error['property'], $error['message']);
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,3 @@
{
"type": "object"
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,174 @@
{
"$schema": "http://json-schema.org/draft-03/schema#",
"id": "http://json-schema.org/draft-03/schema#",
"type": "object",
"properties": {
"type": {
"type": [ "string", "array" ],
"items": {
"type": [ "string", { "$ref": "#" } ]
},
"uniqueItems": true,
"default": "any"
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"additionalProperties": {
"type": [ { "$ref": "#" }, "boolean" ],
"default": {}
},
"items": {
"type": [ { "$ref": "#" }, "array" ],
"items": { "$ref": "#" },
"default": {}
},
"additionalItems": {
"type": [ { "$ref": "#" }, "boolean" ],
"default": {}
},
"required": {
"type": "boolean",
"default": false
},
"dependencies": {
"type": "object",
"additionalProperties": {
"type": [ "string", "array", { "$ref": "#" } ],
"items": {
"type": "string"
}
},
"default": {}
},
"minimum": {
"type": "number"
},
"maximum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minItems": {
"type": "integer",
"minimum": 0,
"default": 0
},
"maxItems": {
"type": "integer",
"minimum": 0
},
"uniqueItems": {
"type": "boolean",
"default": false
},
"pattern": {
"type": "string",
"format": "regex"
},
"minLength": {
"type": "integer",
"minimum": 0,
"default": 0
},
"maxLength": {
"type": "integer"
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"default": {
"type": "any"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"format": {
"type": "string"
},
"divisibleBy": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true,
"default": 1
},
"disallow": {
"type": [ "string", "array" ],
"items": {
"type": [ "string", { "$ref": "#" } ]
},
"uniqueItems": true
},
"extends": {
"type": [ { "$ref": "#" }, "array" ],
"items": { "$ref": "#" },
"default": {}
},
"id": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri"
},
"$schema": {
"type": "string",
"format": "uri"
}
},
"dependencies": {
"exclusiveMinimum": "minimum",
"exclusiveMaximum": "maximum"
},
"default": {}
}

View File

@@ -0,0 +1,150 @@
{
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
},
"simpleTypes": {
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uri"
},
"$schema": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": { "$ref": "#/definitions/positiveInteger" },
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
},
"maxItems": { "$ref": "#/definitions/positiveInteger" },
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"dependencies": {
"exclusiveMaximum": [ "maximum" ],
"exclusiveMinimum": [ "minimum" ]
},
"default": {}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
verbose="true"
>
<testsuites>
<testsuite name="JSON Schema Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/JsonSchema/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,148 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Validator;
/**
* A more basic constraint definition - used for the public
* interface to avoid exposing library internals.
*/
class BaseConstraint
{
/**
* @var array Errors
*/
protected $errors = array();
/**
* @var int All error types which have occurred
*/
protected $errorMask = Validator::ERROR_NONE;
/**
* @var Factory
*/
protected $factory;
/**
* @param Factory $factory
*/
public function __construct(Factory $factory = null)
{
$this->factory = $factory ?: new Factory();
}
public function addError(JsonPointer $path = null, $message, $constraint = '', array $more = null)
{
$error = array(
'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'),
'message' => $message,
'constraint' => $constraint,
'context' => $this->factory->getErrorContext(),
);
if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) {
throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message']));
}
if (is_array($more) && count($more) > 0) {
$error += $more;
}
$this->errors[] = $error;
$this->errorMask |= $error['context'];
}
public function addErrors(array $errors)
{
if ($errors) {
$this->errors = array_merge($this->errors, $errors);
$errorMask = &$this->errorMask;
array_walk($errors, function ($error) use (&$errorMask) {
if (isset($error['context'])) {
$errorMask |= $error['context'];
}
});
}
}
public function getErrors($errorContext = Validator::ERROR_ALL)
{
if ($errorContext === Validator::ERROR_ALL) {
return $this->errors;
}
return array_filter($this->errors, function ($error) use ($errorContext) {
if ($errorContext & $error['context']) {
return true;
}
});
}
public function numErrors($errorContext = Validator::ERROR_ALL)
{
if ($errorContext === Validator::ERROR_ALL) {
return count($this->errors);
}
return count($this->getErrors($errorContext));
}
public function isValid()
{
return !$this->getErrors();
}
/**
* Clears any reported errors. Should be used between
* multiple validation checks.
*/
public function reset()
{
$this->errors = array();
$this->errorMask = Validator::ERROR_NONE;
}
/**
* Get the error mask
*
* @return int
*/
public function getErrorMask()
{
return $this->errorMask;
}
/**
* Recursively cast an associative array to an object
*
* @param array $array
*
* @return object
*/
public static function arrayToObjectRecursive($array)
{
$json = json_encode($array);
if (json_last_error() !== \JSON_ERROR_NONE) {
$message = 'Unable to encode schema array as JSON';
if (function_exists('json_last_error_msg')) {
$message .= ': ' . json_last_error_msg();
}
throw new InvalidArgumentException($message);
}
return (object) json_decode($json);
}
}

View File

@@ -0,0 +1,121 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
/**
* The CollectionConstraint Constraints, validates an array against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class CollectionConstraint extends Constraint
{
/**
* {@inheritdoc}
*/
public function check(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
// Verify minItems
if (isset($schema->minItems) && count($value) < $schema->minItems) {
$this->addError($path, 'There must be a minimum of ' . $schema->minItems . ' items in the array', 'minItems', array('minItems' => $schema->minItems));
}
// Verify maxItems
if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
$this->addError($path, 'There must be a maximum of ' . $schema->maxItems . ' items in the array', 'maxItems', array('maxItems' => $schema->maxItems));
}
// Verify uniqueItems
if (isset($schema->uniqueItems) && $schema->uniqueItems) {
$unique = $value;
if (is_array($value) && count($value)) {
$unique = array_map(function ($e) {
return var_export($e, true);
}, $value);
}
if (count(array_unique($unique)) != count($value)) {
$this->addError($path, 'There are no duplicates allowed in the array', 'uniqueItems');
}
}
// Verify items
if (isset($schema->items)) {
$this->validateItems($value, $schema, $path, $i);
}
}
/**
* Validates the items
*
* @param array $value
* @param \stdClass $schema
* @param JsonPointer|null $path
* @param string $i
*/
protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
if (is_object($schema->items)) {
// just one type definition for the whole array
foreach ($value as $k => &$v) {
$initErrors = $this->getErrors();
// First check if its defined in "items"
$this->checkUndefined($v, $schema->items, $path, $k);
// Recheck with "additionalItems" if the first test fails
if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
$secondErrors = $this->getErrors();
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
}
// Reset errors if needed
if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
$this->errors = $secondErrors;
} elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
$this->errors = $initErrors;
}
}
unset($v); /* remove dangling reference to prevent any future bugs
* caused by accidentally using $v elsewhere */
} else {
// Defined item type definitions
foreach ($value as $k => &$v) {
if (array_key_exists($k, $schema->items)) {
$this->checkUndefined($v, $schema->items[$k], $path, $k);
} else {
// Additional items
if (property_exists($schema, 'additionalItems')) {
if ($schema->additionalItems !== false) {
$this->checkUndefined($v, $schema->additionalItems, $path, $k);
} else {
$this->addError(
$path, 'The item ' . $i . '[' . $k . '] is not defined and the definition does not allow additional items', 'additionalItems', array('additionalItems' => $schema->additionalItems));
}
} else {
// Should be valid against an empty schema
$this->checkUndefined($v, new \stdClass(), $path, $k);
}
}
}
unset($v); /* remove dangling reference to prevent any future bugs
* caused by accidentally using $v elsewhere */
// Treat when we have more schema definitions than values, not for empty arrays
if (count($value) > 0) {
for ($k = count($value); $k < count($schema->items); $k++) {
$undefinedInstance = $this->factory->createInstanceFor('undefined');
$this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k);
}
}
}
}
}

View File

@@ -0,0 +1,213 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
/**
* The Base Constraints, all Validators should extend this class
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
abstract class Constraint extends BaseConstraint implements ConstraintInterface
{
protected $inlineSchemaProperty = '$schema';
const CHECK_MODE_NONE = 0x00000000;
const CHECK_MODE_NORMAL = 0x00000001;
const CHECK_MODE_TYPE_CAST = 0x00000002;
const CHECK_MODE_COERCE_TYPES = 0x00000004;
const CHECK_MODE_APPLY_DEFAULTS = 0x00000008;
const CHECK_MODE_EXCEPTIONS = 0x00000010;
const CHECK_MODE_DISABLE_FORMAT = 0x00000020;
const CHECK_MODE_ONLY_REQUIRED_DEFAULTS = 0x00000080;
const CHECK_MODE_VALIDATE_SCHEMA = 0x00000100;
/**
* Bubble down the path
*
* @param JsonPointer|null $path Current path
* @param mixed $i What to append to the path
*
* @return JsonPointer;
*/
protected function incrementPath(JsonPointer $path = null, $i)
{
$path = $path ?: new JsonPointer('');
$path = $path->withPropertyPaths(
array_merge(
$path->getPropertyPaths(),
array_filter(array($i), 'strlen')
)
);
return $path;
}
/**
* Validates an array
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkArray(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('collection');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Validates an object
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $properties
* @param mixed $additionalProperties
* @param mixed $patternProperties
*/
protected function checkObject(&$value, $schema = null, JsonPointer $path = null, $properties = null,
$additionalProperties = null, $patternProperties = null, $appliedDefaults = array())
{
$validator = $this->factory->createInstanceFor('object');
$validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults);
$this->addErrors($validator->getErrors());
}
/**
* Validates the type of a property
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkType(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('type');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks a undefined element
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkUndefined(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false)
{
$validator = $this->factory->createInstanceFor('undefined');
$validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault);
$this->addErrors($validator->getErrors());
}
/**
* Checks a string element
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkString($value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('string');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks a number element
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer $path
* @param mixed $i
*/
protected function checkNumber($value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('number');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks a enum element
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkEnum($value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('enum');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Checks format of an element
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*/
protected function checkFormat($value, $schema = null, JsonPointer $path = null, $i = null)
{
$validator = $this->factory->createInstanceFor('format');
$validator->check($value, $schema, $path, $i);
$this->addErrors($validator->getErrors());
}
/**
* Get the type check based on the set check mode.
*
* @return TypeCheck\TypeCheckInterface
*/
protected function getTypeCheck()
{
return $this->factory->getTypeCheck();
}
/**
* @param JsonPointer $pointer
*
* @return string property path
*/
protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer)
{
$result = array_map(
function ($path) {
return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path);
},
$pointer->getPropertyPaths()
);
return trim(implode('', $result), '.');
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
/**
* The Constraints Interface
*
* @author Robert Schönthal <seroscho@googlemail.com>
*/
interface ConstraintInterface
{
/**
* returns all collected errors
*
* @return array
*/
public function getErrors();
/**
* adds errors to this validator
*
* @param array $errors
*/
public function addErrors(array $errors);
/**
* adds an error
*
* @param JsonPointer|null $path
* @param string $message
* @param string $constraint the constraint/rule that is broken, e.g.: 'minLength'
* @param array $more more array elements to add to the error
*/
public function addError(JsonPointer $path = null, $message, $constraint='', array $more = null);
/**
* checks if the validator has not raised errors
*
* @return bool
*/
public function isValid();
/**
* invokes the validation of an element
*
* @abstract
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer|null $path
* @param mixed $i
*
* @throws \JsonSchema\Exception\ExceptionInterface
*/
public function check(&$value, $schema = null, JsonPointer $path = null, $i = null);
}

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
/**
* The EnumConstraint Constraints, validates an element against a given set of possibilities
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class EnumConstraint extends Constraint
{
/**
* {@inheritdoc}
*/
public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
{
// Only validate enum if the attribute exists
if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
return;
}
$type = gettype($element);
foreach ($schema->enum as $enum) {
$enumType = gettype($enum);
if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type == 'array' && $enumType == 'object') {
if ((object) $element == $enum) {
return;
}
}
if ($type === gettype($enum)) {
if ($type == 'object') {
if ($element == $enum) {
return;
}
} elseif ($element === $enum) {
return;
}
}
}
$this->addError($path, 'Does not have a value in the enumeration ' . json_encode($schema->enum), 'enum', array('enum' => $schema->enum));
}
}

View File

@@ -0,0 +1,220 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\SchemaStorage;
use JsonSchema\SchemaStorageInterface;
use JsonSchema\Uri\UriRetriever;
use JsonSchema\UriRetrieverInterface;
use JsonSchema\Validator;
/**
* Factory for centralize constraint initialization.
*/
class Factory
{
/**
* @var SchemaStorage
*/
protected $schemaStorage;
/**
* @var UriRetriever
*/
protected $uriRetriever;
/**
* @var int
*/
private $checkMode = Constraint::CHECK_MODE_NORMAL;
/**
* @var TypeCheck\TypeCheckInterface[]
*/
private $typeCheck = array();
/**
* @var int Validation context
*/
protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION;
/**
* @var array
*/
protected $constraintMap = array(
'array' => 'JsonSchema\Constraints\CollectionConstraint',
'collection' => 'JsonSchema\Constraints\CollectionConstraint',
'object' => 'JsonSchema\Constraints\ObjectConstraint',
'type' => 'JsonSchema\Constraints\TypeConstraint',
'undefined' => 'JsonSchema\Constraints\UndefinedConstraint',
'string' => 'JsonSchema\Constraints\StringConstraint',
'number' => 'JsonSchema\Constraints\NumberConstraint',
'enum' => 'JsonSchema\Constraints\EnumConstraint',
'format' => 'JsonSchema\Constraints\FormatConstraint',
'schema' => 'JsonSchema\Constraints\SchemaConstraint',
'validator' => 'JsonSchema\Validator'
);
/**
* @var array<ConstraintInterface>
*/
private $instanceCache = array();
/**
* @param SchemaStorage $schemaStorage
* @param UriRetrieverInterface $uriRetriever
* @param int $checkMode
*/
public function __construct(
SchemaStorageInterface $schemaStorage = null,
UriRetrieverInterface $uriRetriever = null,
$checkMode = Constraint::CHECK_MODE_NORMAL
) {
// set provided config options
$this->setConfig($checkMode);
$this->uriRetriever = $uriRetriever ?: new UriRetriever();
$this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever);
}
/**
* Set config values
*
* @param int $checkMode Set checkMode options - does not preserve existing flags
*/
public function setConfig($checkMode = Constraint::CHECK_MODE_NORMAL)
{
$this->checkMode = $checkMode;
}
/**
* Enable checkMode flags
*
* @param int $options
*/
public function addConfig($options)
{
$this->checkMode |= $options;
}
/**
* Disable checkMode flags
*
* @param int $options
*/
public function removeConfig($options)
{
$this->checkMode &= ~$options;
}
/**
* Get checkMode option
*
* @param int $options Options to get, if null then return entire bitmask
*
* @return int
*/
public function getConfig($options = null)
{
if ($options === null) {
return $this->checkMode;
}
return $this->checkMode & $options;
}
/**
* @return UriRetrieverInterface
*/
public function getUriRetriever()
{
return $this->uriRetriever;
}
public function getSchemaStorage()
{
return $this->schemaStorage;
}
public function getTypeCheck()
{
if (!isset($this->typeCheck[$this->checkMode])) {
$this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST)
? new TypeCheck\LooseTypeCheck()
: new TypeCheck\StrictTypeCheck();
}
return $this->typeCheck[$this->checkMode];
}
/**
* @param string $name
* @param string $class
*
* @return Factory
*/
public function setConstraintClass($name, $class)
{
// Ensure class exists
if (!class_exists($class)) {
throw new InvalidArgumentException('Unknown constraint ' . $name);
}
// Ensure class is appropriate
if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) {
throw new InvalidArgumentException('Invalid class ' . $name);
}
$this->constraintMap[$name] = $class;
return $this;
}
/**
* Create a constraint instance for the given constraint name.
*
* @param string $constraintName
*
* @throws InvalidArgumentException if is not possible create the constraint instance
*
* @return ConstraintInterface|ObjectConstraint
*/
public function createInstanceFor($constraintName)
{
if (!isset($this->constraintMap[$constraintName])) {
throw new InvalidArgumentException('Unknown constraint ' . $constraintName);
}
if (!isset($this->instanceCache[$constraintName])) {
$this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this);
}
return clone $this->instanceCache[$constraintName];
}
/**
* Get the error context
*
* @return string
*/
public function getErrorContext()
{
return $this->errorContext;
}
/**
* Set the error context
*
* @param string $validationContext
*/
public function setErrorContext($errorContext)
{
$this->errorContext = $errorContext;
}
}

View File

@@ -0,0 +1,214 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Rfc3339;
/**
* Validates against the "format" property
*
* @author Justin Rainbow <justin.rainbow@gmail.com>
*
* @see http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
*/
class FormatConstraint extends Constraint
{
/**
* {@inheritdoc}
*/
public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
{
if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) {
return;
}
switch ($schema->format) {
case 'date':
if (!$date = $this->validateDateTime($element, 'Y-m-d')) {
$this->addError($path, sprintf('Invalid date %s, expected format YYYY-MM-DD', json_encode($element)), 'format', array('format' => $schema->format));
}
break;
case 'time':
if (!$this->validateDateTime($element, 'H:i:s')) {
$this->addError($path, sprintf('Invalid time %s, expected format hh:mm:ss', json_encode($element)), 'format', array('format' => $schema->format));
}
break;
case 'date-time':
if (null === Rfc3339::createFromString($element)) {
$this->addError($path, sprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm', json_encode($element)), 'format', array('format' => $schema->format));
}
break;
case 'utc-millisec':
if (!$this->validateDateTime($element, 'U')) {
$this->addError($path, sprintf('Invalid time %s, expected integer of milliseconds since Epoch', json_encode($element)), 'format', array('format' => $schema->format));
}
break;
case 'regex':
if (!$this->validateRegex($element)) {
$this->addError($path, 'Invalid regex format ' . $element, 'format', array('format' => $schema->format));
}
break;
case 'color':
if (!$this->validateColor($element)) {
$this->addError($path, 'Invalid color', 'format', array('format' => $schema->format));
}
break;
case 'style':
if (!$this->validateStyle($element)) {
$this->addError($path, 'Invalid style', 'format', array('format' => $schema->format));
}
break;
case 'phone':
if (!$this->validatePhone($element)) {
$this->addError($path, 'Invalid phone number', 'format', array('format' => $schema->format));
}
break;
case 'uri':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
$this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format));
}
break;
case 'uriref':
case 'uri-reference':
if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
// FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but
// the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them.
// See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information.
if (substr($element, 0, 2) === '//') { // network-path reference
$validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
} elseif (substr($element, 0, 1) === '/') { // absolute-path reference
$validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
} elseif (strlen($element)) { // relative-path reference
$pathParts = explode('/', $element, 2);
if (strpos($pathParts[0], ':') !== false) {
$validURL = null;
} else {
$validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
}
} else {
$validURL = null;
}
if ($validURL === null) {
$this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format));
}
}
break;
case 'email':
$filterFlags = FILTER_NULL_ON_FAILURE;
if (defined('FILTER_FLAG_EMAIL_UNICODE')) {
// Only available from PHP >= 7.1.0, so ignore it for coverage checks
$filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore
}
if (null === filter_var($element, FILTER_VALIDATE_EMAIL, $filterFlags)) {
$this->addError($path, 'Invalid email', 'format', array('format' => $schema->format));
}
break;
case 'ip-address':
case 'ipv4':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
$this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format));
}
break;
case 'ipv6':
if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
$this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format));
}
break;
case 'host-name':
case 'hostname':
if (!$this->validateHostname($element)) {
$this->addError($path, 'Invalid hostname', 'format', array('format' => $schema->format));
}
break;
default:
// Empty as it should be:
// The value of this keyword is called a format attribute. It MUST be a string.
// A format attribute can generally only validate a given set of instance types.
// If the type of the instance to validate is not in this set, validation for
// this format attribute and instance SHOULD succeed.
// http://json-schema.org/latest/json-schema-validation.html#anchor105
break;
}
}
protected function validateDateTime($datetime, $format)
{
$dt = \DateTime::createFromFormat($format, $datetime);
if (!$dt) {
return false;
}
if ($datetime === $dt->format($format)) {
return true;
}
// handles the case where a non-6 digit microsecond datetime is passed
// which will fail the above string comparison because the passed
// $datetime may be '2000-05-01T12:12:12.123Z' but format() will return
// '2000-05-01T12:12:12.123000Z'
if ((strpos('u', $format) !== -1) && (preg_match('/\.\d+Z$/', $datetime))) {
return true;
}
return false;
}
protected function validateRegex($regex)
{
return false !== @preg_match('/' . $regex . '/u', '');
}
protected function validateColor($color)
{
if (in_array(strtolower($color), array('aqua', 'black', 'blue', 'fuchsia',
'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
'red', 'silver', 'teal', 'white', 'yellow'))) {
return true;
}
return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color);
}
protected function validateStyle($style)
{
$properties = explode(';', rtrim($style, ';'));
$invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);
return empty($invalidEntries);
}
protected function validatePhone($phone)
{
return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone);
}
protected function validateHostname($host)
{
$hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i';
return preg_match($hostnameRegex, $host);
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
/**
* The NumberConstraint Constraints, validates an number against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class NumberConstraint extends Constraint
{
/**
* {@inheritdoc}
*/
public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
{
// Verify minimum
if (isset($schema->exclusiveMinimum)) {
if (isset($schema->minimum)) {
if ($schema->exclusiveMinimum && $element <= $schema->minimum) {
$this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'exclusiveMinimum', array('minimum' => $schema->minimum));
} elseif ($element < $schema->minimum) {
$this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum));
}
} else {
$this->addError($path, 'Use of exclusiveMinimum requires presence of minimum', 'missingMinimum');
}
} elseif (isset($schema->minimum) && $element < $schema->minimum) {
$this->addError($path, 'Must have a minimum value of ' . $schema->minimum, 'minimum', array('minimum' => $schema->minimum));
}
// Verify maximum
if (isset($schema->exclusiveMaximum)) {
if (isset($schema->maximum)) {
if ($schema->exclusiveMaximum && $element >= $schema->maximum) {
$this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'exclusiveMaximum', array('maximum' => $schema->maximum));
} elseif ($element > $schema->maximum) {
$this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum));
}
} else {
$this->addError($path, 'Use of exclusiveMaximum requires presence of maximum', 'missingMaximum');
}
} elseif (isset($schema->maximum) && $element > $schema->maximum) {
$this->addError($path, 'Must have a maximum value of ' . $schema->maximum, 'maximum', array('maximum' => $schema->maximum));
}
// Verify divisibleBy - Draft v3
if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) {
$this->addError($path, 'Is not divisible by ' . $schema->divisibleBy, 'divisibleBy', array('divisibleBy' => $schema->divisibleBy));
}
// Verify multipleOf - Draft v4
if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) {
$this->addError($path, 'Must be a multiple of ' . $schema->multipleOf, 'multipleOf', array('multipleOf' => $schema->multipleOf));
}
$this->checkFormat($element, $schema, $path, $i);
}
private function fmod($number1, $number2)
{
$modulus = ($number1 - round($number1 / $number2) * $number2);
$precision = 0.0000000001;
if (-$precision < $modulus && $modulus < $precision) {
return 0.0;
}
return $modulus;
}
}

View File

@@ -0,0 +1,192 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
/**
* The ObjectConstraint Constraints, validates an object against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class ObjectConstraint extends Constraint
{
/**
* @var array List of properties to which a default value has been applied
*/
protected $appliedDefaults = array();
/**
* {@inheritdoc}
*/
public function check(&$element, $schema = null, JsonPointer $path = null, $properties = null,
$additionalProp = null, $patternProperties = null, $appliedDefaults = array())
{
if ($element instanceof UndefinedConstraint) {
return;
}
$this->appliedDefaults = $appliedDefaults;
$matches = array();
if ($patternProperties) {
// validate the element pattern properties
$matches = $this->validatePatternProperties($element, $path, $patternProperties);
}
if ($properties) {
// validate the element properties
$this->validateProperties($element, $properties, $path);
}
// validate additional element properties & constraints
$this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp);
}
public function validatePatternProperties($element, JsonPointer $path = null, $patternProperties)
{
$try = array('/', '#', '+', '~', '%');
$matches = array();
foreach ($patternProperties as $pregex => $schema) {
$delimiter = '/';
// Choose delimiter. Necessary for patterns like ^/ , otherwise you get error
foreach ($try as $delimiter) {
if (strpos($pregex, $delimiter) === false) { // safe to use
break;
}
}
// Validate the pattern before using it to test for matches
if (@preg_match($delimiter . $pregex . $delimiter . 'u', '') === false) {
$this->addError($path, 'The pattern "' . $pregex . '" is invalid', 'pregex', array('pregex' => $pregex));
continue;
}
foreach ($element as $i => $value) {
if (preg_match($delimiter . $pregex . $delimiter . 'u', $i)) {
$matches[] = $i;
$this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults));
}
}
}
return $matches;
}
/**
* Validates the element properties
*
* @param \StdClass $element Element to validate
* @param array $matches Matches from patternProperties (if any)
* @param \StdClass $schema ObjectConstraint definition
* @param JsonPointer|null $path Current test path
* @param \StdClass $properties Properties
* @param mixed $additionalProp Additional properties
*/
public function validateElement($element, $matches, $schema = null, JsonPointer $path = null,
$properties = null, $additionalProp = null)
{
$this->validateMinMaxConstraint($element, $schema, $path);
foreach ($element as $i => $value) {
$definition = $this->getProperty($properties, $i);
// no additional properties allowed
if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
$this->addError($path, 'The property ' . $i . ' is not defined and the definition does not allow additional properties', 'additionalProp');
}
// additional properties defined
if (!in_array($i, $matches) && $additionalProp && !$definition) {
if ($additionalProp === true) {
$this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults));
} else {
$this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults));
}
}
// property requires presence of another
$require = $this->getProperty($definition, 'requires');
if ($require && !$this->getProperty($element, $require)) {
$this->addError($path, 'The presence of the property ' . $i . ' requires that ' . $require . ' also be present', 'requires');
}
$property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined'));
if (is_object($property)) {
$this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path);
}
}
}
/**
* Validates the definition properties
*
* @param \stdClass $element Element to validate
* @param \stdClass $properties Property definitions
* @param JsonPointer|null $path Path?
*/
public function validateProperties(&$element, $properties = null, JsonPointer $path = null)
{
$undefinedConstraint = $this->factory->createInstanceFor('undefined');
foreach ($properties as $i => $value) {
$property = &$this->getProperty($element, $i, $undefinedConstraint);
$definition = $this->getProperty($properties, $i);
if (is_object($definition)) {
// Undefined constraint will check for is_object() and quit if is not - so why pass it?
$this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults));
}
}
}
/**
* retrieves a property from an object or array
*
* @param mixed $element Element to validate
* @param string $property Property to retrieve
* @param mixed $fallback Default value if property is not found
*
* @return mixed
*/
protected function &getProperty(&$element, $property, $fallback = null)
{
if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) {
return $element[$property];
} elseif (is_object($element) && property_exists($element, $property)) {
return $element->$property;
}
return $fallback;
}
/**
* validating minimum and maximum property constraints (if present) against an element
*
* @param \stdClass $element Element to validate
* @param \stdClass $objectDefinition ObjectConstraint definition
* @param JsonPointer|null $path Path to test?
*/
protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null)
{
// Verify minimum number of properties
if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) {
if ($this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) {
$this->addError($path, 'Must contain a minimum of ' . $objectDefinition->minProperties . ' properties', 'minProperties', array('minProperties' => $objectDefinition->minProperties));
}
}
// Verify maximum number of properties
if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) {
if ($this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) {
$this->addError($path, 'Must contain no more than ' . $objectDefinition->maxProperties . ' properties', 'maxProperties', array('maxProperties' => $objectDefinition->maxProperties));
}
}
}
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Exception\InvalidSchemaException;
use JsonSchema\Exception\RuntimeException;
use JsonSchema\Validator;
/**
* The SchemaConstraint Constraints, validates an element against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class SchemaConstraint extends Constraint
{
const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#';
/**
* {@inheritdoc}
*/
public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
{
if ($schema !== null) {
// passed schema
$validationSchema = $schema;
} elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) {
// inline schema
$validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty);
} else {
throw new InvalidArgumentException('no schema found to verify against');
}
// cast array schemas to object
if (is_array($validationSchema)) {
$validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema);
}
// validate schema against whatever is defined in $validationSchema->$schema. If no
// schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04).
if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) {
if (!$this->getTypeCheck()->isObject($validationSchema)) {
throw new RuntimeException('Cannot validate the schema of a non-object');
}
if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) {
$schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema');
} else {
$schemaSpec = self::DEFAULT_SCHEMA_SPEC;
}
// get the spec schema
$schemaStorage = $this->factory->getSchemaStorage();
if (!$this->getTypeCheck()->isObject($schemaSpec)) {
$schemaSpec = $schemaStorage->getSchema($schemaSpec);
}
// save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA
$initialErrorCount = $this->numErrors();
$initialConfig = $this->factory->getConfig();
$initialContext = $this->factory->getErrorContext();
$this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS);
$this->factory->addConfig(self::CHECK_MODE_TYPE_CAST);
$this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION);
// validate schema
try {
$this->check($validationSchema, $schemaSpec);
} catch (\Exception $e) {
if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) {
throw new InvalidSchemaException('Schema did not pass validation', 0, $e);
}
}
if ($this->numErrors() > $initialErrorCount) {
$this->addError($path, 'Schema is not valid', 'schema');
}
// restore the initial config
$this->factory->setConfig($initialConfig);
$this->factory->setErrorContext($initialContext);
}
// validate element against $validationSchema
$this->checkUndefined($element, $validationSchema, $path, $i);
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
/**
* The StringConstraint Constraints, validates an string against a given schema
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class StringConstraint extends Constraint
{
/**
* {@inheritdoc}
*/
public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
{
// Verify maxLength
if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
$this->addError($path, 'Must be at most ' . $schema->maxLength . ' characters long', 'maxLength', array(
'maxLength' => $schema->maxLength,
));
}
//verify minLength
if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) {
$this->addError($path, 'Must be at least ' . $schema->minLength . ' characters long', 'minLength', array(
'minLength' => $schema->minLength,
));
}
// Verify a regex pattern
if (isset($schema->pattern) && !preg_match('#' . str_replace('#', '\\#', $schema->pattern) . '#u', $element)) {
$this->addError($path, 'Does not match the regex pattern ' . $schema->pattern, 'pattern', array(
'pattern' => $schema->pattern,
));
}
$this->checkFormat($element, $schema, $path, $i);
}
private function strlen($string)
{
if (extension_loaded('mbstring')) {
return mb_strlen($string, mb_detect_encoding($string));
}
// mbstring is present on all test platforms, so strlen() can be ignored for coverage
return strlen($string); // @codeCoverageIgnore
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace JsonSchema\Constraints\TypeCheck;
class LooseTypeCheck implements TypeCheckInterface
{
public static function isObject($value)
{
return
is_object($value) ||
(is_array($value) && (count($value) == 0 || self::isAssociativeArray($value)));
}
public static function isArray($value)
{
return
is_array($value) &&
(count($value) == 0 || !self::isAssociativeArray($value));
}
public static function propertyGet($value, $property)
{
if (is_object($value)) {
return $value->{$property};
}
return $value[$property];
}
public static function propertySet(&$value, $property, $data)
{
if (is_object($value)) {
$value->{$property} = $data;
} else {
$value[$property] = $data;
}
}
public static function propertyExists($value, $property)
{
if (is_object($value)) {
return property_exists($value, $property);
}
return array_key_exists($property, $value);
}
public static function propertyCount($value)
{
if (is_object($value)) {
return count(get_object_vars($value));
}
return count($value);
}
/**
* Check if the provided array is associative or not
*
* @param array $arr
*
* @return bool
*/
private static function isAssociativeArray($arr)
{
return array_keys($arr) !== range(0, count($arr) - 1);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace JsonSchema\Constraints\TypeCheck;
class StrictTypeCheck implements TypeCheckInterface
{
public static function isObject($value)
{
return is_object($value);
}
public static function isArray($value)
{
return is_array($value);
}
public static function propertyGet($value, $property)
{
return $value->{$property};
}
public static function propertySet(&$value, $property, $data)
{
$value->{$property} = $data;
}
public static function propertyExists($value, $property)
{
return property_exists($value, $property);
}
public static function propertyCount($value)
{
if (!is_object($value)) {
return 0;
}
return count(get_object_vars($value));
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace JsonSchema\Constraints\TypeCheck;
interface TypeCheckInterface
{
public static function isObject($value);
public static function isArray($value);
public static function propertyGet($value, $property);
public static function propertySet(&$value, $property, $data);
public static function propertyExists($value, $property);
public static function propertyCount($value);
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,261 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use UnexpectedValueException as StandardUnexpectedValueException;
/**
* The TypeConstraint Constraints, validates an element against a given type
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class TypeConstraint extends Constraint
{
/**
* @var array|string[] type wordings for validation error messages
*/
public static $wording = array(
'integer' => 'an integer',
'number' => 'a number',
'boolean' => 'a boolean',
'object' => 'an object',
'array' => 'an array',
'string' => 'a string',
'null' => 'a null',
'any' => null, // validation of 'any' is always true so is not needed in message wording
0 => null, // validation of a false-y value is always true, so not needed as well
);
/**
* {@inheritdoc}
*/
public function check(&$value = null, $schema = null, JsonPointer $path = null, $i = null)
{
$type = isset($schema->type) ? $schema->type : null;
$isValid = false;
$wording = array();
if (is_array($type)) {
$this->validateTypesArray($value, $type, $wording, $isValid, $path);
} elseif (is_object($type)) {
$this->checkUndefined($value, $type, $path);
return;
} else {
$isValid = $this->validateType($value, $type);
}
if ($isValid === false) {
if (!is_array($type)) {
$this->validateTypeNameWording($type);
$wording[] = self::$wording[$type];
}
$this->addError($path, ucwords(gettype($value)) . ' value found, but ' .
$this->implodeWith($wording, ', ', 'or') . ' is required', 'type');
}
}
/**
* Validates the given $value against the array of types in $type. Sets the value
* of $isValid to true, if at least one $type mateches the type of $value or the value
* passed as $isValid is already true.
*
* @param mixed $value Value to validate
* @param array $type TypeConstraints to check agains
* @param array $validTypesWording An array of wordings of the valid types of the array $type
* @param bool $isValid The current validation value
* @param $path
*/
protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path)
{
foreach ($type as $tp) {
// $tp can be an object, if it's a schema instead of a simple type, validate it
// with a new type constraint
if (is_object($tp)) {
if (!$isValid) {
$validator = $this->factory->createInstanceFor('type');
$subSchema = new \stdClass();
$subSchema->type = $tp;
$validator->check($value, $subSchema, $path, null);
$error = $validator->getErrors();
$isValid = !(bool) $error;
$validTypesWording[] = self::$wording['object'];
}
} else {
$this->validateTypeNameWording($tp);
$validTypesWording[] = self::$wording[$tp];
if (!$isValid) {
$isValid = $this->validateType($value, $tp);
}
}
}
}
/**
* Implodes the given array like implode() with turned around parameters and with the
* difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of
* $delimiter.
*
* @param array $elements The elements to implode
* @param string $delimiter The delimiter to use
* @param bool $listEnd The last delimiter to use (defaults to $delimiter)
*
* @return string
*/
protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false)
{
if ($listEnd === false || !isset($elements[1])) {
return implode($delimiter, $elements);
}
$lastElement = array_slice($elements, -1);
$firsElements = join($delimiter, array_slice($elements, 0, -1));
$implodedElements = array_merge(array($firsElements), $lastElement);
return join(" $listEnd ", $implodedElements);
}
/**
* Validates the given $type, if there's an associated self::$wording. If not, throws an
* exception.
*
* @param string $type The type to validate
*
* @throws StandardUnexpectedValueException
*/
protected function validateTypeNameWording($type)
{
if (!isset(self::$wording[$type])) {
throw new StandardUnexpectedValueException(
sprintf(
'No wording for %s available, expected wordings are: [%s]',
var_export($type, true),
implode(', ', array_filter(self::$wording)))
);
}
}
/**
* Verifies that a given value is of a certain type
*
* @param mixed $value Value to validate
* @param string $type TypeConstraint to check against
*
* @throws InvalidArgumentException
*
* @return bool
*/
protected function validateType(&$value, $type)
{
//mostly the case for inline schema
if (!$type) {
return true;
}
if ('any' === $type) {
return true;
}
if ('object' === $type) {
return $this->getTypeCheck()->isObject($value);
}
if ('array' === $type) {
return $this->getTypeCheck()->isArray($value);
}
$coerce = $this->factory->getConfig(Constraint::CHECK_MODE_COERCE_TYPES);
if ('integer' === $type) {
if ($coerce) {
$value = $this->toInteger($value);
}
return is_int($value);
}
if ('number' === $type) {
if ($coerce) {
$value = $this->toNumber($value);
}
return is_numeric($value) && !is_string($value);
}
if ('boolean' === $type) {
if ($coerce) {
$value = $this->toBoolean($value);
}
return is_bool($value);
}
if ('string' === $type) {
return is_string($value);
}
if ('email' === $type) {
return is_string($value);
}
if ('null' === $type) {
return is_null($value);
}
throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type);
}
/**
* Converts a value to boolean. For example, "true" becomes true.
*
* @param $value The value to convert to boolean
*
* @return bool|mixed
*/
protected function toBoolean($value)
{
if ($value === 'true') {
return true;
}
if ($value === 'false') {
return false;
}
return $value;
}
/**
* Converts a numeric string to a number. For example, "4" becomes 4.
*
* @param mixed $value the value to convert to a number
*
* @return int|float|mixed
*/
protected function toNumber($value)
{
if (is_numeric($value)) {
return $value + 0; // cast to number
}
return $value;
}
protected function toInteger($value)
{
if (is_numeric($value) && (int) $value == $value) {
return (int) $value; // cast to number
}
return $value;
}
}

View File

@@ -0,0 +1,413 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Constraints;
use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Uri\UriResolver;
/**
* The UndefinedConstraint Constraints
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*/
class UndefinedConstraint extends Constraint
{
/**
* @var array List of properties to which a default value has been applied
*/
protected $appliedDefaults = array();
/**
* {@inheritdoc}
*/
public function check(&$value, $schema = null, JsonPointer $path = null, $i = null, $fromDefault = false)
{
if (is_null($schema) || !is_object($schema)) {
return;
}
$path = $this->incrementPath($path ?: new JsonPointer(''), $i);
if ($fromDefault) {
$path->setFromDefault();
}
// check special properties
$this->validateCommonProperties($value, $schema, $path, $i);
// check allOf, anyOf, and oneOf properties
$this->validateOfProperties($value, $schema, $path, '');
// check known types
$this->validateTypes($value, $schema, $path, $i);
}
/**
* Validates the value against the types
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer $path
* @param string $i
*/
public function validateTypes(&$value, $schema = null, JsonPointer $path, $i = null)
{
// check array
if ($this->getTypeCheck()->isArray($value)) {
$this->checkArray($value, $schema, $path, $i);
}
// check object
if (LooseTypeCheck::isObject($value)) { // object processing should always be run on assoc arrays,
// so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST
// is not set (i.e. don't use $this->getTypeCheck() here).
$this->checkObject(
$value,
$schema,
$path,
isset($schema->properties) ? $schema->properties : null,
isset($schema->additionalProperties) ? $schema->additionalProperties : null,
isset($schema->patternProperties) ? $schema->patternProperties : null,
$this->appliedDefaults
);
}
// check string
if (is_string($value)) {
$this->checkString($value, $schema, $path, $i);
}
// check numeric
if (is_numeric($value)) {
$this->checkNumber($value, $schema, $path, $i);
}
// check enum
if (isset($schema->enum)) {
$this->checkEnum($value, $schema, $path, $i);
}
}
/**
* Validates common properties
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer $path
* @param string $i
*/
protected function validateCommonProperties(&$value, $schema = null, JsonPointer $path, $i = '')
{
// if it extends another schema, it must pass that schema as well
if (isset($schema->extends)) {
if (is_string($schema->extends)) {
$schema->extends = $this->validateUri($schema, $schema->extends);
}
if (is_array($schema->extends)) {
foreach ($schema->extends as $extends) {
$this->checkUndefined($value, $extends, $path, $i);
}
} else {
$this->checkUndefined($value, $schema->extends, $path, $i);
}
}
// Apply default values from schema
if (!$path->fromDefault()) {
$this->applyDefaultValues($value, $schema, $path);
}
// Verify required values
if ($this->getTypeCheck()->isObject($value)) {
if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) {
// Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
foreach ($schema->required as $required) {
if (!$this->getTypeCheck()->propertyExists($value, $required)) {
$this->addError(
$this->incrementPath($path ?: new JsonPointer(''), $required),
'The property ' . $required . ' is required',
'required'
);
}
}
} elseif (isset($schema->required) && !is_array($schema->required)) {
// Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
if ($schema->required && $value instanceof self) {
$propertyPaths = $path->getPropertyPaths();
$propertyName = end($propertyPaths);
$this->addError(
$path,
'The property ' . $propertyName . ' is required',
'required'
);
}
}
}
// Verify type
if (!($value instanceof self)) {
$this->checkType($value, $schema, $path, $i);
}
// Verify disallowed items
if (isset($schema->disallow)) {
$initErrors = $this->getErrors();
$typeSchema = new \stdClass();
$typeSchema->type = $schema->disallow;
$this->checkType($value, $typeSchema, $path);
// if no new errors were raised it must be a disallowed value
if (count($this->getErrors()) == count($initErrors)) {
$this->addError($path, 'Disallowed value was matched', 'disallow');
} else {
$this->errors = $initErrors;
}
}
if (isset($schema->not)) {
$initErrors = $this->getErrors();
$this->checkUndefined($value, $schema->not, $path, $i);
// if no new errors were raised then the instance validated against the "not" schema
if (count($this->getErrors()) == count($initErrors)) {
$this->addError($path, 'Matched a schema which it should not', 'not');
} else {
$this->errors = $initErrors;
}
}
// Verify that dependencies are met
if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) {
$this->validateDependencies($value, $schema->dependencies, $path);
}
}
/**
* Check whether a default should be applied for this value
*
* @param mixed $schema
* @param mixed $parentSchema
* @param bool $requiredOnly
*
* @return bool
*/
private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null)
{
// required-only mode is off
if (!$requiredOnly) {
return true;
}
// draft-04 required is set
if (
$name !== null
&& isset($parentSchema->required)
&& is_array($parentSchema->required)
&& in_array($name, $parentSchema->required)
) {
return true;
}
// draft-03 required is set
if (isset($schema->required) && !is_array($schema->required) && $schema->required) {
return true;
}
// default case
return false;
}
/**
* Apply default values
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer $path
*/
protected function applyDefaultValues(&$value, $schema, $path)
{
// only apply defaults if feature is enabled
if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) {
return;
}
// apply defaults if appropriate
$requiredOnly = $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS);
if (isset($schema->properties) && LooseTypeCheck::isObject($value)) {
// $value is an object or assoc array, and properties are defined - treat as an object
foreach ($schema->properties as $currentProperty => $propertyDefinition) {
$propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition);
if (
!LooseTypeCheck::propertyExists($value, $currentProperty)
&& property_exists($propertyDefinition, 'default')
&& $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema)
) {
// assign default value
if (is_object($propertyDefinition->default)) {
LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default);
} else {
LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default);
}
$this->appliedDefaults[] = $currentProperty;
}
}
} elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) {
$items = array();
if (LooseTypeCheck::isArray($schema->items)) {
$items = $schema->items;
} elseif (isset($schema->minItems) && count($value) < $schema->minItems) {
$items = array_fill(count($value), $schema->minItems - count($value), $schema->items);
}
// $value is an array, and items are defined - treat as plain array
foreach ($items as $currentItem => $itemDefinition) {
$itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition);
if (
!array_key_exists($currentItem, $value)
&& property_exists($itemDefinition, 'default')
&& $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) {
if (is_object($itemDefinition->default)) {
$value[$currentItem] = clone $itemDefinition->default;
} else {
$value[$currentItem] = $itemDefinition->default;
}
}
$path->setFromDefault();
}
} elseif (
$value instanceof self
&& property_exists($schema, 'default')
&& $this->shouldApplyDefaultValue($requiredOnly, $schema)) {
// $value is a leaf, not a container - apply the default directly
$value = is_object($schema->default) ? clone $schema->default : $schema->default;
$path->setFromDefault();
}
}
/**
* Validate allOf, anyOf, and oneOf properties
*
* @param mixed $value
* @param mixed $schema
* @param JsonPointer $path
* @param string $i
*/
protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '')
{
// Verify type
if ($value instanceof self) {
return;
}
if (isset($schema->allOf)) {
$isValid = true;
foreach ($schema->allOf as $allOf) {
$initErrors = $this->getErrors();
$this->checkUndefined($value, $allOf, $path, $i);
$isValid = $isValid && (count($this->getErrors()) == count($initErrors));
}
if (!$isValid) {
$this->addError($path, 'Failed to match all schemas', 'allOf');
}
}
if (isset($schema->anyOf)) {
$isValid = false;
$startErrors = $this->getErrors();
$caughtException = null;
foreach ($schema->anyOf as $anyOf) {
$initErrors = $this->getErrors();
try {
$this->checkUndefined($value, $anyOf, $path, $i);
if ($isValid = (count($this->getErrors()) == count($initErrors))) {
break;
}
} catch (ValidationException $e) {
$isValid = false;
}
}
if (!$isValid) {
$this->addError($path, 'Failed to match at least one schema', 'anyOf');
} else {
$this->errors = $startErrors;
}
}
if (isset($schema->oneOf)) {
$allErrors = array();
$matchedSchemas = 0;
$startErrors = $this->getErrors();
foreach ($schema->oneOf as $oneOf) {
try {
$this->errors = array();
$this->checkUndefined($value, $oneOf, $path, $i);
if (count($this->getErrors()) == 0) {
$matchedSchemas++;
}
$allErrors = array_merge($allErrors, array_values($this->getErrors()));
} catch (ValidationException $e) {
// deliberately do nothing here - validation failed, but we want to check
// other schema options in the OneOf field.
}
}
if ($matchedSchemas !== 1) {
$this->addErrors(array_merge($allErrors, $startErrors));
$this->addError($path, 'Failed to match exactly one schema', 'oneOf');
} else {
$this->errors = $startErrors;
}
}
}
/**
* Validate dependencies
*
* @param mixed $value
* @param mixed $dependencies
* @param JsonPointer $path
* @param string $i
*/
protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '')
{
foreach ($dependencies as $key => $dependency) {
if ($this->getTypeCheck()->propertyExists($value, $key)) {
if (is_string($dependency)) {
// Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
if (!$this->getTypeCheck()->propertyExists($value, $dependency)) {
$this->addError($path, "$key depends on $dependency and $dependency is missing", 'dependencies');
}
} elseif (is_array($dependency)) {
// Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
foreach ($dependency as $d) {
if (!$this->getTypeCheck()->propertyExists($value, $d)) {
$this->addError($path, "$key depends on $d and $d is missing", 'dependencies');
}
}
} elseif (is_object($dependency)) {
// Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}}
$this->checkUndefined($value, $dependency, $path, $i);
}
}
}
}
protected function validateUri($schema, $schemaUri = null)
{
$resolver = new UriResolver();
$retriever = $this->factory->getUriRetriever();
$jsonSchema = null;
if ($resolver->isValid($schemaUri)) {
$schemaId = property_exists($schema, 'id') ? $schema->id : null;
$jsonSchema = $retriever->retrieve($schemaId, $schemaUri);
}
return $jsonSchema;
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,161 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Entity;
use JsonSchema\Exception\InvalidArgumentException;
/**
* @package JsonSchema\Entity
*
* @author Joost Nijhuis <jnijhuis81@gmail.com>
*/
class JsonPointer
{
/** @var string */
private $filename;
/** @var string[] */
private $propertyPaths = array();
/**
* @var bool Whether the value at this path was set from a schema default
*/
private $fromDefault = false;
/**
* @param string $value
*
* @throws InvalidArgumentException when $value is not a string
*/
public function __construct($value)
{
if (!is_string($value)) {
throw new InvalidArgumentException('Ref value must be a string');
}
$splitRef = explode('#', $value, 2);
$this->filename = $splitRef[0];
if (array_key_exists(1, $splitRef)) {
$this->propertyPaths = $this->decodePropertyPaths($splitRef[1]);
}
}
/**
* @param string $propertyPathString
*
* @return string[]
*/
private function decodePropertyPaths($propertyPathString)
{
$paths = array();
foreach (explode('/', trim($propertyPathString, '/')) as $path) {
$path = $this->decodePath($path);
if (is_string($path) && '' !== $path) {
$paths[] = $path;
}
}
return $paths;
}
/**
* @return array
*/
private function encodePropertyPaths()
{
return array_map(
array($this, 'encodePath'),
$this->getPropertyPaths()
);
}
/**
* @param string $path
*
* @return string
*/
private function decodePath($path)
{
return strtr($path, array('~1' => '/', '~0' => '~', '%25' => '%'));
}
/**
* @param string $path
*
* @return string
*/
private function encodePath($path)
{
return strtr($path, array('/' => '~1', '~' => '~0', '%' => '%25'));
}
/**
* @return string
*/
public function getFilename()
{
return $this->filename;
}
/**
* @return string[]
*/
public function getPropertyPaths()
{
return $this->propertyPaths;
}
/**
* @param array $propertyPaths
*
* @return JsonPointer
*/
public function withPropertyPaths(array $propertyPaths)
{
$new = clone $this;
$new->propertyPaths = $propertyPaths;
return $new;
}
/**
* @return string
*/
public function getPropertyPathAsString()
{
return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/');
}
/**
* @return string
*/
public function __toString()
{
return $this->getFilename() . $this->getPropertyPathAsString();
}
/**
* Mark the value at this path as being set from a schema default
*/
public function setFromDefault()
{
$this->fromDefault = true;
}
/**
* Check whether the value at this path was set from a schema default
*
* @return bool
*/
public function fromDefault()
{
return $this->fromDefault;
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,7 @@
<?php
namespace JsonSchema\Exception;
interface ExceptionInterface
{
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the InvalidArgumentException
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the ResourceNotFoundException
*/
class InvalidConfigException extends RuntimeException
{
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the InvalidSchemaMediaType
*/
class InvalidSchemaException extends RuntimeException
{
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the InvalidSchemaMediaType
*/
class InvalidSchemaMediaTypeException extends RuntimeException
{
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the InvalidSourceUriException
*/
class InvalidSourceUriException extends InvalidArgumentException
{
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the JsonDecodingException
*/
class JsonDecodingException extends RuntimeException
{
public function __construct($code = JSON_ERROR_NONE, \Exception $previous = null)
{
switch ($code) {
case JSON_ERROR_DEPTH:
$message = 'The maximum stack depth has been exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$message = 'Invalid or malformed JSON';
break;
case JSON_ERROR_CTRL_CHAR:
$message = 'Control character error, possibly incorrectly encoded';
break;
case JSON_ERROR_UTF8:
$message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
case JSON_ERROR_SYNTAX:
$message = 'JSON syntax is malformed';
break;
default:
$message = 'Syntax error';
}
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the ResourceNotFoundException
*/
class ResourceNotFoundException extends RuntimeException
{
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the RuntimeException
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@@ -0,0 +1,19 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* @package JsonSchema\Exception
*
* @author Joost Nijhuis <jnijhuis81@gmail.com>
*/
class UnresolvableJsonPointerException extends InvalidArgumentException
{
}

View File

@@ -0,0 +1,17 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
/**
* Wrapper for the UriResolverException
*/
class UriResolverException extends RuntimeException
{
}

View File

@@ -0,0 +1,14 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Exception;
class ValidationException extends RuntimeException
{
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,149 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Iterator;
/**
* @package JsonSchema\Iterator
*
* @author Joost Nijhuis <jnijhuis81@gmail.com>
*/
class ObjectIterator implements \Iterator, \Countable
{
/** @var object */
private $object;
/** @var int */
private $position = 0;
/** @var array */
private $data = array();
/** @var bool */
private $initialized = false;
/**
* @param object $object
*/
public function __construct($object)
{
$this->object = $object;
}
/**
* {@inheritdoc}
*/
public function current()
{
$this->initialize();
return $this->data[$this->position];
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->initialize();
$this->position++;
}
/**
* {@inheritdoc}
*/
public function key()
{
$this->initialize();
return $this->position;
}
/**
* {@inheritdoc}
*/
public function valid()
{
$this->initialize();
return isset($this->data[$this->position]);
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->initialize();
$this->position = 0;
}
/**
* {@inheritdoc}
*/
public function count()
{
$this->initialize();
return count($this->data);
}
/**
* Initializer
*/
private function initialize()
{
if (!$this->initialized) {
$this->data = $this->buildDataFromObject($this->object);
$this->initialized = true;
}
}
/**
* @param object $object
*
* @return array
*/
private function buildDataFromObject($object)
{
$result = array();
$stack = new \SplStack();
$stack->push($object);
while (!$stack->isEmpty()) {
$current = $stack->pop();
if (is_object($current)) {
array_push($result, $current);
}
foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) {
if (is_object($propertyValue) || is_array($propertyValue)) {
$stack->push($propertyValue);
}
}
}
return $result;
}
/**
* @param object|array $item
*
* @return array
*/
private function getDataFromItem($item)
{
if (!is_object($item) && !is_array($item)) {
return array();
}
return is_object($item) ? get_object_vars($item) : $item;
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,30 @@
<?php
namespace JsonSchema;
class Rfc3339
{
const REGEX = '/^(\d{4}-\d{2}-\d{2}[T ]{1}\d{2}:\d{2}:\d{2})(\.\d+)?(Z|([+-]\d{2}):?(\d{2}))$/';
/**
* Try creating a DateTime instance
*
* @param string $string
*
* @return \DateTime|null
*/
public static function createFromString($string)
{
if (!preg_match(self::REGEX, strtoupper($string), $matches)) {
return null;
}
$dateAndTime = $matches[1];
$microseconds = $matches[2] ?: '.000000';
$timeZone = 'Z' !== $matches[3] ? $matches[4] . ':' . $matches[5] : '+00:00';
$dateFormat = strpos($dateAndTime, 'T') === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP';
$dateTime = \DateTime::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC'));
return $dateTime ?: null;
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace JsonSchema;
use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\UnresolvableJsonPointerException;
use JsonSchema\Uri\UriResolver;
use JsonSchema\Uri\UriRetriever;
class SchemaStorage implements SchemaStorageInterface
{
const INTERNAL_PROVIDED_SCHEMA_URI = 'internal://provided-schema/';
protected $uriRetriever;
protected $uriResolver;
protected $schemas = array();
public function __construct(
UriRetrieverInterface $uriRetriever = null,
UriResolverInterface $uriResolver = null
) {
$this->uriRetriever = $uriRetriever ?: new UriRetriever();
$this->uriResolver = $uriResolver ?: new UriResolver();
}
/**
* @return UriRetrieverInterface
*/
public function getUriRetriever()
{
return $this->uriRetriever;
}
/**
* @return UriResolverInterface
*/
public function getUriResolver()
{
return $this->uriResolver;
}
/**
* {@inheritdoc}
*/
public function addSchema($id, $schema = null)
{
if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) {
// if the schema was user-provided to Validator and is still null, then assume this is
// what the user intended, as there's no way for us to retrieve anything else. User-supplied
// schemas do not have an associated URI when passed via Validator::validate().
$schema = $this->uriRetriever->retrieve($id);
}
// cast array schemas to object
if (is_array($schema)) {
$schema = BaseConstraint::arrayToObjectRecursive($schema);
}
// workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format)
// see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367
if (is_object($schema) && property_exists($schema, 'id')) {
if ($schema->id == 'http://json-schema.org/draft-04/schema#') {
$schema->properties->id->format = 'uri-reference';
} elseif ($schema->id == 'http://json-schema.org/draft-03/schema#') {
$schema->properties->id->format = 'uri-reference';
$schema->properties->{'$ref'}->format = 'uri-reference';
}
}
// resolve references
$this->expandRefs($schema, $id);
$this->schemas[$id] = $schema;
}
/**
* Recursively resolve all references against the provided base
*
* @param mixed $schema
* @param string $base
*/
private function expandRefs(&$schema, $base = null)
{
if (!is_object($schema)) {
if (is_array($schema)) {
foreach ($schema as &$member) {
$this->expandRefs($member, $base);
}
}
return;
}
if (property_exists($schema, 'id') && is_string($schema->id) && $base != $schema->id) {
$base = $this->uriResolver->resolve($schema->id, $base);
}
if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) {
$refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base));
$schema->{'$ref'} = (string) $refPointer;
}
foreach ($schema as &$member) {
$this->expandRefs($member, $base);
}
}
/**
* {@inheritdoc}
*/
public function getSchema($id)
{
if (!array_key_exists($id, $this->schemas)) {
$this->addSchema($id);
}
return $this->schemas[$id];
}
/**
* {@inheritdoc}
*/
public function resolveRef($ref)
{
$jsonPointer = new JsonPointer($ref);
// resolve filename for pointer
$fileName = $jsonPointer->getFilename();
if (!strlen($fileName)) {
throw new UnresolvableJsonPointerException(sprintf(
"Could not resolve fragment '%s': no file is defined",
$jsonPointer->getPropertyPathAsString()
));
}
// get & process the schema
$refSchema = $this->getSchema($fileName);
foreach ($jsonPointer->getPropertyPaths() as $path) {
if (is_object($refSchema) && property_exists($refSchema, $path)) {
$refSchema = $this->resolveRefSchema($refSchema->{$path});
} elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) {
$refSchema = $this->resolveRefSchema($refSchema[$path]);
} else {
throw new UnresolvableJsonPointerException(sprintf(
'File: %s is found, but could not resolve fragment: %s',
$jsonPointer->getFilename(),
$jsonPointer->getPropertyPathAsString()
));
}
}
return $refSchema;
}
/**
* {@inheritdoc}
*/
public function resolveRefSchema($refSchema)
{
if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) {
$newSchema = $this->resolveRef($refSchema->{'$ref'});
$refSchema = (object) (get_object_vars($refSchema) + get_object_vars($newSchema));
unset($refSchema->{'$ref'});
}
return $refSchema;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace JsonSchema;
interface SchemaStorageInterface
{
/**
* Adds schema with given identifier
*
* @param string $id
* @param object $schema
*/
public function addSchema($id, $schema = null);
/**
* Returns schema for given identifier, or null if it does not exist
*
* @param string $id
*
* @return object
*/
public function getSchema($id);
/**
* Returns schema for given reference with all sub-references resolved
*
* @param string $ref
*
* @return object
*/
public function resolveRef($ref);
/**
* Returns schema referenced by '$ref' property
*
* @param mixed $refSchema
*
* @return object
*/
public function resolveRefSchema($refSchema);
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* JsonSchema
*
* @filesource
*/
namespace JsonSchema\Uri\Retrievers;
/**
* AbstractRetriever implements the default shared behavior
* that all descendant Retrievers should inherit
*
* @author Steven Garcia <webwhammy@gmail.com>
*/
abstract class AbstractRetriever implements UriRetrieverInterface
{
/**
* Media content type
*
* @var string
*/
protected $contentType;
/**
* {@inheritdoc}
*
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType()
*/
public function getContentType()
{
return $this->contentType;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri\Retrievers;
use JsonSchema\Exception\RuntimeException;
use JsonSchema\Validator;
/**
* Tries to retrieve JSON schemas from a URI using cURL library
*
* @author Sander Coolen <sander@jibber.nl>
*/
class Curl extends AbstractRetriever
{
protected $messageBody;
public function __construct()
{
if (!function_exists('curl_init')) {
// Cannot test this, because curl_init is present on all test platforms plus mock
throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore
}
}
/**
* {@inheritdoc}
*
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
*/
public function retrieve($uri)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: ' . Validator::SCHEMA_MEDIA_TYPE));
$response = curl_exec($ch);
if (false === $response) {
throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found');
}
$this->fetchMessageBody($response);
$this->fetchContentType($response);
curl_close($ch);
return $this->messageBody;
}
/**
* @param string $response cURL HTTP response
*/
private function fetchMessageBody($response)
{
preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match);
$this->messageBody = $match[1];
}
/**
* @param string $response cURL HTTP response
*
* @return bool Whether the Content-Type header was found or not
*/
protected function fetchContentType($response)
{
if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) {
$this->contentType = trim($match[1]);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,93 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri\Retrievers;
use JsonSchema\Exception\ResourceNotFoundException;
/**
* Tries to retrieve JSON schemas from a URI using file_get_contents()
*
* @author Sander Coolen <sander@jibber.nl>
*/
class FileGetContents extends AbstractRetriever
{
protected $messageBody;
/**
* {@inheritdoc}
*
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
*/
public function retrieve($uri)
{
$errorMessage = null;
set_error_handler(function ($errno, $errstr) use (&$errorMessage) {
$errorMessage = $errstr;
});
$response = file_get_contents($uri);
restore_error_handler();
if ($errorMessage) {
throw new ResourceNotFoundException($errorMessage);
}
if (false === $response) {
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
}
if ($response == ''
&& substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/'
) {
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
}
$this->messageBody = $response;
if (!empty($http_response_header)) {
// $http_response_header cannot be tested, because it's defined in the method's local scope
// See http://php.net/manual/en/reserved.variables.httpresponseheader.php for more info.
$this->fetchContentType($http_response_header); // @codeCoverageIgnore
} else { // @codeCoverageIgnore
// Could be a "file://" url or something else - fake up the response
$this->contentType = null;
}
return $this->messageBody;
}
/**
* @param array $headers HTTP Response Headers
*
* @return bool Whether the Content-Type header was found or not
*/
private function fetchContentType(array $headers)
{
foreach ($headers as $header) {
if ($this->contentType = self::getContentTypeMatchInHeader($header)) {
return true;
}
}
return false;
}
/**
* @param string $header
*
* @return string|null
*/
protected static function getContentTypeMatchInHeader($header)
{
if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) {
return trim($match[1]);
}
return null;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace JsonSchema\Uri\Retrievers;
use JsonSchema\Validator;
/**
* URI retrieved based on a predefined array of schemas
*
* @example
*
* $retriever = new PredefinedArray(array(
* 'http://acme.com/schemas/person#' => '{ ... }',
* 'http://acme.com/schemas/address#' => '{ ... }',
* ))
*
* $schema = $retriever->retrieve('http://acme.com/schemas/person#');
*/
class PredefinedArray extends AbstractRetriever
{
/**
* Contains schemas as URI => JSON
*
* @var array
*/
private $schemas;
/**
* Constructor
*
* @param array $schemas
* @param string $contentType
*/
public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE)
{
$this->schemas = $schemas;
$this->contentType = $contentType;
}
/**
* {@inheritdoc}
*
* @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
*/
public function retrieve($uri)
{
if (!array_key_exists($uri, $this->schemas)) {
throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf(
'The JSON schema "%s" was not found.',
$uri
));
}
return $this->schemas[$uri];
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri\Retrievers;
/**
* Interface for URI retrievers
*
* @author Sander Coolen <sander@jibber.nl>
*/
interface UriRetrieverInterface
{
/**
* Retrieve a schema from the specified URI
*
* @param string $uri URI that resolves to a JSON schema
*
* @throws \JsonSchema\Exception\ResourceNotFoundException
*
* @return mixed string|null
*/
public function retrieve($uri);
/**
* Get media content type
*
* @return string
*/
public function getContentType();
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,175 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri;
use JsonSchema\Exception\UriResolverException;
use JsonSchema\UriResolverInterface;
/**
* Resolves JSON Schema URIs
*
* @author Sander Coolen <sander@jibber.nl>
*/
class UriResolver implements UriResolverInterface
{
/**
* Parses a URI into five main components
*
* @param string $uri
*
* @return array
*/
public function parse($uri)
{
preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);
$components = array();
if (5 < count($match)) {
$components = array(
'scheme' => $match[2],
'authority' => $match[4],
'path' => $match[5]
);
}
if (7 < count($match)) {
$components['query'] = $match[7];
}
if (9 < count($match)) {
$components['fragment'] = $match[9];
}
return $components;
}
/**
* Builds a URI based on n array with the main components
*
* @param array $components
*
* @return string
*/
public function generate(array $components)
{
$uri = $components['scheme'] . '://'
. $components['authority']
. $components['path'];
if (array_key_exists('query', $components) && strlen($components['query'])) {
$uri .= '?' . $components['query'];
}
if (array_key_exists('fragment', $components)) {
$uri .= '#' . $components['fragment'];
}
return $uri;
}
/**
* {@inheritdoc}
*/
public function resolve($uri, $baseUri = null)
{
// treat non-uri base as local file path
if (
!is_null($baseUri) &&
!filter_var($baseUri, \FILTER_VALIDATE_URL) &&
!preg_match('|^[^/]+://|u', $baseUri)
) {
if (is_file($baseUri)) {
$baseUri = 'file://' . realpath($baseUri);
} elseif (is_dir($baseUri)) {
$baseUri = 'file://' . realpath($baseUri) . '/';
} else {
$baseUri = 'file://' . getcwd() . '/' . $baseUri;
}
}
if ($uri == '') {
return $baseUri;
}
$components = $this->parse($uri);
$path = $components['path'];
if (!empty($components['scheme'])) {
return $uri;
}
$baseComponents = $this->parse($baseUri);
$basePath = $baseComponents['path'];
$baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath);
if (isset($components['fragment'])) {
$baseComponents['fragment'] = $components['fragment'];
}
return $this->generate($baseComponents);
}
/**
* Tries to glue a relative path onto an absolute one
*
* @param string $relativePath
* @param string $basePath
*
* @throws UriResolverException
*
* @return string Merged path
*/
public static function combineRelativePathWithBasePath($relativePath, $basePath)
{
$relativePath = self::normalizePath($relativePath);
if ($relativePath == '') {
return $basePath;
}
if ($relativePath[0] == '/') {
return $relativePath;
}
$basePathSegments = explode('/', $basePath);
preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match);
$numLevelUp = strlen($match[0]) /3 + 1;
if ($numLevelUp >= count($basePathSegments)) {
throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
}
$basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp);
$path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath);
return implode('/', $basePathSegments) . '/' . $path;
}
/**
* Normalizes a URI path component by removing dot-slash and double slashes
*
* @param string $path
*
* @return string
*/
private static function normalizePath($path)
{
$path = preg_replace('|((?<!\.)\./)*|', '', $path);
$path = preg_replace('|//|', '/', $path);
return $path;
}
/**
* @param string $uri
*
* @return bool
*/
public function isValid($uri)
{
$components = $this->parse($uri);
return !empty($components);
}
}

View File

@@ -0,0 +1,349 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema\Uri;
use JsonSchema\Exception\InvalidSchemaMediaTypeException;
use JsonSchema\Exception\JsonDecodingException;
use JsonSchema\Exception\ResourceNotFoundException;
use JsonSchema\Uri\Retrievers\FileGetContents;
use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface;
use JsonSchema\Validator;
/**
* Retrieves JSON Schema URIs
*
* @author Tyler Akins <fidian@rumkin.com>
*/
class UriRetriever implements BaseUriRetrieverInterface
{
/**
* @var array Map of URL translations
*/
protected $translationMap = array(
// use local copies of the spec schemas
'|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json'
);
/**
* @var array A list of endpoints for media type check exclusion
*/
protected $allowedInvalidContentTypeEndpoints = array(
'http://json-schema.org/',
'https://json-schema.org/'
);
/**
* @var null|UriRetrieverInterface
*/
protected $uriRetriever = null;
/**
* @var array|object[]
*
* @see loadSchema
*/
private $schemaCache = array();
/**
* Adds an endpoint to the media type validation exclusion list
*
* @param string $endpoint
*/
public function addInvalidContentTypeEndpoint($endpoint)
{
$this->allowedInvalidContentTypeEndpoints[] = $endpoint;
}
/**
* Guarantee the correct media type was encountered
*
* @param UriRetrieverInterface $uriRetriever
* @param string $uri
*
* @return bool|void
*/
public function confirmMediaType($uriRetriever, $uri)
{
$contentType = $uriRetriever->getContentType();
if (is_null($contentType)) {
// Well, we didn't get an invalid one
return;
}
if (in_array($contentType, array(Validator::SCHEMA_MEDIA_TYPE, 'application/json'))) {
return;
}
foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) {
if (strpos($uri, $endpoint) === 0) {
return true;
}
}
throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE));
}
/**
* Get a URI Retriever
*
* If none is specified, sets a default FileGetContents retriever and
* returns that object.
*
* @return UriRetrieverInterface
*/
public function getUriRetriever()
{
if (is_null($this->uriRetriever)) {
$this->setUriRetriever(new FileGetContents());
}
return $this->uriRetriever;
}
/**
* Resolve a schema based on pointer
*
* URIs can have a fragment at the end in the format of
* #/path/to/object and we are to look up the 'path' property of
* the first object then the 'to' and 'object' properties.
*
* @param object $jsonSchema JSON Schema contents
* @param string $uri JSON Schema URI
*
* @throws ResourceNotFoundException
*
* @return object JSON Schema after walking down the fragment pieces
*/
public function resolvePointer($jsonSchema, $uri)
{
$resolver = new UriResolver();
$parsed = $resolver->parse($uri);
if (empty($parsed['fragment'])) {
return $jsonSchema;
}
$path = explode('/', $parsed['fragment']);
while ($path) {
$pathElement = array_shift($path);
if (!empty($pathElement)) {
$pathElement = str_replace('~1', '/', $pathElement);
$pathElement = str_replace('~0', '~', $pathElement);
if (!empty($jsonSchema->$pathElement)) {
$jsonSchema = $jsonSchema->$pathElement;
} else {
throw new ResourceNotFoundException(
'Fragment "' . $parsed['fragment'] . '" not found'
. ' in ' . $uri
);
}
if (!is_object($jsonSchema)) {
throw new ResourceNotFoundException(
'Fragment part "' . $pathElement . '" is no object '
. ' in ' . $uri
);
}
}
}
return $jsonSchema;
}
/**
* {@inheritdoc}
*/
public function retrieve($uri, $baseUri = null, $translate = true)
{
$resolver = new UriResolver();
$resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri);
//fetch URL without #fragment
$arParts = $resolver->parse($resolvedUri);
if (isset($arParts['fragment'])) {
unset($arParts['fragment']);
$fetchUri = $resolver->generate($arParts);
}
// apply URI translations
if ($translate) {
$fetchUri = $this->translate($fetchUri);
}
$jsonSchema = $this->loadSchema($fetchUri);
// Use the JSON pointer if specified
$jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri);
if ($jsonSchema instanceof \stdClass) {
$jsonSchema->id = $resolvedUri;
}
return $jsonSchema;
}
/**
* Fetch a schema from the given URI, json-decode it and return it.
* Caches schema objects.
*
* @param string $fetchUri Absolute URI
*
* @return object JSON schema object
*/
protected function loadSchema($fetchUri)
{
if (isset($this->schemaCache[$fetchUri])) {
return $this->schemaCache[$fetchUri];
}
$uriRetriever = $this->getUriRetriever();
$contents = $this->uriRetriever->retrieve($fetchUri);
$this->confirmMediaType($uriRetriever, $fetchUri);
$jsonSchema = json_decode($contents);
if (JSON_ERROR_NONE < $error = json_last_error()) {
throw new JsonDecodingException($error);
}
$this->schemaCache[$fetchUri] = $jsonSchema;
return $jsonSchema;
}
/**
* Set the URI Retriever
*
* @param UriRetrieverInterface $uriRetriever
*
* @return $this for chaining
*/
public function setUriRetriever(UriRetrieverInterface $uriRetriever)
{
$this->uriRetriever = $uriRetriever;
return $this;
}
/**
* Parses a URI into five main components
*
* @param string $uri
*
* @return array
*/
public function parse($uri)
{
preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);
$components = array();
if (5 < count($match)) {
$components = array(
'scheme' => $match[2],
'authority' => $match[4],
'path' => $match[5]
);
}
if (7 < count($match)) {
$components['query'] = $match[7];
}
if (9 < count($match)) {
$components['fragment'] = $match[9];
}
return $components;
}
/**
* Builds a URI based on n array with the main components
*
* @param array $components
*
* @return string
*/
public function generate(array $components)
{
$uri = $components['scheme'] . '://'
. $components['authority']
. $components['path'];
if (array_key_exists('query', $components)) {
$uri .= $components['query'];
}
if (array_key_exists('fragment', $components)) {
$uri .= $components['fragment'];
}
return $uri;
}
/**
* Resolves a URI
*
* @param string $uri Absolute or relative
* @param string $baseUri Optional base URI
*
* @return string
*/
public function resolve($uri, $baseUri = null)
{
$components = $this->parse($uri);
$path = $components['path'];
if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) {
return $uri;
}
$baseComponents = $this->parse($baseUri);
$basePath = $baseComponents['path'];
$baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath);
return $this->generate($baseComponents);
}
/**
* @param string $uri
*
* @return bool
*/
public function isValid($uri)
{
$components = $this->parse($uri);
return !empty($components);
}
/**
* Set a URL translation rule
*/
public function setTranslation($from, $to)
{
$this->translationMap[$from] = $to;
}
/**
* Apply URI translation rules
*/
public function translate($uri)
{
foreach ($this->translationMap as $from => $to) {
$uri = preg_replace($from, $to, $uri);
}
// translate references to local files within the json-schema package
$uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri);
return $uri;
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema;
/**
* @package JsonSchema
*/
interface UriResolverInterface
{
/**
* Resolves a URI
*
* @param string $uri Absolute or relative
* @param null|string $baseUri Optional base URI
*
* @return string Absolute URI
*/
public function resolve($uri, $baseUri = null);
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema;
/**
* @package JsonSchema
*/
interface UriRetrieverInterface
{
/**
* Retrieve a URI
*
* @param string $uri JSON Schema URI
* @param null|string $baseUri
*
* @return object JSON Schema contents
*/
public function retrieve($uri, $baseUri = null);
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the JsonSchema package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace JsonSchema;
use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Constraints\Constraint;
/**
* A JsonSchema Constraint
*
* @author Robert Schönthal <seroscho@googlemail.com>
* @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
*
* @see README.md
*/
class Validator extends BaseConstraint
{
const SCHEMA_MEDIA_TYPE = 'application/schema+json';
const ERROR_NONE = 0x00000000;
const ERROR_ALL = 0xFFFFFFFF;
const ERROR_DOCUMENT_VALIDATION = 0x00000001;
const ERROR_SCHEMA_VALIDATION = 0x00000002;
/**
* Validates the given data against the schema and returns an object containing the results
* Both the php object and the schema are supposed to be a result of a json_decode call.
* The validation works as defined by the schema proposal in http://json-schema.org.
*
* Note that the first argument is passwd by reference, so you must pass in a variable.
*
* {@inheritdoc}
*/
public function validate(&$value, $schema = null, $checkMode = null)
{
// make sure $schema is an object
if (is_array($schema)) {
$schema = self::arrayToObjectRecursive($schema);
}
// set checkMode
$initialCheckMode = $this->factory->getConfig();
if ($checkMode !== null) {
$this->factory->setConfig($checkMode);
}
// add provided schema to SchemaStorage with internal URI to allow internal $ref resolution
if (is_object($schema) && property_exists($schema, 'id')) {
$schemaURI = $schema->id;
} else {
$schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI;
}
$this->factory->getSchemaStorage()->addSchema($schemaURI, $schema);
$validator = $this->factory->createInstanceFor('schema');
$validator->check(
$value,
$this->factory->getSchemaStorage()->getSchema($schemaURI)
);
$this->factory->setConfig($initialCheckMode);
$this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR));
return $validator->getErrorMask();
}
/**
* Alias to validate(), to maintain backwards-compatibility with the previous API
*/
public function check($value, $schema)
{
return $this->validate($value, $schema);
}
/**
* Alias to validate(), to maintain backwards-compatibility with the previous API
*/
public function coerce(&$value, $schema)
{
return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
}
}

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,11 @@
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Location: ../");
exit;

View File

@@ -0,0 +1,18 @@
<?php
define('TWISTO_PATH', dirname(__FILE__) . '/Twisto/');
require_once TWISTO_PATH . 'BaseAddress.php';
require_once TWISTO_PATH . 'Address.php';
require_once TWISTO_PATH . 'Customer.php';
require_once TWISTO_PATH . 'Error.php';
require_once TWISTO_PATH . 'Invoice.php';
require_once TWISTO_PATH . 'Item.php';
require_once TWISTO_PATH . 'ItemReturn.php';
require_once TWISTO_PATH . 'ItemDiscountReturn.php';
require_once TWISTO_PATH . 'Order.php';
require_once TWISTO_PATH . 'SharedDbCustomer.php';
require_once TWISTO_PATH . 'SharedDbOrder.php';
require_once TWISTO_PATH . 'SharedDbResponse.php';
require_once TWISTO_PATH . 'ShortAddress.php';
require_once TWISTO_PATH . 'Twisto.php';

View File

@@ -0,0 +1,63 @@
<?php
namespace Twisto;
class Address implements BaseAddress
{
/** @var string */
public $name;
/** @var string */
public $street;
/** @var string */
public $city;
/** @var string */
public $zipcode;
/** @var string */
public $phone_number;
/** @var string */
public $country;
/**
* @param string $name
* @param string $street
* @param string $city
* @param string $zipcode
* @param string $country
* @param string $phone_number
*/
public function __construct($name, $street, $city, $zipcode, $country, $phone_number)
{
$this->name = $name;
$this->street = $street;
$this->city = $city;
$this->zipcode = $zipcode;
$this->country = $country;
$this->phone_number = $phone_number;
}
/**
* @return array
*/
public function serialize()
{
return array(
'name' => $this->name,
'street' => $this->street,
'city' => $this->city,
'zipcode' => $this->zipcode,
'country' => $this->country,
'phone_number' => $this->phone_number
);
}
public static function deserialize($data)
{
return new self($data['name'], $data['street'], $data['city'], $data['zipcode'], $data['country'], $data['phone_number']);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Twisto;
interface BaseAddress
{
const TYPE_FULL = 1;
const TYPE_SHORT = 2;
/**
* @return array
*/
function serialize();
static function deserialize($data);
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Twisto;
class Customer
{
/** @var string */
public $email;
/** @var string */
public $name;
/** @var string */
public $facebook_id;
/** @var string */
public $company_id;
/** @var string */
public $vat_id;
/**
* @param string $email
* @param string $name
* @param string $facebook_id
* @param string $company_id
* @param string $vat_id
*/
public function __construct($email, $name=null, $facebook_id=null, $company_id=null, $vat_id=null)
{
$this->email = $email;
$this->name = $name;
$this->facebook_id = $facebook_id;
$this->company_id = $company_id;
$this->vat_id = $vat_id;
}
/**
* @return array
*/
public function serialize()
{
return array(
'email' => $this->email,
'name' => $this->name,
'facebook_id' => $this->facebook_id,
'company_id' => $this->company_id,
'vat_id' => $this->vat_id
);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Twisto;
class Error extends \Exception
{
public $data;
public function __construct($message = "", $data = null, $code = 0, \Exception $previous = null)
{
$this->data = $data;
parent::__construct($message, $code, $previous);
}
}

Some files were not shown because too many files have changed in this diff Show More