Skip to content
This repository has been archived by the owner on Jan 6, 2023. It is now read-only.

Commit

Permalink
v2.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
rijkvanzanten committed Jun 7, 2019
1 parent cb8ab58 commit 8ac1fbb
Show file tree
Hide file tree
Showing 211 changed files with 5,902 additions and 1,190 deletions.
13 changes: 1 addition & 12 deletions src/core/Directus/Application/CoreServicesProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -640,18 +640,7 @@ protected function getEmitter()

return $payload;
};
$preventNonAdminFromUpdateRoles = function (array $payload) use ($container) {
/** @var Acl $acl */
$acl = $container->get('acl');

if (!$acl->isAdmin()) {
throw new ForbiddenException('You are not allowed to create, update or delete roles');
}
};

$emitter->addAction('item.create.directus_user_roles:before', $preventNonAdminFromUpdateRoles);
$emitter->addAction('item.update.directus_user_roles:before', $preventNonAdminFromUpdateRoles);
$emitter->addAction('item.delete.directus_user_roles:before', $preventNonAdminFromUpdateRoles);

$generateExternalId = function (Payload $payload) {
// generate an external id if none is passed
if (!$payload->get('external_id')) {
Expand Down
10 changes: 10 additions & 0 deletions src/core/Directus/Database/Schema/Object/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ public function getName()
return $this->attributes->get('field');
}

/**
* Gets the field name with proper formatisation
*
* @return string
*/
public function getFormatisedName()
{
return ucwords(str_replace("_", " ", $this->attributes->get('field')));
}

/**
* Gets the field type
*
Expand Down
117 changes: 100 additions & 17 deletions src/core/Directus/Database/TableGateway/RelationalTableGateway.php
Original file line number Diff line number Diff line change
Expand Up @@ -524,8 +524,8 @@ public function updateRecord($id, array $recordData, array $params = [])

$statusField = $tableSchema->getStatusField();
$logEntryAction = ArrayUtils::get($params, 'revert') === true
? DirectusActivityTableGateway::ACTION_REVERT
: DirectusActivityTableGateway::ACTION_UPDATE;
? DirectusActivityTableGateway::ACTION_REVERT
: DirectusActivityTableGateway::ACTION_UPDATE;

if ($statusField && $logEntryAction === DirectusActivityTableGateway::ACTION_UPDATE) {
try {
Expand Down Expand Up @@ -592,6 +592,13 @@ public function addOrUpdateManyToOneRelationships($schema, $parentRow, &$childLo
$primaryKey = $foreignTableSchema->getPrimaryKeyName();
$ForeignTable = new RelationalTableGateway($foreignTableName, $this->adapter, $this->acl);

// If a system table is joined, stop relational update here.
if (strpos($foreignTableName, 'directus_') === 0) {
// Once they're managed, remove the foreign collections from the record array
unset($parentRow[$fieldName]);
continue;
}

if ($primaryKey && ArrayUtils::get($foreignRow, $this->deleteFlag) === true) {
$Where = new Where();
$Where->equalTo($primaryKey, $foreignRow[$primaryKey]);
Expand Down Expand Up @@ -941,7 +948,7 @@ public function createGlobalMetadata($single, array $list = [])
*/
public function createEntriesMetadata(array $entries, array $list = [])
{
$allKeys = ['result_count', 'total_count', 'status'];
$allKeys = ['result_count', 'total_count', 'filter_count', 'status', 'page'];
$tableSchema = $this->getTableSchema($this->table);

$metadata = [];
Expand Down Expand Up @@ -969,6 +976,78 @@ public function createEntriesMetadata(array $entries, array $list = [])
$metadata['status_count'] = $statusCount;
}

if (in_array('filter_count', $list) || in_array('page', $list)) {
$metadata = $this->createMetadataPagination($metadata, $_GET);
}

return $metadata;
}

