diff --git a/classes/admin.php b/classes/admin.php
index 7fc59123..4bba71b8 100644
--- a/classes/admin.php
+++ b/classes/admin.php
@@ -233,9 +233,11 @@ class Admin
*
* @return Page
*/
- public function page($route = false)
+ public function page($route = false, $path = null)
{
- $path = $this->route;
+ if (!$path) {
+ $path = $this->route;
+ }
if ($route && !$path) {
$path = '/';
@@ -285,88 +287,57 @@ class Admin
$post = isset($_POST['data']) ? $_POST['data'] : [];
}
- switch ($type) {
- case 'configuration':
- case 'system':
- $type = 'system';
- $blueprints = $this->blueprints("config/{$type}");
- $config = $this->grav['config'];
- $obj = new Data\Data($config->get('system'), $blueprints);
- $obj->merge($post);
- $file = CompiledYamlFile::instance($this->grav['locator']->findResource("config://{$type}.yaml"));
- $obj->file($file);
- $data[$type] = $obj;
- break;
+ /** @var UniformResourceLocator $locator */
+ $locator = $this->grav['locator'];
+ $filename = $locator->findResource("config://{$type}.yaml", true, true);
+ $file = CompiledYamlFile::instance($filename);
- case 'settings':
- case 'site':
- $type = 'site';
- $blueprints = $this->blueprints("config/{$type}");
- $config = $this->grav['config'];
- $obj = new Data\Data($config->get('site'), $blueprints);
- $obj->merge($post);
- $file = CompiledYamlFile::instance($this->grav['locator']->findResource("config://{$type}.yaml"));
- $obj->file($file);
- $data[$type] = $obj;
- break;
+ if (preg_match('|plugins/|', $type)) {
+ /** @var Plugins $plugins */
+ $plugins = $this->grav['plugins'];
+ $obj = $plugins->get(preg_replace('|plugins/|', '', $type));
- case 'login':
- $data[$type] = null;
- break;
+ if (!$obj) { return []; }
- default:
- /** @var UniformResourceLocator $locator */
- $locator = $this->grav['locator'];
- $filename = $locator->findResource("config://{$type}.yaml", true, true);
- $file = CompiledYamlFile::instance($filename);
+ $obj->merge($post);
+ $obj->file($file);
- if (preg_match('|plugins/|', $type)) {
- /** @var Plugins $plugins */
- $plugins = $this->grav['plugins'];
- $obj = $plugins->get(preg_replace('|plugins/|', '', $type));
+ $data[$type] = $obj;
+ } elseif (preg_match('|themes/|', $type)) {
+ /** @var Themes $themes */
+ $themes = $this->grav['themes'];
+ $obj = $themes->get(preg_replace('|themes/|', '', $type));
- if (!$obj) { return []; }
+ if (!$obj) { return []; }
- $obj->merge($post);
- $obj->file($file);
+ $obj->merge($post);
+ $obj->file($file);
- $data[$type] = $obj;
- } elseif (preg_match('|themes/|', $type)) {
- /** @var Themes $themes */
- $themes = $this->grav['themes'];
- $obj = $themes->get(preg_replace('|themes/|', '', $type));
+ $data[$type] = $obj;
+ } elseif (preg_match('|users/|', $type)) {
+ $obj = User::load(preg_replace('|users/|', '', $type));
+ $obj->merge($post);
- if (!$obj) { return []; }
+ $data[$type] = $obj;
+ } elseif (preg_match('|user/|', $type)) {
+ $obj = User::load(preg_replace('|user/|', '', $type));
+ $obj->merge($post);
- $obj->merge($post);
- $obj->file($file);
-
- $data[$type] = $obj;
- } elseif (preg_match('|users/|', $type)) {
- $obj = User::load(preg_replace('|users/|', '', $type));
- $obj->merge($post);
-
- $data[$type] = $obj;
- } elseif (preg_match('|user/|', $type)) {
- $obj = User::load(preg_replace('|user/|', '', $type));
- $obj->merge($post);
-
- $data[$type] = $obj;
- } elseif (preg_match('|config/|', $type)) {
- $type = preg_replace('|config/|', '', $type);
- $blueprints = $this->blueprints("config/{$type}");
- $config = $this->grav['config'];
- $obj = new Data\Data($config->get($type, []), $blueprints);
- $obj->merge($post);
- // FIXME: We shouldn't allow user to change configuration files in system folder!
- $filename = $this->grav['locator']->findResource("config://{$type}.yaml")
- ?: $this->grav['locator']->findResource("config://{$type}.yaml", true, true);
- $file = CompiledYamlFile::instance($filename);
- $obj->file($file);
- $data[$type] = $obj;
- } else {
- throw new \RuntimeException("Data type '{$type}' doesn't exist!");
- }
+ $data[$type] = $obj;
+ } elseif (preg_match('|config/|', $type)) {
+ $type = preg_replace('|config/|', '', $type);
+ $blueprints = $this->blueprints("config/{$type}");
+ $config = $this->grav['config'];
+ $obj = new Data\Data($config->get($type, []), $blueprints);
+ $obj->merge($post);
+ // FIXME: We shouldn't allow user to change configuration files in system folder!
+ $filename = $this->grav['locator']->findResource("config://{$type}.yaml")
+ ?: $this->grav['locator']->findResource("config://{$type}.yaml", true, true);
+ $file = CompiledYamlFile::instance($filename);
+ $obj->file($file);
+ $data[$type] = $obj;
+ } else {
+ throw new \RuntimeException("Data type '{$type}' doesn't exist!");
}
return $data[$type];
@@ -1180,4 +1151,63 @@ class Admin
{
$this->permissions = array_merge($this->permissions, $permissions);
}
+
+ public function findFormFields($type, $fields, $found_fields = [])
+ {
+ foreach ($fields as $key => $field) {
+
+ if (isset($field['type']) && $field['type'] == $type) {
+ $found_fields[$key] = $field;
+ } elseif (isset($field['fields'])) {
+ $result = $this->findFormFields($type, $field['fields'], $found_fields);
+ if (!empty($result)) {
+ $found_fields = array_merge($found_fields, $result);
+ }
+ }
+ }
+ return $found_fields;
+ }
+
+ public function getPagePathFromToken($path)
+ {
+ $path_parts = pathinfo($path);
+
+ $basename = '';
+ if (isset($path_parts['extension'])) {
+ $basename = '/'.$path_parts['basename'];
+ $path = $path_parts['dirname'];
+ }
+
+ $regex = '/(@self|self@)|((?:@page|page@):(?:.*))|((?:@theme|theme@):(?:.*))/';
+ preg_match($regex, $path, $matches);
+
+ if ($matches) {
+ if ($matches[1]) {
+ // self@
+ $page = $this->page(true);
+ } elseif ($matches[2]) {
+ // page@
+ $parts = explode(':', $path);
+ $route = $parts[1];
+ $page = $this->grav['page']->find($route);
+ } elseif ($matches[3]) {
+ // theme@
+ $parts = explode(':', $path);
+ $route = $parts[1];
+ $theme = str_replace(ROOT_DIR, '', $this->grav['locator']->findResource("theme://"));
+ return $theme . $route . $basename;
+ }
+ } else {
+ return $path . $basename;
+ }
+
+ if (!$page) {
+ throw new \RuntimeException('Page route not found: ' . $path);
+ }
+
+ $path = str_replace($matches[0], rtrim($page->relativePagePath(), '/'), $path);
+
+ return $path . $basename;
+ }
+
}
diff --git a/classes/controller.php b/classes/controller.php
index 2d878305..14299fb1 100644
--- a/classes/controller.php
+++ b/classes/controller.php
@@ -85,11 +85,11 @@ class AdminController
];
/**
- * @param Grav $grav
+ * @param Grav $grav
* @param string $view
* @param string $task
* @param string $route
- * @param array $post
+ * @param array $post
*/
public function __construct(Grav $grav, $view, $task, $route, $post)
{
@@ -123,15 +123,14 @@ class AdminController
$nonce = $this->grav['uri']->param('admin-nonce');
}
- if (!$nonce || !Utils::verifyNonce($nonce, 'admin-form'))
- {
+ if (!$nonce || !Utils::verifyNonce($nonce, 'admin-form')) {
if ($this->task == 'addmedia') {
$message = sprintf($this->admin->translate('PLUGIN_ADMIN.FILE_TOO_LARGE', null, true), ini_get('post_max_size'));
//In this case it's more likely that the image is too big than POST can handle. Show message
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $message
];
@@ -140,7 +139,7 @@ class AdminController
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
];
@@ -154,7 +153,7 @@ class AdminController
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),
'error');
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
];
@@ -166,7 +165,7 @@ class AdminController
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),
'error');
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
];
@@ -283,7 +282,7 @@ class AdminController
$language = $this->grav['user']->authenticated ? $this->grav['user']->language : ($this->grav['language']->getLanguage() ?: 'en');
$this->admin->session()->invalidate()->start();
- $this->setRedirect('/logout/lang:'.$language);
+ $this->setRedirect('/logout/lang:' . $language);
return true;
}
@@ -464,7 +463,7 @@ class AdminController
}
if ($result) {
- $this->admin->json_response = ['status' => 'success', 'dependencies' => $dependencies, 'message' => $this->admin->translate(is_string($result) ? $result :'PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL')];
+ $this->admin->json_response = ['status' => 'success', 'dependencies' => $dependencies, 'message' => $this->admin->translate(is_string($result) ? $result : 'PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL')];
} else {
$this->admin->json_response = ['status' => 'error', 'message' => $this->admin->translate('PLUGIN_ADMIN.UNINSTALL_FAILED')];
}
@@ -651,12 +650,12 @@ class AdminController
$results = Cache::clearCache($clear);
if (count($results) > 0) {
$this->admin->json_response = [
- 'status' => 'success',
+ 'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.CACHE_CLEARED') . '
' . $this->admin->translate('PLUGIN_ADMIN.METHOD') . ': ' . $clear . ''
];
} else {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.ERROR_CLEARING_CACHE')
];
}
@@ -696,7 +695,7 @@ class AdminController
$backup = ZipBackup::backup();
} catch (\Exception $e) {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.AN_ERROR_OCCURRED') . '. ' . $e->getMessage()
];
@@ -708,18 +707,18 @@ class AdminController
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
$log->content([
- 'time' => time(),
+ 'time' => time(),
'location' => $backup
]);
$log->save();
$this->admin->json_response = [
- 'status' => 'success',
+ 'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.YOUR_BACKUP_IS_READY_FOR_DOWNLOAD') . '. ' . $this->admin->translate('PLUGIN_ADMIN.DOWNLOAD_BACKUP') . '',
- 'toastr' => [
- 'timeOut' => 0,
+ 'toastr' => [
+ 'timeOut' => 0,
'extendedTimeOut' => 0,
- 'closeButton' => true
+ 'closeButton' => true
]
];
@@ -838,7 +837,7 @@ class AdminController
}
$this->admin->json_response = [
- 'status' => 'success',
+ 'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.PAGES_FILTERED'),
'results' => $results
];
@@ -860,7 +859,7 @@ class AdminController
if (!$page) {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
@@ -894,7 +893,7 @@ class AdminController
if (!isset($_FILES['file']['error']) || is_array($_FILES['file']['error'])) {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_PARAMETERS')
];
@@ -907,7 +906,7 @@ class AdminController
break;
case UPLOAD_ERR_NO_FILE:
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_FILES_SENT')
];
@@ -915,14 +914,14 @@ class AdminController
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT')
];
return false;
default:
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')
];
@@ -933,7 +932,7 @@ class AdminController
// You should also check filesize here.
if ($grav_limit > 0 && $_FILES['file']['size'] > $grav_limit) {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')
];
@@ -952,7 +951,7 @@ class AdminController
// If not a supported type, return
if (!$fileExt || !$config->get("media.{$fileExt}")) {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $fileExt
];
@@ -964,7 +963,7 @@ class AdminController
sprintf('%s/%s', $page->path(), $_FILES['file']['name']))
) {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE')
];
@@ -973,7 +972,7 @@ class AdminController
Cache::clearCache();
$this->admin->json_response = [
- 'status' => 'success',
+ 'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY')
];
@@ -995,7 +994,7 @@ class AdminController
if (!$page) {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
@@ -1010,12 +1009,12 @@ class AdminController
if (unlink($targetPath)) {
Cache::clearCache();
$this->admin->json_response = [
- 'status' => 'success',
+ 'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename
];
} else {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename
];
}
@@ -1043,12 +1042,12 @@ class AdminController
if ($deletedResponsiveImage) {
Cache::clearCache();
$this->admin->json_response = [
- 'status' => 'success',
+ 'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename
];
} else {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_NOT_FOUND') . ': ' . $filename
];
}
@@ -1056,7 +1055,7 @@ class AdminController
}
} else {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_FILE_FOUND')
];
}
@@ -1080,7 +1079,7 @@ class AdminController
if (!$page) {
$this->admin->json_response = [
- 'status' => 'error',
+ 'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
@@ -1216,15 +1215,15 @@ class AdminController
if ($result) {
$this->admin->json_response = [
- 'status' => 'success',
- 'type' => 'updategrav',
+ 'status' => 'success',
+ 'type' => 'updategrav',
'version' => $version,
'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_WAS_SUCCESSFULLY_UPDATED_TO') . ' ' . $version
];
} else {
$this->admin->json_response = [
- 'status' => 'error',
- 'type' => 'updategrav',
+ 'status' => 'error',
+ 'type' => 'updategrav',
'version' => GRAV_VERSION,
'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_UPDATE_FAILED') . '
' . Installer::lastErrorMsg()
];
@@ -1265,87 +1264,74 @@ class AdminController
}
/**
+ * @param $field
* @return array
*/
- private function cleanFilesData()
+ private function cleanFilesData($field)
{
/** @var Page $page */
$page = null;
$cleanFiles = [];
- $type = trim("{$this->view}/{$this->admin->route}", '/');
- $data = $this->admin->data($type, $this->post);
-
- $blueprints = $data->blueprints();
-
- if (!isset($blueprints['form']['fields'])) {
- throw new \RuntimeException('Blueprints missing form fields definition');
- }
- $blueprint = $blueprints['form']['fields'];
-
$file = $_FILES['data'];
- foreach ((array)$file['error'] as $index => $errors) {
- $errors = !is_array($errors) ? [$errors] : $errors;
+ $errors = (array) Utils::getDotNotation($file['error'], $field['name']);
- foreach($errors as $multiple_index => $error) {
- if ($error == UPLOAD_ERR_OK) {
- if (is_array($file['name'][$index])) {
- $tmp_name = $file['tmp_name'][$index][$multiple_index];
- $name = $file['name'][$index][$multiple_index];
- $type = $file['type'][$index][$multiple_index];
- $size = $file['size'][$index][$multiple_index];
- } else {
- $tmp_name = $file['tmp_name'][$index];
- $name = $file['name'][$index];
- $type = $file['type'][$index];
- $size = $file['size'][$index];
- }
+ foreach ($errors as $index => $error) {
+ if ($error == UPLOAD_ERR_OK) {
- $destination = Folder::getRelativePath(rtrim($blueprint[$index]['destination'], '/'));
+ $fieldname = $field['name'];
- if (!$this->match_in_array($type, $blueprint[$index]['accept'])) {
- throw new \RuntimeException('File "' . $name . '" is not an accepted MIME type.');
- }
+ // Deal with multiple files
+ if (isset($field['multiple']) && $field['multiple'] == true) {
+ $fieldname = $fieldname . ".$index";
+ }
- if (Utils::startsWith($destination, '@page:')) {
- $parts = explode(':', $destination);
- $route = $parts[1];
- $page = $this->grav['page']->find($route);
+ $tmp_name = Utils::getDotNotation($file['tmp_name'], $fieldname);
+ $name = Utils::getDotNotation($file['name'], $fieldname);
+ $type = Utils::getDotNotation($file['type'], $fieldname);
+ $size = Utils::getDotNotation($file['size'], $fieldname);
- if (!$page) {
- throw new \RuntimeException('Unable to upload file to destination. Page route not found.');
- }
+ $original_destination = null ;
+ $destination = Folder::getRelativePath(rtrim($field['destination'], '/'));
- $destination = $page->relativePagePath();
- } else {
- if ($destination == '@self') {
- $page = $this->admin->page(true);
- $destination = $page->relativePagePath();
- } else {
- Folder::mkdir($destination);
- }
- }
+ if (!$this->match_in_array($type, $field['accept'])) {
+ throw new \RuntimeException('File "' . $name . '" is not an accepted MIME type.');
+ }
- if (move_uploaded_file($tmp_name, "$destination/$name")) {
- $path = $page ? $this->grav['uri']->convertUrl($page, $page->route() . '/' . $name) : $destination . '/' . $name;
- $fileData = [
- 'name' => $name,
- 'path' => $path,
- 'type' => $type,
- 'size' => $size,
- 'file' => $destination . '/' . $name,
- 'route' => $page ? $path : null
- ];
+ if (isset($field['random_name']) && $field['random_name'] === true) {
+ $path_parts = pathinfo($name);
+ $name = Utils::generateRandomString(15) . '.' . $path_parts['extension'];
+ }
- $cleanFiles[$index][$path] = $fileData;
- } else {
- throw new \RuntimeException("Unable to upload file(s) to $destination/$name");
- }
+ $resolved_destination = $this->admin->getPagePathFromToken($destination);
+ $upload_path = $resolved_destination . '/' . $name;
+
+ // Create dir if need be
+ if (!is_dir($resolved_destination)) {
+ Folder::mkdir($resolved_destination);
+ }
+
+ if (move_uploaded_file($tmp_name, $upload_path)) {
+ $path = $destination . '/' . $name;
+ $fileData = [
+ 'name' => $name,
+ 'path' => $path,
+ 'type' => $type,
+ 'size' => $size,
+ 'file' => $destination . '/' . $name,
+ 'route' => $page ? $path : null
+ ];
+
+ $cleanFiles[$field['name']][$path] = $fileData;
} else {
- if ($error != UPLOAD_ERR_NO_FILE) {
- throw new \RuntimeException("Unable to upload file(s) - Error: ".$file['name'][$index].": ".$this->upload_errors[$error]);
- }
+ throw new \RuntimeException("Unable to upload file(s) to $destination/$name");
+ }
+
+
+ } else {
+ if ($error != UPLOAD_ERR_NO_FILE) {
+ throw new \RuntimeException("Unable to upload file(s) - Error: " . $field['name'] . ": " . $this->upload_errors[$error]);
}
}
}
@@ -1354,7 +1340,7 @@ class AdminController
}
/**
- * @param string $needle
+ * @param string $needle
* @param array|string $haystack
*
* @return bool
@@ -1382,16 +1368,60 @@ class AdminController
if (!isset($_FILES['data'])) {
return $obj;
}
-
- $cleanFiles = $this->cleanFilesData();
- foreach ($cleanFiles as $key => $data) {
- $obj->set($key, $data);
+ $blueprints = $obj->blueprints();
+
+ if (!isset($blueprints['form']['fields'])) {
+ throw new \RuntimeException('Blueprints missing form fields definition');
+ }
+ $fields = $blueprints['form']['fields'];
+
+ $found_files = $this->findFields('file', $fields);
+
+ foreach ($found_files as $key => $data) {
+ if ($this->view == 'pages') {
+ $keys = explode('.', preg_replace('/^header./', '', $key));
+ $init_key = array_shift($keys);
+ if (count($keys) > 0) {
+ $new_data = isset($obj->header()->$init_key) ? $obj->header()->$init_key: [];
+ Utils::setDotNotation($new_data, implode('.', $keys), $data);
+ } else {
+ $new_data = $data;
+ }
+ $obj->modifyHeader($init_key, $new_data);
+ } else {
+ $obj->set($key, $data);
+ }
}
return $obj;
}
+ public function findFields($type, $fields, $found = [])
+ {
+ foreach ($fields as $key => $field) {
+
+ if (isset($field['type']) && $field['type'] == $type) {
+ $file_field = $this->cleanFilesData($field);
+ } elseif (isset($field['fields'])) {
+ $result = $this->findFields($type, $field['fields'], $found);
+ if (!empty($result)) {
+ $found = array_merge($found, $result);
+ }
+ } else {
+ $file_field = null;
+ }
+
+ if (isset($file_field) && (!is_array($file_field) || !empty($file_field))) {
+ $found = array_merge($file_field, $found);
+ }
+ }
+
+ return $found;
+ }
+
+
+
/**
* Handles creating an empty page folder (without markdown file)
*
@@ -1493,8 +1523,12 @@ class AdminController
// Find new parent page in order to build the path.
$route = !isset($data['route']) ? dirname($this->admin->route) : $data['route'];
+
+ /** @var Page $obj */
$obj = $this->admin->page(true);
+
+
// Ensure route is prefixed with a forward slash.
$route = '/' . ltrim($route, '/');
@@ -1530,6 +1564,8 @@ class AdminController
$obj = $obj->move($parent);
$this->preparePage($obj, false, $obj->language());
+ $obj = $this->processFiles($obj);
+
// Reset slug and route. For now we do not support slug twig variable on save.
$obj->slug($original_slug);
@@ -1883,11 +1919,11 @@ class AdminController
/**
* Determine if the user can edit media
*
+ * @param string $type
* @return bool True if the media action is allowed
*/
- protected function canEditMedia()
+ protected function canEditMedia($type = 'media')
{
- $type = 'media';
if (!$this->authorizeTask('edit media', ['admin.' . $type, 'admin.super'])) {
return false;
}
@@ -1907,6 +1943,7 @@ class AdminController
}
$filename = base64_decode($this->route);
+
$file = File::instance($filename);
$resultRemoveMedia = false;
$resultRemoveMediaMeta = true;
@@ -1943,31 +1980,58 @@ class AdminController
protected function taskRemoveFileFromBlueprint()
{
$uri = $this->grav['uri'];
- $this->taskRemoveMedia();
-
+ $blueprint = base64_decode($uri->param('blueprint'));
+ $path = base64_decode($uri->param('path'));
+ $proute = base64_decode($uri->param('proute'));
+ $type = $uri->param('type');
$field = $uri->param('field');
- $blueprint = $uri->param('blueprint');
+ $this->taskRemoveMedia();
- $path = base64_decode($uri->param('path'));
+ if ($type == 'pages') {
+ $page = $this->admin->page(true, $proute);
+ $keys = explode('.', preg_replace('/^header./', '', $field));
+ $header = (array) $page->header();
+ $data_path = implode('.', $keys);
+ $data = Utils::getDotNotation($header, $data_path);
- $files = $this->grav['config']->get($blueprint . '.' . $field);
+ if (isset($data[$path])) {
+ unset($data[$path]);
+ Utils::setDotNotation($header, $data_path, $data);
+ $page->header($header);
+ }
- foreach ($files as $key => $value) {
- if ($key == $path) {
- unset($files[$key]);
+ $page->save();
+ } else {
+ $blueprint_prefix = $type == 'config' ? '': $type . '.';
+ $blueprint_name = str_replace('/blueprints', '', str_replace('config/', '', $blueprint));
+ $blueprint_field = $blueprint_prefix . $blueprint_name . '.' . $field;
+ $files = $this->grav['config']->get($blueprint_field);
+
+ foreach ($files as $key => $value) {
+ if ($key == $path) {
+ unset($files[$key]);
+ }
+ }
+
+ $this->grav['config']->set($blueprint_field, $files);
+
+ switch ($type) {
+ case 'config':
+ $data = $this->grav['config']->get($blueprint_name);
+ $config = $this->admin->data($blueprint, $data);
+ $config->save();
+ break;
+ case 'themes':
+ Theme::saveConfig($blueprint_name);
+ break;
+ case 'plugins':
+ Plugin::saveConfig($blueprint_name);
+ break;
}
}
-
- $this->grav['config']->set($blueprint . '.' . $field, $files);
-
- if (substr($blueprint, 0, 7) == 'plugins') {
- Plugin::saveConfig(substr($blueprint, 8));
- }
- if (substr($blueprint, 0, 6) == 'themes') {
- Theme::saveConfig(substr($blueprint, 7));
- }
-
+//
+//
$redirect = base64_decode($uri->param('redirect'));
$route = $this->grav['config']->get('plugins.admin.route');
diff --git a/languages/en.yaml b/languages/en.yaml
index 47376eed..44c69254 100644
--- a/languages/en.yaml
+++ b/languages/en.yaml
@@ -528,8 +528,9 @@ PLUGIN_ADMIN:
ORDERING_DISABLED_BECAUSE_PARENT_SETTING_ORDER: "Parent setting order, ordering disabled"
ORDERING_DISABLED_BECAUSE_PAGE_NOT_VISIBLE: "Page is not visible, ordering disabled"
ORDERING_DISABLED_BECAUSE_TOO_MANY_SIBLINGS: "Ordering via the admin is unsupported because there are more than 200 siblings"
- CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "You cannot add media files until you save the page. Just click 'Save' on top"
- DROP_FILES_HERE_TO_UPLOAD: "Drop files here to upload"
+ CANNOT_ADD_MEDIA_FILES_PAGE_NOT_SAVED: "NOTE: You cannot add media files until you save the page. Just click 'Save' on top"
+ CANNOT_ADD_FILES_PAGE_NOT_SAVED: "NOTE: Page must be saved before you can upload files to it."
+ DROP_FILES_HERE_TO_UPLOAD: "Drop your files here or click in this area"
INSERT: "Insert"
UNDO: "Undo"
REDO: "Redo"
diff --git a/themes/grav/app/forms/fields/files.js b/themes/grav/app/forms/fields/files.js
new file mode 100644
index 00000000..6ea9ef2a
--- /dev/null
+++ b/themes/grav/app/forms/fields/files.js
@@ -0,0 +1,22 @@
+import $ from 'jquery';
+import format from '../../utils/formatbytes';
+
+$('body').on('change', '.form-input-file > input[type="file"]', (event) => {
+ let input = event.target;
+ let files = input.files;
+ let container = $(input).next();
+
+ if (files.length) {
+ let plural = files.length > 1 ? 's' : '';
+ let html = '';
+ html += `${files.length} file${plural} selected`;
+ html += '
"+t.stack+""),console.error(t.message+" at "+t.stack)}Object.defineProperty(e,"__esModule",{value:!0}),e.parseStatus=i,e.parseJSON=o,
e.userFeedback=a,e.userFeedbackError=s;var u=n(303),c=r(u),l=n(306),f=function d(t){var d=new Error(t.statusText||t||"");return d.response=t,d}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(304),o=r(i);o["default"].options.positionClass="toast-top-right",o["default"].options.preventDuplicates=!0,e["default"]=o["default"]},,,function(t,e){t.exports=GravAdmin},function(t,e){function n(){this._events=this._events||{},this._maxListeners=this._maxListeners||void 0}function r(t){return"function"==typeof t}function i(t){return"number"==typeof t}function o(t){return"object"==typeof t&&null!==t}function a(t){return void 0===t}t.exports=n,n.EventEmitter=n,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.prototype.setMaxListeners=function(t){if(!i(t)||0>t||isNaN(t))throw TypeError("n must be a positive number");return this._maxListeners=t,this},n.prototype.emit=function(t){var e,n,i,s,u,c;if(this._events||(this._events={}),"error"===t&&(!this._events.error||o(this._events.error)&&!this._events.error.length)){if(e=arguments[1],e instanceof Error)throw e;throw TypeError('Uncaught, unspecified "error" event.')}if(n=this._events[t],a(n))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:s=Array.prototype.slice.call(arguments,1),n.apply(this,s)}else if(o(n))for(s=Array.prototype.slice.call(arguments,1),c=n.slice(),i=c.length,u=0;i>u;u++)c[u].apply(this,s);return!0},n.prototype.addListener=function(t,e){var i;if(!r(e))throw TypeError("listener must be a function");return this._events||(this._events={}),this._events.newListener&&this.emit("newListener",t,r(e.listener)?e.listener:e),this._events[t]?o(this._events[t])?this._events[t].push(e):this._events[t]=[this._events[t],e]:this._events[t]=e,o(this._events[t])&&!this._events[t].warned&&(i=a(this._maxListeners)?n.defaultMaxListeners:this._maxListeners,i&&i>0&&this._events[t].length>i&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),"function"==typeof console.trace&&console.trace())),this},n.prototype.on=n.prototype.addListener,n.prototype.once=function(t,e){function n(){this.removeListener(t,n),i||(i=!0,e.apply(this,arguments))}if(!r(e))throw TypeError("listener must be a function");var i=!1;return n.listener=e,this.on(t,n),this},n.prototype.removeListener=function(t,e){var n,i,a,s;if(!r(e))throw TypeError("listener must be a function");if(!this._events||!this._events[t])return this;if(n=this._events[t],a=n.length,i=-1,n===e||r(n.listener)&&n.listener===e)delete this._events[t],this._events.removeListener&&this.emit("removeListener",t,e);else if(o(n)){for(s=a;s-- >0;)if(n[s]===e||n[s].listener&&n[s].listener===e){i=s;break}if(0>i)return this;1===n.length?(n.length=0,delete this._events[t]):n.splice(i,1),this._events.removeListener&&this.emit("removeListener",t,e)}return this},n.prototype.removeAllListeners=function(t){var e,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[t]&&delete this._events[t],this;if(0===arguments.length){for(e in this._events)"removeListener"!==e&&this.removeAllListeners(e);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[t],r(n))this.removeListener(t,n);else if(n)for(;n.length;)this.removeListener(t,n[n.length-1]);return delete this._events[t],this},n.prototype.listeners=function(t){var e;return e=this._events&&this._events[t]?r(this._events[t])?[this._events[t]]:this._events[t].slice():[]},n.prototype.listenerCount=function(t){if(this._events){var e=this._events[t];if(r(e))return 1;if(e)return e.length}return 0},n.listenerCount=function(t,e){return t.listenerCount(e)}},function(t,e,n){(function(t){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var i=function(){function t(t,e){for(var n=0;n"+n+"
")}return(0,s["default"])("#grav-update-button").on("click",function(){(0,s["default"])(this).html(l.translations.PLUGIN_ADMIN.UPDATING_PLEASE_WAIT+" "+(0,d["default"])(t.assets["grav-update"].size)+"..")}),this}},{key:"resources",value:function(){if(!this.payload.resources.total)return this.maintenance("hide");var t=["plugins","themes"],e=["plugin","theme"],n=this.payload.resources,r=n.plugins,i=n.themes;return this.payload.resources.total?void[r,i].forEach(function(n,r){if(n&&!Array.isArray(n)){var i=Object.keys(n).length,o=t[r];(0,s["default"])('#admin-menu a[href$="/'+t[r]+'"]').find(".badges").addClass("with-updates").find(".badge.updates").text(i);var a=o.charAt(0).toUpperCase()+o.substr(1).toLowerCase(),u=(0,s["default"])(".grav-update."+o);u.css("display","block").html('\n\n '+l.translations.PLUGIN_ADMIN.UPDATE+" All "+a+'\n \n '+i+" "+l.translations.PLUGIN_ADMIN.OF_YOUR+" "+o+" "+l.translations.PLUGIN_ADMIN.HAVE_AN_UPDATE_AVAILABLE+"\n
\n ");var f=(0,s["default"])("[data-update-packages]").attr("data-packages-slugs")||"";f=f?f.split(","):[];var d=(0,c["default"])(f.concat(Object.keys(n))).join();(0,s["default"])("[data-update-packages]").attr("data-packages-slugs",""+d),Object.keys(n).forEach(function(t){var i=(0,s["default"])("[data-gpm-"+e[r]+'="'+t+'"]'),a=i.find(".gpm-name"),u=a.find("a");if("plugins"!==o||a.find(".badge.update").length?"themes"===o&&a.append('"):a.append(''+l.translations.PLUGIN_ADMIN.UPDATE_AVAILABLE+"!"),i.length){var c=(0,s["default"])(".grav-update."+e[r]);if(c.length){var f="testing"===n[t].type?'test release':"";c.html('\n\n '+l.translations.PLUGIN_ADMIN.UPDATE+" "+(e[r].charAt(0).toUpperCase()+e[r].substr(1).toLowerCase())+'\n \n v'+n[t].available+" "+f+" "+l.translations.PLUGIN_ADMIN.OF_THIS+" "+e[r]+" "+l.translations.PLUGIN_ADMIN.IS_NOW_AVAILABLE+"!\n
\n ").css("display","block")}}}),(0,s["default"])("[data-update-packages]").removeClass("hidden")}}):this}}]),t}();e["default"]=p;var m=new p;e.Instance=m,h.Instance.on("fetched",function(t,e){m.setPayload(t.payload||{}),m.grav().resources()}),"1"===l.config.enable_auto_updates_check&&h.Instance.fetch()},function(t,e,n){function r(t,e){return e=e||i,o(t,function(t,n,r){for(var i=r.length;++n .fa").removeClass("fa-cloud-download").addClass("fa-refresh fa-spin"),(0,s["default"])(e,function(e){"updategrav"===e.type&&((0,o["default"])("[data-gpm-grav]").remove(),(0,o["default"])("#footer .grav-version").html(e.version)),t.removeAttr("disabled").find("> .fa").removeClass("fa-refresh fa-spin").addClass("fa-cloud-download")})})},function(t,e,n){(function(t){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(302),i=n(306),o=void 0,a=function(e){var n=arguments.length<=1||void 0===arguments[1]?{}:arguments[1],a=arguments.length<=2||void 0===arguments[2]?function(){return!0}:arguments[2];return"function"==typeof n&&(a=n,n={}),n.method&&"post"===n.method&&n.body&&!function(){var t=new FormData;n.body=Object.assign({"admin-nonce":i.config.admin_nonce},n.body),Object.keys(n.body).map(function(e){return t.append(e,n.body[e])}),n.body=t}(),n=Object.assign({credentials:"same-origin",headers:{Accept:"application/json"}},n),t(e,n).then(function(t){return o=t,t}).then(r.parseStatus).then(r.parseJSON).then(r.userFeedback).then(function(t){return a(t,o)})["catch"](r.userFeedbackError)};e["default"]=a}).call(e,n(3))},function(t,e,n){(function(t){"use strict";function e(t){return t&&t.__esModule?t:{"default":t}}var r=n(1),i=e(r),o=n(325),a=e(o),s=(0,i["default"])('input[type="radio"][name="channel-switch"]');s&&s.on("change",function(e){var n=(0,i["default"])(e.target),r=""+n.parent("[data-url]").data("url");(0,a["default"])(r,{method:"post",body:{task:"gpmRelease",release:n.val()}},function(e){e.reload&&t.location.reload()})})}).call(e,function(){return this}())},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(328),o=r(i),a=n(330);n(331),e["default"]={Chart:{Chart:o["default"],UpdatesChart:i.UpdatesChart,Instances:i.Instances},Cache:a.Instance}},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function a(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0}),e.Instances=e.UpdatesChart=e.defaults=void 0;var s=function k(t,e,n){null===t&&(t=Function.prototype);var r=Object.getOwnPropertyDescriptor(t,e);if(void 0===r){var i=Object.getPrototypeOf(t);return null===i?void 0:k(i,e,n)}if("value"in r)return r.value;var o=r.get;if(void 0!==o)return o.call(n)},u=function(){function t(t,e){for(var n=0;n{{ "PLUGIN_ADMIN.DROP_FILES_HERE_TO_UPLOAD"|tu|raw }}
+ {% endif %} +