diff --git a/package.json b/package.json index be08d776b6..8cb52febdf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@directus/api", "private": true, - "version": "2.3.1", + "version": "2.4.0", "description": "Directus API", "main": "index.js", "repository": "directus/api", diff --git a/public/.htaccess b/public/.htaccess index e02f624c38..939244f897 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -20,7 +20,8 @@ Options +SymLinksIfOwnerMatch RewriteRule !^admin index.php?%{QUERY_STRING} [L] - - php_value upload_max_filesize 50M - php_value post_max_size 100M - \ No newline at end of file +# Uncomment the following lines to modify PHP settings. The lines below can be used to increase the max upload size of files in Directus +# + #php_value upload_max_filesize 50M + #php_value post_max_size 100M +# diff --git a/src/core/Directus/Application/Application.php b/src/core/Directus/Application/Application.php index cd26de8a0c..0b9d1c7149 100644 --- a/src/core/Directus/Application/Application.php +++ b/src/core/Directus/Application/Application.php @@ -13,7 +13,7 @@ class Application extends App * * @var string */ - const DIRECTUS_VERSION = '2.3.1'; + const DIRECTUS_VERSION = '2.4.0'; /** * NOT USED diff --git a/src/core/Directus/Config/Schema/Base.php b/src/core/Directus/Config/Schema/Base.php index 64fd0a7bdc..5fe3dc252f 100644 --- a/src/core/Directus/Config/Schema/Base.php +++ b/src/core/Directus/Config/Schema/Base.php @@ -113,4 +113,17 @@ public function optional($value = null) } return $this->_optional; } + + /** + * Returns the $context with normalized array keys. + * @param $context + * @return mixed + */ + protected function normalize($context) { + foreach ($context as $context_key => $context_value) { + $context[strtolower(str_replace("-", "", str_replace("_", "", $context_key)))] = $context_value; + } + + return $context; + } } diff --git a/src/core/Directus/Config/Schema/Group.php b/src/core/Directus/Config/Schema/Group.php index 463c8b85ce..a7112c1f37 100644 --- a/src/core/Directus/Config/Schema/Group.php +++ b/src/core/Directus/Config/Schema/Group.php @@ -25,9 +25,7 @@ public function value($context) $value = []; $current = []; - foreach ($context as $context_key => $context_value) { - $context[strtolower(str_replace("-", "", str_replace("_", "", $context_key)))] = $context_value; - } + $context = $this->normalize($context); if (!isset($context[$this->key()])) { if ($this->optional()) { diff --git a/src/core/Directus/Config/Schema/Schema.php b/src/core/Directus/Config/Schema/Schema.php index a49c977b57..f26566f75e 100644 --- a/src/core/Directus/Config/Schema/Schema.php +++ b/src/core/Directus/Config/Schema/Schema.php @@ -77,8 +77,8 @@ public static function get() { ]), new Group('cors', [ new Value('enabled', Types::BOOLEAN, true), - new Value('origin', 'array', ['*']), - new Value('methods', 'array', [ + new Value('origin', Types::ARRAY, ['*']), + new Value('methods', Types::ARRAY, [ 'GET', 'POST', 'PUT', @@ -86,8 +86,8 @@ public static function get() { 'DELETE', 'HEAD' ]), - new Value('headers', 'array', []), - new Value('exposed_headers', 'array', []), + new Value('headers', Types::ARRAY, []), + new Value('exposed_headers', Types::ARRAY, []), new Value('max_age', Types::INTEGER, null), new Value('credentials', Types::BOOLEAN, false), ]), @@ -101,14 +101,14 @@ public static function get() { new Value('timeout', Types::INTEGER, 10), ]), new Group('hooks', [ - new Value('actions', 'array', []), - new Value('filters', 'array', []), + new Value('actions', Types::ARRAY, []), + new Value('filters', Types::ARRAY, []), ]), new Group('feedback', [ new Value('token', Types::STRING, 'a-kind-of-unique-token'), new Value('login', Types::STRING, true), ]), - new Value('tableBlacklist', 'array', []), + new Value('tableBlacklist', Types::ARRAY, []), new Group('auth', [ new Value('secret_key', Types::STRING, ''), new Value('public_key', Types::STRING, ''), @@ -139,6 +139,7 @@ public static function get() { ]), ]), ]), + new Value('ext?', Types::ARRAY, []), ]); } } diff --git a/src/core/Directus/Config/Schema/Types.php b/src/core/Directus/Config/Schema/Types.php index 7e21ac34d1..982a6f16fc 100644 --- a/src/core/Directus/Config/Schema/Types.php +++ b/src/core/Directus/Config/Schema/Types.php @@ -11,4 +11,5 @@ interface Types const FLOAT = 'float'; const STRING = 'string'; const BOOLEAN = 'boolean'; + const ARRAY = 'array'; } diff --git a/src/core/Directus/Config/Schema/Value.php b/src/core/Directus/Config/Schema/Value.php index e15c09a1f8..e00eef8ca0 100644 --- a/src/core/Directus/Config/Schema/Value.php +++ b/src/core/Directus/Config/Schema/Value.php @@ -35,9 +35,7 @@ public function __construct($name, $type, $default = null) */ public function value($context) { - foreach ($context as $context_key => $context_value) { - $context[strtolower(str_replace("-", "", str_replace("_", "", $context_key)))] = $context_value; - } + $context = $this->normalize($context); if (!isset($context) || !isset($context[$this->key()])) { if ($this->optional()) { @@ -50,19 +48,20 @@ public function value($context) $value = $context[$this->key()]; switch ($this->_type) { - case Types::INTEGER: - return intval($value); - case Types::BOOLEAN: - $value = strtolower($value); - return $value === "true" || $value === "1" || $value === "on" || $value === "yes" || boolval($value); - case Types::FLOAT: - return floatval($value); - // TODO: add support to arrays - case 'array': - return $this->_default; - case Types::STRING: - default: - return $value; + case Types::INTEGER: + return intval($value); + case Types::BOOLEAN: + $value = strtolower($value); + return $value === "true" || $value === "1" || $value === "on" || $value === "yes" || boolval($value); + case Types::FLOAT: + return floatval($value); + case Types::ARRAY: + if (!is_array($value)) { + return $this->_default; + } + case Types::STRING: + default: + return $value; } } } diff --git a/src/core/Directus/Filesystem/Files.php b/src/core/Directus/Filesystem/Files.php index 5778d066da..f7a5fdc20b 100644 --- a/src/core/Directus/Filesystem/Files.php +++ b/src/core/Directus/Filesystem/Files.php @@ -259,6 +259,9 @@ public function saveData($fileData, $fileName, $replace = false) // When file is uploaded via multipart form data then We will get object of Slim\Http\UploadFile // When file is uploaded via URL (Youtube, Vimeo, or image link) then we will get base64 encode string. $size = null; + + $title = $fileName; + if (is_object($fileData)) { $size = $fileData->getSize(); $checksum = hash_file('md5', $fileData->file); @@ -281,7 +284,7 @@ public function saveData($fileData, $fileName, $replace = false) unset($fileData); $fileData = $this->getFileInfo($fileName); - $fileData['title'] = Formatting::fileNameToFileTitle($fileName); + $fileData['title'] = Formatting::fileNameToFileTitle($title); $fileData['filename'] = basename($filePath); $fileData['storage'] = $this->config['adapter']; diff --git a/src/core/Directus/Filesystem/Thumbnailer.php b/src/core/Directus/Filesystem/Thumbnailer.php index 94ee68d7d0..e30f187429 100644 --- a/src/core/Directus/Filesystem/Thumbnailer.php +++ b/src/core/Directus/Filesystem/Thumbnailer.php @@ -179,9 +179,12 @@ public function contain() // crop image $img->resize($this->width, $this->height, function ($constraint) { $constraint->aspectRatio(); + if (ArrayUtils::get($options, 'preventUpsize')) { + $constraint->upsize(); + } }); - if( ArrayUtils::get($options, 'resizeCanvas')) { + if (ArrayUtils::get($options, 'resizeCanvas')) { $img->resizeCanvas($this->width, $this->height, ArrayUtils::get($options, 'position', 'center'), ArrayUtils::get($options, 'resizeRelative', false), ArrayUtils::get($options, 'canvasBackground', [255, 255, 255, 0])); } diff --git a/src/core/Directus/Services/ItemsService.php b/src/core/Directus/Services/ItemsService.php index fcc4396b89..d85535fa1e 100644 --- a/src/core/Directus/Services/ItemsService.php +++ b/src/core/Directus/Services/ItemsService.php @@ -30,7 +30,7 @@ public function createItem($collection, $payload, $params = []) { $this->enforceCreatePermissions($collection, $payload, $params); $this->validatePayload($collection, null, $payload, $params); - + // Validate Password if password policy settled in the system settings. if($collection == SchemaManager::COLLECTION_USERS){ $passwordValidation = get_directus_setting('password_policy'); @@ -42,39 +42,15 @@ public function createItem($collection, $payload, $params = []) //Validate nested payload $tableSchema = SchemaService::getCollection($collection); $collectionAliasColumns = $tableSchema->getAliasFields(); - + foreach ($collectionAliasColumns as $aliasColumnDetails) { - $colName = $aliasColumnDetails->getName(); - - $relationalCollectionName = ""; - if($this->isManyToManyField($aliasColumnDetails)){ - $relationalCollectionName = $aliasColumnDetails->getRelationship()->getCollectionManyToMany(); - - if($relationalCollectionName && isset($payload[$colName])){ - foreach($payload[$colName] as $individual){ - if(!isset($individual['$delete'])){ - $validatePayload = $individual[$aliasColumnDetails->getRelationship()->getJunctionOtherRelatedField()]; - $this->validatePayload($relationalCollectionName, null, $validatePayload,$params); - } - } - } + + $this->validateManyToManyCollection($payload, $params, $aliasColumnDetails); }else{ - if($aliasColumnDetails->isOneToMany()){ - $relationalCollectionName = $aliasColumnDetails->getRelationship()->getCollectionMany(); - }else if($aliasColumnDetails->isManyToOne()){ - $relationalCollectionName = $aliasColumnDetails->getRelationship()->getCollectionOne(); - } - if($relationalCollectionName && isset($payload[$colName])){ - foreach($payload[$colName] as $individual){ - if(!isset($individual['$delete'])){ - $this->validatePayload($relationalCollectionName, null, $individual,$params,$collection); - } - } - } - } + $this->validateAliasCollection($payload, $params, $aliasColumnDetails, []); + } } - $tableGateway = $this->createTableGateway($collection); $newRecord = $tableGateway->createRecord($payload, $this->getCRUDParams($params)); @@ -185,63 +161,59 @@ public function findOne($collection, array $params = []) 'single' => true ])); } - + /** * Validate Parent Collection Fields */ public function validateParentCollectionFields($collection, $payload, $params, $recordData){ $tableColumns = SchemaService::getAllCollectionFields($collection); $collectionFields = $payload; - + foreach($tableColumns as $key => $column){ - if(!empty($recordData) && !$column->hasPrimaryKey()){ - $columnName = $column->getName(); + if(!empty($recordData)){ + + $columnName = $column->getName(); + $collectionFields[$columnName] = array_key_exists($column->getName(), $collectionFields) ? $collectionFields[$column->getName()]: (DataTypes::isJson($column->getType()) ? (array) $recordData[$columnName] : $recordData[$columnName]); } } - + $this->validatePayload($collection, null, $collectionFields, $params); } - + /** * Validate Many To Many Collection Fields */ - public function validateManyToManyCollection($payload, $params, $aliasColumnDetails, $recordData){ + public function validateManyToManyCollection($payload, $params, $aliasColumnDetails){ $colName = $aliasColumnDetails->getName(); $relationalCollectionName = $aliasColumnDetails->getRelationship()->getCollectionManyToMany(); if($relationalCollectionName && isset($payload[$colName])){ $relationalCollectionPrimaryKey = SchemaService::getCollectionPrimaryKey($relationalCollectionName); $relationalCollectionColumns = SchemaService::getAllCollectionFields($relationalCollectionName); - foreach($payload[$colName] as $individual){ - if(!isset($individual['$delete'])){ + foreach($payload[$colName] as $individual){ + if(!isset($individual['$delete'])){ $aliasField = $aliasColumnDetails->getRelationship()->getJunctionOtherRelatedField(); $validatePayload = $individual[$aliasField]; - $storedData = (!empty($recordData) && isset($recordData[$colName])) ? $recordData[$colName] : [] ; - + + foreach($relationalCollectionColumns as $column){ - if(!empty($recordData) && !$column->isAlias() && !$column->hasPrimaryKey() && isset($recordData[$colName])){ - $search = array_search($individual[$relationalCollectionPrimaryKey], array_column($storedData, $relationalCollectionPrimaryKey)); + if(!$column->isAlias() && !$column->hasPrimaryKey() && !empty($validatePayload[$relationalCollectionPrimaryKey])){ $columnName = $column->getName(); - if($search !== false){ - $dbObj = isset($storedData[$search][$aliasField]) ? $storedData[$search][$aliasField] : []; - $validatePayload[$columnName] = array_key_exists($columnName, $validatePayload) ? $validatePayload[$columnName]: (isset($dbObj[$columnName]) ? ((DataTypes::isJson($column->getType()) ? (array) $dbObj[$columnName] : $dbObj[$columnName])) : null); - }else{ - $relationalCollectionData = $this->findByIds( - $relationalCollectionName, - $validatePayload[$relationalCollectionPrimaryKey], - $params - ); - $validatePayload[$columnName] = array_key_exists($columnName, $validatePayload) ? $validatePayload[$columnName]: (isset($relationalCollectionData['data'][$columnName]) ? ((DataTypes::isJson($column->getType()) ? (array) $relationalCollectionData['data'][$columnName] : $relationalCollectionData['data'][$columnName])) : null); - } + $relationalCollectionData = $this->findByIds( + $relationalCollectionName, + $validatePayload[$relationalCollectionPrimaryKey], + $params + ); + $validatePayload[$columnName] = array_key_exists($columnName, $validatePayload) ? $validatePayload[$columnName]: (isset($relationalCollectionData['data'][$columnName]) ? ((DataTypes::isJson($column->getType()) ? (array) $relationalCollectionData['data'][$columnName] : $relationalCollectionData['data'][$columnName])) : null); } } $this->validatePayload($relationalCollectionName, null, $validatePayload,$params); } } - } + } } - + /** * Validate Alias Collection Fields (O2M and M2O - Including Translations and Files) */ @@ -256,33 +228,30 @@ public function validateAliasCollection($payload, $params, $aliasColumnDetails, $parentCollectionName = $aliasColumnDetails->getRelationship()->getCollectionMany(); } if($relationalCollectionName && isset($payload[$colName])){ - + $relationalCollectionPrimaryKey = SchemaService::getCollectionPrimaryKey($relationalCollectionName); $parentCollectionPrimaryKey = SchemaService::getCollectionPrimaryKey($parentCollectionName); $relationalCollectionColumns = SchemaService::getAllCollectionFields($relationalCollectionName); $foreignJoinColumn = $aliasColumnDetails->getRelationship()->getFieldMany(); - - foreach($payload[$colName] as $individual){ - if(!isset($individual['$delete'])){ + + foreach($payload[$colName] as $individual){ + + if(!isset($individual['$delete'])){ foreach($relationalCollectionColumns as $key => $column){ - if(!empty($recordData) && !$column->isAlias() && !$column->hasPrimaryKey() && isset($recordData[$colName])){ - $search = array_search($individual[$relationalCollectionPrimaryKey], array_column($recordData[$colName], $relationalCollectionPrimaryKey)); + + if(!$column->isAlias() && !$column->hasPrimaryKey() && !empty($individual[$relationalCollectionPrimaryKey])){ $columnName = $column->getName(); - if($search !== false){ - $individual[$columnName] = array_key_exists($columnName, $individual) ? $individual[$columnName]: (isset($recordData[$colName][$search][$columnName]) ? ((DataTypes::isJson($column->getType()) ? (array) $recordData[$colName][$search][$columnName] : $recordData[$colName][$search][$columnName])) : null); - }else{ - $relationalCollectionData = $this->findByIds( - $relationalCollectionName, - $individual[$relationalCollectionPrimaryKey], - $params - ); - $individual[$columnName] = array_key_exists($columnName, $individual) ? $individual[$columnName]: (isset($relationalCollectionData['data'][$columnName]) ? ((DataTypes::isJson($column->getType()) ? (array) $relationalCollectionData['data'][$columnName] : $relationalCollectionData['data'][$columnName])) : null); - } + $relationalCollectionData = $this->findByIds( + $relationalCollectionName, + $individual[$relationalCollectionPrimaryKey], + $params + ); + $individual[$columnName] = array_key_exists($columnName, $individual) ? $individual[$columnName]: (isset($relationalCollectionData['data'][$columnName]) ? ((DataTypes::isJson($column->getType()) ? (array) $relationalCollectionData['data'][$columnName] : $relationalCollectionData['data'][$columnName])) : null); } } // only add parent id's to items that are lacking the parent column - if (empty($individual[$foreignJoinColumn])) { + if (empty($individual[$foreignJoinColumn]) && !empty($recordData[$parentCollectionPrimaryKey])) { $individual[$foreignJoinColumn] = $recordData[$parentCollectionPrimaryKey]; } $this->validatePayload($relationalCollectionName, null, $individual,$params); @@ -303,10 +272,10 @@ public function checkRelationalItemDeletable($collection, $payload, $recordData) // Check if field is O2M and required if($column->hasRelationship() && $column->isOneToMany() && ($column->isRequired() || (!$column->isNullable() && $column->getDefaultValue() == null))){ if(!empty($recordData)){ - $columnName = $column->getName(); - + $columnName = $column->getName(); + if(isset($recordData[$columnName]) && isset($payload[$columnName]) && count($recordData[$columnName]) == count($payload[$columnName]) ){ - $fieldMany = $column->getRelationship()->getFieldMany(); + $fieldMany = $column->getRelationship()->getFieldMany(); $collectionMany = SchemaService::getCollection($column->getRelationship()->getCollectionMany()); $primaryKeyCollectionMany = $collectionMany->getPrimaryKeyName(); $alreadyStoredEntries = array_column($recordData[$columnName],$primaryKeyCollectionMany); @@ -325,7 +294,7 @@ public function checkRelationalItemDeletable($collection, $payload, $recordData) } } } - + /** * Updates a single item in the given collection and id * @@ -344,22 +313,22 @@ public function update($collection, $id, $payload, array $params = []) $dbData = $this->findByIds($collection,$id,['fields' => '*.*.*']); $recordData = !empty($dbData['data']) ? $dbData['data'] : []; $this->validateParentCollectionFields($collection, $payload, $params, $recordData); - + //Validate alias field payload $tableSchema = SchemaService::getCollection($collection); $collectionAliasColumns = $tableSchema->getAliasFields(); - + foreach ($collectionAliasColumns as $aliasColumnDetails) { - if($this->isManyToManyField($aliasColumnDetails)){ - $this->validateManyToManyCollection($payload, $params, $aliasColumnDetails, $recordData); + if($this->isManyToManyField($aliasColumnDetails)){ + $this->validateManyToManyCollection($payload, $params, $aliasColumnDetails); }else{ - $this->validateAliasCollection($payload, $params, $aliasColumnDetails, $recordData); - } + $this->validateAliasCollection($payload, $params, $aliasColumnDetails, $recordData); + } } // There is a scenario in which user tries to delete all the relational data although it is required. This can be possible for o2M only and API have to restrict that. - $this->checkRelationalItemDeletable($collection, $payload, $recordData); - + $this->checkRelationalItemDeletable($collection, $payload, $recordData); + $this->checkItemExists($collection, $id); $tableGateway = $this->createTableGateway($collection); @@ -561,10 +530,10 @@ protected function getStatusValue($collection, $id) return $row[$collectionObject->getStatusField()->getName()]; } - + /** * Checks whether the relationship is MANY TO MANY - * + * * @param $fieldMany * @param $collectionMany * @@ -578,7 +547,7 @@ protected function isManyToManyField($field){ 'collection_many' => $relationship->getCollectionMany(), ]; $tableGateway = $this->createTableGateway(SchemaManager::COLLECTION_RELATIONS); - $junctionEntries = $tableGateway->getItems(['filter' => $junctionConditions]); + $junctionEntries = $tableGateway->getItems(['filter' => $junctionConditions]); return !empty($junctionEntries['data']) ? true : false; } return false; diff --git a/src/core/Directus/Services/ProjectService.php b/src/core/Directus/Services/ProjectService.php index 6bbac8f3ba..2097804a57 100644 --- a/src/core/Directus/Services/ProjectService.php +++ b/src/core/Directus/Services/ProjectService.php @@ -46,6 +46,7 @@ public function create(array $data) 'timezone' => 'string', 'locale' => 'string', + 'logs_path' => 'string', 'project_name' => 'string', 'app_url' => 'string', diff --git a/src/core/Directus/Services/TablesService.php b/src/core/Directus/Services/TablesService.php index d42f792e43..c6580334c8 100644 --- a/src/core/Directus/Services/TablesService.php +++ b/src/core/Directus/Services/TablesService.php @@ -294,7 +294,7 @@ public function deleteField($collection, $field, array $params = []) $hookEmitter->run('field.delete:before', [$collection, $field]); $hookEmitter->run('field.delete.' . $collection . ':before', [$field]); - $tableService->dropColumn($collection, $field); + $tableService->dropColumn($collection, $field, $params); $hookEmitter->run('field.delete', [$collection, $field]); $hookEmitter->run('field.delete.' . $collection, [$field]); @@ -730,7 +730,7 @@ public function batchUpdateFieldWithIds($collectionName, array $fieldNames, arra return $allItems; } - public function dropColumn($collectionName, $fieldName) + public function dropColumn($collectionName, $fieldName, array $params = []) { $tableObject = $this->getSchemaManager()->getCollection($collectionName); if (!$tableObject) { @@ -753,7 +753,7 @@ public function dropColumn($collectionName, $fieldName) } if ($columnObject->hasRelationship()) { - $this->removeColumnRelationship($columnObject); + $this->removeColumnRelationship($columnObject,$params); } if ($columnObject->isManaged()) { @@ -900,7 +900,7 @@ public function removeColumnInfo($collectionName, $fieldName) * * @return bool|int */ - public function removeColumnRelationship(Field $field) + public function removeColumnRelationship(Field $field, array $params = []) { if (!$field->hasRelationship()) { return false; @@ -909,7 +909,7 @@ public function removeColumnRelationship(Field $field) if ($this->shouldRemoveRelationshipRecord($field)) { $result = $this->removeRelationshipRecord($field); } else { - $result = $this->removeRelationshipFromRecord($field); + $result = $this->removeRelationshipFromRecord($field, $params); } return $result; @@ -950,7 +950,7 @@ protected function removeRelationshipRecord(Field $field) * * @return int */ - protected function removeRelationshipFromRecord(Field $field) + protected function removeRelationshipFromRecord(Field $field, array $params = []) { $tableGateway = $this->getRelationsTableGateway(); @@ -975,7 +975,10 @@ protected function removeRelationshipFromRecord(Field $field) */ if (!empty($junctionEntries['data'])) { $tableGateway->delete($junctionConditions); - $this->dropTable($relationship->getCollectionMany()); + + if(isset($params['delete_junction'])){ + $this->dropTable($relationship->getCollectionMany()); + } return $tableGateway->delete($conditions['values']); } else { diff --git a/src/core/Directus/Util/Installation/InstallerUtils.php b/src/core/Directus/Util/Installation/InstallerUtils.php index 5556eb467a..d80be3e47f 100644 --- a/src/core/Directus/Util/Installation/InstallerUtils.php +++ b/src/core/Directus/Util/Installation/InstallerUtils.php @@ -880,7 +880,7 @@ private static function dropTables($basePath, $projectName) private static function createConfigData(array $data) { $corsEnabled = ArrayUtils::get($data, 'cors_enabled', true); - $authSecret = ArrayUtils::get($data, 'auth_secret', StringUtils::randomString(32)); + $authSecret = ArrayUtils::get($data, 'auth_secret', StringUtils::randomString(32, false)); $authPublic = ArrayUtils::get($data, 'auth_public', generate_uuid4()); return ArrayUtils::defaults([ @@ -891,9 +891,10 @@ private static function createConfigData(array $data) 'db_password' => null, 'db_socket' => '', 'mail_from' => 'admin@example.com', - 'feedback_token' => sha1(gmdate('U') . StringUtils::randomString(32)), + 'feedback_token' => sha1(gmdate('U') . StringUtils::randomString(32, false)), 'feedback_login' => true, 'timezone' => get_default_timezone(), + 'logs_path' => __DIR__ . '/../../../../../logs', 'cache' => [ 'enabled' => false, 'response_ttl' => 3600, diff --git a/src/core/Directus/Util/Installation/stubs/config.stub b/src/core/Directus/Util/Installation/stubs/config.stub index 117cfc087c..6613eca69e 100644 --- a/src/core/Directus/Util/Installation/stubs/config.stub +++ b/src/core/Directus/Util/Installation/stubs/config.stub @@ -8,7 +8,7 @@ return [ 'settings' => [ 'logger' => [ - 'path' => __DIR__ . '/../logs', + 'path' => '{{logs_path}}', ], ], diff --git a/src/core/Directus/Util/StringUtils.php b/src/core/Directus/Util/StringUtils.php index bf80fe2c88..dac277322c 100644 --- a/src/core/Directus/Util/StringUtils.php +++ b/src/core/Directus/Util/StringUtils.php @@ -123,10 +123,14 @@ public static function random($length = 16) * * @return string */ - public static function randomString($length = 16) + public static function randomString($length = 16, $special_chars = true) { // TODO: Add options to allow symbols or user provided characters to extend the list - $pool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+}{';'?>.<,"; + $pool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + if ($special_chars) { + $pool .= "!@#$%^&*()_+}{;?>.<,"; + } return substr(str_shuffle(str_repeat($pool, $length)), 0, $length); } @@ -234,7 +238,7 @@ public static function replacePlaceholder($string, $data = [], $placeHolderForma } /** - * If any variable of the given string have null value as a replacement then the + * If any variable of the given string have null value as a replacement then the * result will be 'null'(string). So we need to replace it with blank string. */ $string = str_replace("'null'", "''", $string); diff --git a/src/endpoints/Settings.php b/src/endpoints/Settings.php index 6ce625c789..b2c3bc41f9 100644 --- a/src/endpoints/Settings.php +++ b/src/endpoints/Settings.php @@ -7,6 +7,7 @@ use Directus\Application\Http\Response; use Directus\Application\Route; use Directus\Services\SettingsService; +use Directus\Services\FilesServices; use function Directus\regex_numeric_ids; class Settings extends Route @@ -149,13 +150,8 @@ public function read(Request $request, Response $response) * * @return Response */ - public function getInterfaceBasedInput($request, $setting) + public function getInterfaceBasedInput($request, $setting, $fieldData) { - $service = new SettingsService($this->container); - $fieldData = $service->findAllFields( - $request->getQueryParams() - ); - $inputData = $request->getParsedBody(); foreach ($fieldData['data'] as $key => $value) { if ($value['field'] == $setting) { @@ -180,6 +176,33 @@ public function getInterfaceBasedInput($request, $setting) return $inputData; } + /** + * @param Request $request + * @param Response $response + * + * @return Response + */ + public function getInterfaceBasedOutput($setting, $fieldData) + { + $fileService = new FilesServices($this->container); + $response = $setting['value']; + foreach ($fieldData['data'] as $value) { + if ($value['field'] == $setting['key']) { + if ($setting['value'] != null) { + switch ($value['type']) { + case 'file': + $responseData = $fileService->findByIds($setting['value'],[]); + if( !empty($responseData['data']) ){ + $response = $responseData['data']; + } + break; + } + } + } + } + return $response; + } + /** * @param Request $request * @param Response $response @@ -212,7 +235,10 @@ public function update(Request $request, Response $response) * */ - $inputData = $this->getInterfaceBasedInput($request, $serviceData['data']['key']); + $fieldData = $service->findAllFields( + $request->getQueryParams() + ); + $inputData = $this->getInterfaceBasedInput($request, $serviceData['data']['key'], $fieldData); $responseData = $service->update( $request->getAttribute('id'), $inputData, @@ -220,6 +246,7 @@ public function update(Request $request, Response $response) ); $responseData['data']['value'] = $payload['value']; + $responseData['data']['value'] = $this->getInterfaceBasedOutput($responseData['data'], $fieldData); return $this->responseWithData($request, $response, $responseData); } diff --git a/src/helpers/app.php b/src/helpers/app.php index 7c54b220b9..dea3dc3de2 100644 --- a/src/helpers/app.php +++ b/src/helpers/app.php @@ -100,9 +100,7 @@ function get_project_config($name = null, $basePath = null) if (!file_exists($configFilePath)) { throw new UnknownProjectException($name); } - $configData = $schema->value([ - "directus" => Context::from_file($configFilePath) - ]); + $configData = $schema->value(['directus' => Context::from_file($configFilePath)]); } $config = new Config($configData); diff --git a/src/helpers/file.php b/src/helpers/file.php index d36069b559..e102056f87 100644 --- a/src/helpers/file.php +++ b/src/helpers/file.php @@ -191,6 +191,7 @@ function add_default_thumbnail_dimensions(array &$list) */ function get_thumbnails(array $row) { + $filename = $row['filename']; $type = array_get($row, 'type'); $thumbnailFilenameParts = explode('.', $filename); @@ -218,6 +219,7 @@ function get_thumbnails(array $row) } $size = explode('x', $dimension); + if (count($size) == 2) { $thumbnailUrl = get_thumbnail_url($filename, $size[0], $size[1]); $thumbnailRelativeUrl = get_thumbnail_path($filename, $size[0], $size[1]); @@ -297,7 +299,8 @@ function get_proxy_path($path) // env/width/height/mode/quality/name return sprintf( '/downloads/%s/%s', - $projectName, $path + $projectName, + $path ); } } @@ -365,17 +368,16 @@ function is_a_url($value) * * @return bool */ - function validate_file($value,$constraint,$options = null) + function validate_file($value, $constraint, $options = null) { - switch ($constraint) { + switch ($constraint) { case 'mimeTypes': - validate_file_mime_type($value,$options); - break; + validate_file_mime_type($value, $options); + break; case 'maxSize': - validate_file_size($value,$options); - break; - - } + validate_file_size($value, $options); + break; + } } } @@ -387,14 +389,14 @@ function validate_file($value,$constraint,$options = null) * * @return bool */ - function validate_file_mime_type($value,$options) + function validate_file_mime_type($value, $options) { $mimeTypes = $options; $mime = $value; - - if($options == null) { - $options=get_directus_setting('file_mimetype_whitelist'); - $mimeTypes = explode(",",$options); + + if ($options == null) { + $options = get_directus_setting('file_mimetype_whitelist'); + $mimeTypes = explode(",", $options); } foreach ($mimeTypes as $mimeType) { if ($mimeType === $mime) { @@ -406,7 +408,7 @@ function validate_file_mime_type($value,$options) } } } - $message='The mime type of the file is invalid.Allowed mime types are '.$options.'.'; + $message = 'The mime type of the file is invalid.Allowed mime types are ' . $options . '.'; throw new InvalidRequestException($message); } } @@ -418,13 +420,13 @@ function validate_file_mime_type($value,$options) * * @return bool */ - function validate_file_size($value,$options) + function validate_file_size($value, $options) { - $maxSize=$options; - if($options == null) { - $maxSize=get_directus_setting('file_max_size'); + $maxSize = $options; + if ($options == null) { + $maxSize = get_directus_setting('file_max_size'); } - $size=$maxSize; + $size = $maxSize; $factors = [ 'KB' => 1000, 'MB' => 1000000, @@ -433,21 +435,20 @@ function validate_file_size($value,$options) ]; if (ctype_digit((string) $maxSize)) { $maxSize = (int) $maxSize; - } elseif (preg_match('/^(\d++)('.implode('|', array_keys($factors)).')$/', $maxSize, $matches)) { + } elseif (preg_match('/^(\d++)(' . implode('|', array_keys($factors)) . ')$/', $maxSize, $matches)) { $maxSize = $matches[1] * $factors[$unit = $matches[2]]; } else { throw new InvalidRequestException(sprintf('"%s" is not a valid maximum size.', $size)); } if (0 === $value) { - $message='An empty file is not allowed.'; + $message = 'An empty file is not allowed.'; throw new InvalidRequestException($message); } - if($value > $maxSize){ - $message='The file is too large. Allowed maximum size is '.$size.'.'; + if ($value > $maxSize) { + $message = 'The file is too large. Allowed maximum size is ' . $size . '.'; throw new InvalidRequestException($message); } } } - diff --git a/tests/api/Config/ContextTest.php b/tests/api/Config/ContextTest.php index 9c99ca2c21..28343298e8 100644 --- a/tests/api/Config/ContextTest.php +++ b/tests/api/Config/ContextTest.php @@ -120,6 +120,9 @@ public function testContextFile() "b" => "2" ], ], + 'arrayval' => [ + 'some' => 'array' + ] ]; // Should load values from php source file diff --git a/tests/api/Config/SchemaTest.php b/tests/api/Config/SchemaTest.php index cc5d8d40c2..97e28c8e65 100644 --- a/tests/api/Config/SchemaTest.php +++ b/tests/api/Config/SchemaTest.php @@ -115,6 +115,7 @@ public function testDefaults() 'social_providers' => [], ], + 'ext' => [] ], $data); } diff --git a/tests/api/Config/sources/source.json b/tests/api/Config/sources/source.json index 1a0c533ded..1ab116fa24 100644 --- a/tests/api/Config/sources/source.json +++ b/tests/api/Config/sources/source.json @@ -4,5 +4,10 @@ "a": 1, "b": 2 } - } + }, + "arrayval": [ + { + "some": "object", + } + ] } diff --git a/tests/api/Config/sources/source.php b/tests/api/Config/sources/source.php index 6fcd56e723..6de17fe03b 100644 --- a/tests/api/Config/sources/source.php +++ b/tests/api/Config/sources/source.php @@ -7,4 +7,7 @@ "b" => 2, ], ], + 'arrayval' => [ + 'some' => 'array' + ] ];