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

Commit 8ac1fbb

Browse files
committed
v2.1.1
1 parent cb8ab58 commit 8ac1fbb

File tree

211 files changed

+5902
-1190
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

211 files changed

+5902
-1190
lines changed

src/core/Directus/Application/CoreServicesProvider.php

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -640,18 +640,7 @@ protected function getEmitter()
640640

641641
return $payload;
642642
};
643-
$preventNonAdminFromUpdateRoles = function (array $payload) use ($container) {
644-
/** @var Acl $acl */
645-
$acl = $container->get('acl');
646-
647-
if (!$acl->isAdmin()) {
648-
throw new ForbiddenException('You are not allowed to create, update or delete roles');
649-
}
650-
};
651-
652-
$emitter->addAction('item.create.directus_user_roles:before', $preventNonAdminFromUpdateRoles);
653-
$emitter->addAction('item.update.directus_user_roles:before', $preventNonAdminFromUpdateRoles);
654-
$emitter->addAction('item.delete.directus_user_roles:before', $preventNonAdminFromUpdateRoles);
643+
655644
$generateExternalId = function (Payload $payload) {
656645
// generate an external id if none is passed
657646
if (!$payload->get('external_id')) {

src/core/Directus/Database/Schema/Object/Field.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ public function getName()
3232
return $this->attributes->get('field');
3333
}
3434

35+
/**
36+
* Gets the field name with proper formatisation
37+
*
38+
* @return string
39+
*/
40+
public function getFormatisedName()
41+
{
42+
return ucwords(str_replace("_", " ", $this->attributes->get('field')));
43+
}
44+
3545
/**
3646
* Gets the field type
3747
*

src/core/Directus/Database/TableGateway/RelationalTableGateway.php

Lines changed: 100 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,8 @@ public function updateRecord($id, array $recordData, array $params = [])
524524

525525
$statusField = $tableSchema->getStatusField();
526526
$logEntryAction = ArrayUtils::get($params, 'revert') === true
527-
? DirectusActivityTableGateway::ACTION_REVERT
528-
: DirectusActivityTableGateway::ACTION_UPDATE;
527+
? DirectusActivityTableGateway::ACTION_REVERT
528+
: DirectusActivityTableGateway::ACTION_UPDATE;
529529

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

595+
// If a system table is joined, stop relational update here.
596+
if (strpos($foreignTableName, 'directus_') === 0) {
597+
// Once they're managed, remove the foreign collections from the record array
598+
unset($parentRow[$fieldName]);
599+
continue;
600+
}
601+
595602
if ($primaryKey && ArrayUtils::get($foreignRow, $this->deleteFlag) === true) {
596603
$Where = new Where();
597604
$Where->equalTo($primaryKey, $foreignRow[$primaryKey]);
@@ -941,7 +948,7 @@ public function createGlobalMetadata($single, array $list = [])
941948
*/
942949
public function createEntriesMetadata(array $entries, array $list = [])
943950
{
944-
$allKeys = ['result_count', 'total_count', 'status'];
951+
$allKeys = ['result_count', 'total_count', 'filter_count', 'status', 'page'];
945952
$tableSchema = $this->getTableSchema($this->table);
946953

947954
$metadata = [];
@@ -969,6 +976,78 @@ public function createEntriesMetadata(array $entries, array $list = [])
969976
$metadata['status_count'] = $statusCount;
970977
}
971978

979+
if (in_array('filter_count', $list) || in_array('page', $list)) {
980+
$metadata = $this->createMetadataPagination($metadata, $_GET);
981+
}
982+
983+
return $metadata;
984+
}
985+
986+
/**
987+
* Updates Metadata Object with Pagination
988+
*
989+
* @param $metadata - Existing metadata object
990+
* @param $params - GET Parameters
991+
*
992+
* @return array
993+
*/
994+
public function createMetadataPagination(array $metadata = [], array $params = [])
995+
{
996+
if (empty($params)) $params = $_GET;
997+
998+
$filtered = ArrayUtils::get($params, 'filter') || ArrayUtils::get($params, 'q');
999+
1000+
$limit = intval( ArrayUtils::get($params, 'limit', 0) );
1001+
$page = intval( ArrayUtils::get($params, 'page', 1) );
1002+
$offset = intval( ArrayUtils::get($params, 'offset', -1) );
1003+
1004+
$total = intval(ArrayUtils::get($metadata, 'Published') ?: ArrayUtils::get($metadata, 'total_count'));
1005+
$rows = intval(ArrayUtils::get($metadata, 'result_count'));
1006+
$pathname = explode('?', ArrayUtils::get($_SERVER, 'REQUEST_URI'));
1007+
$url = trim(\Directus\get_url(), '/') . reset($pathname);
1008+
1009+
if (!$rows || !$total) return $metadata;
1010+
1011+
if ($filtered) {
1012+
$filteredparams = array_merge($params, [
1013+
"depth" => 0,
1014+
"fields" => $this->primaryKeyFieldName,
1015+
"limit" => -1
1016+
]);
1017+
1018+
$entries = $this->fetchItems($filteredparams);
1019+
$total = count($entries);
1020+
$metadata['filter_count'] = $total;
1021+
}
1022+
1023+
$limit = $limit < 1 ? $rows : $limit;
1024+
$pages = $total ? ceil($total / $limit) : 1;
1025+
$page = $page > $pages ? $pages : ( $page && $offset >= 0 ? ( floor($offset / $limit) + 1 ) : $page );
1026+
$offset = $offset >= 0 ? $offset : ($page ? (($page - 1) * $limit) : 0);
1027+
$next = $previous = $last = $first = -1;
1028+
1029+
if ($pages > 1) {
1030+
$next = ($pages > $page) ? ($offset + $limit) : null;
1031+
$previous = ($offset >= $limit) ? ($offset - $limit) : ($limit * ( $pages - 1 ));
1032+
$first = ($pages < 2 || $limit < 1) ? null : 0;
1033+
$last = ($pages < 2) ? null : ( ($pages - 1) * $limit );
1034+
}
1035+
1036+
$metadata = array_merge($metadata, [
1037+
"limit" => $limit,
1038+
"offset" => $offset,
1039+
"page" => $page,
1040+
"page_count" => $pages,
1041+
"links" => [
1042+
"self" => $url,
1043+
"current" => "{$url}?" . urldecode( http_build_query(array_merge($params, ["page" => $page]))),
1044+
"next" => $next > 0 && $page < $pages ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $next, "page" => $page + 1])) ) ) : null,
1045+
"previous" => $previous >= 0 && $page > 1 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $previous, "page" => $page - 1])) ) ) : null,
1046+
"first" => $first >= 0 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $first, "page" => 1])) ) ) : null,
1047+
"last" => $last > 0 ? ( "{$url}?" . urldecode( http_build_query(array_merge($params, ["offset" => $last, "page" => $pages])) ) ) : null
1048+
]
1049+
]);
1050+
9721051
return $metadata;
9731052
}
9741053

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

