452 lines
14 KiB
PHP
452 lines
14 KiB
PHP
|
|
<?php
|
||
|
|
namespace MercadoPago;
|
||
|
|
/**
|
||
|
|
* Class Manager
|
||
|
|
*
|
||
|
|
* @package MercadoPago
|
||
|
|
*/
|
||
|
|
class Manager
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* @var RestClient
|
||
|
|
*/
|
||
|
|
private $_client;
|
||
|
|
/**
|
||
|
|
* @var Config
|
||
|
|
*/
|
||
|
|
private $_config;
|
||
|
|
/**
|
||
|
|
* @var
|
||
|
|
*/
|
||
|
|
private $_customTrackingParams=array();
|
||
|
|
|
||
|
|
private $_entityConfiguration;
|
||
|
|
/**
|
||
|
|
* @var MetaDataReader
|
||
|
|
*/
|
||
|
|
private $_metadataReader;
|
||
|
|
/**
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
static $CIPHER = 'sha256';
|
||
|
|
/**
|
||
|
|
* Manager constructor.
|
||
|
|
*
|
||
|
|
* @param RestClient $client
|
||
|
|
* @param Config $config
|
||
|
|
*/
|
||
|
|
public function __construct(RestClient $client, Config $config)
|
||
|
|
{
|
||
|
|
$this->_client = $client;
|
||
|
|
$this->_config = $config;
|
||
|
|
$this->_metadataReader = new MetaDataReader();
|
||
|
|
}
|
||
|
|
protected function _getEntityConfiguration($entity)
|
||
|
|
{
|
||
|
|
$className = $this->_getEntityClassName($entity);
|
||
|
|
if (isset($this->_entityConfiguration[$className])) {
|
||
|
|
return $this->_entityConfiguration[$className];
|
||
|
|
}
|
||
|
|
$this->_entityConfiguration[$className] = $this->_metadataReader->getMetaData($entity);
|
||
|
|
return $this->_entityConfiguration[$className];
|
||
|
|
}
|
||
|
|
|
||
|
|
protected function _updateEntityConfiguration($entity, $new_configuration)
|
||
|
|
{
|
||
|
|
$className = $this->_getEntityClassName($entity);
|
||
|
|
$this->_entityConfiguration[$className] = $new_configuration;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function addCustomTrackingParam($key, $value)
|
||
|
|
{
|
||
|
|
$this->_customTrackingParams[$key] = $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
* @param string $method
|
||
|
|
* @param null $parameters
|
||
|
|
*
|
||
|
|
* @return mixed
|
||
|
|
*/
|
||
|
|
public function execute($entity, $method = 'get', $options = [])
|
||
|
|
{
|
||
|
|
|
||
|
|
$configuration = $this->_getEntityConfiguration($entity);
|
||
|
|
|
||
|
|
if ($method != 'get'){
|
||
|
|
foreach ($configuration->attributes as $key => $attribute) {
|
||
|
|
$this->validateAttribute($entity, $key, ['required']);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->processOptions($options, $configuration);
|
||
|
|
|
||
|
|
$this->_setDefaultHeaders($configuration->query);
|
||
|
|
$this->_setCustomHeaders($entity, $configuration->query);
|
||
|
|
$this->_setIdempotencyHeader($configuration->query, $method);
|
||
|
|
$this->setQueryParams($entity);
|
||
|
|
|
||
|
|
return $this->_client->{$method}($configuration->url, $configuration->query);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function processOptions($options, $configuration)
|
||
|
|
{
|
||
|
|
$configuration_vars = $this->_config->all();
|
||
|
|
|
||
|
|
foreach($options as $option => $value) {
|
||
|
|
$configuration->query["url_query"][$option] = $value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public function validateAttribute($entity, $attribute, array $properties, $value = null)
|
||
|
|
{
|
||
|
|
$configuration = $this->_getEntityConfiguration($entity);
|
||
|
|
foreach ($properties as $property) {
|
||
|
|
if ($configuration->attributes[$attribute][$property]) {
|
||
|
|
$result = $this->_isValidProperty($attribute, $property, $entity, $configuration->attributes[$attribute], $value);
|
||
|
|
if (!$result) {
|
||
|
|
throw new \Exception('Error ' . $property . ' in attribute ' . $attribute);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
protected function _isValidProperty($key, $property, $entity, $attribute, $value)
|
||
|
|
{
|
||
|
|
switch ($property) {
|
||
|
|
case 'required':
|
||
|
|
return ($entity->{$key} !== null);
|
||
|
|
case 'maxLength':
|
||
|
|
return (strlen($value) <= $attribute['maxLength']);
|
||
|
|
case 'readOnly':
|
||
|
|
return !$attribute['readOnly'];
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
* @param $ormMethod
|
||
|
|
*
|
||
|
|
* @throws \Exception
|
||
|
|
*/
|
||
|
|
public function setEntityUrl($entity, $ormMethod, $params = [])
|
||
|
|
{
|
||
|
|
$className = $this->_getEntityClassName($entity);
|
||
|
|
if (!isset($this->_entityConfiguration[$className]->methods[$ormMethod])) {
|
||
|
|
throw new \Exception('ORM method ' . $ormMethod . ' not available for entity:' . $className);
|
||
|
|
}
|
||
|
|
|
||
|
|
$url = $this->_entityConfiguration[$className]->methods[$ormMethod]['resource'];
|
||
|
|
$matches = [];
|
||
|
|
preg_match_all('/\\:\\w+/', $url, $matches);
|
||
|
|
|
||
|
|
$configuration_vars = $this->_config->all();
|
||
|
|
|
||
|
|
foreach ($matches[0] as $match) {
|
||
|
|
$key = substr($match, 1);
|
||
|
|
|
||
|
|
if (array_key_exists($key, $params)) {
|
||
|
|
$url = str_replace($match, $params[$key], $url);
|
||
|
|
} elseif (array_key_exists(strtoupper($key), $configuration_vars)) {
|
||
|
|
$url = str_replace($match, $configuration_vars[strtoupper($key)], $url);
|
||
|
|
} elseif (!empty($entity->$key)) {
|
||
|
|
$url = str_replace($match, $entity->$key, $url);
|
||
|
|
} else {
|
||
|
|
$url = str_replace($match, $entity->{$key} ?? '', $url);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$this->_entityConfiguration[$className]->url = $url;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
*
|
||
|
|
* @return mixed
|
||
|
|
*/
|
||
|
|
public function setEntityMetadata($entity)
|
||
|
|
{
|
||
|
|
return $this->_getEntityConfiguration($this->_getEntityClassName($entity));
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
protected function _getEntityClassName($entity)
|
||
|
|
{
|
||
|
|
if (is_object($entity)) {
|
||
|
|
$className = get_class($entity);
|
||
|
|
} else {
|
||
|
|
$className = $entity;
|
||
|
|
}
|
||
|
|
return $className;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
*/
|
||
|
|
public function getExcludedAttributes($entity){
|
||
|
|
|
||
|
|
$className = $this->_getEntityClassName($entity);
|
||
|
|
$configuration = $this->_getEntityConfiguration($entity);
|
||
|
|
|
||
|
|
$excluded_attributes = array();
|
||
|
|
$attributes = $entity->getAttributes();
|
||
|
|
|
||
|
|
// if ($className == "MercadoPago\Refund") {
|
||
|
|
// print_r($configuration->attributes);
|
||
|
|
// }
|
||
|
|
|
||
|
|
foreach ($attributes as $key => $val) {
|
||
|
|
|
||
|
|
if (array_key_exists($key, $configuration->attributes)){
|
||
|
|
$attribute_conf = $configuration->attributes[$key];
|
||
|
|
|
||
|
|
if ($attribute_conf['serialize'] == False) {
|
||
|
|
// Do nothing
|
||
|
|
array_push($excluded_attributes, $key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $excluded_attributes;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
*/
|
||
|
|
public function setEntityQueryJsonData($entity)
|
||
|
|
{
|
||
|
|
$className = $this->_getEntityClassName($entity);
|
||
|
|
$result = [];
|
||
|
|
$this->_attributesToJson($entity, $result, $this->_entityConfiguration[$className]);
|
||
|
|
$this->_entityConfiguration[$className]->query['json_data'] = json_encode($result);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setRawQueryJsonData($entity, $data)
|
||
|
|
{
|
||
|
|
$className = $this->_getEntityClassName($entity);
|
||
|
|
$this->_entityConfiguration[$className]->query['json_data'] = json_encode($data);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
*/
|
||
|
|
public function cleanEntityDeltaQueryJsonData($entity)
|
||
|
|
{
|
||
|
|
$className = $this->_getEntityClassName($entity);
|
||
|
|
$this->_entityConfiguration[$className]->query['json_data'] = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
*/
|
||
|
|
public function setEntityDeltaQueryJsonData($entity)
|
||
|
|
{
|
||
|
|
$className = $this->_getEntityClassName($entity);
|
||
|
|
$result = [];
|
||
|
|
|
||
|
|
$last_attributes = $entity->_last->toArray();
|
||
|
|
$new_attributes = $entity->toArray();
|
||
|
|
|
||
|
|
$result = $this->_arrayDiffRecursive($last_attributes, $new_attributes);
|
||
|
|
|
||
|
|
$this->_entityConfiguration[$className]->query['json_data'] = json_encode($result);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $configuration
|
||
|
|
*/
|
||
|
|
public function cleanQueryParams($entity)
|
||
|
|
{
|
||
|
|
$configuration = $this->_getEntityConfiguration($entity);
|
||
|
|
$configuration->query['url_query'] = null;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $configuration
|
||
|
|
*/
|
||
|
|
public function setQueryParams($entity, $urlParams = [])
|
||
|
|
{
|
||
|
|
$configuration = $this->_getEntityConfiguration($entity);
|
||
|
|
|
||
|
|
if (!isset($configuration->query) || !isset($configuration->query['url_query'])) {
|
||
|
|
$configuration->query['url_query'] = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
$params = [];
|
||
|
|
if (isset($configuration->params)) {
|
||
|
|
foreach ($configuration->params as $value) {
|
||
|
|
$params[$value] = $this->_config->get(strtoupper($value));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$arrayMerge = array_merge($urlParams, $params, $configuration->query['url_query']);
|
||
|
|
$configuration->query['url_query'] = $arrayMerge;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
* @param $result
|
||
|
|
* @param $configuration
|
||
|
|
*/
|
||
|
|
protected function _attributesToJson($entity, &$result)
|
||
|
|
{
|
||
|
|
if (is_array($entity)) {
|
||
|
|
$attributes = array_filter($entity, function($entity) {
|
||
|
|
return ($entity !== null && $entity !== false && $entity !== '');
|
||
|
|
});
|
||
|
|
} else {
|
||
|
|
$attributes = $entity->toArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($attributes as $key => $value) {
|
||
|
|
if ($value instanceof Entity || is_array($value)) {
|
||
|
|
$this->_attributesToJson($value, $result[$key]);
|
||
|
|
} else {
|
||
|
|
if ($value != null || is_bool($value) || is_numeric($value)){
|
||
|
|
$result[$key] = $value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected function _arrayDiffRecursive($firstArray, $secondArray)
|
||
|
|
{
|
||
|
|
$difference = [];
|
||
|
|
|
||
|
|
foreach (array_keys($secondArray) as $key) {
|
||
|
|
$secondArray[$key] = $secondArray[$key] instanceof MercadoPagoEntity ? $secondArray[$key]->toArray() : $secondArray[$key];
|
||
|
|
if (array_key_exists($key, $firstArray) && $firstArray[$key] instanceof MercadoPagoEntity){
|
||
|
|
$firstArray[$key] = $firstArray[$key]->toArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!array_key_exists($key, $firstArray)){
|
||
|
|
$difference[$key] = $secondArray[$key];
|
||
|
|
}elseif (is_array($firstArray[$key]) && is_array($secondArray[$key])) {
|
||
|
|
$newDiff = $this->_arrayDiffRecursive($firstArray[$key], $secondArray[$key]);
|
||
|
|
if (!empty($newDiff)) {
|
||
|
|
$difference[$key] = $newDiff;
|
||
|
|
}
|
||
|
|
}elseif ($firstArray[$key] !== $secondArray[$key]){
|
||
|
|
$difference[$key] = $secondArray[$key];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $difference;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
* @param $result
|
||
|
|
* @param $configuration
|
||
|
|
*/
|
||
|
|
protected function _deltaToJson($entity, &$result){
|
||
|
|
$specialAttributes = array("_last"); // TODO: Refactor this
|
||
|
|
|
||
|
|
if (!is_array($entity)) { // TODO: Refactor this
|
||
|
|
$attributes = array_filter($entity->toArray());
|
||
|
|
} else {
|
||
|
|
$attributes = $entity;
|
||
|
|
};
|
||
|
|
|
||
|
|
foreach ($attributes as $key => $value) {
|
||
|
|
if (!in_array($key, $specialAttributes)){
|
||
|
|
if ($value instanceof Entity || is_array($value)) {
|
||
|
|
//$this->_deltaToJson($value, $result[$key]);
|
||
|
|
} else {
|
||
|
|
$last = $entity->_last;
|
||
|
|
$last_value = $last->$key;
|
||
|
|
if ($last_value != $value){
|
||
|
|
$result[$key] = $value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
* @param $property
|
||
|
|
*
|
||
|
|
* @return mixed
|
||
|
|
*/
|
||
|
|
public function getPropertyType($entity, $property)
|
||
|
|
{
|
||
|
|
$metaData = $this->_getEntityConfiguration($entity);
|
||
|
|
return $metaData->attributes[$property]['type'];
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $entity
|
||
|
|
*
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function getDynamicAttributeDenied($entity)
|
||
|
|
{
|
||
|
|
$metaData = $this->_getEntityConfiguration($entity);
|
||
|
|
return isset($metaData->denyDynamicAttribute);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param $query
|
||
|
|
*/
|
||
|
|
protected function _setCustomHeaders(&$entity, &$query)
|
||
|
|
{
|
||
|
|
foreach ($entity::getCustomHeaders() as $key => $value){
|
||
|
|
$query['headers'][$key] = $value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param $query
|
||
|
|
*/
|
||
|
|
protected function _setDefaultHeaders(&$query)
|
||
|
|
{
|
||
|
|
$query['headers']['Accept'] = 'application/json';
|
||
|
|
$query['headers']['Content-Type'] = 'application/json';
|
||
|
|
$query['headers']['User-Agent'] = 'MercadoPago DX-PHP SDK/ v'. Version::$_VERSION;
|
||
|
|
$query['headers']['x-product-id'] = 'BC32A7RU643001OI3940';
|
||
|
|
$query['headers']['x-tracking-id'] = 'platform:' . PHP_MAJOR_VERSION .'|' . PHP_VERSION . ',type:SDK' . Version::$_VERSION . ',so;';
|
||
|
|
foreach ($this->_customTrackingParams as $key => $value){
|
||
|
|
$query['headers'][$key] = $value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $query
|
||
|
|
* @param $configuration
|
||
|
|
* @param string $method
|
||
|
|
*/
|
||
|
|
protected function _setIdempotencyHeader(&$query, $method)
|
||
|
|
{
|
||
|
|
if ($method != 'get' && $method != 'delete') {
|
||
|
|
if (array_key_exists('headers', array_change_key_case($query)) && !array_key_exists('x-idempotency-key', array_change_key_case($query['headers']))){
|
||
|
|
$query['headers']['x-idempotency-key'] = $this->_generateUUID();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* @param $attributes
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
protected function _generateUUID()
|
||
|
|
{
|
||
|
|
return sprintf(
|
||
|
|
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||
|
|
// 32 bits for "time_low"
|
||
|
|
mt_rand(0, 0xffff),
|
||
|
|
mt_rand(0, 0xffff),
|
||
|
|
|
||
|
|
// 16 bits for "time_mid"
|
||
|
|
mt_rand(0, 0xffff),
|
||
|
|
|
||
|
|
// 16 bits for "time_hi_and_version",
|
||
|
|
// four most significant bits holds version number 4
|
||
|
|
mt_rand(0, 0x0fff) | 0x4000,
|
||
|
|
|
||
|
|
// 16 bits, 8 bits for "clk_seq_hi_res",
|
||
|
|
// 8 bits for "clk_seq_low",
|
||
|
|
// two most significant bits holds zero and one for variant DCE1.1
|
||
|
|
mt_rand(0, 0x3fff) | 0x8000,
|
||
|
|
|
||
|
|
// 48 bits for "node"
|
||
|
|
mt_rand(0, 0xffff),
|
||
|
|
mt_rand(0, 0xffff),
|
||
|
|
mt_rand(0, 0xffff)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|