/**
* Updates Metadata Object with Pagination
*
* @param $metadata - Existing metadata object
* @param $params - GET Parameters
*
* @return array
*/
public function createMetadataPagination(array $metadata = [], array $params = [])
{
if (empty($params)) $params = $_GET;

$filtered = ArrayUtils::get($params, 'filter') || ArrayUtils::get($params, 'q');

$limit = intval( ArrayUtils::get($params, 'limit', 0) );
$page = intval( ArrayUtils::get($params, 'page', 1) );
$offset = intval( ArrayUtils::get($params, 'offset', -1) );

$total = intval(ArrayUtils::get($metadata, 'Published') ?: ArrayUtils::get($metadata, 'total_count'));
$rows = intval(ArrayUtils::get($metadata, 'result_count'));
$pathname = explode('?', ArrayUtils::get($_SERVER, 'REQUEST_URI'));
$url = trim(\Directus\get_url(), '/') . reset($pathname);

if (!$rows || !$total) return $metadata;

if ($filtered) {
$filteredparams = array_merge($params, [
"depth" => 0,
"fields" => $this->primaryKeyFieldName,
"limit" => -1
]);

$entries = $this->fetchItems($filteredparams);
$total = count($entries);
$metadata['filter_count'] = $total;
}

$limit = $limit < 1 ? $rows : $limit;
$pages = $total ? ceil($total / $limit) : 1;
$page = $page > $pages ? $pages : ( $page && $offset >= 0 ? ( floor($offset / $limit) + 1 ) : $page );
$offset = $offset >= 0 ? $offset : ($page ? (($page - 1) * $limit) : 0);
$next = $previous = $last = $first = -1;

if ($pages > 1) {
$next = ($pages > $page) ? ($offset + $limit) : null;
$previous = ($offset >= $limit) ? ($offset - $limit) : ($limit * ( $pages - 1 ));
$first = ($pages < 2 || $limit < 1) ? null : 0;
$last = ($pages < 2) ? null : ( ($pages - 1) * $limit );
}

$metadata = array_merge($metadata, [
"limit" => $limit,
"offset" => $offset,
"page" => $page,
"page_count" => $pages,
"links" => [
"self" => $url,
"current" => "{$url}?" . urldecode( http_build_query(array_merge($params, ["page" => $page]))),
"next" => $next > 0 && $page < $pages ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $next, "page" => $page + 1])) ) ) : null,
"previous" => $previous >= 0 && $page > 1 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $previous, "page" => $page - 1])) ) ) : null,
"first" => $first >= 0 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $first, "page" => 1])) ) ) : null,
"last" => $last > 0 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $last, "page" => $pages])) ) ) : null
]
]);

return $metadata;
}

Expand Down Expand Up @@ -1020,7 +1099,11 @@ public function fetchItems(array $params = [], \Closure $queryCallback = null)
$builder->orderBy($this->primaryKeyFieldName);

try {
$this->enforceReadPermission($builder);
$this->enforceReadPermission($builder);

//If collection is directus_fields, also check permission of actual collection of which fields are retrieving
if($this->getTable() == SchemaManager::COLLECTION_FIELDS && ArrayUtils::has($params['filter'], 'collection'))
$this->acl->enforceReadOnce(ArrayUtils::get($params['filter'], 'collection'));
} catch (PermissionException $e) {
$isForbiddenRead = $e instanceof ForbiddenCollectionReadException;
$isUnableFindItems = $e instanceof UnableFindOwnerItemsException;
Expand Down Expand Up @@ -1220,7 +1303,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
$relational = SchemaService::hasRelationship($nextTable, $nextColumn);
$columnsTable[] = $nextTable;
}

// if one of the column in the list has not relationship
// it will break the loop before going over all the columns
// which we will call this as column not found
Expand All @@ -1231,7 +1314,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)

// Remove the original filter column with dot-notation
unset($filters[$column]);

//Prepare relational data for all the fields
$columnRelationalData = [];
foreach($filterColumns as $filterColumn){
Expand All @@ -1247,7 +1330,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
];
}
}

