diff --git a/.php_cs.cache b/.php_cs.cache
new file mode 100644
index 00000000..2eece6cf
--- /dev/null
+++ b/.php_cs.cache
@@ -0,0 +1 @@
+{"php":"7.2.20","version":"2.15.0","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"braces":true,"class_definition":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_constants":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":true,"encoding":true,"full_opening_tag":true},"hashes":{"vendor\/flextype-components\/arr\/Arr.php":3460238454}}
\ No newline at end of file
diff --git a/api.md b/api.md
new file mode 100644
index 00000000..80f4e3ac
--- /dev/null
+++ b/api.md
@@ -0,0 +1,31 @@
+
+Warning: PHP Startup: Unable to load dynamic library 'curl' (tried: /usr/local/Cellar/php@7.2/7.2.20/lib/php/20170718/curl (dlopen(/usr/local/Cellar/php@7.2/7.2.20/lib/php/20170718/curl, 9): image not found), /usr/local/Cellar/php@7.2/7.2.20/lib/php/20170718/curl.so (dlopen(/usr/local/Cellar/php@7.2/7.2.20/lib/php/20170718/curl.so, 9): image not found)) in Unknown on line 0
+## Table of contents
+
+- [\Flextype\Component\Arr\Arr](#class-flextypecomponentarrarr)
+
+
+
+### Class: \Flextype\Component\Arr\Arr
+
+| Visibility | Function |
+|:-----------|:---------|
+| public static | average(array $array, int/\integer $decimals) : int/\Flextype\Component\Arr\double
Returns the average value of the current array. |
+| public static | createFromJson(\string $json, \boolean $assoc=true, \integer $depth=512, int/\integer $options) : array
Create an new Array from JSON string. |
+| public static | createFromString(\string $str, \string $delimiter=null, \string $regEx=null) : array
Create an new Array object via string. |
+| public static | delete(array $array, \string $path) : mixed
Deletes an array value using "dot notation". |
+| public static | dot(array $array, \string $prepend=`''`) : array
Flatten a multi-dimensional associative array with dots. |
+| public static | first(array $array) : mixed The first element
Returns the first element of an array |
+| public static | get(array $array, \string $path, mixed $default=null) : mixed
Returns value from array using "dot notation". If the key does not exist in the array, the default value will be returned instead. |
+| public static | isAssoc(array $array) : bool
Returns TRUE if the array is associative and FALSE if not. |
+| public static | keyExists(array $array, mixed $path) : bool
Checks if the given dot-notated key exists in the array. |
+| public static | last(array $array) : mixed The last element
Returns the last element of an array |
+| public static | overwrite(array $array1, array $array2) : array
Overwrites an array with values from input arrays. Keys that do not exist in the first array will not be added! |
+| public static | random(array $array) : mixed
Returns a random value from an array. |
+| public | reverse(array $array, \boolean $preserve_keys=false) : array
Return an array with elements in reverse order. Non-numeric keys are not affected by this setting and will always be preserved. |
+| public static | set(array $array, \string $path, mixed $value) : void
Sets an array value using "dot notation". |
+| public static | size(array $array, int/\integer $mode) : void
Counts all elements in an array. COUNT_RECURSIVE (or 1), count will recursively count the array. This is particularly useful for counting all the elements of a multidimensional array. count does not detect infinite recursion. |
+| public static | sort(array $array, \string $field, \string $direction=`'ASC'`, \Flextype\Component\Arr\const $method) : array
Sorts a multi-dimensional array by a certain field path |
+| public static | toJson(array $array, int/\integer $options, \integer $depth=512) : string The JSON string
Converts an array to a JSON string |
+| public static | undot(array $array) : array
Expands a dot notation array into a full multi-dimensional array. |
+
diff --git a/composer.json b/composer.json
index ccadc2dc..94e11c0e 100755
--- a/composer.json
+++ b/composer.json
@@ -26,7 +26,7 @@
"doctrine/cache": "~1.10.0",
"doctrine/collections": "~1.6.4",
- "flextype-components/arr" : "1.2.5",
+ "flextype-components/arr" : "1.2.8",
"flextype-components/cookie" : "1.2.0",
"flextype-components/date" : "1.1.0",
"flextype-components/filesystem" : "2.0.6",
@@ -77,8 +77,8 @@
]
},
"require-dev": {
- "doctrine/coding-standard": "~6.0.0",
- "victorjonsson/markdowndocs": "dev-master",
+ "doctrine/coding-standard": "7.0.2",
+ "victorjonsson/markdowndocs": "^1.3",
"phpstan/phpstan": "^0.11.19",
"symfony/var-dumper": "^4.4"
}
diff --git a/src/flextype/bootstrap.php b/src/flextype/bootstrap.php
index 3454c915..65fb57c1 100755
--- a/src/flextype/bootstrap.php
+++ b/src/flextype/bootstrap.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Flextype;
+use Flextype\Component\Arr\Arr;
use Flextype\Component\Filesystem\Filesystem;
use Flextype\Component\Registry\Registry;
use Flextype\Component\Session\Session;
@@ -131,7 +132,12 @@ include_once 'dependencies.php';
include_once 'endpoints/access/access.php';
include_once 'endpoints/delivery/entries.php';
include_once 'endpoints/delivery/registry.php';
+<<<<<<< HEAD
include_once 'endpoints/delivery/config.php';
+=======
+include_once 'endpoints/delivery/media/files.php';
+include_once 'endpoints/delivery/media/folders.php';
+>>>>>>> 428-files-api
include_once 'endpoints/management/entries.php';
include_once 'endpoints/management/config.php';
include_once 'endpoints/images/images.php';
diff --git a/src/flextype/config/settings.yaml b/src/flextype/config/settings.yaml
index 600d0258..0c2204a0 100644
--- a/src/flextype/config/settings.yaml
+++ b/src/flextype/config/settings.yaml
@@ -236,6 +236,17 @@ cors:
expose: []
credentials: false
+# Media
+media:
+ accept_file_types: 'gif, jpg, jpeg, png, ico, zip, tgz, txt, md, doc, docx, pdf, epub, xls, xlsx, ppt, pptx, mp3, ogg, wav, m4a, mp4, m4v, ogv, wmv, avi, webm, svg'
+ max_file_size: 5000000
+ safe_names: true
+ image_width: 1600
+ image_height: 0
+ image_quality: 70
+ max_image_width: null
+ max_image_height: null
+
# Content APIs
api:
delivery:
@@ -245,7 +256,11 @@ api:
registry:
enabled: true
default_token:
+<<<<<<< HEAD
config:
+=======
+ files:
+>>>>>>> 428-files-api
enabled: true
default_token:
management:
diff --git a/src/flextype/core/Entries/Entries.php b/src/flextype/core/Entries/Entries.php
index a208cca9..25c47959 100755
--- a/src/flextype/core/Entries/Entries.php
+++ b/src/flextype/core/Entries/Entries.php
@@ -14,6 +14,7 @@ use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Flextype\Component\Filesystem\Filesystem;
use Flextype\Component\Session\Session;
+use Flextype\Component\Arr\Arr;
use Ramsey\Uuid\Uuid;
use function array_merge;
use function count;
@@ -391,6 +392,9 @@ class Entries
// For each founded entry we should create $entries array.
$entry = $this->fetch($uid);
+ // Flatten a multi-dimensional entries array with dots.
+ $entry = Arr::dot($entry);
+
// Add entry into the entries
$entries[$uid] = $entry;
@@ -483,6 +487,11 @@ class Entries
// Gets a native PHP array representation of the collection.
$entries = $entries->toArray();
+ // Magic is here... dot and undot for entries array
+ // 1. Flatten a multi-dimensional entries array with dots.
+ // 2. Restore entries array with dots into correct multi-dimensional entries array
+ $entries = Arr::undot(Arr::dot($entries));
+
// Restore error_reporting
error_reporting($oldErrorReporting);
diff --git a/src/flextype/core/Media/MediaFiles.php b/src/flextype/core/Media/MediaFiles.php
new file mode 100644
index 00000000..46a23409
--- /dev/null
+++ b/src/flextype/core/Media/MediaFiles.php
@@ -0,0 +1,328 @@
+flextype = $flextype;
+ }
+
+ /**
+ * Create a media file
+ *
+ * @param array $file Raw file data (multipart/form-data).
+ * @param string $folder The folder you're targetting.
+ *
+ * @access public
+ */
+ public function create(array $file, string $folder)
+ {
+ $upload_folder = PATH['project'] . '/uploads/' . $folder . '/';
+ $upload_metadata_folder = PATH['project'] . '/uploads/.meta/' . $folder . '/';
+
+ if (! Filesystem::has($upload_folder)) {
+ Filesystem::createDir($upload_folder);
+ }
+
+ if (! Filesystem::has($upload_metadata_folder)) {
+ Filesystem::createDir($upload_metadata_folder);
+ }
+
+ $accept_file_types = $this->flextype['registry']->get('flextype.settings.media.accept_file_types');
+ $max_file_size = $this->flextype['registry']->get('flextype.settings.media.max_file_size');
+ $safe_names = $this->flextype['registry']->get('flextype.settings.media.safe_names');
+ $max_image_width = $this->flextype['registry']->get('flextype.settings.media.max_image_width');
+ $max_image_height = $this->flextype['registry']->get('flextype.settings.media.max_image_height');
+
+ $exact = false;
+ $chmod = 0644;
+ $filename = null;
+ $exif_data = [];
+
+ //
+ // Tests if a successful upload has been made.
+ //
+ if (isset($file['error'])
+ and isset($file['tmp_name'])
+ and $file['error'] === UPLOAD_ERR_OK
+ and is_uploaded_file($file['tmp_name'])) {
+ //
+ // Tests if upload data is valid, even if no file was uploaded.
+ //
+ if (isset($file['error'])
+ and isset($file['name'])
+ and isset($file['type'])
+ and isset($file['tmp_name'])
+ and isset($file['size'])) {
+ //
+ // Test if an uploaded file is an allowed file type, by extension.
+ //
+ if (strpos($accept_file_types, strtolower(pathinfo($file['name'], PATHINFO_EXTENSION))) !== false) {
+ //
+ // Validation rule to test if an uploaded file is allowed by file size.
+ //
+ if (($file['error'] != UPLOAD_ERR_INI_SIZE)
+ and ($file['error'] == UPLOAD_ERR_OK)
+ and ($file['size'] <= $max_file_size)) {
+ //
+ // Validation rule to test if an upload is an image and, optionally, is the correct size.
+ //
+ if (in_array(mime_content_type($file['tmp_name']), ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
+ function validateImage($file, $max_image_width, $max_image_height, $exact)
+ {
+ try {
+ // Get the width and height from the uploaded image
+ list($width, $height) = getimagesize($file['tmp_name']);
+ } catch (ErrorException $e) {
+ // Ignore read errors
+ }
+ if (empty($width) or empty($height)) {
+ // Cannot get image size, cannot validate
+ return false;
+ }
+ if (!$max_image_width) {
+ // No limit, use the image width
+ $max_image_width = $width;
+ }
+ if (!$max_image_height) {
+ // No limit, use the image height
+ $max_image_height = $height;
+ }
+ if ($exact) {
+ // Check if dimensions match exactly
+ return ($width === $max_image_width and $height === $max_image_height);
+ } else {
+ // Check if size is within maximum dimensions
+ return ($width <= $max_image_width and $height <= $max_image_height);
+ }
+ return false;
+ }
+ if (validateImage($file, $max_image_width, $max_image_height, $exact) === false) {
+ return false;
+ }
+ }
+ if (!isset($file['tmp_name']) or !is_uploaded_file($file['tmp_name'])) {
+ // Ignore corrupted uploads
+ return false;
+ }
+ if ($filename === null) {
+ // Use the default filename
+ $filename = $file['name'];
+ }
+ if ($safe_names === true) {
+ // Remove spaces from the filename
+ $filename = $this->flextype['slugify']->slugify(pathinfo($filename)['filename']) . '.' . pathinfo($filename)['extension'];
+ }
+ if (!is_dir($upload_folder) or !is_writable(realpath($upload_folder))) {
+ throw new \RuntimeException("Directory {$upload_folder} must be writable");
+ }
+ // Make the filename into a complete path
+ $filename = realpath($upload_folder) . DIRECTORY_SEPARATOR . $filename;
+ if (move_uploaded_file($file['tmp_name'], $filename)) {
+ // Set permissions on filename
+ chmod($filename, $chmod);
+
+ if (in_array(mime_content_type($filename), ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) {
+ // open an image file
+ $img = Image::make($filename);
+
+ // now you are able to resize the instance
+ if ($this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_width') > 0 && $this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_height') > 0) {
+ $img->resize($this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_width'), $this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_height'), function($constraint) {
+ $constraint->aspectRatio();
+ $constraint->upsize();
+ });
+ } elseif ($this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_width') > 0) {
+ $img->resize($this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_width'), null, function($constraint) {
+ $constraint->aspectRatio();
+ $constraint->upsize();
+ });
+ } elseif ($this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_height') > 0) {
+ $img->resize(null, $this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_height'), function($constraint) {
+ $constraint->aspectRatio();
+ $constraint->upsize();
+ });
+ }
+
+ // finally we save the image as a new file
+ $img->save($filename, $this->flextype['registry']->get('plugins.admin.settings.entries.media.upload_images_quality'));
+
+ // destroy
+ $img->destroy();
+
+ $headers = exif_read_data($filename);
+
+ $exif_data = [];
+
+ foreach ($headers['COMPUTED'] as $header => $value) {
+ $exif_data[$header] = $value;
+ }
+ }
+
+ $metadata = ['title' => substr(basename($filename), 0, strrpos(basename($filename), '.')),
+ 'description' => '',
+ 'type' => mime_content_type($filename),
+ 'filesize' => Filesystem::getSize($filename),
+ 'uploaded_on' => time(),
+ 'exif' => $exif_data];
+
+ Filesystem::write($upload_metadata_folder . basename($filename) . '.yaml',
+ $this->flextype['serializer']->encode($metadata, 'yaml'));
+
+
+ // Return new file path
+ return $filename;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Fetch single file
+ *
+ * @param string $directory The directory to list.
+ *
+ * @return array A list of file metadata.
+ */
+ public function fetchsingle(string $id) : array
+ {
+ $result = [];
+
+ if (Filesystem::has($this->flextype['media_files_meta']->getFileMetaLocation($id))) {
+ $result = $this->flextype['serializer']->decode(Filesystem::read($this->flextype['media_files_meta']->getFileMetaLocation($id)), 'yaml');
+
+ $result['filename'] = pathinfo(str_replace("/.meta", "", $this->flextype['media_files_meta']->getFileMetaLocation($id)))['filename'];
+ $result['basename'] = explode(".", basename($this->flextype['media_files_meta']->getFileMetaLocation($id)))[0];
+ $result['extension'] = ltrim(strstr($id, '.'), '.');
+ $result['dirname'] = pathinfo(str_replace("/.meta", "", $this->flextype['media_files_meta']->getFileMetaLocation($id)))['dirname'];
+
+ $result['url'] = 'project/uploads/' . $id;
+
+ if ($this->flextype['registry']->has('flextype.settings.url') && $this->flextype['registry']->get('flextype.settings.url') != '') {
+ $full_url = $this->flextype['registry']->get('flextype.settings.url');
+ } else {
+ $full_url = Uri::createFromEnvironment(new Environment($_SERVER))->getBaseUrl();
+ }
+
+ $result['full_url'] = $full_url . '/project/uploads/' . $id;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch files collection
+ *
+ * @param string $folder The directory to list.
+ *
+ * @return array A list of file metadata.
+ */
+ public function fetchCollection(string $folder) : array
+ {
+ $result = [];
+
+ foreach (Filesystem::listContents($this->flextype['media_folders_meta']->getDirMetaLocation($folder)) as $file) {
+ $result[$file['basename']] = $this->flextype['serializer']->decode(Filesystem::read($file['path']), 'yaml');
+
+ $result[$file['basename']]['filename'] = pathinfo(str_replace("/.meta", "", $this->flextype['media_files_meta']->getFileMetaLocation($file['basename'])))['filename'];
+ $result[$file['basename']]['basename'] = explode(".", basename($this->flextype['media_files_meta']->getFileMetaLocation($file['basename'])))[0];
+ $result[$file['basename']]['extension'] = ltrim(strstr($file['basename'], '.'), '.');
+ $result[$file['basename']]['dirname'] = pathinfo(str_replace("/.meta", "", $file['path']))['dirname'];
+
+ $result[$file['basename']]['url'] = 'project/uploads/' . $folder . '/' . $file['basename'];
+
+ if ($this->flextype['registry']->has('flextype.settings.url') && $this->flextype['registry']->get('flextype.settings.url') != '') {
+ $full_url = $this->flextype['registry']->get('flextype.settings.url');
+ } else {
+ $full_url = Uri::createFromEnvironment(new Environment($_SERVER))->getBaseUrl();
+ }
+
+ $result[$file['basename']]['full_url'] = $full_url . '/project/uploads/' . $folder . '/' . $file['basename'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Rename file
+ *
+ * @param string $id Unique identifier of the file.
+ * @param string $new_id New Unique identifier of the file.
+ *
+ * @return bool True on success, false on failure.
+ *
+ * @access public
+ */
+ public function rename(string $id, string $new_id) : bool
+ {
+ if (!Filesystem::has($this->getFileLocation($new_id)) && !Filesystem::has($this->flextype['media_files_meta']->getFileMetaLocation($new_id))) {
+ if (rename($this->getFileLocation($id), $this->getFileLocation($new_id)) && rename($this->flextype['media_files_meta']->getFileMetaLocation($id), $this->flextype['media_files_meta']->getFileMetaLocation($new_id))) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Delete file
+ *
+ * @param string $id Unique identifier of the file.
+ *
+ * @return bool True on success, false on failure.
+ *
+ * @access public
+ */
+ public function delete(string $id)
+ {
+ Filesystem::delete($this->getFileLocation($id));
+ Filesystem::delete($this->flextype['media_files_meta']->getFileMetaLocation($id));
+ }
+
+ /**
+ * Get file location
+ *
+ * @param string $id Unique identifier of the file.
+ *
+ * @return string entry file location
+ *
+ * @access public
+ */
+ public function getFileLocation(string $id) : string
+ {
+ return PATH['project'] . '/uploads/' . $id;
+ }
+}
diff --git a/src/flextype/core/Media/MediaFilesMeta.php b/src/flextype/core/Media/MediaFilesMeta.php
new file mode 100644
index 00000000..69180ec8
--- /dev/null
+++ b/src/flextype/core/Media/MediaFilesMeta.php
@@ -0,0 +1,101 @@
+flextype['serializer']->decode(Filesystem::read($this->getFileMetaLocation($id)), 'yaml');
+
+ if (Arr::keyExists($file_data, $field)) {
+ Arr::set($file_data, $field, $value);
+ return Filesystem::write($this->getFileMetaLocation($id), $this->flextype['serializer']->encode($file_data, 'yaml'));
+ }
+
+ return false;
+ }
+
+ /**
+ * Add file meta information
+ *
+ * @param string $id Unique identifier of the file.
+ * @param string $field Field name
+ * @param string $value Field value
+ *
+ * @return bool True on success, false on failure.
+ *
+ * @access public
+ */
+ public function addMeta(string $id, string $field, string $value) : bool
+ {
+ $file_data = $this->flextype['serializer']->decode(Filesystem::read($this->getFileMetaLocation($id)), 'yaml');
+
+ if (!Arr::keyExists($file_data, $field)) {
+ Arr::set($file_data, $field, $value);
+ return Filesystem::write($this->getFileMetaLocation($id), $this->flextype['serializer']->encode($file_data, 'yaml'));
+ }
+
+ return false;
+ }
+
+ /**
+ * Delete file meta information
+ *
+ * @param string $id Unique identifier of the file.
+ * @param string $field Field name
+ *
+ * @return bool True on success, false on failure.
+ *
+ * @access public
+ */
+ public function deleteMeta(string $id, string $field) : bool
+ {
+ $file_data = $this->flextype['serializer']->decode(Filesystem::read($this->getFileMetaLocation($id)), 'yaml');
+
+ if (Arr::keyExists($file_data, $field)) {
+ Arr::delete($file_data, $field);
+ return Filesystem::write($this->getFileMetaLocation($id), $this->flextype['serializer']->encode($file_data, 'yaml'));
+ }
+
+ return false;
+ }
+
+ /**
+ * Get file meta location
+ *
+ * @param string $id Unique identifier of the file.
+ *
+ * @return string entry file location
+ *
+ * @access public
+ */
+ public function getFileMetaLocation(string $id) : string
+ {
+ return PATH['project'] . '/uploads/.meta/' . $id . '.yaml';
+ }
+}
diff --git a/src/flextype/core/Media/MediaFolders.php b/src/flextype/core/Media/MediaFolders.php
new file mode 100644
index 00000000..05f396d5
--- /dev/null
+++ b/src/flextype/core/Media/MediaFolders.php
@@ -0,0 +1,110 @@
+flextype = $flextype;
+ }
+
+ /**
+ * Create folder
+ *
+ * @param string $id Unique identifier of the folder.
+ *
+ * @return bool True on success, false on failure.
+ *
+ * @access public
+ */
+ public function create(string $id) : bool
+ {
+ if (!Filesystem::has($this->getDirLocation($id)) && !Filesystem::has($this->flextype['media_folders_meta']->getDirMetaLocation($id))) {
+ if (Filesystem::createDir($this->getDirLocation($id)) && Filesystem::createDir($this->flextype['media_folders_meta']->getDirMetaLocation($id))) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Rename folder
+ *
+ * @param string $id Unique identifier of the folder.
+ * @param string $new_id New Unique identifier of the folder.
+ *
+ * @return bool True on success, false on failure.
+ *
+ * @access public
+ */
+ public function rename(string $id, string $new_id) : bool
+ {
+ if (!Filesystem::has($this->getDirLocation($new_id)) && !Filesystem::has($this->flextype['media_folders_meta']->getDirMetaLocation($new_id))) {
+ if (rename($this->getDirLocation($id), $this->getDirLocation($new_id)) && rename($this->flextype['media_folders_meta']->getDirMetaLocation($id), $this->flextype['media_folders_meta']->getDirMetaLocation($new_id))) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Delete dir
+ *
+ * @param string $id Unique identifier of the file.
+ *
+ * @return bool True on success, false on failure.
+ *
+ * @access public
+ */
+ public function delete(string $id)
+ {
+ Filesystem::deleteDir($this->getDirLocation($id));
+ Filesystem::deleteDir($this->flextype['media_folders_meta']->getDirMetaLocation($id));
+ }
+
+ /**
+ * Get files directory location
+ *
+ * @param string $id Unique identifier of the folder.
+ *
+ * @return string entry directory location
+ *
+ * @access public
+ */
+ public function getDirLocation(string $id) : string
+ {
+ return PATH['project'] . '/uploads/' . $id;
+ }
+}
diff --git a/src/flextype/core/Media/MediaFoldersMeta.php b/src/flextype/core/Media/MediaFoldersMeta.php
new file mode 100644
index 00000000..fa8ea7fe
--- /dev/null
+++ b/src/flextype/core/Media/MediaFoldersMeta.php
@@ -0,0 +1,27 @@
+ ['type' => 'Error', 'id' => 'AccessTokenInvalid'], 'message' => 'The access token you sent could not be found or is invalid.'];
+$api_sys_messages['NotFound'] = ['sys' => ['type' => 'Error', 'id' => 'NotFound'], 'message' => 'The resource could not be found.'];
+
+/**
+ * Validate delivery media files token
+ */
+function validate_delivery_media_files_token($token) : bool
+{
+ return Filesystem::has(PATH['project'] . '/tokens/delivery/media/files/' . $token . '/token.yaml');
+}
+
+/**
+ * Fetch media file(s)
+ *
+ * endpoint: GET /api/delivery/media/files
+ *
+ * Query:
+ * path - [REQUIRED] - Unique identifier of the file path.
+ * token - [REQUIRED] - Valid Content Delivery API token for Entries.
+ *
+ * Returns:
+ * An array of entry item objects.
+ */
+$app->get('/api/delivery/media/files', function (Request $request, Response $response) use ($flextype, $api_sys_messages) {
+
+ // Get Query Params
+ $query = $request->getQueryParams();
+
+ // Set variables
+ $path = $query['path'];
+ $token = $query['token'];
+
+ if ($flextype['registry']->get('flextype.settings.api.delivery.media.files.enabled')) {
+
+ // Validate delivery token
+ if (validate_delivery_media_files_token($token)) {
+ $delivery_files_token_file_path = PATH['project'] . '/tokens/delivery/media/files/' . $token. '/token.yaml';
+
+ // Set delivery token file
+ if ($delivery_files_token_file_data = $flextype['serializer']->decode(Filesystem::read($delivery_files_token_file_path), 'yaml')) {
+ if ($delivery_files_token_file_data['state'] === 'disabled' ||
+ ($delivery_files_token_file_data['limit_calls'] !== 0 && $delivery_files_token_file_data['calls'] >= $delivery_files_token_file_data['limit_calls'])) {
+ return $response->withJson($api_sys_messages['AccessTokenInvalid'], 401);
+ }
+
+ // Create files array
+ $files = [];
+
+ // Get list if file or files for specific folder
+ if (is_dir($path)) {
+ $files = $flextype['media_files']->fetchCollection($path);
+ } else {
+ $files = $flextype['media_files']->fetchSingle($path);
+ }
+
+ // Write response data
+ $response_data['data'] = $files;
+
+ // Set response code
+ $response_code = count($response_data['data']) > 0 ? 200 : 404;
+
+ // Update calls counter
+ Filesystem::write($delivery_files_token_file_path, $flextype['serializer']->encode(array_replace_recursive($delivery_files_token_file_data, ['calls' => $delivery_files_token_file_data['calls'] + 1]), 'yaml'));
+
+ if ($response_code == 404) {
+
+ // Return response
+ return $response
+ ->withJson($api_sys_messages['NotFound'], $response_code);
+ }
+
+ // Return response
+ return $response
+ ->withJson($response_data, $response_code);
+ }
+
+ return $response
+ ->withJson($api_sys_messages['AccessTokenInvalid'], 401);
+ }
+
+ return $response
+ ->withJson($api_sys_messages['AccessTokenInvalid'], 401);
+ }
+
+ return $response
+ ->withJson($api_sys_messages['AccessTokenInvalid'], 401);
+});
diff --git a/src/flextype/endpoints/management/media/files.php b/src/flextype/endpoints/management/media/files.php
new file mode 100644
index 00000000..d2dfb32a
--- /dev/null
+++ b/src/flextype/endpoints/management/media/files.php
@@ -0,0 +1,106 @@
+ ['type' => 'Error', 'id' => 'AccessTokenInvalid'], 'message' => 'The access token you sent could not be found or is invalid.'];
+$api_sys_messages['NotFound'] = ['sys' => ['type' => 'Error', 'id' => 'NotFound'], 'message' => 'The resource could not be found.'];
+
+/**
+ * Validate delivery media files token
+ */
+function validate_delivery_media_files_token($token) : bool
+{
+ return Filesystem::has(PATH['project'] . '/tokens/delivery/media/files/' . $token . '/token.yaml');
+}
+
+/**
+ * Fetch media file(s)
+ *
+ * endpoint: GET /api/delivery/media/files
+ *
+ * Query:
+ * path - [REQUIRED] - Unique identifier of the file path.
+ * token - [REQUIRED] - Valid Content Delivery API token for Entries.
+ *
+ * Returns:
+ * An array of entry item objects.
+ */
+$app->get('/api/delivery/media/files', function (Request $request, Response $response) use ($flextype, $api_sys_messages) {
+
+ // Get Query Params
+ $query = $request->getQueryParams();
+
+ // Set variables
+ $path = $query['path'];
+ $token = $query['token'];
+
+ if ($flextype['registry']->get('flextype.settings.api.delivery.media.files.enabled')) {
+
+ // Validate delivery token
+ if (validate_delivery_media_files_token($token)) {
+ $delivery_files_token_file_path = PATH['project'] . '/tokens/delivery/media/files/' . $token. '/token.yaml';
+
+ // Set delivery token file
+ if ($delivery_files_token_file_data = $flextype['serializer']->decode(Filesystem::read($delivery_files_token_file_path), 'yaml')) {
+ if ($delivery_files_token_file_data['state'] === 'disabled' ||
+ ($delivery_files_token_file_data['limit_calls'] !== 0 && $delivery_files_token_file_data['calls'] >= $delivery_files_token_file_data['limit_calls'])) {
+ return $response->withJson($api_sys_messages['AccessTokenInvalid'], 401);
+ }
+
+ // Create files array
+ $files = [];
+
+ // Get list if file or files for specific folder
+ if (is_dir($path)) {
+ $files = $flextype['media_files']->fetchCollection($path);
+ } else {
+ $files = $flextype['media_files']->fetchSingle($path);
+ }
+
+ // Write response data
+ $response_data['data'] = $files;
+
+ // Set response code
+ $response_code = count($response_data['data']) > 0 ? 200 : 404;
+
+ // Update calls counter
+ Filesystem::write($delivery_files_token_file_path, $flextype['serializer']->encode(array_replace_recursive($delivery_files_token_file_data, ['calls' => $delivery_files_token_file_data['calls'] + 1]), 'yaml'));
+
+ if ($response_code == 404) {
+
+ // Return response
+ return $response
+ ->withJson($api_sys_messages['NotFound'], $response_code);
+ }
+
+ // Return response
+ return $response
+ ->withJson($response_data, $response_code);
+ }
+
+ return $response
+ ->withJson($api_sys_messages['AccessTokenInvalid'], 401);
+ }
+
+ return $response
+ ->withJson($api_sys_messages['AccessTokenInvalid'], 401);
+ }
+
+ return $response
+ ->withJson($api_sys_messages['AccessTokenInvalid'], 401);
+});
diff --git a/src/flextype/endpoints/management/media/folders.php b/src/flextype/endpoints/management/media/folders.php
new file mode 100644
index 00000000..e69de29b