View source
<?php
namespace Drupal\restful\Plugin\resource\DataProvider;
use Doctrine\Common\Collections\ArrayCollection;
use Drupal\restful\Exception\BadRequestException;
use Drupal\restful\Exception\UnprocessableEntityException;
use Drupal\restful\Http\HttpHeader;
use Drupal\restful\Http\RequestInterface;
use Drupal\restful\Plugin\resource\Decorators\CacheDecoratedResource;
use Drupal\restful\Plugin\resource\Field\PublicFieldInfo\PublicFieldInfoBase;
use Drupal\restful\Plugin\resource\Field\ResourceFieldCollection;
use Drupal\restful\Plugin\resource\Field\ResourceFieldCollectionInterface;
use Drupal\restful\Plugin\resource\Field\ResourceFieldInterface;
abstract class DataProvider implements DataProviderInterface {
protected $fieldDefinitions;
protected $request;
protected $range = 50;
protected $account;
protected $langcode;
protected $options = array();
protected $resourcePath;
protected $metadata;
protected $pluginId;
public static function processFilterInput($filter, $public_field) {
if (!is_array($filter)) {
$filter = array(
'value' => $filter,
);
}
if (!isset($filter['value'])) {
throw new BadRequestException(sprintf('Value not present for the "%s" filter. Please check the URL format.', $public_field));
}
if (!is_array($filter['value'])) {
$filter['value'] = array(
$filter['value'],
);
}
$filter['public_field'] = $public_field;
$filter += array(
'operator' => array_fill(0, count($filter['value']), '='),
);
if (!is_array($filter['operator'])) {
$filter['operator'] = array(
$filter['operator'],
);
}
$first_operator = strtoupper($filter['operator'][0]);
if (!in_array($first_operator, array(
'IN',
'NOT IN',
'BETWEEN',
)) && count($filter['value']) != count($filter['operator'])) {
throw new BadRequestException('The number of operators and values has to be the same.');
}
if ($first_operator == 'BETWEEN' && count($filter['value']) != 2) {
throw new BadRequestException('The BETWEEN operator takes exactly 2 values.');
}
$filter += array(
'conjunction' => 'AND',
);
$filter['operator'] = str_replace(array(
'"',
"'",
), '', $filter['operator']);
static::isValidOperatorsForFilter($filter['operator']);
static::isValidConjunctionForFilter($filter['conjunction']);
return $filter;
}
public function __construct(RequestInterface $request, ResourceFieldCollectionInterface $field_definitions, $account, $plugin_id, $resource_path = NULL, array $options = array(), $langcode = NULL) {
$this->request = $request;
$this->fieldDefinitions = $field_definitions;
$this->account = $account;
$this->pluginId = $plugin_id;
$this->options = $options;
$this->resourcePath = $resource_path;
if (!empty($options['range'])) {
$this->range = $options['range'];
}
$this->langcode = $langcode ?: static::getLanguage();
$this->metadata = new ArrayCollection();
}
public function getRange() {
return $this->range;
}
public function setRange($range) {
$this->range = $range;
}
public function getAccount() {
return $this->account;
}
public function setAccount($account) {
$this->account = $account;
}
public function getRequest() {
return $this->request;
}
public function setRequest(RequestInterface $request) {
$this->request = $request;
}
public function getLangCode() {
return $this->langcode;
}
public function setLangCode($langcode) {
$this->langcode = $langcode;
}
public function getOptions() {
return $this->options;
}
public function setOptions(array $options) {
$this->options = $options;
}
public function addOptions(array $options) {
$this->options = array_merge($this->options, $options);
}
public function getCacheFragments($identifier) {
if (is_array($identifier)) {
$identifier = implode(',', $identifier);
}
$fragments = new ArrayCollection(array(
'resource' => CacheDecoratedResource::serializeKeyValue($this->pluginId, $this
->canonicalPath($identifier)),
));
$options = $this
->getOptions();
switch ($options['renderCache']['granularity']) {
case DRUPAL_CACHE_PER_USER:
if ($uid = $this
->getAccount()->uid) {
$fragments
->set('user_id', (int) $uid);
}
break;
case DRUPAL_CACHE_PER_ROLE:
$fragments
->set('user_role', implode(',', $this
->getAccount()->roles));
break;
}
return $fragments;
}
public function index() {
if (!($ids = $this
->getIndexIds())) {
return array();
}
return $this
->viewMultiple($ids);
}
public function discover($path = NULL) {
foreach ($this->fieldDefinitions as $public_field_name => $resource_field) {
if (method_exists($resource_field, 'autoDiscovery')) {
$callable = array(
$resource_field,
'autoDiscovery',
);
}
else {
$callable = array(
'\\Drupal\\restful\\Plugin\\resource\\Field\\ResourceFieldBase::emptyDiscoveryInfo',
array(
$public_field_name,
),
);
}
$resource_field
->setCallback($callable);
$resource_field
->setProcessCallbacks(array());
$definition = $resource_field
->getDefinition();
$discovery_info = empty($definition['discovery']) ? array() : $definition['discovery'];
$resource_field
->setPublicFieldInfo(new PublicFieldInfoBase($resource_field
->getPublicName(), $discovery_info));
}
return $path ? $this
->viewMultiple(array(
$path,
)) : $this
->index();
}
public function canonicalPath($path) {
return $path;
}
public function methodAccess(ResourceFieldInterface $resource_field) {
return in_array($this
->getRequest()
->getMethod(), $resource_field
->getMethods());
}
protected function parseRequestForListSort() {
$input = $this
->getRequest()
->getParsedInput();
if (empty($input['sort'])) {
return array();
}
$url_params = $this->options['urlParams'];
if (!$url_params['sort']) {
throw new UnprocessableEntityException('Sort parameters have been disabled in server configuration.');
}
$sorts = array();
foreach (explode(',', $input['sort']) as $sort) {
$direction = $sort[0] == '-' ? 'DESC' : 'ASC';
$sort = str_replace('-', '', $sort);
if (!$this->fieldDefinitions
->get($sort)) {
throw new BadRequestException(format_string('The sort @sort is not allowed for this path.', array(
'@sort' => $sort,
)));
}
$sorts[$sort] = $direction;
}
return $sorts;
}
protected function parseRequestForListFilter() {
if (!$this->request
->isListRequest($this
->getResourcePath())) {
return array();
}
$input = $this
->getRequest()
->getParsedInput();
if (empty($input['filter'])) {
return array();
}
$url_params = empty($this->options['urlParams']) ? array() : $this->options['urlParams'];
if (isset($url_params['filter']) && !$url_params['filter']) {
throw new UnprocessableEntityException('Filter parameters have been disabled in server configuration.');
}
$filters = array();
foreach ($input['filter'] as $public_field => $value) {
if (!static::isNestedField($public_field) && !$this->fieldDefinitions
->get($public_field)) {
throw new BadRequestException(format_string('The filter @filter is not allowed for this path.', array(
'@filter' => $public_field,
)));
}
$filter = static::processFilterInput($value, $public_field);
$filters[] = $filter + array(
'resource_id' => $this->pluginId,
);
}
return $filters;
}
protected function parseRequestForListPagination() {
$pager_input = $this
->getRequest()
->getPagerInput();
$page = isset($pager_input['number']) ? $pager_input['number'] : 1;
if (!ctype_digit((string) $page) || $page < 1) {
throw new BadRequestException('"Page" property should be numeric and equal or higher than 1.');
}
$range = isset($pager_input['size']) ? (int) $pager_input['size'] : $this
->getRange();
$range = $range > $this
->getRange() ? $this
->getRange() : $range;
if (!ctype_digit((string) $range) || $range < 1) {
throw new BadRequestException('"Range" property should be numeric and equal or higher than 1.');
}
$url_params = empty($this->options['urlParams']) ? array() : $this->options['urlParams'];
if (isset($url_params['range']) && !$url_params['range']) {
throw new UnprocessableEntityException('Range parameters have been disabled in server configuration.');
}
$offset = ($page - 1) * $range;
return array(
$offset,
$range,
);
}
protected function addExtraInfoToQuery($query) {
$query
->addTag('restful');
$query
->addMetaData('account', $this
->getAccount());
}
protected static function isValidOperatorsForFilter(array $operators) {
$allowed_operators = array(
'=',
'>',
'<',
'>=',
'<=',
'<>',
'!=',
'NOT IN',
'BETWEEN',
'CONTAINS',
'IN',
'NOT IN',
'STARTS_WITH',
);
foreach ($operators as $operator) {
if (!in_array($operator, $allowed_operators)) {
throw new BadRequestException(sprintf('Operator "%s" is not allowed for filtering on this resource. Allowed operators are: %s', $operator, implode(', ', $allowed_operators)));
}
}
}
protected static function isValidConjunctionForFilter($conjunction) {
$allowed_conjunctions = array(
'AND',
'OR',
'XOR',
);
if (!in_array(strtoupper($conjunction), $allowed_conjunctions)) {
throw new BadRequestException(format_string('Conjunction "@conjunction" is not allowed for filtering on this resource. Allowed conjunctions are: !allowed', array(
'@conjunction' => $conjunction,
'!allowed' => implode(', ', $allowed_conjunctions),
)));
}
}
protected static function getLanguage() {
return $GLOBALS['language']->language;
}
protected function setHttpHeader($name, $value) {
$this
->getRequest()
->getHeaders()
->add(HttpHeader::create($name, $value));
}
public function setResourcePath($resource_path) {
$this->resourcePath = $resource_path;
}
public function getResourcePath() {
return $this->resourcePath;
}
public static function isNestedField($field_name) {
return strpos($field_name, '.') !== FALSE;
}
public function getMetadata() {
return $this->metadata;
}
protected function initResourceFieldCollection($identifier) {
$resource_field_collection = new ResourceFieldCollection(array(), $this
->getRequest());
$interpreter = $this
->initDataInterpreter($identifier);
$resource_field_collection
->setInterpreter($interpreter);
$id_field_name = empty($this->options['idField']) ? 'id' : $this->options['idField'];
$resource_field_collection
->setIdField($this->fieldDefinitions
->get($id_field_name));
$resource_field_collection
->setResourceId($this->pluginId);
return $resource_field_collection;
}
protected abstract function initDataInterpreter($identifier);
}