// Reverse all the columns from comments.author.id to id.author.comments
// To filter from the most deep relationship to their parents
$columns = explode('.', \Directus\column_identifier_reverse($column));
Expand All @@ -1264,19 +1347,19 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
$query = new Builder($this->getAdapter());
$mainTableObject = $this->getTableSchema($table);
$selectColumn = $mainTableObject->getPrimaryField()->getName();

//check if column type is alias and relationship is O2M
$previousRelation = isset($filterColumns[array_search($column, $filterColumns)-1])?$filterColumns[array_search($column, $filterColumns)-1]:'';
if ($previousRelation && $columnRelationalData[$previousRelation]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
if ($previousRelation && $columnRelationalData[$previousRelation]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
$selectColumn = $columnRelationalData[$previousRelation]['field_many'];
}

//get last relationship
if ($mainColumn && !empty($mainColumn) && $columnRelationalData[$mainColumn]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
$mainColumn = $mainTableObject->getPrimaryField()->getName();
}
$query->columns([$selectColumn]);

$query->from($table);

$this->doFilter($query, $column, $condition, $table);
Expand Down Expand Up @@ -1446,8 +1529,8 @@ protected function doFilter(Builder $query, $column, $condition, $table)
$relatedTable = $relationship->getCollectionMany();
$relatedRightColumn = $relationship->getFieldMany();
$tableSchema = SchemaService::getCollection($relatedTable);
$relatedTableColumns = $tableSchema->getFields();
$relatedTableColumns = $tableSchema->getFields();

$query->orWhereRelational($this->primaryKeyFieldName, $relatedTable, null, $relatedRightColumn, function(Builder $query) use ($column, $relatedTable, $relatedTableColumns, $value) {
foreach ($relatedTableColumns as $column) {
$isNumeric = $this->getSchemaManager()->isNumericType($column->getType());
Expand Down Expand Up @@ -1512,12 +1595,12 @@ protected function processFilter(Builder $query, array $filters = [])
if (isset($fieldReadBlackListDetails['isReadBlackList']) && $fieldReadBlackListDetails['isReadBlackList']) {
throw new Exception\ForbiddenFieldAccessException($column);
}else if(isset($fieldReadBlackListDetails['statuses']) && !empty ($fieldReadBlackListDetails['statuses'])){
$blackListStatuses = array_merge($blackListStatuses,array_values($fieldReadBlackListDetails['statuses']));
$blackListStatuses = array_merge($blackListStatuses,array_values($fieldReadBlackListDetails['statuses']));
}
}
$filters = $this->parseDotFilters($query, $filters);
foreach ($filters as $column => $conditions) {
$filters = $this->parseDotFilters($query, $filters);

foreach ($filters as $column => $conditions) {
if ($conditions instanceof Filter) {
$column = $conditions->getIdentifier();
$conditions = $conditions->getValue();
Expand Down
33 changes: 33 additions & 0 deletions src/core/Directus/Permissions/Acl.php
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,39 @@ public function getStatusesOnReadFieldBlacklist($collection, $field)
return $blackListStatuses;
}

/**
* Gets the statuses on which field has been write blacklisted
*
* @param string $collection
* @param mixed $status
*
* @return array
*/
public function getStatusesOnWriteFieldBlacklist($collection, $field)
{
$blackListStatuses = [];
$collectionPermission = $this->getCollectionPermissions($collection);
$statuses = $this->getCollectionStatuses($collection);
if($statuses){
foreach($statuses as $status){
$writeFieldBlackList = isset($collectionPermission[$status]['write_field_blacklist']) ? $collectionPermission[$status]['write_field_blacklist'] : [];
if($writeFieldBlackList && in_array($field, $writeFieldBlackList)){
$blackListStatuses['statuses'][] = $status;
}
}
//Set flag for field which is blacklist for all statuses
if(isset($blackListStatuses['statuses']) && count($blackListStatuses['statuses']) == count($statuses)){
$blackListStatuses['isWriteBlackList'] = true;
}
}else{
$writeFieldBlackList = isset($collectionPermission['write_field_blacklist']) ? $collectionPermission['write_field_blacklist'] : [];
if($writeFieldBlackList && in_array($field, $writeFieldBlackList)){
$blackListStatuses['isWriteBlackList'] = true;
}
}
return $blackListStatuses;
}

/**
* Returns a list of status the given collection has permission to read
*
Expand Down
35 changes: 35 additions & 0 deletions src/core/Directus/Services/AbstractService.php
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,8 @@ protected function validatePayloadWithFieldsValidation($collectionName, array $p
continue;
}

$this->validateFieldLength($field, $value);

if ($validation = $field->getValidation()) {
$isCustomValidation = \Directus\is_custom_validation($validation);

Expand All @@ -470,6 +472,39 @@ protected function validatePayloadWithFieldsValidation($collectionName, array $p
$this->throwErrorIfAny($violations);
}

/**
* To validate the length of input with the DB.
*
* @param array $field
* @param string $value
*
* @throws UnprocessableEntityException
*/
protected function validateFieldLength($field, $value)
{
if($field->getType() == "decimal"){
$precision = $field->getPrecision();
$scale = $field->getScale();
$number = $precision - $scale;
$input = explode(".",$value);
$inputLengthScale = isset($input[1]) ? strlen($input[1]) : 0;
$inputLengthNumber = isset($input[0]) ? strlen($input[0]) : 0;
$inputLengthPrecision = $inputLengthScale+$inputLengthNumber;

if($inputLengthNumber > $number || $inputLengthScale > $scale){
throw new UnprocessableEntityException(
sprintf("The value submitted (%s) for '%s' is longer than the field's supported length (%s). Please submit a shorter value or ask an Admin to increase the length.",$value,$field->getFormatisedName(),$field['length'])
);
}
}else{
if(!is_null($field['length']) && $field['length'] < strlen($value) ){
throw new UnprocessableEntityException(
sprintf("The value submitted (%s) for '%s' is longer than the field's supported length (%s). Please submit a shorter value or ask an Admin to increase the length.",$value,$field->getFormatisedName(),$field['length'])
);
}
}
}

/**
* @param string $collection
* @param array $payload
Expand Down
11 changes: 8 additions & 3 deletions src/core/Directus/Services/ItemsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,27 @@ public function find($collection, $id, array $params = [])
* @param string $collection
* @param mixed $ids
* @param array $params
* @params bool $acl default true
*
* @return array
*
* @throws ItemNotFoundException
* @throws ForbiddenCollectionReadException
*/
public function findByIds($collection, $ids, array $params = [])
public function findByIds($collection, $ids, array $params = [], $acl = true)
{
$params = ArrayUtils::omit($params, static::SINGLE_ITEM_PARAMS_BLACKLIST);

$statusValue = $this->getStatusValue($collection, $ids);
$tableGateway = $this->createTableGateway($collection);
$tableGateway = $this->createTableGateway($collection, $acl);
$ids = StringUtils::safeCvs($ids, false, false);

try {
$this->getAcl()->enforceRead($collection, $statusValue);
// if acl check is disabled (e.g. fetching the logo from the settings endpoint/service) do not
// enforce permissions here!
if (false !== $acl) {
$this->getAcl()->enforceRead($collection, $statusValue);
}
} catch (ForbiddenCollectionReadException $e) {
if (is_array($ids) && count($ids) > 1) {
throw $e;
Expand Down
3 changes: 2 additions & 1 deletion src/core/Directus/Services/SettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public function findAll(array $params = [])

public function findFile($id,array $params = [])
{
return $this->itemsService->findByIds(SchemaManager::COLLECTION_FILES, $id,$params);
$noAcl = false;
return $this->itemsService->findByIds(SchemaManager::COLLECTION_FILES, $id,$params, $noAcl);
}

public function findAllFields(array $params = [])
Expand Down

0 comments on commit 8ac1fbb

Please sign in to comment.