10221101
try {
1023-
$this->enforceReadPermission($builder);
1102+
$this->enforceReadPermission($builder);
1103+
1104+
//If collection is directus_fields, also check permission of actual collection of which fields are retrieving
1105+
if($this->getTable() == SchemaManager::COLLECTION_FIELDS && ArrayUtils::has($params['filter'], 'collection'))
1106+
$this->acl->enforceReadOnce(ArrayUtils::get($params['filter'], 'collection'));
10241107
} catch (PermissionException $e) {
10251108
$isForbiddenRead = $e instanceof ForbiddenCollectionReadException;
10261109
$isUnableFindItems = $e instanceof UnableFindOwnerItemsException;
@@ -1220,7 +1303,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
12201303
$relational = SchemaService::hasRelationship($nextTable, $nextColumn);
12211304
$columnsTable[] = $nextTable;
12221305
}
1223-
1306+
12241307
// if one of the column in the list has not relationship
12251308
// it will break the loop before going over all the columns
12261309
// which we will call this as column not found
@@ -1231,7 +1314,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
12311314

12321315
// Remove the original filter column with dot-notation
12331316
unset($filters[$column]);
1234-
1317+
12351318
//Prepare relational data for all the fields
12361319
$columnRelationalData = [];
12371320
foreach($filterColumns as $filterColumn){
@@ -1247,7 +1330,7 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
12471330
];
12481331
}
12491332
}
1250-
1333+
12511334
// Reverse all the columns from comments.author.id to id.author.comments
12521335
// To filter from the most deep relationship to their parents
12531336
$columns = explode('.', \Directus\column_identifier_reverse($column));
@@ -1264,19 +1347,19 @@ protected function parseDotFilters(Builder $mainQuery, array $filters)
12641347
$query = new Builder($this->getAdapter());
12651348
$mainTableObject = $this->getTableSchema($table);
12661349
$selectColumn = $mainTableObject->getPrimaryField()->getName();
1267-
1350+
12681351
//check if column type is alias and relationship is O2M
12691352
$previousRelation = isset($filterColumns[array_search($column, $filterColumns)-1])?$filterColumns[array_search($column, $filterColumns)-1]:'';
1270-
if ($previousRelation && $columnRelationalData[$previousRelation]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
1353+
if ($previousRelation && $columnRelationalData[$previousRelation]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
12711354
$selectColumn = $columnRelationalData[$previousRelation]['field_many'];
12721355
}
1273-
1356+
12741357
//get last relationship
12751358
if ($mainColumn && !empty($mainColumn) && $columnRelationalData[$mainColumn]['type'] == \Directus\Database\Schema\Object\FieldRelationship::ONE_TO_MANY) {
12761359
$mainColumn = $mainTableObject->getPrimaryField()->getName();
12771360
}
12781361
$query->columns([$selectColumn]);
1279-
1362+
12801363
$query->from($table);
12811364

12821365
$this->doFilter($query, $column, $condition, $table);
@@ -1446,8 +1529,8 @@ protected function doFilter(Builder $query, $column, $condition, $table)
14461529
$relatedTable = $relationship->getCollectionMany();
14471530
$relatedRightColumn = $relationship->getFieldMany();
14481531
$tableSchema = SchemaService::getCollection($relatedTable);
1449-
$relatedTableColumns = $tableSchema->getFields();
1450-
1532+
$relatedTableColumns = $tableSchema->getFields();
1533+
14511534
$query->orWhereRelational($this->primaryKeyFieldName, $relatedTable, null, $relatedRightColumn, function(Builder $query) use ($column, $relatedTable, $relatedTableColumns, $value) {
14521535
foreach ($relatedTableColumns as $column) {
14531536
$isNumeric = $this->getSchemaManager()->isNumericType($column->getType());
@@ -1512,12 +1595,12 @@ protected function processFilter(Builder $query, array $filters = [])
15121595
if (isset($fieldReadBlackListDetails['isReadBlackList']) && $fieldReadBlackListDetails['isReadBlackList']) {
15131596
throw new Exception\ForbiddenFieldAccessException($column);
15141597
}else if(isset($fieldReadBlackListDetails['statuses']) && !empty ($fieldReadBlackListDetails['statuses'])){
1515-
$blackListStatuses = array_merge($blackListStatuses,array_values($fieldReadBlackListDetails['statuses']));
1598+
$blackListStatuses = array_merge($blackListStatuses,array_values($fieldReadBlackListDetails['statuses']));
15161599
}
15171600
}
1518-
$filters = $this->parseDotFilters($query, $filters);
1519-
1520-
foreach ($filters as $column => $conditions) {
1601+
$filters = $this->parseDotFilters($query, $filters);
1602+
1603+
foreach ($filters as $column => $conditions) {
15211604
if ($conditions instanceof Filter) {
15221605
$column = $conditions->getIdentifier();
15231606
$conditions = $conditions->getValue();

src/core/Directus/Permissions/Acl.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,39 @@ public function getStatusesOnReadFieldBlacklist($collection, $field)
13281328
return $blackListStatuses;
13291329
}
13301330

1331+
/**
1332+
* Gets the statuses on which field has been write blacklisted
1333+
*
1334+
* @param string $collection
1335+
* @param mixed $status
1336+
*
1337+
* @return array
1338+
*/
1339+
public function getStatusesOnWriteFieldBlacklist($collection, $field)
1340+
{
1341+
$blackListStatuses = [];
1342+
$collectionPermission = $this->getCollectionPermissions($collection);
1343+
$statuses = $this->getCollectionStatuses($collection);
1344+
if($statuses){
1345+
foreach($statuses as $status){
1346+
$writeFieldBlackList = isset($collectionPermission[$status]['write_field_blacklist']) ? $collectionPermission[$status]['write_field_blacklist'] : [];
1347+
if($writeFieldBlackList && in_array($field, $writeFieldBlackList)){
1348+
$blackListStatuses['statuses'][] = $status;
1349+
}
1350+
}
1351+
//Set flag for field which is blacklist for all statuses
1352+
if(isset($blackListStatuses['statuses']) && count($blackListStatuses['statuses']) == count($statuses)){
1353+
$blackListStatuses['isWriteBlackList'] = true;
1354+
}
1355+
}else{
1356+
$writeFieldBlackList = isset($collectionPermission['write_field_blacklist']) ? $collectionPermission['write_field_blacklist'] : [];
1357+
if($writeFieldBlackList && in_array($field, $writeFieldBlackList)){
1358+
$blackListStatuses['isWriteBlackList'] = true;
1359+
}
1360+
}
1361+
return $blackListStatuses;
1362+
}
1363+
13311364
/**
13321365
* Returns a list of status the given collection has permission to read
13331366
*

src/core/Directus/Services/AbstractService.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,8 @@ protected function validatePayloadWithFieldsValidation($collectionName, array $p
446446
continue;
447447
}
448448

449+
$this->validateFieldLength($field, $value);
450+
449451
if ($validation = $field->getValidation()) {
450452
$isCustomValidation = \Directus\is_custom_validation($validation);
451453

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

475+
/**
476+
* To validate the length of input with the DB.
477+
*
478+
* @param array $field
479+
* @param string $value
480+
*
481+
* @throws UnprocessableEntityException
482+
*/
483+
protected function validateFieldLength($field, $value)
484+
{
485+
if($field->getType() == "decimal"){
486+
$precision = $field->getPrecision();
487+
$scale = $field->getScale();
488+
$number = $precision - $scale;
489+
$input = explode(".",$value);
490+
$inputLengthScale = isset($input[1]) ? strlen($input[1]) : 0;
491+
$inputLengthNumber = isset($input[0]) ? strlen($input[0]) : 0;
492+
$inputLengthPrecision = $inputLengthScale+$inputLengthNumber;
493+
494+
if($inputLengthNumber > $number || $inputLengthScale > $scale){
495+
throw new UnprocessableEntityException(
496+
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'])
497+
);
498+
}
499+
}else{
500+
if(!is_null($field['length']) && $field['length'] < strlen($value) ){
501+
throw new UnprocessableEntityException(
502+
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'])
503+
);
504+
}
505+
}
506+
}
507+
473508
/**
474509
* @param string $collection
475510
* @param array $payload

src/core/Directus/Services/ItemsService.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,22 +85,27 @@ public function find($collection, $id, array $params = [])
8585
* @param string $collection
8686
* @param mixed $ids
8787
* @param array $params
88+
* @params bool $acl default true
8889
*
8990
* @return array
9091
*
9192
* @throws ItemNotFoundException
9293
* @throws ForbiddenCollectionReadException
9394
*/
94-
public function findByIds($collection, $ids, array $params = [])
95+
public function findByIds($collection, $ids, array $params = [], $acl = true)
9596
{
9697
$params = ArrayUtils::omit($params, static::SINGLE_ITEM_PARAMS_BLACKLIST);
9798

9899
$statusValue = $this->getStatusValue($collection, $ids);
99-
$tableGateway = $this->createTableGateway($collection);
100+
$tableGateway = $this->createTableGateway($collection, $acl);
100101
$ids = StringUtils::safeCvs($ids, false, false);
101102

102103
try {
103-
$this->getAcl()->enforceRead($collection, $statusValue);
104+
// if acl check is disabled (e.g. fetching the logo from the settings endpoint/service) do not
105+
// enforce permissions here!
106+
if (false !== $acl) {
107+
$this->getAcl()->enforceRead($collection, $statusValue);
108+
}
104109
} catch (ForbiddenCollectionReadException $e) {
105110
if (is_array($ids) && count($ids) > 1) {
106111
throw $e;

src/core/Directus/Services/SettingsService.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ public function findAll(array $params = [])
5757

5858
public function findFile($id,array $params = [])
5959
{
60-
return $this->itemsService->findByIds(SchemaManager::COLLECTION_FILES, $id,$params);
60+
$noAcl = false;
61+
return $this->itemsService->findByIds(SchemaManager::COLLECTION_FILES, $id,$params, $noAcl);
6162
}
6263

6364
public function findAllFields(array $params = [])

0 commit comments

Comments
 (0)