mirror of
https://github.com/getgrav/grav-plugin-admin.git
synced 2025-11-02 03:16:11 +01:00
Feature/admin fragmentation (#856)
* Separated Admin Controller into a generic and extendable Base controller. Added Autoload for properly loading classes * Implemented custom class loader to force lowercase * Removed composer autoloader for classes * Updates * Ability to pass custom upload URL for files * Added new onAdminCanSave event for 3rd party plugins * Moved files upload GC in onOutputGenerated event * Cleanup * Moved autoloader so it is always registering * Fixed onOutputGenerated event location * Moved `taskRemoveFileFromBlueprint`, `taskRemoveMedia `, `canEditMedia` methods to admin base controller * Allow to globally define `blueprint_type` and `file_url_remove` for the file field * Moved `isMultilang()` into base controller * Properly generate thumbnails in proportions for file fields * Simplified execute restrictions with blacklist
This commit is contained in:
70
admin.php
70
admin.php
@@ -10,8 +10,13 @@ use Grav\Common\Page\Page;
|
||||
use Grav\Common\Page\Pages;
|
||||
use Grav\Common\Plugin;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\User\User;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
use Grav\Plugin\Admin\Admin;
|
||||
use Grav\Plugin\Admin\AdminTwigExtension;
|
||||
use Grav\Plugin\Admin\Popularity;
|
||||
use Grav\Plugin\Admin\Themes;
|
||||
use Grav\Plugin\Admin\AdminController;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\Session\Session;
|
||||
|
||||
@@ -96,6 +101,13 @@ class AdminPlugin extends Plugin
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
// Autoloader
|
||||
spl_autoload_register(function ($class) {
|
||||
if (Utils::startsWith($class, 'Grav\Plugin\Admin')) {
|
||||
require_once __DIR__ .'/classes/' . strtolower(basename(str_replace("\\", "/", $class))) . '.php';
|
||||
}
|
||||
});
|
||||
|
||||
$route = $this->config->get('plugins.admin.route');
|
||||
if (!$route) {
|
||||
return;
|
||||
@@ -303,14 +315,11 @@ class AdminPlugin extends Plugin
|
||||
|
||||
// Replace themes service with admin.
|
||||
$this->grav['themes'] = function () {
|
||||
require_once __DIR__ . '/classes/themes.php';
|
||||
|
||||
return new Themes($this->grav);
|
||||
};
|
||||
}
|
||||
|
||||
// We need popularity no matter what
|
||||
require_once __DIR__ . '/classes/popularity.php';
|
||||
$this->popularity = new Popularity();
|
||||
|
||||
// Fire even to register permissions from other plugins
|
||||
@@ -319,8 +328,8 @@ class AdminPlugin extends Plugin
|
||||
|
||||
protected function initializeController($task, $post)
|
||||
{
|
||||
require_once __DIR__ . '/classes/controller.php';
|
||||
$controller = new AdminController($this->grav, $this->template, $task, $this->route, $post);
|
||||
$controller = new AdminController();
|
||||
$controller->initialize($this->grav, $this->template, $task, $this->route, $post);
|
||||
$controller->execute();
|
||||
$controller->redirect();
|
||||
}
|
||||
@@ -375,23 +384,6 @@ class AdminPlugin extends Plugin
|
||||
exit();
|
||||
}
|
||||
|
||||
// Clear flash objects for previously uploaded files
|
||||
// whenever the user switches page / reloads
|
||||
// ignoring any JSON / extension call
|
||||
if (is_null($this->uri->extension()) && $task !== 'save') {
|
||||
// Discard any previously uploaded files session.
|
||||
// and if there were any uploaded file, remove them from the filesystem
|
||||
if ($flash = $this->session->getFlashObject('files-upload')) {
|
||||
$flash = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($flash));
|
||||
foreach ($flash as $key => $value) {
|
||||
if ($key !== 'tmp_name') {
|
||||
continue;
|
||||
}
|
||||
@unlink($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self = $this;
|
||||
|
||||
// make sure page is not frozen!
|
||||
@@ -633,17 +625,12 @@ class AdminPlugin extends Plugin
|
||||
'onAssetsInitialized' => ['onAssetsInitialized', 1000],
|
||||
'onTask.GPM' => ['onTaskGPM', 0],
|
||||
'onAdminRegisterPermissions' => ['onAdminRegisterPermissions', 0],
|
||||
'onOutputGenerated' => ['onOutputGenerated', 0],
|
||||
]);
|
||||
|
||||
// Initialize admin class.
|
||||
require_once __DIR__ . '/classes/admin.php';
|
||||
|
||||
// Autoload classes
|
||||
$autoload = __DIR__ . '/vendor/autoload.php';
|
||||
if (!is_file($autoload)) {
|
||||
throw new \Exception('Admin Plugin failed to load. Composer dependencies not met.');
|
||||
}
|
||||
require_once $autoload;
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
|
||||
// Check for required plugins
|
||||
if (!$this->grav['config']->get('plugins.login.enabled') || !$this->grav['config']->get('plugins.form.enabled') || !$this->grav['config']->get('plugins.email.enabled')) {
|
||||
@@ -671,6 +658,7 @@ class AdminPlugin extends Plugin
|
||||
$this->route = array_shift($array);
|
||||
}
|
||||
|
||||
// Initialize admin class.
|
||||
$this->admin = new Admin($this->grav, $this->admin_route, $this->template, $this->route);
|
||||
|
||||
|
||||
@@ -812,6 +800,26 @@ class AdminPlugin extends Plugin
|
||||
$this->grav['twig']->plugins_hooked_dashboard_widgets_main[] = ['template' => 'dashboard-pages'];
|
||||
}
|
||||
|
||||
public function onOutputGenerated()
|
||||
{
|
||||
// Clear flash objects for previously uploaded files
|
||||
// whenever the user switches page / reloads
|
||||
// ignoring any JSON / extension call
|
||||
if (is_null($this->uri->extension()) && $this->admin->task !== 'save') {
|
||||
// Discard any previously uploaded files session.
|
||||
// and if there were any uploaded file, remove them from the filesystem
|
||||
if ($flash = $this->session->getFlashObject('files-upload')) {
|
||||
$flash = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($flash));
|
||||
foreach ($flash as $key => $value) {
|
||||
if ($key !== 'tmp_name') {
|
||||
continue;
|
||||
}
|
||||
@unlink($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial stab at registering permissions (WIP)
|
||||
*
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
929
classes/adminbasecontroller.php
Normal file
929
classes/adminbasecontroller.php
Normal file
@@ -0,0 +1,929 @@
|
||||
<?php
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Media;
|
||||
use Grav\Common\Utils;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use RocketTheme\Toolbox\File\File;
|
||||
|
||||
/**
|
||||
* Class AdminController
|
||||
*
|
||||
* @package Grav\Plugin
|
||||
*/
|
||||
class AdminBaseController
|
||||
{
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
public $grav;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $view;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $task;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $route;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $post;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* @var \Grav\Common\Uri
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* @var Admin
|
||||
*/
|
||||
protected $admin;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $redirect;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $redirectCode;
|
||||
|
||||
protected $upload_errors = [
|
||||
0 => "There is no error, the file uploaded with success",
|
||||
1 => "The uploaded file exceeds the max upload size",
|
||||
2 => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML",
|
||||
3 => "The uploaded file was only partially uploaded",
|
||||
4 => "No file was uploaded",
|
||||
6 => "Missing a temporary folder",
|
||||
7 => "Failed to write file to disk",
|
||||
8 => "A PHP extension stopped the file upload"
|
||||
];
|
||||
|
||||
/** @var array */
|
||||
public $blacklist_views = [];
|
||||
|
||||
/**
|
||||
* Performs a task.
|
||||
*
|
||||
* @return bool True if the action was performed successfully.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
if (in_array($this->view, $this->blacklist_views)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->validateNonce()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$method = 'task' . ucfirst($this->task);
|
||||
|
||||
if (method_exists($this, $method)) {
|
||||
try {
|
||||
$success = call_user_func([$this, $method]);
|
||||
} catch (\RuntimeException $e) {
|
||||
$success = true;
|
||||
$this->admin->setMessage($e->getMessage(), 'error');
|
||||
}
|
||||
} else {
|
||||
$success = $this->grav->fireEvent('onAdminTaskExecute', new Event(['controller' => $this, 'method' => $method]));
|
||||
}
|
||||
|
||||
// Grab redirect parameter.
|
||||
$redirect = isset($this->post['_redirect']) ? $this->post['_redirect'] : null;
|
||||
unset($this->post['_redirect']);
|
||||
|
||||
// Redirect if requested.
|
||||
if ($redirect) {
|
||||
$this->setRedirect($redirect);
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
protected function validateNonce()
|
||||
{
|
||||
if (method_exists('Grav\Common\Utils', 'getNonce')) {
|
||||
if (strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
|
||||
if (isset($this->post['admin-nonce'])) {
|
||||
$nonce = $this->post['admin-nonce'];
|
||||
} else {
|
||||
$nonce = $this->grav['uri']->param('admin-nonce');
|
||||
}
|
||||
|
||||
if (!$nonce || !Utils::verifyNonce($nonce, 'admin-form')) {
|
||||
if ($this->task == 'addmedia') {
|
||||
|
||||
$message = sprintf($this->admin->translate('PLUGIN_ADMIN.FILE_TOO_LARGE', null),
|
||||
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',
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
unset($this->post['admin-nonce']);
|
||||
} else {
|
||||
if ($this->task == 'logout') {
|
||||
$nonce = $this->grav['uri']->param('logout-nonce');
|
||||
if (!isset($nonce) || !Utils::verifyNonce($nonce, 'logout-form')) {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),
|
||||
'error');
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$nonce = $this->grav['uri']->param('admin-nonce');
|
||||
if (!isset($nonce) || !Utils::verifyNonce($nonce, 'admin-form')) {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),
|
||||
'error');
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the page redirect.
|
||||
*
|
||||
* @param string $path The path to redirect to
|
||||
* @param int $code The HTTP redirect code
|
||||
*/
|
||||
public function setRedirect($path, $code = 303)
|
||||
{
|
||||
$this->redirect = $path;
|
||||
$this->redirectCode = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles ajax upload for files.
|
||||
* Stores in a flash object the temporary file and deals with potential file errors.
|
||||
*
|
||||
* @return bool True if the action was performed.
|
||||
*/
|
||||
public function taskFilesUpload()
|
||||
{
|
||||
if (!$this->authorizeTask('save', $this->dataPermissions()) || !isset($_FILES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
$data = $this->view == 'pages' ? $this->admin->page(true) : $this->prepareData([]);
|
||||
$settings = $data->blueprints()->schema()->getProperty($this->post['name']);
|
||||
$settings = (object)array_merge(
|
||||
['avoid_overwriting' => false,
|
||||
'random_name' => false,
|
||||
'accept' => ['image/*'],
|
||||
'limit' => 10,
|
||||
'filesize' => $config->get('system.media.upload_limit', 5242880) // 5MB
|
||||
],
|
||||
(array)$settings,
|
||||
['name' => $this->post['name']]
|
||||
);
|
||||
|
||||
$upload = $this->normalizeFiles($_FILES['data'], $settings->name);
|
||||
|
||||
if (!isset($settings->destination)) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED', null)
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not use self@ outside of pages
|
||||
if ($this->view != 'pages' && in_array($settings->destination, ['@self', 'self@'])) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), $settings->destination)
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle errors and breaks without proceeding further
|
||||
if ($upload->file->error != UPLOAD_ERR_OK) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null), $upload->file->name,
|
||||
$this->upload_errors[$upload->file->error])
|
||||
];
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// Remove the error object to avoid storing it
|
||||
unset($upload->file->error);
|
||||
|
||||
// we need to move the file at this stage or else
|
||||
// it won't be available upon save later on
|
||||
// since php removes it from the upload location
|
||||
$tmp_dir = Admin::getTempDir();
|
||||
$tmp_file = $upload->file->tmp_name;
|
||||
$tmp = $tmp_dir . '/uploaded-files/' . basename($tmp_file);
|
||||
|
||||
Folder::create(dirname($tmp));
|
||||
if (!move_uploaded_file($tmp_file, $tmp)) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_MOVE', null), '', $tmp)
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload->file->tmp_name = $tmp;
|
||||
}
|
||||
|
||||
// Handle file size limits
|
||||
$settings->filesize *= 1048576; // 2^20 [MB in Bytes]
|
||||
if ($settings->filesize > 0 && $upload->file->size > $settings->filesize) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Handle Accepted file types
|
||||
// Accept can only be mime types (image/png | image/*) or file extensions (.pdf|.jpg)
|
||||
$accepted = false;
|
||||
$errors = [];
|
||||
foreach ((array)$settings->accept as $type) {
|
||||
// Force acceptance of any file when star notation
|
||||
if ($type == '*') {
|
||||
$accepted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$isMime = strstr($type, '/');
|
||||
$find = str_replace('*', '.*', $type);
|
||||
|
||||
$match = preg_match('#' . $find . '$#', $isMime ? $upload->file->type : $upload->file->name);
|
||||
if (!$match) {
|
||||
$message = $isMime ? 'The MIME type "' . $upload->file->type . '"' : 'The File Extension';
|
||||
$errors[] = $message . ' for the file "' . $upload->file->name . '" is not an accepted.';
|
||||
$accepted |= false;
|
||||
} else {
|
||||
$accepted |= true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$accepted) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => implode('<br />', $errors)
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve the current session of the uploaded files for the field
|
||||
// and initialize it if it doesn't exist
|
||||
$sessionField = base64_encode($this->grav['uri']->url());
|
||||
$flash = $this->admin->session()->getFlashObject('files-upload');
|
||||
if (!$flash) {
|
||||
$flash = [];
|
||||
}
|
||||
if (!isset($flash[$sessionField])) {
|
||||
$flash[$sessionField] = [];
|
||||
}
|
||||
if (!isset($flash[$sessionField][$upload->field])) {
|
||||
$flash[$sessionField][$upload->field] = [];
|
||||
}
|
||||
|
||||
// Set destination
|
||||
$destination = Folder::getRelativePath(rtrim($settings->destination, '/'));
|
||||
$destination = $this->admin->getPagePathFromToken($destination);
|
||||
|
||||
// Create destination if needed
|
||||
if (!is_dir($destination)) {
|
||||
Folder::mkdir($destination);
|
||||
}
|
||||
|
||||
// Generate random name if required
|
||||
if ($settings->random_name) { // TODO: document
|
||||
$extension = pathinfo($upload->file->name)['extension'];
|
||||
$upload->file->name = Utils::generateRandomString(15) . '.' . $extension;
|
||||
}
|
||||
|
||||
// Handle conflicting name if needed
|
||||
if ($settings->avoid_overwriting) { // TODO: document
|
||||
if (file_exists($destination . '/' . $upload->file->name)) {
|
||||
$upload->file->name = date('YmdHis') . '-' . $upload->file->name;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare object for later save
|
||||
$path = $destination . '/' . $upload->file->name;
|
||||
$upload->file->path = $path;
|
||||
// $upload->file->route = $page ? $path : null;
|
||||
|
||||
// Prepare data to be saved later
|
||||
$flash[$sessionField][$upload->field][$path] = (array)$upload->file;
|
||||
|
||||
// Finally store the new uploaded file in the field session
|
||||
$this->admin->session()->setFlashObject('files-upload', $flash);
|
||||
$this->admin->json_response = [
|
||||
'status' => 'success',
|
||||
'session' => \json_encode([
|
||||
'sessionField' => base64_encode($this->grav['uri']->url()),
|
||||
'path' => $upload->file->path,
|
||||
'field' => $settings->name
|
||||
])
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user is allowed to perform the given task with its associated permissions
|
||||
*
|
||||
* @param string $task The task to execute
|
||||
* @param array $permissions The permissions given
|
||||
*
|
||||
* @return bool True if authorized. False if not.
|
||||
*/
|
||||
protected function authorizeTask($task = '', $permissions = [])
|
||||
{
|
||||
if (!$this->admin->authorize($permissions)) {
|
||||
if ($this->grav['uri']->extension() === 'json') {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'unauthorized',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' ' . $task . '.'
|
||||
];
|
||||
} else {
|
||||
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' ' . $task . '.',
|
||||
'error');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the permissions needed to access a given view
|
||||
*
|
||||
* @return array An array of permissions
|
||||
*/
|
||||
protected function dataPermissions()
|
||||
{
|
||||
$type = $this->view;
|
||||
$permissions = ['admin.super'];
|
||||
|
||||
switch ($type) {
|
||||
case 'configuration':
|
||||
case 'system':
|
||||
$permissions[] = 'admin.configuration';
|
||||
break;
|
||||
case 'settings':
|
||||
case 'site':
|
||||
$permissions[] = 'admin.settings';
|
||||
break;
|
||||
case 'plugins':
|
||||
$permissions[] = 'admin.plugins';
|
||||
break;
|
||||
case 'themes':
|
||||
$permissions[] = 'admin.themes';
|
||||
break;
|
||||
case 'users':
|
||||
$permissions[] = 'admin.users';
|
||||
break;
|
||||
case 'pages':
|
||||
$permissions[] = 'admin.pages';
|
||||
break;
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration data for a given view & post
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareData(array $data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to normalize the $_FILES array
|
||||
*
|
||||
* @param array $data $_FILES starting point data
|
||||
* @param string $key
|
||||
*
|
||||
* @return object a new Object with a normalized list of files
|
||||
*/
|
||||
protected function normalizeFiles($data, $key = '')
|
||||
{
|
||||
$files = new \stdClass();
|
||||
$files->field = $key;
|
||||
$files->file = new \stdClass();
|
||||
|
||||
foreach ($data as $fieldName => $fieldValue) {
|
||||
// Since Files Upload are always happening via Ajax
|
||||
// we are not interested in handling `multiple="true"`
|
||||
// because they are always handled one at a time.
|
||||
// For this reason we normalize the value to string,
|
||||
// in case it is arriving as an array.
|
||||
$value = (array)Utils::getDotNotation($fieldValue, $key);
|
||||
$files->file->{$fieldName} = array_shift($value);
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a file from the flash object session, before it gets saved
|
||||
*
|
||||
* @return bool True if the action was performed.
|
||||
*/
|
||||
public function taskFilesSessionRemove()
|
||||
{
|
||||
if (!$this->authorizeTask('save', $this->dataPermissions()) || !isset($_FILES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve the current session of the uploaded files for the field
|
||||
// and initialize it if it doesn't exist
|
||||
$sessionField = base64_encode($this->grav['uri']->url());
|
||||
$request = \json_decode($this->post['session']);
|
||||
|
||||
// Ensure the URI requested matches the current one, otherwise fail
|
||||
if ($request->sessionField !== $sessionField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve the flash object and remove the requested file from it
|
||||
$flash = $this->admin->session()->getFlashObject('files-upload');
|
||||
$endpoint = $flash[$request->sessionField][$request->field][$request->path];
|
||||
|
||||
if (isset($endpoint)) {
|
||||
if (file_exists($endpoint['tmp_name'])) {
|
||||
unlink($endpoint['tmp_name']);
|
||||
}
|
||||
|
||||
unset($endpoint);
|
||||
}
|
||||
|
||||
// Walk backward to cleanup any empty field that's left
|
||||
// Field
|
||||
if (!count($flash[$request->sessionField][$request->field])) {
|
||||
unset($flash[$request->sessionField][$request->field]);
|
||||
}
|
||||
|
||||
// Session Field
|
||||
if (!count($flash[$request->sessionField])) {
|
||||
unset($flash[$request->sessionField]);
|
||||
}
|
||||
|
||||
|
||||
// If there's anything left to restore in the flash object, do so
|
||||
if (count($flash)) {
|
||||
$this->admin->session()->setFlashObject('files-upload', $flash);
|
||||
}
|
||||
|
||||
$this->admin->json_response = ['status' => 'success'];
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the route stored in $this->redirect
|
||||
*/
|
||||
public function redirect()
|
||||
{
|
||||
if (!$this->redirect) {
|
||||
return;
|
||||
}
|
||||
|
||||
$base = $this->admin->base;
|
||||
$this->redirect = '/' . ltrim($this->redirect, '/');
|
||||
$multilang = $this->isMultilang();
|
||||
|
||||
$redirect = '';
|
||||
if ($multilang) {
|
||||
// if base path does not already contain the lang code, add it
|
||||
$langPrefix = '/' . $this->grav['session']->admin_lang;
|
||||
if (!Utils::startsWith($base, $langPrefix . '/')) {
|
||||
$base = $langPrefix . $base;
|
||||
}
|
||||
|
||||
// now the first 4 chars of base contain the lang code.
|
||||
// if redirect path already contains the lang code, and is != than the base lang code, then use redirect path as-is
|
||||
if (Utils::pathPrefixedByLangCode($base) && Utils::pathPrefixedByLangCode($this->redirect)
|
||||
&& substr($base,
|
||||
0, 4) != substr($this->redirect, 0, 4)
|
||||
) {
|
||||
$redirect = $this->redirect;
|
||||
} else {
|
||||
if (!Utils::startsWith($this->redirect, $base)) {
|
||||
$this->redirect = $base . $this->redirect;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!Utils::startsWith($this->redirect, $base)) {
|
||||
$this->redirect = $base . $this->redirect;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$redirect) {
|
||||
$redirect = $this->redirect;
|
||||
}
|
||||
|
||||
$this->grav->redirect($redirect, $this->redirectCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and return POST data.
|
||||
*
|
||||
* @param array $post
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getPost($post)
|
||||
{
|
||||
unset($post['task']);
|
||||
|
||||
// Decode JSON encoded fields and merge them to data.
|
||||
if (isset($post['_json'])) {
|
||||
$post = array_replace_recursive($post, $this->jsonDecode($post['_json']));
|
||||
unset($post['_json']);
|
||||
}
|
||||
|
||||
$post = $this->cleanDataKeys($post);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively JSON decode data.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function jsonDecode(array $data)
|
||||
{
|
||||
foreach ($data as &$value) {
|
||||
if (is_array($value)) {
|
||||
$value = $this->jsonDecode($value);
|
||||
} else {
|
||||
$value = json_decode($value, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function cleanDataKeys($source = [])
|
||||
{
|
||||
$out = [];
|
||||
|
||||
if (is_array($source)) {
|
||||
foreach ($source as $key => $value) {
|
||||
$key = str_replace('%5B', '[', str_replace('%5D', ']', $key));
|
||||
if (is_array($value)) {
|
||||
$out[$key] = $this->cleanDataKeys($value);
|
||||
} else {
|
||||
$out[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if multilang is active
|
||||
*
|
||||
* @return bool True if multilang is active
|
||||
*/
|
||||
protected function isMultilang()
|
||||
{
|
||||
return count($this->grav['config']->get('system.languages.supported', [])) > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Grav\Common\Page\Page|\Grav\Common\Data\Data $obj
|
||||
*
|
||||
* @return \Grav\Common\Page\Page|\Grav\Common\Data\Data
|
||||
*/
|
||||
protected function storeFiles($obj)
|
||||
{
|
||||
// Process previously uploaded files for the current URI
|
||||
// and finally store them. Everything else will get discarded
|
||||
$queue = $this->admin->session()->getFlashObject('files-upload');
|
||||
$queue = $queue[base64_encode($this->grav['uri']->url())];
|
||||
if (is_array($queue)) {
|
||||
foreach ($queue as $key => $files) {
|
||||
foreach ($files as $destination => $file) {
|
||||
if (!rename($file['tmp_name'], $destination)) {
|
||||
throw new \RuntimeException(sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_MOVE', null),
|
||||
'"' . $file['tmp_name'] . '"', $destination));
|
||||
}
|
||||
|
||||
unset($files[$destination]['tmp_name']);
|
||||
}
|
||||
|
||||
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), $files, true);
|
||||
} else {
|
||||
$new_data = $files;
|
||||
}
|
||||
if (isset($data['header'][$init_key])) {
|
||||
$obj->modifyHeader($init_key, array_replace_recursive([], $data['header'][$init_key], $new_data));
|
||||
} else {
|
||||
$obj->modifyHeader($init_key, $new_data);
|
||||
}
|
||||
} else {
|
||||
// TODO: [this is JS handled] if it's single file, remove existing and use set, if it's multiple, use join
|
||||
$obj->join($key, $files); // stores
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the filepicker field to get a list of files in a folder.
|
||||
*/
|
||||
protected function taskGetFilesInFolder()
|
||||
{
|
||||
if (!$this->authorizeTask('save', $this->dataPermissions())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->view == 'pages' ? $this->admin->page(true) : $this->prepareData([]);
|
||||
$settings = $data->blueprints()->schema()->getProperty($this->post['name']);
|
||||
|
||||
if (isset($settings['folder'])) {
|
||||
$folder = $settings['folder'];
|
||||
} else {
|
||||
$folder = '@self';
|
||||
}
|
||||
|
||||
// Do not use self@ outside of pages
|
||||
if ($this->view != 'pages' && in_array($folder, ['@self', 'self@'])) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'error',
|
||||
'message' => sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), $folder)
|
||||
];
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set destination
|
||||
$folder = Folder::getRelativePath(rtrim($folder, '/'));
|
||||
$folder = $this->admin->getPagePathFromToken($folder);
|
||||
|
||||
$media = new Media($folder);
|
||||
$available_files = [];
|
||||
|
||||
foreach ($media->all() as $name => $medium) {
|
||||
$available_files[] = $name;
|
||||
}
|
||||
|
||||
// Peak in the flashObject for optimistic filepicker updates
|
||||
$pending_files = [];
|
||||
$sessionField = base64_encode($this->grav['uri']->url());
|
||||
$flash = $this->admin->session()->getFlashObject('files-upload');
|
||||
|
||||
if ($flash && isset($flash[$sessionField])) {
|
||||
foreach ($flash[$sessionField] as $field => $data) {
|
||||
foreach ($data as $file) {
|
||||
if (dirname($file['path']) === $folder) {
|
||||
$pending_files[] = $file['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->admin->session()->setFlashObject('files-upload', $flash);
|
||||
|
||||
// Handle Accepted file types
|
||||
// Accept can only be file extensions (.pdf|.jpg)
|
||||
if (isset($settings['accept'])) {
|
||||
$available_files = array_filter($available_files, function ($file) use ($settings) {
|
||||
return $this->filterAcceptedFiles($file, $settings);
|
||||
});
|
||||
|
||||
$pending_files = array_filter($pending_files, function ($file) use ($settings) {
|
||||
return $this->filterAcceptedFiles($file, $settings);
|
||||
});
|
||||
}
|
||||
|
||||
$this->admin->json_response = [
|
||||
'status' => 'success',
|
||||
'files' => array_values($available_files),
|
||||
'pending' => array_values($pending_files),
|
||||
'folder' => $folder
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function filterAcceptedFiles($file, $settings)
|
||||
{
|
||||
$valid = false;
|
||||
|
||||
foreach ((array)$settings['accept'] as $type) {
|
||||
$find = str_replace('*', '.*', $type);
|
||||
$valid |= preg_match('#' . $find . '$#', $file);
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle deleting a file from a blueprint
|
||||
*
|
||||
* @return bool True if the action was performed.
|
||||
*/
|
||||
protected function taskRemoveFileFromBlueprint()
|
||||
{
|
||||
$uri = $this->grav['uri'];
|
||||
$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');
|
||||
|
||||
$event = $this->grav->fireEvent('onAdminCanSave', new Event(['controller' => &$this]));
|
||||
if (!$event['can_save']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->taskRemoveMedia();
|
||||
|
||||
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);
|
||||
|
||||
if (isset($data[$path])) {
|
||||
unset($data[$path]);
|
||||
Utils::setDotNotation($header, $data_path, $data);
|
||||
$page->header($header);
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
if ($files) {
|
||||
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->admin->json_response = [
|
||||
'status' => 'success',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles removing a media file
|
||||
*
|
||||
* @return bool True if the action was performed
|
||||
*/
|
||||
public function taskRemoveMedia()
|
||||
{
|
||||
if (!$this->canEditMedia()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filename = base64_decode($this->grav['uri']->param('route'));
|
||||
|
||||
$file = File::instance($filename);
|
||||
$resultRemoveMedia = false;
|
||||
$resultRemoveMediaMeta = true;
|
||||
|
||||
if ($file->exists()) {
|
||||
$resultRemoveMedia = $file->delete();
|
||||
|
||||
$metaFilePath = $filename . '.meta.yaml';
|
||||
$metaFilePath = str_replace('@3x', '', $metaFilePath);
|
||||
$metaFilePath = str_replace('@2x', '', $metaFilePath);
|
||||
|
||||
if (is_file($metaFilePath)) {
|
||||
$metaFile = File::instance($metaFilePath);
|
||||
$resultRemoveMediaMeta = $metaFile->delete();
|
||||
}
|
||||
}
|
||||
|
||||
if ($resultRemoveMedia && $resultRemoveMediaMeta) {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'success',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')
|
||||
];
|
||||
|
||||
return true;
|
||||
} else {
|
||||
$this->admin->json_response = [
|
||||
'status' => 'success',
|
||||
'message' => $this->admin->translate('PLUGIN_ADMIN.REMOVE_FAILED')
|
||||
];
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user can edit media
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return bool True if the media action is allowed
|
||||
*/
|
||||
protected function canEditMedia($type = 'media')
|
||||
{
|
||||
if (!$this->authorizeTask('edit media', ['admin.' . $type, 'admin.super'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Page\Page;
|
||||
use Grav\Common\Data;
|
||||
|
||||
/**
|
||||
* Class Popularity
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
/**
|
||||
* Admin theme object
|
||||
|
||||
@@ -14,6 +14,8 @@ class Utils
|
||||
/**
|
||||
* Matches an email to a user
|
||||
*
|
||||
* @param $email
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
public static function findUserByEmail($email)
|
||||
|
||||
111
composer.lock
generated
111
composer.lock
generated
@@ -166,16 +166,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "behat/gherkin",
|
||||
"version": "v4.4.4",
|
||||
"version": "v4.4.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Behat/Gherkin.git",
|
||||
"reference": "cf8cc94647101e02a33d690245896d83d880aea1"
|
||||
"reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Behat/Gherkin/zipball/cf8cc94647101e02a33d690245896d83d880aea1",
|
||||
"reference": "cf8cc94647101e02a33d690245896d83d880aea1",
|
||||
"url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74",
|
||||
"reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -221,20 +221,20 @@
|
||||
"gherkin",
|
||||
"parser"
|
||||
],
|
||||
"time": "2016-09-18 12:16:14"
|
||||
"time": "2016-10-30 11:50:56"
|
||||
},
|
||||
{
|
||||
"name": "codeception/codeception",
|
||||
"version": "2.2.5",
|
||||
"version": "2.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Codeception/Codeception.git",
|
||||
"reference": "b4729341e469d0f174f3cade85718ff5bf8dd751"
|
||||
"reference": "5fbe312c8138e71458ec1e715b0ce262331ca5a2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/b4729341e469d0f174f3cade85718ff5bf8dd751",
|
||||
"reference": "b4729341e469d0f174f3cade85718ff5bf8dd751",
|
||||
"url": "https://api.github.com/repos/Codeception/Codeception/zipball/5fbe312c8138e71458ec1e715b0ce262331ca5a2",
|
||||
"reference": "5fbe312c8138e71458ec1e715b0ce262331ca5a2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -312,7 +312,7 @@
|
||||
"functional testing",
|
||||
"unit testing"
|
||||
],
|
||||
"time": "2016-09-29 01:29:59"
|
||||
"time": "2016-10-27 00:00:34"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
@@ -370,24 +370,25 @@
|
||||
},
|
||||
{
|
||||
"name": "facebook/webdriver",
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/php-webdriver.git",
|
||||
"reference": "b7186fb1bcfda956d237f59face250d06ef47253"
|
||||
"reference": "af21de3ae5306a8ca0bcc02a19735dadc43e83f3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/b7186fb1bcfda956d237f59face250d06ef47253",
|
||||
"reference": "b7186fb1bcfda956d237f59face250d06ef47253",
|
||||
"url": "https://api.github.com/repos/facebook/php-webdriver/zipball/af21de3ae5306a8ca0bcc02a19735dadc43e83f3",
|
||||
"reference": "af21de3ae5306a8ca0bcc02a19735dadc43e83f3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"php": ">=5.3.19"
|
||||
"php": "^5.5 || ~7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^1.11",
|
||||
"php-mock/php-mock-phpunit": "^1.1",
|
||||
"phpunit/phpunit": "4.6.* || ~5.0",
|
||||
"squizlabs/php_codesniffer": "^2.6"
|
||||
},
|
||||
@@ -412,7 +413,7 @@
|
||||
"selenium",
|
||||
"webdriver"
|
||||
],
|
||||
"time": "2016-08-10 00:44:08"
|
||||
"time": "2016-10-14 15:16:51"
|
||||
},
|
||||
{
|
||||
"name": "fzaninotto/faker",
|
||||
@@ -635,16 +636,16 @@
|
||||
},
|
||||
{
|
||||
"name": "myclabs/deep-copy",
|
||||
"version": "1.5.4",
|
||||
"version": "1.5.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/DeepCopy.git",
|
||||
"reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f"
|
||||
"reference": "399c1f9781e222f6eb6cc238796f5200d1b7f108"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/ea74994a3dc7f8d2f65a06009348f2d63c81e61f",
|
||||
"reference": "ea74994a3dc7f8d2f65a06009348f2d63c81e61f",
|
||||
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/399c1f9781e222f6eb6cc238796f5200d1b7f108",
|
||||
"reference": "399c1f9781e222f6eb6cc238796f5200d1b7f108",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -673,7 +674,7 @@
|
||||
"object",
|
||||
"object graph"
|
||||
],
|
||||
"time": "2016-09-16 13:37:59"
|
||||
"time": "2016-10-31 17:19:45"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-common",
|
||||
@@ -885,16 +886,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "4.0.1",
|
||||
"version": "4.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3"
|
||||
"reference": "6cba06ff75a1a63a71033e1a01b89056f3af1e8d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3",
|
||||
"reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6cba06ff75a1a63a71033e1a01b89056f3af1e8d",
|
||||
"reference": "6cba06ff75a1a63a71033e1a01b89056f3af1e8d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -944,7 +945,7 @@
|
||||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2016-07-26 14:39:29"
|
||||
"time": "2016-11-01 05:06:24"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
@@ -1129,16 +1130,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "5.6.1",
|
||||
"version": "5.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7"
|
||||
"reference": "cd13b23ac5a519a4708e00736c26ee0bb28b2e01"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60c32c5b5e79c2248001efa2560f831da11cc2d7",
|
||||
"reference": "60c32c5b5e79c2248001efa2560f831da11cc2d7",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/cd13b23ac5a519a4708e00736c26ee0bb28b2e01",
|
||||
"reference": "cd13b23ac5a519a4708e00736c26ee0bb28b2e01",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1207,7 +1208,7 @@
|
||||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"time": "2016-10-07 13:03:26"
|
||||
"time": "2016-10-25 07:40:25"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit-mock-objects",
|
||||
@@ -1880,7 +1881,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/browser-kit",
|
||||
"version": "v3.1.5",
|
||||
"version": "v3.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/browser-kit.git",
|
||||
@@ -1937,16 +1938,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v3.1.5",
|
||||
"version": "v3.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0"
|
||||
"reference": "c99da1119ae61e15de0e4829196b9fba6f73d065"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
|
||||
"reference": "6cb0872fb57b38b3b09ff213c21ed693956b9eb0",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/c99da1119ae61e15de0e4829196b9fba6f73d065",
|
||||
"reference": "c99da1119ae61e15de0e4829196b9fba6f73d065",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1994,11 +1995,11 @@
|
||||
],
|
||||
"description": "Symfony Console Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-09-28 00:11:12"
|
||||
"time": "2016-10-06 01:44:51"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v3.1.5",
|
||||
"version": "v3.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
@@ -2051,7 +2052,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/debug",
|
||||
"version": "v3.1.5",
|
||||
"version": "v3.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/debug.git",
|
||||
@@ -2108,16 +2109,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/dom-crawler",
|
||||
"version": "v3.1.5",
|
||||
"version": "v3.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/dom-crawler.git",
|
||||
"reference": "bb7395e8b1db3654de82b9f35d019958276de4d7"
|
||||
"reference": "59eee3c76eb89f21857798620ebdad7a05ad14f4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/bb7395e8b1db3654de82b9f35d019958276de4d7",
|
||||
"reference": "bb7395e8b1db3654de82b9f35d019958276de4d7",
|
||||
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/59eee3c76eb89f21857798620ebdad7a05ad14f4",
|
||||
"reference": "59eee3c76eb89f21857798620ebdad7a05ad14f4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2160,20 +2161,20 @@
|
||||
],
|
||||
"description": "Symfony DomCrawler Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-08-05 08:37:39"
|
||||
"time": "2016-10-18 15:46:07"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v3.1.5",
|
||||
"version": "v3.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "c0c00c80b3a69132c4e55c3e7db32b4a387615e5"
|
||||
"reference": "28b0832b2553ffb80cabef6a7a812ff1e670c0bc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/c0c00c80b3a69132c4e55c3e7db32b4a387615e5",
|
||||
"reference": "c0c00c80b3a69132c4e55c3e7db32b4a387615e5",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/28b0832b2553ffb80cabef6a7a812ff1e670c0bc",
|
||||
"reference": "28b0832b2553ffb80cabef6a7a812ff1e670c0bc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2220,11 +2221,11 @@
|
||||
],
|
||||
"description": "Symfony EventDispatcher Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-07-19 10:45:57"
|
||||
"time": "2016-10-13 06:28:43"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v3.1.5",
|
||||
"version": "v3.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
@@ -2332,16 +2333,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v3.1.5",
|
||||
"version": "v3.1.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3"
|
||||
"reference": "7ff51b06c6c3d5cc6686df69004a42c69df09e27"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/368b9738d4033c8b93454cb0dbd45d305135a6d3",
|
||||
"reference": "368b9738d4033c8b93454cb0dbd45d305135a6d3",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/7ff51b06c6c3d5cc6686df69004a42c69df09e27",
|
||||
"reference": "7ff51b06c6c3d5cc6686df69004a42c69df09e27",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2377,7 +2378,7 @@
|
||||
],
|
||||
"description": "Symfony Yaml Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2016-09-25 08:27:07"
|
||||
"time": "2016-10-24 18:41:13"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
|
||||
@@ -108,7 +108,10 @@ export default class FilesField {
|
||||
|
||||
dropzone.files.push(mock);
|
||||
dropzone.options.addedfile.call(dropzone, mock);
|
||||
if (mock.type.match(/^image\//)) dropzone.options.thumbnail.call(dropzone, mock, data.path);
|
||||
if (mock.type.match(/^image\//)) {
|
||||
dropzone.options.thumbnail.call(dropzone, mock, data.path);
|
||||
dropzone.createThumbnailFromUrl(mock, data.path);
|
||||
}
|
||||
|
||||
file.remove();
|
||||
});
|
||||
|
||||
2
themes/grav/js/admin.min.js
vendored
2
themes/grav/js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -30,7 +30,7 @@
|
||||
{% set files = global.files %}
|
||||
{% set config = global.grav.config %}
|
||||
{% set route = global.context.route() %}
|
||||
{% set type = get_class(global.context) == 'Grav\\Common\\Page\\Page' ? 'pages' : global.plugin ? 'plugins' : global.theme ? 'themes' : 'config' %}
|
||||
{% set type = global.blueprint_type ? global.blueprint_type : (get_class(global.context) == 'Grav\\Common\\Page\\Page' ? 'pages' : global.plugin ? 'plugins' : global.theme ? 'themes' : 'config') %}
|
||||
|
||||
{% set blueprint_name = global.blueprints.getFilename %}
|
||||
{% if type == 'pages' %}
|
||||
@@ -38,8 +38,9 @@
|
||||
{% endif %}
|
||||
{% set blueprint = base64_encode(blueprint_name) %}
|
||||
{% set real_path = global.admin.getPagePathFromToken(path) %}
|
||||
{% set remove = uri.addNonce(global.base_url_relative ~
|
||||
'/media.json' ~
|
||||
{% set remove = global.file_url_remove ? global.file_url_remove : (global.base_url_relative ~ '/media.json') %}
|
||||
{% set remove = uri.addNonce(
|
||||
remove ~
|
||||
'/route' ~ config.system.param_sep ~ base64_encode(global.base_path ~ '/' ~ real_path) ~
|
||||
'/task' ~ config.system.param_sep ~ 'removeFileFromBlueprint' ~
|
||||
'/proute' ~ config.system.param_sep ~ base64_encode(route) ~
|
||||
@@ -59,7 +60,7 @@
|
||||
|
||||
{% set settings = {name: field.name, paramName: (scope ~ field.name)|fieldName ~ (files.multiple ? '[]' : ''), limit: limit, filesize: files.filesize, accept: files.accept} %}
|
||||
|
||||
<div class="form-input-wrapper dropzone files-upload {% if field.fancy is not same as(false) %}form-input-file{% endif %} {{ field.size|default('xlarge') }}" data-grav-file-settings="{{ settings|json_encode|e('html_attr') }}">
|
||||
<div class="form-input-wrapper dropzone files-upload {% if field.fancy is not same as(false) %}form-input-file{% endif %} {{ field.size|default('xlarge') }}" data-grav-file-settings="{{ settings|json_encode|e('html_attr') }}" {% if file_url_add %}data-file-url-add="{{ file_url_add }}"{% endif %} {% if file_url_add %}data-file-url-remove="{{ file_url_add }}"{% endif %}>
|
||||
<input
|
||||
{# required attribute structures #}
|
||||
{% block input_attributes %}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
namespace Grav\Plugin\Admin;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Language\Language;
|
||||
|
||||
12
vendor/composer/ClassLoader.php
vendored
12
vendor/composer/ClassLoader.php
vendored
@@ -53,8 +53,8 @@ class ClassLoader
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
@@ -322,20 +322,20 @@ class ClassLoader
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative) {
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if ($file === null && defined('HHVM_VERSION')) {
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if ($file === null) {
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
return $this->classMap[$class] = false;
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
@@ -399,6 +399,8 @@ class ClassLoader
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user