Files
Grav-Admin-Plugin/classes/controller.php

2652 lines
85 KiB
PHP
Raw Normal View History

2014-08-05 13:06:38 -07:00
<?php
namespace Grav\Plugin;
use Grav\Common\Cache;
2014-10-01 22:28:16 +03:00
use Grav\Common\Config\Config;
use Grav\Common\File\CompiledYamlFile;
2014-08-05 13:06:38 -07:00
use Grav\Common\Filesystem\Folder;
use Grav\Common\GPM\Installer;
use Grav\Common\Grav;
2014-08-05 13:06:38 -07:00
use Grav\Common\Data;
2016-07-18 15:42:38 -06:00
use Grav\Common\Page\Media;
2016-01-21 09:46:38 +02:00
use Grav\Common\Page\Page;
2015-10-21 19:46:51 +02:00
use Grav\Common\Page\Pages;
use Grav\Common\Page\Collection;
use Grav\Common\Plugin;
use Grav\Common\Theme;
use Grav\Common\User\User;
2015-04-27 13:37:22 +02:00
use Grav\Common\Utils;
use Grav\Common\Backup\ZipBackup;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\File\File;
2015-04-27 13:37:22 +02:00
use RocketTheme\Toolbox\File\JsonFile;
use Symfony\Component\Yaml\Exception\ParseException;
2015-08-04 19:28:42 +02:00
use Symfony\Component\Yaml\Yaml;
2014-08-05 13:06:38 -07:00
2016-02-12 10:16:28 +01:00
/**
* Class AdminController
* @package Grav\Plugin
*/
2014-08-05 13:06:38 -07:00
class AdminController
{
/**
* @var Grav
*/
public $grav;
2014-08-05 13:06:38 -07:00
/**
* @var string
*/
public $view;
/**
* @var string
*/
public $task;
/**
* @var string
*/
public $route;
/**
* @var array
*/
public $post;
/**
* @var array|null
*/
public $data;
2014-08-05 13:06:38 -07:00
/**
* @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"
];
2014-08-05 13:06:38 -07:00
/**
2016-07-07 18:55:52 +02:00
* @param Grav $grav
2014-08-05 13:06:38 -07:00
* @param string $view
* @param string $task
* @param string $route
2016-07-07 18:55:52 +02:00
* @param array $post
2014-08-05 13:06:38 -07:00
*/
public function __construct(Grav $grav, $view, $task, $route, $post)
2014-08-05 13:06:38 -07:00
{
$this->grav = $grav;
2014-08-05 13:06:38 -07:00
$this->view = $view;
$this->task = $task ? $task : 'display';
if (isset($post['data'])) {
$this->data = $this->getPost($post['data']);
unset($post['data']);
} else {
// Backwards compatibility for Form plugin <= 1.2
$this->data = $this->getPost($post);
}
2014-08-05 13:06:38 -07:00
$this->post = $this->getPost($post);
$this->route = $route;
$this->admin = $this->grav['admin'];
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
$this->uri = $this->grav['uri']->url();
2014-08-05 13:06:38 -07:00
}
/**
* Performs a task.
2015-07-30 12:20:25 +02:00
*
* @return bool True if the action was performed successfully.
2014-08-05 13:06:38 -07:00
*/
public function execute()
{
2015-11-17 11:56:21 +01:00
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') {
2016-07-07 18:55:52 +02:00
$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 = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
'message' => $message
];
return false;
}
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
];
return false;
}
unset($this->post['admin-nonce']);
2015-11-17 11:56:21 +01:00
} else {
if ($this->task == 'logout') {
2015-11-17 11:56:21 +01:00
$nonce = $this->grav['uri']->param('logout-nonce');
if (!isset($nonce) || !Utils::verifyNonce($nonce, 'logout-form')) {
2016-03-05 12:11:13 +01:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),
'error');
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'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')) {
2016-03-05 12:11:13 +01:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),
'error');
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')
];
return false;
2015-11-17 11:56:21 +01:00
}
}
2015-11-10 17:53:09 +01:00
}
2015-11-06 15:32:26 +01:00
}
2014-08-05 13:06:38 -07:00
$success = false;
$method = 'task' . ucfirst($this->task);
2014-08-05 13:06:38 -07:00
if (method_exists($this, $method)) {
try {
2016-02-12 10:16:28 +01:00
$success = call_user_func([$this, $method]);
2014-08-05 13:06:38 -07:00
} catch (\RuntimeException $e) {
$success = true;
$this->admin->setMessage($e->getMessage(), 'error');
2014-08-05 13:06:38 -07:00
}
// Grab redirect parameter.
$redirect = isset($this->post['_redirect']) ? $this->post['_redirect'] : null;
unset($this->post['_redirect']);
2014-08-05 13:06:38 -07:00
// Redirect if requested.
if ($redirect) {
$this->setRedirect($redirect);
}
}
2016-03-05 12:11:13 +01:00
2014-08-05 13:06:38 -07:00
return $success;
}
2015-07-30 12:20:25 +02:00
/**
* Redirect to the route stored in $this->redirect
*/
2014-08-05 13:06:38 -07:00
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
2016-03-05 12:11:13 +01:00
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;
}
2014-08-05 13:06:38 -07:00
$this->grav->redirect($redirect, $this->redirectCode);
2014-08-05 13:06:38 -07:00
}
/**
* Return true if multilang is active
*
* @return bool True if multilang is active
*/
2016-01-10 17:17:04 +01:00
protected function isMultilang()
{
return count($this->grav['config']->get('system.languages.supported', [])) > 1;
}
2014-08-05 13:06:38 -07:00
/**
* Handle login.
*
* @return bool True if the action was performed.
*/
protected function taskLogin()
{
$this->data['username'] = strip_tags(strtolower($this->data['username']));
if ($this->admin->authenticate($this->data, $this->post)) {
// should never reach here, redirects first
} else {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.LOGIN_FAILED'), 'error');
}
2014-08-05 13:06:38 -07:00
return true;
}
/**
* Handle logout.
*
* @return bool True if the action was performed.
*/
protected function taskLogout()
{
$language = $this->grav['user']->authenticated ? $this->grav['user']->language : ($this->grav['language']->getLanguage() ?: 'en');
2014-08-05 13:06:38 -07:00
$this->admin->session()->invalidate()->start();
$this->setRedirect('/logout/lang:' . $language);
2014-08-05 13:06:38 -07:00
return true;
}
/**
* Toggle the gpm.releases setting
*/
protected function taskGpmRelease()
{
if (!$this->authorizeTask('configuration', ['admin.configuration', 'admin.super'])) {
return false;
}
// Default release state
$release = 'stable';
$reload = false;
// Get the testing release value if set
if ($this->post['release'] == 'testing') {
$release = 'testing';
}
$config = $this->grav['config'];
$current_release = $config->get('system.gpm.releases');
// If the releases setting is different, save it in the system config
if ($current_release != $release) {
$data = new Data\Data($config->get('system'));
$data->set('gpm.releases', $release);
// Get the file location
$file = CompiledYamlFile::instance($this->grav['locator']->findResource("config://system.yaml"));
$data->file($file);
// Save the configuration
$data->save();
$config->reload();
$reload = true;
}
$this->admin->json_response = ['status' => 'success', 'reload' => $reload];
2016-07-07 18:55:52 +02:00
return true;
}
/**
* Keep alive
*/
protected function taskKeepAlive()
{
exit();
}
protected function taskGetNewsFeed()
{
$cache = $this->grav['cache'];
if ($this->post['refresh'] == 'true') {
$cache->delete('news-feed');
}
$feed_data = $cache->fetch('news-feed');
if (!$feed_data) {
try {
$feed = $this->admin->getFeed();
if (is_object($feed)) {
require_once(__DIR__ . '/../twig/AdminTwigExtension.php');
$adminTwigExtension = new AdminTwigExtension();
$feed_items = $feed->getItems();
// Feed should only every contain 10, but just in case!
if (count($feed_items > 10)) {
$feed_items = array_slice($feed_items, 0, 10);
}
foreach($feed_items as $item) {
$datetime = $adminTwigExtension->adminNicetimeFilter($item->getDate()->getTimestamp());
$feed_data[] = '<li><span class="date">'.$datetime.'</span> <a href="'.$item->getUrl().'" target="_blank" title="'.str_replace('"', '″', $item->getTitle()).'">'.$item->getTitle().'</a></li>';
}
}
// cache for 1 hour
$cache->save('news-feed', $feed_data, 60*60);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return;
}
}
$this->admin->json_response = ['status' => 'success', 'feed_data' => $feed_data];
}
/**
* Get Notifications from cache.
*
*/
protected function taskGetNotifications()
{
$cache = $this->grav['cache'];
if (!(bool)$this->grav['config']->get('system.cache.enabled') || !$notifications = $cache->fetch('notifications')) {
//No notifications cache (first time)
$this->admin->json_response = ['status' => 'success', 'notifications' => [], 'need_update' => true];
return;
}
$need_update = false;
if (!$last_checked = $cache->fetch('notifications_last_checked')) {
$need_update = true;
} else {
if (time() - $last_checked > 86400) {
$need_update = true;
}
}
try {
$notifications = $this->admin->processNotifications($notifications);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return;
}
$this->admin->json_response = ['status' => 'success', 'notifications' => $notifications, 'need_update' => $need_update];
}
/**
* Process Notifications. Store the notifications object locally.
*
* @return bool
*/
protected function taskProcessNotifications()
{
$cache = $this->grav['cache'];
$data = $this->post;
$notifications = json_decode($data['notifications']);
try {
$notifications = $this->admin->processNotifications($notifications);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return;
}
$show_immediately = false;
if (!$cache->fetch('notifications_last_checked')) {
$show_immediately = true;
}
$cache->save('notifications', $notifications);
$cache->save('notifications_last_checked', time());
$this->admin->json_response = ['status' => 'success', 'notifications' => $notifications, 'show_immediately' => $show_immediately];
return true;
}
/**
* Handle getting a new package dependencies needed to be installed
*
* @return bool
*/
protected function taskGetPackagesDependencies()
{
$data = $this->post;
$packages = isset($data['packages']) ? $data['packages'] : '';
$packages = (array)$packages;
try {
$this->admin->checkPackagesCanBeInstalled($packages);
$dependencies = $this->admin->getDependenciesNeededToInstall($packages);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
2016-07-07 18:55:52 +02:00
return;
}
$this->admin->json_response = ['status' => 'success', 'dependencies' => $dependencies];
2016-07-07 18:55:52 +02:00
return true;
}
protected function taskInstallDependenciesOfPackages()
{
$data = $this->post;
$packages = isset($data['packages']) ? $data['packages'] : '';
$packages = (array)$packages;
$type = isset($data['type']) ? $data['type'] : '';
if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
2016-07-07 18:55:52 +02:00
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
return false;
}
require_once __DIR__ . '/gpm.php';
try {
$dependencies = $this->admin->getDependenciesNeededToInstall($packages);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
2016-07-07 18:55:52 +02:00
return;
}
$result = \Grav\Plugin\Admin\Gpm::install(array_keys($dependencies), ['theme' => ($type == 'theme')]);
if ($result) {
$this->admin->json_response = ['status' => 'success', 'message' => 'Dependencies installed successfully'];
} else {
2016-07-07 18:55:52 +02:00
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INSTALLATION_FAILED')
];
}
return true;
}
protected function taskInstallPackage()
{
$data = $this->post;
$package = isset($data['package']) ? $data['package'] : '';
$type = isset($data['type']) ? $data['type'] : '';
if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
2016-07-07 18:55:52 +02:00
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
return false;
}
require_once __DIR__ . '/gpm.php';
try {
$result = \Grav\Plugin\Admin\Gpm::install($package, ['theme' => ($type == 'theme')]);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
2016-07-07 18:55:52 +02:00
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
return false;
}
if ($result) {
2016-07-07 18:55:52 +02:00
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin->translate(is_string($result) ? $result : sprintf($this->admin->translate('PLUGIN_ADMIN.PACKAGE_X_INSTALLED_SUCCESSFULLY',
null, true), $package))
];
} else {
2016-07-07 18:55:52 +02:00
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INSTALLATION_FAILED')
];
}
return true;
}
/**
* Handle removing a package
*
* @return bool
*/
protected function taskRemovePackage()
{
$data = $this->post;
$package = isset($data['package']) ? $data['package'] : '';
$type = isset($data['type']) ? $data['type'] : '';
if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) {
2016-07-07 18:55:52 +02:00
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
return false;
}
require_once __DIR__ . '/gpm.php';
//check if there are packages that have this as a dependency. Abort and show which ones
$dependent_packages = $this->admin->getPackagesThatDependOnPackage($package);
if (count($dependent_packages) > 0) {
if (count($dependent_packages) > 1) {
2016-07-07 18:55:52 +02:00
$message = "The installed packages <cyan>" . implode('</cyan>, <cyan>',
$dependent_packages) . "</cyan> depends on this package. Please remove those first.";
} else {
2016-07-07 18:55:52 +02:00
$message = "The installed package <cyan>" . implode('</cyan>, <cyan>',
$dependent_packages) . "</cyan> depends on this package. Please remove it first.";
}
$this->admin->json_response = ['status' => 'error', 'message' => $message];
2016-07-07 18:55:52 +02:00
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
return false;
}
try {
$dependencies = $this->admin->dependenciesThatCanBeRemovedWhenRemoving($package);
$result = \Grav\Plugin\Admin\Gpm::uninstall($package, []);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
2016-07-07 18:55:52 +02:00
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
return false;
}
if ($result) {
2016-07-07 18:55:52 +02:00
$this->admin->json_response = [
'status' => 'success',
'dependencies' => $dependencies,
'message' => $this->admin->translate(is_string($result) ? $result : 'PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL')
];
} else {
2016-07-07 18:55:52 +02:00
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin->translate('PLUGIN_ADMIN.UNINSTALL_FAILED')
];
}
return true;
}
2015-07-30 12:20:25 +02:00
/**
* Handle the email password recovery procedure.
*
* @return bool True if the action was performed.
*/
2015-04-20 16:01:20 +02:00
protected function taskForgot()
{
2015-08-17 21:34:41 +02:00
$param_sep = $this->grav['config']->get('system.param_sep', ':');
2016-05-06 13:34:54 -06:00
$post = $this->post;
$data = $this->data;
2015-04-20 16:01:20 +02:00
$username = isset($data['username']) ? strip_tags(strtolower($data['username'])) : '';
2015-04-20 16:01:20 +02:00
$user = !empty($username) ? User::load($username) : null;
if (!isset($this->grav['Email'])) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
2016-05-06 13:34:54 -06:00
$this->setRedirect($post['redirect']);
2016-03-05 12:11:13 +01:00
2015-04-20 16:01:20 +02:00
return true;
}
if (!$user || !$user->exists()) {
2016-03-05 12:11:13 +01:00
$this->admin->setMessage($this->admin->translate([
'PLUGIN_ADMIN.FORGOT_USERNAME_DOES_NOT_EXIST',
$username
]), 'error');
2016-05-06 13:34:54 -06:00
$this->setRedirect($post['redirect']);
2016-03-05 12:11:13 +01:00
2015-04-20 16:01:20 +02:00
return true;
}
if (empty($user->email)) {
2016-03-05 12:11:13 +01:00
$this->admin->setMessage($this->admin->translate([
'PLUGIN_ADMIN.FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL',
$username
]), 'error');
2016-05-06 13:34:54 -06:00
$this->setRedirect($post['redirect']);
2016-03-05 12:11:13 +01:00
2015-04-20 16:01:20 +02:00
return true;
}
$token = md5(uniqid(mt_rand(), true));
$expire = time() + 604800; // next week
$user->reset = $token . '::' . $expire;
$user->save();
$author = $this->grav['config']->get('site.author.name', '');
$fullname = $user->fullname ?: $username;
2016-03-05 12:11:13 +01:00
$reset_link = rtrim($this->grav['uri']->rootUrl(true), '/') . '/' . trim($this->admin->base,
'/') . '/reset/task' . $param_sep . 'reset/user' . $param_sep . $username . '/token' . $param_sep . $token . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
2015-04-20 16:01:20 +02:00
2015-07-21 22:02:13 -06:00
$sitename = $this->grav['config']->get('site.title', 'Website');
$from = $this->grav['config']->get('plugins.email.from');
if (empty($from)) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
2016-05-06 13:34:54 -06:00
$this->setRedirect($post['redirect']);
2016-03-05 12:11:13 +01:00
return true;
}
2015-04-20 16:01:20 +02:00
$to = $user->email;
2015-07-21 22:02:13 -06:00
$subject = $this->admin->translate(['PLUGIN_ADMIN.FORGOT_EMAIL_SUBJECT', $sitename]);
2016-03-05 12:11:13 +01:00
$content = $this->admin->translate([
'PLUGIN_ADMIN.FORGOT_EMAIL_BODY',
$fullname,
$reset_link,
$author,
$sitename
]);
2015-07-21 22:02:13 -06:00
$body = $this->grav['twig']->processTemplate('email/base.html.twig', ['content' => $content]);
2015-04-20 16:01:20 +02:00
2016-03-05 12:11:13 +01:00
$message = $this->grav['Email']->message($subject, $body, 'text/html')->setFrom($from)->setTo($to);
2015-04-20 16:01:20 +02:00
$sent = $this->grav['Email']->send($message);
if ($sent < 1) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.FORGOT_FAILED_TO_EMAIL'), 'error');
2015-04-20 16:01:20 +02:00
} else {
2016-07-07 18:55:52 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
'info');
2015-04-20 16:01:20 +02:00
}
$this->setRedirect('/');
2016-03-05 12:11:13 +01:00
2015-04-20 16:01:20 +02:00
return true;
}
2015-07-30 12:20:25 +02:00
/**
* Handle the reset password action.
*
* @return bool True if the action was performed.
*/
2015-04-20 16:01:20 +02:00
public function taskReset()
{
$data = $this->data;
2015-04-20 16:01:20 +02:00
if (isset($data['password'])) {
$username = isset($data['username']) ? strip_tags(strtolower($data['username'])) : null;
2015-04-20 16:01:20 +02:00
$user = !empty($username) ? User::load($username) : null;
$password = isset($data['password']) ? $data['password'] : null;
$token = isset($data['token']) ? $data['token'] : null;
if (!empty($user) && $user->exists() && !empty($user->reset)) {
list($good_token, $expire) = explode('::', $user->reset);
if ($good_token === $token) {
if (time() > $expire) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.RESET_LINK_EXPIRED'), 'error');
2015-04-20 16:01:20 +02:00
$this->setRedirect('/forgot');
2016-03-05 12:11:13 +01:00
2015-04-20 16:01:20 +02:00
return true;
}
unset($user->hashed_password);
unset($user->reset);
$user->password = $password;
2015-07-22 14:31:09 -06:00
$user->validate();
$user->filter();
2015-04-20 16:01:20 +02:00
$user->save();
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.RESET_PASSWORD_RESET'), 'info');
2015-04-20 16:01:20 +02:00
$this->setRedirect('/');
2016-03-05 12:11:13 +01:00
2015-04-20 16:01:20 +02:00
return true;
}
}
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
2015-04-20 16:01:20 +02:00
$this->setRedirect('/forgot');
2016-03-05 12:11:13 +01:00
2015-04-20 16:01:20 +02:00
return true;
} else {
$user = $this->grav['uri']->param('user');
$token = $this->grav['uri']->param('token');
if (empty($user) || empty($token)) {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
2015-04-20 16:01:20 +02:00
$this->setRedirect('/forgot');
2016-03-05 12:11:13 +01:00
2015-04-20 16:01:20 +02:00
return true;
} else {
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.RESET_NEW_PASSWORD'), 'info');
2015-04-20 16:01:20 +02:00
}
2016-03-05 12:11:13 +01:00
$this->admin->forgot = ['username' => $user, 'token' => $token];
2015-04-20 16:01:20 +02:00
}
return true;
}
2015-07-30 12:20:25 +02:00
/**
* Clear the cache.
*
* @return bool True if the action was performed.
*/
protected function taskClearCache()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('clear cache', ['admin.cache', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2015-08-06 16:19:36 -06:00
// get optional cleartype param
2015-08-06 16:16:18 -06:00
$clear_type = $this->grav['uri']->param('cleartype');
2015-08-06 16:19:36 -06:00
if ($clear_type) {
2015-08-06 16:16:18 -06:00
$clear = $clear_type;
} else {
2015-08-06 16:19:36 -06:00
$clear = 'standard';
2015-08-06 16:16:18 -06:00
}
$results = Cache::clearCache($clear);
if (count($results) > 0) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'success',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.CACHE_CLEARED') . ' <br />' . $this->admin->translate('PLUGIN_ADMIN.METHOD') . ': ' . $clear . ''
];
} else {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.ERROR_CLEARING_CACHE')
];
}
2014-08-05 13:06:38 -07:00
return true;
}
/**
* Clear the cache.
*
* @return bool True if the action was performed.
*/
protected function taskHideNotification()
{
if (!$this->authorizeTask('hide notification', ['admin.login'])) {
return false;
}
$notification_id = $this->grav['uri']->param('notification_id');
if (!$notification_id) {
$this->admin->json_response = [
'status' => 'error'
];
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
return false;
}
$filename = $this->grav['locator']->findResource('user://data/notifications/' . $this->grav['user']->username . YAML_EXT, true, true);
$file = CompiledYamlFile::instance($filename);
$data = $file->content();
$data[] = $notification_id;
$file->save($data);
$this->admin->json_response = [
'status' => 'success'
];
return true;
}
2015-07-30 12:20:25 +02:00
/**
* Handle the backup action
*
* @return bool True if the action was performed.
*/
2015-04-27 13:37:22 +02:00
protected function taskBackup()
{
2015-08-17 21:34:41 +02:00
$param_sep = $this->grav['config']->get('system.param_sep', ':');
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('backup', ['admin.maintenance', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2015-04-27 13:37:22 +02:00
$download = $this->grav['uri']->param('download');
if ($download) {
$file = base64_decode(urldecode($download));
$backups_root_dir = $this->grav['locator']->findResource('backup://', true);
if (substr($file, 0, strlen($backups_root_dir)) !== $backups_root_dir) {
header('HTTP/1.1 401 Unauthorized');
exit();
}
Utils::download($file, true);
2015-04-27 13:37:22 +02:00
}
$log = JsonFile::instance($this->grav['locator']->findResource("log://backup.log", true, true));
2015-05-04 11:53:29 +02:00
try {
$backup = ZipBackup::backup();
} catch (\Exception $e) {
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.AN_ERROR_OCCURRED') . '. ' . $e->getMessage()
2015-05-04 11:53:29 +02:00
];
return true;
}
2015-04-27 13:37:22 +02:00
$download = urlencode(base64_encode($backup));
2016-03-05 12:11:13 +01:00
$url = rtrim($this->grav['uri']->rootUrl(true), '/') . '/' . trim($this->admin->base,
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
2015-04-27 13:37:22 +02:00
$log->content([
2016-07-07 18:55:52 +02:00
'time' => time(),
2015-04-27 13:37:22 +02:00
'location' => $backup
]);
$log->save();
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'success',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.YOUR_BACKUP_IS_READY_FOR_DOWNLOAD') . '. <a href="' . $url . '" class="button">' . $this->admin->translate('PLUGIN_ADMIN.DOWNLOAD_BACKUP') . '</a>',
2016-07-07 18:55:52 +02:00
'toastr' => [
'timeOut' => 0,
2016-02-01 11:27:34 -08:00
'extendedTimeOut' => 0,
2016-07-07 18:55:52 +02:00
'closeButton' => true
2015-04-27 13:37:22 +02:00
]
];
return true;
}
2015-07-30 12:20:25 +02:00
/**
* Handles filtering the page by modular/visible/routable in the pages list.
*/
protected function taskFilterPages()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('filter pages', ['admin.pages', 'admin.super'])) {
return;
}
$data = $this->post;
$flags = !empty($data['flags']) ? array_map('strtolower', explode(',', $data['flags'])) : [];
$queries = !empty($data['query']) ? explode(',', $data['query']) : [];
2015-09-09 19:37:29 -06:00
/** @var Collection $collection */
$collection = $this->grav['pages']->all();
if (count($flags)) {
// Filter by state
2016-03-05 12:11:13 +01:00
$pageStates = [
'modular',
'nonmodular',
'visible',
'nonvisible',
'routable',
'nonroutable',
'published',
'nonpublished'
];
if (count(array_intersect($pageStates, $flags)) > 0) {
2016-03-05 12:11:13 +01:00
if (in_array('modular', $flags)) {
$collection = $collection->modular();
2016-03-05 12:11:13 +01:00
}
2016-03-05 12:11:13 +01:00
if (in_array('nonmodular', $flags)) {
2015-09-09 21:38:28 -06:00
$collection = $collection->nonModular();
2016-03-05 12:11:13 +01:00
}
2015-09-09 21:38:28 -06:00
2016-03-05 12:11:13 +01:00
if (in_array('visible', $flags)) {
$collection = $collection->visible();
2016-03-05 12:11:13 +01:00
}
2016-03-05 12:11:13 +01:00
if (in_array('nonvisible', $flags)) {
2015-09-09 19:37:29 -06:00
$collection = $collection->nonVisible();
2016-03-05 12:11:13 +01:00
}
2015-09-09 19:37:29 -06:00
2016-03-05 12:11:13 +01:00
if (in_array('routable', $flags)) {
$collection = $collection->routable();
2016-03-05 12:11:13 +01:00
}
2015-09-09 19:37:29 -06:00
2016-03-05 12:11:13 +01:00
if (in_array('nonroutable', $flags)) {
2015-09-09 19:37:29 -06:00
$collection = $collection->nonRoutable();
2016-03-05 12:11:13 +01:00
}
2015-09-09 19:37:29 -06:00
2016-03-05 12:11:13 +01:00
if (in_array('published', $flags)) {
2015-09-09 19:37:29 -06:00
$collection = $collection->published();
2016-03-05 12:11:13 +01:00
}
2015-09-09 19:37:29 -06:00
2016-03-05 12:11:13 +01:00
if (in_array('nonpublished', $flags)) {
2015-09-09 19:37:29 -06:00
$collection = $collection->nonPublished();
2016-03-05 12:11:13 +01:00
}
}
foreach ($pageStates as $pageState) {
if (($pageState = array_search($pageState, $flags)) !== false) {
unset($flags[$pageState]);
}
}
// Filter by page type
if (count($flags)) {
2015-10-21 19:46:51 +02:00
$types = [];
2016-08-26 13:45:22 -06:00
$pageTypes = array_keys(Pages::pageTypes());
2015-10-21 19:46:51 +02:00
foreach ($pageTypes as $pageType) {
2016-08-26 13:45:22 -06:00
if (($pageKey = array_search($pageType, $flags)) !== false) {
2015-10-21 19:46:51 +02:00
$types[] = $pageType;
2016-08-26 13:45:22 -06:00
unset($flags[$pageKey]);
2015-10-21 19:46:51 +02:00
}
}
if (count($types)) {
$collection = $collection->ofOneOfTheseTypes($types);
}
}
// Filter by page type
if (count($flags)) {
$accessLevels = $flags;
$collection = $collection->ofOneOfTheseAccessLevels($accessLevels);
}
}
if (!empty($queries)) {
foreach ($collection as $page) {
foreach ($queries as $query) {
$query = trim($query);
2016-03-05 12:11:13 +01:00
if (stripos($page->getRawContent(), $query) === false && stripos($page->title(),
$query) === false
) {
$collection->remove($page);
}
}
}
}
$results = [];
foreach ($collection as $path => $page) {
$results[] = $page->route();
}
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'success',
2015-08-17 17:18:21 +02:00
'message' => $this->admin->translate('PLUGIN_ADMIN.PAGES_FILTERED'),
'results' => $results
];
$this->admin->collection = $collection;
}
2015-07-30 12:20:25 +02:00
/**
* Determines the file types allowed to be uploaded
*
* @return bool True if the action was performed.
*/
2014-09-22 15:49:53 -06:00
protected function taskListmedia()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('list media', ['admin.pages', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2014-09-22 15:49:53 -06:00
$page = $this->admin->page(true);
2014-09-22 17:13:19 -06:00
if (!$page) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
2014-10-01 22:28:16 +03:00
return false;
2014-09-22 17:13:19 -06:00
}
2016-02-12 10:16:28 +01:00
$media_list = [];
2016-07-18 15:42:38 -06:00
$media = new Media($page->path());
foreach ($media->all() as $name => $medium) {
$media_list[$name] = ['url' => $medium->cropZoom(150, 100)->url(), 'size' => $medium->get('size')];
2014-09-22 15:49:53 -06:00
}
2016-02-01 11:27:34 -08:00
$this->admin->json_response = ['status' => 'success', 'results' => $media_list];
2014-09-22 15:49:53 -06:00
return true;
}
2015-07-30 12:20:25 +02:00
/**
* Handles adding a media file to a page
2016-01-21 09:46:38 +02:00
*
* @return bool True if the action was performed.
2015-07-30 12:20:25 +02:00
*/
2014-09-22 16:35:11 -06:00
protected function taskAddmedia()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('add media', ['admin.pages', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2014-09-22 16:35:11 -06:00
$page = $this->admin->page(true);
2014-10-01 22:28:16 +03:00
/** @var Config $config */
2014-09-22 16:35:11 -06:00
$config = $this->grav['config'];
2014-10-07 15:32:07 -06:00
if (!isset($_FILES['file']['error']) || is_array($_FILES['file']['error'])) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.INVALID_PARAMETERS')
];
2016-01-21 09:46:38 +02:00
return false;
2014-10-07 15:32:07 -06:00
}
2014-09-22 16:35:11 -06:00
2014-10-07 15:32:07 -06:00
// Check $_FILES['file']['error'] value.
switch ($_FILES['file']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_FILES_SENT')
];
2016-01-21 09:46:38 +02:00
return false;
2014-10-07 15:32:07 -06:00
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT')
];
2016-01-21 09:46:38 +02:00
return false;
2014-10-07 15:32:07 -06:00
default:
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')
];
2016-01-21 09:46:38 +02:00
return false;
2014-10-07 15:32:07 -06:00
}
2014-09-22 16:35:11 -06:00
2015-04-27 19:33:48 +02:00
$grav_limit = $config->get('system.media.upload_limit', 0);
2014-10-07 15:32:07 -06:00
// You should also check filesize here.
if ($grav_limit > 0 && $_FILES['file']['size'] > $grav_limit) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')
];
2016-01-21 09:46:38 +02:00
return false;
2014-09-22 16:35:11 -06:00
}
2014-10-07 15:32:07 -06:00
// Check extension
$fileParts = pathinfo($_FILES['file']['name']);
$fileExt = '';
if (isset($fileParts['extension'])) {
$fileExt = strtolower($fileParts['extension']);
}
2014-10-07 15:32:07 -06:00
// If not a supported type, return
2016-06-02 15:14:14 -06:00
if (!$fileExt || !$config->get("media.types.{$fileExt}")) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $fileExt
];
2016-01-21 09:46:38 +02:00
return false;
2014-10-07 15:32:07 -06:00
}
// Upload it
2016-03-05 12:11:13 +01:00
if (!move_uploaded_file($_FILES['file']['tmp_name'],
sprintf('%s/%s', $page->path(), $_FILES['file']['name']))
) {
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE')
];
2016-01-21 09:46:38 +02:00
return false;
2014-10-07 15:32:07 -06:00
}
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'success',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY')
];
2014-10-07 15:32:07 -06:00
2016-01-21 09:46:38 +02:00
return true;
2014-09-22 17:13:19 -06:00
}
2015-07-30 12:20:25 +02:00
/**
* Handles deleting a media file from a page
*
* @return bool True if the action was performed.
*/
2014-09-22 17:13:19 -06:00
protected function taskDelmedia()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('delete media', ['admin.pages', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2014-09-22 17:27:48 -06:00
$page = $this->admin->page(true);
if (!$page) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
2014-10-01 22:28:16 +03:00
return false;
2014-09-22 17:27:48 -06:00
}
2014-09-22 17:13:19 -06:00
2014-09-22 17:27:48 -06:00
$filename = !empty($this->post['filename']) ? $this->post['filename'] : null;
if ($filename) {
$targetPath = $page->path() . '/' . $filename;
2014-09-22 17:27:48 -06:00
if (file_exists($targetPath)) {
2014-12-10 10:23:22 -07:00
if (unlink($targetPath)) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'success',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename
];
} else {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename
];
}
2014-09-22 17:27:48 -06:00
} else {
//Try with responsive images @1x, @2x, @3x
$ext = pathinfo($targetPath, PATHINFO_EXTENSION);
2016-03-05 12:11:13 +01:00
$fullPathFilename = $page->path() . '/' . basename($targetPath, ".$ext");
$responsiveTargetPath = $fullPathFilename . '@1x.' . $ext;
$deletedResponsiveImage = false;
if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) {
$deletedResponsiveImage = true;
}
$responsiveTargetPath = $fullPathFilename . '@2x.' . $ext;
if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) {
$deletedResponsiveImage = true;
}
$responsiveTargetPath = $fullPathFilename . '@3x.' . $ext;
if (file_exists($responsiveTargetPath) && unlink($responsiveTargetPath)) {
$deletedResponsiveImage = true;
}
if ($deletedResponsiveImage) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'success',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename
];
} else {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.FILE_NOT_FOUND') . ': ' . $filename
];
}
2014-09-22 17:27:48 -06:00
}
} else {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_FILE_FOUND')
];
2014-09-22 17:27:48 -06:00
}
2015-07-30 12:20:25 +02:00
2014-09-22 17:27:48 -06:00
return true;
2014-09-22 16:35:11 -06:00
}
2015-07-30 12:20:25 +02:00
/**
* Process the page Markdown
2016-01-21 09:46:38 +02:00
*
* @return bool True if the action was performed.
2015-07-30 12:20:25 +02:00
*/
protected function taskProcessMarkdown()
{
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
/*if (!$this->authorizeTask('process markdown', ['admin.pages', 'admin.super'])) {
return;
}*/
try {
$page = $this->admin->page(true);
if (!$page) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
2016-03-05 12:11:13 +01:00
'message' => $this->admin->translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
return false;
}
$this->preparePage($page, true);
$page->header();
// Add theme template paths to Twig loader
$template_paths = $this->grav['locator']->findResources('theme://templates');
2016-03-05 12:11:13 +01:00
$this->grav['twig']->twig->getLoader()->addLoader(new \Twig_Loader_Filesystem($template_paths));
$html = $page->content();
$this->admin->json_response = ['status' => 'success', 'preview' => $html];
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
2016-01-21 09:46:38 +02:00
return false;
}
2016-01-21 09:46:38 +02:00
return true;
}
2014-08-05 13:06:38 -07:00
/**
2015-07-30 12:20:25 +02:00
* Enable a plugin.
2014-08-05 13:06:38 -07:00
*
* @return bool True if the action was performed.
*/
public function taskEnable()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('enable plugin', ['admin.plugins', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2014-08-05 13:06:38 -07:00
if ($this->view != 'plugins') {
return false;
}
// Filter value and save it.
2016-02-12 10:16:28 +01:00
$this->post = ['enabled' => true];
$obj = $this->prepareData($this->post);
$obj->save();
2016-02-12 10:16:28 +01:00
$this->post = ['_redirect' => 'plugins'];
2015-08-17 10:26:35 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_ENABLED_PLUGIN'), 'info');
return true;
}
/**
2015-07-30 12:20:25 +02:00
* Disable a plugin.
*
* @return bool True if the action was performed.
*/
public function taskDisable()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('disable plugin', ['admin.plugins', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
if ($this->view != 'plugins') {
return false;
}
// Filter value and save it.
2016-03-05 12:11:13 +01:00
$this->post = ['enabled' => false];
$obj = $this->prepareData($this->post);
2014-08-05 13:06:38 -07:00
$obj->save();
2016-03-05 12:11:13 +01:00
$this->post = ['_redirect' => 'plugins'];
2015-08-17 10:26:35 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_DISABLED_PLUGIN'), 'info');
2014-08-05 13:06:38 -07:00
return true;
}
/**
2015-07-30 12:20:25 +02:00
* Set the default theme.
2014-08-05 13:06:38 -07:00
*
* @return bool True if the action was performed.
*/
public function taskActivate()
2014-08-05 13:06:38 -07:00
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('activate theme', ['admin.themes', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2014-08-05 13:06:38 -07:00
if ($this->view != 'themes') {
return false;
}
2016-03-05 12:11:13 +01:00
$this->post = ['_redirect' => 'themes'];
2014-08-05 13:06:38 -07:00
// Make sure theme exists (throws exception)
$name = $this->route;
$this->grav['themes']->get($name);
2014-08-05 13:06:38 -07:00
// Store system configuration.
$system = $this->admin->data('config/system');
2014-08-05 13:06:38 -07:00
$system->set('pages.theme', $name);
$system->save();
// Force configuration reload and save.
/** @var Config $config */
$config = $this->grav['config'];
2014-08-05 13:06:38 -07:00
$config->reload()->save();
$config->set('system.pages.theme', $name);
2015-08-17 10:26:35 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_CHANGED_THEME'), 'info');
2014-08-05 13:06:38 -07:00
return true;
}
/**
* Handles updating Grav
*
2015-11-04 14:43:25 +01:00
* @return bool True if the action was performed
*/
public function taskUpdategrav()
{
require_once __DIR__ . '/gpm.php';
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('install grav', ['admin.super'])) {
return false;
2015-08-06 15:16:22 +02:00
}
2016-04-29 15:20:45 +02:00
$gpm = \Grav\Plugin\Admin\Gpm::GPM();
$version = $gpm->grav->getVersion();
$result = \Grav\Plugin\Admin\Gpm::selfupgrade();
if ($result) {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'success',
'type' => 'updategrav',
'version' => $version,
'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_WAS_SUCCESSFULLY_UPDATED_TO') . ' ' . $version
2016-03-05 12:11:13 +01:00
];
} else {
2016-03-05 12:11:13 +01:00
$this->admin->json_response = [
2016-07-07 18:55:52 +02:00
'status' => 'error',
'type' => 'updategrav',
2016-03-05 12:11:13 +01:00
'version' => GRAV_VERSION,
'message' => $this->admin->translate('PLUGIN_ADMIN.GRAV_UPDATE_FAILED') . ' <br>' . Installer::lastErrorMsg()
];
}
return true;
}
2015-04-13 21:37:12 +02:00
/**
* Handles uninstalling plugins and themes
*
* @deprecated
*
2015-11-04 14:43:25 +01:00
* @return bool True if the action was performed
2015-04-13 21:37:12 +02:00
*/
public function taskUninstall()
{
$type = $this->view === 'plugins' ? 'plugins' : 'themes';
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2015-04-13 21:37:12 +02:00
require_once __DIR__ . '/gpm.php';
$package = $this->route;
2015-04-13 21:37:12 +02:00
$result = \Grav\Plugin\Admin\Gpm::uninstall($package, []);
if ($result) {
2015-08-19 16:53:21 -06:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL'), 'info');
2015-04-13 21:37:12 +02:00
} else {
2015-08-19 16:53:21 -06:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.UNINSTALL_FAILED'), 'error');
2015-04-13 21:37:12 +02:00
}
2016-03-05 12:11:13 +01:00
$this->post = ['_redirect' => $this->view];
2015-04-13 21:37:12 +02:00
return true;
}
/**
* Get the next available ordering number in a folder
2016-01-15 12:57:09 +01:00
*
* @return string the correct order string to prepend
2016-01-15 12:57:09 +01:00
*/
private function getNextOrderInFolder($path)
2016-01-15 12:57:09 +01:00
{
$files = Folder::all($path, ['recursive' => false]);
2016-01-15 12:57:09 +01:00
$highestOrder = 0;
foreach ($files as $file) {
preg_match(PAGE_ORDER_PREFIX_REGEX, $file, $order);
if (isset($order[0])) {
$theOrder = intval(trim($order[0], '.'));
} else {
$theOrder = 0;
2016-01-15 12:57:09 +01:00
}
if ($theOrder >= $highestOrder) {
$highestOrder = $theOrder;
}
}
$orderOfNewFolder = $highestOrder + 1;
if ($orderOfNewFolder < 10) {
$orderOfNewFolder = '0' . $orderOfNewFolder;
}
return $orderOfNewFolder;
}
/**
* Handles creating an empty page folder (without markdown file)
*
* @return bool True if the action was performed.
*/
public function taskSaveNewFolder()
{
if (!$this->authorizeTask('save', $this->dataPermissions())) {
return false;
}
2016-07-07 18:55:52 +02:00
$data = (array)$this->data;
if ($data['route'] == '/') {
$path = $this->grav['locator']->findResource('page://');
} else {
$path = $this->grav['page']->find($data['route'])->path();
}
$orderOfNewFolder = $this->getNextOrderInFolder($path);
Folder::mkdir($path . '/' . $orderOfNewFolder . '.' . $data['folder']);
Cache::clearCache('standard');
2016-01-15 12:57:09 +01:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
$multilang = $this->isMultilang();
$admin_route = $this->grav['config']->get('plugins.admin.route');
$redirect_url = '/' . ($multilang ? ($this->grav['session']->admin_lang) : '') . $admin_route . '/' . $this->view;
$this->setRedirect($redirect_url);
return true;
}
2016-02-12 10:16:28 +01:00
/**
* @param string $frontmatter
2016-02-12 10:16:28 +01:00
*
* @return bool
*/
public function checkValidFrontmatter($frontmatter)
{
try {
// Try native PECL YAML PHP extension first if available.
if (function_exists('yaml_parse')) {
$saved = @ini_get('yaml.decode_php');
@ini_set('yaml.decode_php', 0);
@yaml_parse("---\n" . $frontmatter . "\n...");
@ini_set('yaml.decode_php', $saved);
} else {
Yaml::parse($frontmatter);
}
} catch (ParseException $e) {
return false;
}
return true;
}
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
/**
* 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);
// 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, true), $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, true), $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 = Grav::instance()['locator']->findResource('tmp://', true, true);
$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, true), '', $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->uri);
$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->uri),
'path' => $upload->file->path,
'field' => $settings->name
])
];
return true;
}
/**
* 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->uri);
$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;
}
2014-08-05 13:06:38 -07:00
/**
* Handles form and saves the input data if its valid.
*
* @return bool True if the action was performed.
*/
public function taskSave()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('save', $this->dataPermissions())) {
2016-01-21 09:46:38 +02:00
return false;
}
$reorder = true;
2016-07-07 18:55:52 +02:00
$data = (array)$this->data;
2014-08-05 13:06:38 -07:00
2016-01-21 09:46:38 +02:00
$config = $this->grav['config'];
2014-08-05 13:06:38 -07:00
// Special handler for pages data.
if ($this->view == 'pages') {
2016-01-21 09:46:38 +02:00
/** @var Pages $pages */
$pages = $this->grav['pages'];
2014-08-05 13:06:38 -07:00
// Find new parent page in order to build the path.
$route = !isset($data['route']) ? dirname($this->admin->route) : $data['route'];
/** @var Page $obj */
2015-08-05 18:10:15 -06:00
$obj = $this->admin->page(true);
// Ensure route is prefixed with a forward slash.
$route = '/' . ltrim($route, '/');
if (isset($data['frontmatter']) && !$this->checkValidFrontmatter($data['frontmatter'])) {
2016-03-05 12:11:13 +01:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.INVALID_FRONTMATTER_COULD_NOT_SAVE'),
'error');
return false;
}
2015-12-29 19:31:02 +01:00
//Handle system.home.hide_in_urls
$hide_home_route = $config->get('system.home.hide_in_urls', false);
if ($hide_home_route) {
$home_route = $config->get('system.home.alias');
$topParent = $obj->topParent();
if (isset($topParent)) {
if ($topParent->route() == $home_route) {
2016-03-05 12:11:13 +01:00
$baseRoute = (string)$topParent->route();
if ($obj->parent() != $topParent) {
$baseRoute .= $obj->parent()->route();
}
$route = isset($baseRoute) ? $baseRoute : null;
}
}
}
$parent = $route && $route != '/' && $route != '.' ? $pages->dispatch($route, true) : $pages->root();
2015-05-13 14:29:17 +02:00
$original_slug = $obj->slug();
2015-08-14 06:23:16 -06:00
$original_order = intval(trim($obj->order(), '.'));
2015-05-13 14:29:17 +02:00
2014-08-05 13:06:38 -07:00
// Change parent if needed and initialize move (might be needed also on ordering/folder change).
$obj = $obj->move($parent);
$this->preparePage($obj, false, $obj->language());
2014-08-05 13:06:38 -07:00
// Reset slug and route. For now we do not support slug twig variable on save.
2015-05-13 14:29:17 +02:00
$obj->slug($original_slug);
2014-08-05 13:06:38 -07:00
try {
$obj->validate();
} catch (\Exception $e) {
$this->admin->setMessage($e->getMessage(), 'error');
2016-07-07 18:55:52 +02:00
return false;
}
2014-08-05 13:06:38 -07:00
$obj->filter();
2015-08-05 18:10:15 -06:00
// rename folder based on visible
2015-08-14 06:23:16 -06:00
if ($original_order == 1000) {
// increment order to force reshuffle
$obj->order($original_order + 1);
}
2015-08-14 14:28:24 -06:00
// add or remove numeric prefix based on ordering value
if (isset($data['ordering'])) {
if ($data['ordering'] && !$obj->order()) {
$obj->order($this->getNextOrderInFolder($obj->parent()->path()));
$reorder = false;
2015-08-14 14:28:24 -06:00
} elseif (!$data['ordering'] && $obj->order()) {
$obj->folder($obj->slug());
}
}
2015-08-14 14:28:24 -06:00
2014-08-05 13:06:38 -07:00
} else {
// Handle standard data types.
$obj = $this->prepareData($data);
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
try {
$obj->validate();
} catch (\Exception $e) {
$this->admin->setMessage($e->getMessage(), 'error');
2016-07-07 18:55:52 +02:00
return false;
}
2014-08-05 13:06:38 -07:00
$obj->filter();
2015-08-05 18:10:15 -06:00
}
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
// 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->uri)];
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, true), '"' . $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_merge([], $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
}
}
}
2015-08-05 18:10:15 -06:00
if ($obj) {
// Event to manipulate data before saving the object
$this->grav->fireEvent('onAdminSave', new Event(['object' => &$obj]));
$obj->save($reorder);
2015-08-17 10:26:35 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
2014-08-05 13:06:38 -07:00
}
2014-10-07 12:12:21 +03:00
if ($this->view != 'pages') {
// Force configuration reload.
/** @var Config $config */
$config = $this->grav['config'];
$config->reload();
2016-04-27 21:06:09 +02:00
if ($this->view === 'user') {
$this->grav['user']->merge(User::load($this->admin->route)->toArray());
}
2014-10-07 12:12:21 +03:00
}
2015-08-03 11:46:55 +02:00
// Always redirect if a page route was changed, to refresh it
2016-01-21 09:46:38 +02:00
if ($obj instanceof Page) {
if (method_exists($obj, 'unsetRouteSlug')) {
$obj->unsetRouteSlug();
}
$multilang = $this->isMultilang();
if ($multilang) {
if (!$obj->language()) {
$obj->language($this->grav['session']->admin_lang);
}
2015-08-20 16:03:54 +02:00
}
$admin_route = $this->grav['config']->get('plugins.admin.route');
2015-12-29 19:31:02 +01:00
//Handle system.home.hide_in_urls
$route = $obj->route();
$hide_home_route = $config->get('system.home.hide_in_urls', false);
if ($hide_home_route) {
$home_route = $config->get('system.home.alias');
$topParent = $obj->topParent();
if (isset($topParent)) {
if ($topParent->route() == $home_route) {
2016-03-05 12:11:13 +01:00
$route = (string)$topParent->route() . $route;
2015-12-29 19:31:02 +01:00
}
}
}
$redirect_url = '/' . ($multilang ? ($obj->language()) : '') . $admin_route . '/' . $this->view . $route;
$this->setRedirect($redirect_url);
2014-08-05 13:06:38 -07:00
}
return true;
}
/**
* Continue to the new page.
*
* @return bool True if the action was performed.
*/
public function taskContinue()
{
2016-07-07 18:55:52 +02:00
$data = (array)$this->data;
2014-10-10 15:25:07 +03:00
if ($this->view == 'users') {
2016-05-17 16:52:22 -06:00
$username = strip_tags(strtolower($data['username']));
$this->setRedirect("{$this->view}/{$username}");
2016-03-05 12:11:13 +01:00
2014-10-10 15:25:07 +03:00
return true;
}
2015-10-20 16:36:39 +02:00
if ($this->view == 'groups') {
$this->setRedirect("{$this->view}/{$data['groupname']}");
2016-03-05 12:11:13 +01:00
2015-10-20 16:36:39 +02:00
return true;
}
2014-08-05 13:06:38 -07:00
if ($this->view != 'pages') {
return false;
}
2014-09-17 11:54:57 +03:00
$route = $data['route'] != '/' ? $data['route'] : '';
2014-10-10 11:57:57 +03:00
$folder = ltrim($data['folder'], '_');
if (!empty($data['modular'])) {
$folder = '_' . $folder;
}
2014-08-05 13:06:38 -07:00
$path = $route . '/' . $folder;
2014-09-17 11:54:57 +03:00
$this->admin->session()->{$path} = $data;
// Store the name and route of a page, to be used prefilled defaults of the form in the future
$this->admin->session()->lastPageName = $data['name'];
$this->admin->session()->lastPageRoute = $data['route'];
2016-03-05 12:11:13 +01:00
$this->setRedirect("{$this->view}/" . ltrim($path, '/'));
2014-08-05 13:06:38 -07:00
return true;
}
/**
* Find the first available $item ('slug' | 'folder') for a page
* Used when copying a page, to determine the first available slot
*
* @param string $item
2016-07-07 18:55:52 +02:00
* @param Page $page
*
* @return string The first available slot
*/
protected function findFirstAvailable($item, $page)
{
if (!$page->parent()->children()) {
return $page->$item();
}
$withoutPrefix = function ($string) {
$match = preg_split('/^[0-9]+\./u', $string, 2, PREG_SPLIT_DELIM_CAPTURE);
2016-07-07 18:55:52 +02:00
return isset($match[1]) ? $match[1] : $match[0];
};
$withoutPostfix = function ($string) {
$match = preg_split('/-(\d+)$/', $string, 2, PREG_SPLIT_DELIM_CAPTURE);
2016-07-07 18:55:52 +02:00
return $match[0];
};
$appendedNumber = function ($string) {
$match = preg_split('/-(\d+)$/', $string, 2, PREG_SPLIT_DELIM_CAPTURE);
$append = (isset($match[1]) ? (int)$match[1] + 1 : 2);
2016-07-07 18:55:52 +02:00
return $append;
};
$highest = 1;
$siblings = $page->parent()->children();
2016-07-07 18:55:52 +02:00
$findCorrectAppendedNumber = function ($item, $page_item, $highest) use (
$siblings,
&$findCorrectAppendedNumber,
&$withoutPrefix
) {
foreach ($siblings as $sibling) {
if ($withoutPrefix($sibling->$item()) == ($highest === 1 ? $page_item : $page_item . '-' . $highest)) {
$highest = $findCorrectAppendedNumber($item, $page_item, $highest + 1);
2016-07-07 18:55:52 +02:00
return $highest;
}
}
2016-07-07 18:55:52 +02:00
return $highest;
};
$base = $withoutPrefix($withoutPostfix($page->$item()));
$return = $base;
$highest = $findCorrectAppendedNumber($item, $base, $highest);
if ($highest > 1) {
$return .= '-' . $highest;
}
return $return;
}
2014-08-05 13:06:38 -07:00
/**
* Save page as a new copy.
*
* @return bool True if the action was performed.
* @throws \RuntimeException
*/
protected function taskCopy()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('copy page', ['admin.pages', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2014-08-05 13:06:38 -07:00
// Only applies to pages.
if ($this->view != 'pages') {
return false;
}
try {
2016-01-21 09:46:38 +02:00
/** @var Pages $pages */
$pages = $this->grav['pages'];
2014-08-05 13:06:38 -07:00
// Get the current page.
$original_page = $this->admin->page(true);
// Find new parent page in order to build the path.
$parent = $original_page->parent() ?: $pages->root();
2014-08-05 13:06:38 -07:00
// Make a copy of the current page and fill the updated information into it.
$page = $original_page->copy($parent);
if ($page->order()) {
$order = $this->getNextOrderInFolder($page->parent()->path());
}
2014-08-05 13:06:38 -07:00
$this->preparePage($page);
2015-04-27 17:43:58 +02:00
// Make sure the header is loaded in case content was set through raw() (expert mode)
$page->header();
if ($page->order()) {
$page->order($order);
2014-08-05 13:06:38 -07:00
}
$folder = $this->findFirstAvailable('folder', $page);
$slug = $this->findFirstAvailable('slug', $page);
$page->path($page->parent()->path() . DS . $page->order() . $folder);
$page->route($page->parent()->route() . '/' . $slug);
$page->rawRoute($page->parent()->rawRoute() . '/' . $slug);
// Append progressivenumber to the copied page title
$match = preg_split('/(\d+)(?!.*\d)/', $original_page->title(), 2, PREG_SPLIT_DELIM_CAPTURE);
$header = $page->header();
if (!isset($match[1])) {
$header->title = $match[0] . ' 2';
} else {
$header->title = $match[0] . ((int)$match[1] + 1);
}
$page->header($header);
$page->save(false);
$redirect = $this->view . $page->rawRoute();
$header = $page->header();
2014-08-05 13:06:38 -07:00
if (isset($header->slug)) {
$match = preg_split('/-(\d+)$/', $header->slug, 2, PREG_SPLIT_DELIM_CAPTURE);
$header->slug = $match[0] . '-' . (isset($match[1]) ? (int)$match[1] + 1 : 2);
}
$page->header($header);
$page->save();
2014-08-05 13:06:38 -07:00
// Enqueue message and redirect to new location.
2015-08-17 10:26:35 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_COPIED'), 'info');
$this->setRedirect($redirect);
2014-08-05 13:06:38 -07:00
} catch (\Exception $e) {
throw new \RuntimeException('Copying page failed on error: ' . $e->getMessage());
}
return true;
}
/**
* Reorder pages.
*
* @return bool True if the action was performed.
*/
protected function taskReorder()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('reorder pages', ['admin.pages', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2014-08-05 13:06:38 -07:00
// Only applies to pages.
if ($this->view != 'pages') {
return false;
}
2015-08-17 10:26:35 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.REORDERING_WAS_SUCCESSFUL'), 'info');
2016-03-05 12:11:13 +01:00
2014-08-05 13:06:38 -07:00
return true;
}
/**
* Delete page.
*
* @return bool True if the action was performed.
* @throws \RuntimeException
*/
protected function taskDelete()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('delete page', ['admin.pages', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
2014-08-05 13:06:38 -07:00
// Only applies to pages.
if ($this->view != 'pages') {
return false;
}
try {
$page = $this->admin->page();
if (count($page->translatedLanguages()) > 1) {
$page->file()->delete();
} else {
Folder::delete($page->path());
}
2016-02-12 10:16:28 +01:00
Cache::clearCache('standard');
2015-08-05 19:11:09 -06:00
2014-09-30 17:41:45 -06:00
// Set redirect to either referrer or pages list.
2015-08-21 15:54:21 +02:00
$redirect = 'pages';
2014-08-05 13:06:38 -07:00
2015-08-17 10:26:35 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_DELETED'), 'info');
2014-08-05 13:06:38 -07:00
$this->setRedirect($redirect);
} catch (\Exception $e) {
throw new \RuntimeException('Deleting page failed on error: ' . $e->getMessage());
}
return true;
}
/**
* Switch the content language. Optionally redirect to a different page.
*
* @return bool True if the action was performed.
*/
2016-01-10 17:17:04 +01:00
protected function taskSwitchlanguage()
{
2016-07-07 18:55:52 +02:00
$data = (array)$this->data;
2015-08-21 18:01:20 +02:00
if (isset($data['lang'])) {
$language = $data['lang'];
} else {
$language = $this->grav['uri']->param('lang');
}
if (isset($data['redirect'])) {
$redirect = 'pages/' . $data['redirect'];
} else {
$redirect = 'pages';
}
if ($language) {
$this->grav['session']->admin_lang = $language ?: 'en';
}
2015-08-17 10:26:35 +02:00
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_SWITCHED_LANGUAGE'), 'info');
$admin_route = $this->grav['config']->get('plugins.admin.route');
$this->setRedirect('/' . $language . $admin_route . '/' . $redirect);
}
/**
* Save the current page in a different language. Automatically switches to that language.
*
* @return bool True if the action was performed.
*/
2016-01-10 17:17:04 +01:00
protected function taskSaveas()
{
2015-09-11 15:00:03 +02:00
if (!$this->authorizeTask('save', $this->dataPermissions())) {
2016-01-21 09:46:38 +02:00
return false;
}
2016-07-07 18:55:52 +02:00
$data = (array)$this->data;
$language = $data['lang'];
if ($language) {
$this->grav['session']->admin_lang = $language ?: 'en';
}
$uri = $this->grav['uri'];
$obj = $this->admin->page($uri->route());
$this->preparePage($obj, false, $language);
$file = $obj->file();
if ($file) {
$filename = substr($obj->name(), 0, -(strlen('.' . $language . '.md')));
if (substr($filename, -3, 1) == '.') {
if (substr($filename, -2) == substr($language, 0, 2)) {
$filename = str_replace(substr($filename, -2), $language, $filename);
}
} elseif (substr($filename, -6, 1) == '.') {
if (substr($filename, -5) == substr($language, 0, 5)) {
$filename = str_replace(substr($filename, -5), $language, $filename);
}
} else {
$filename .= '.' . $language;
}
$path = $obj->path() . DS . $filename . '.md';
$aFile = File::instance($path);
$aFile->save();
2016-01-21 09:46:38 +02:00
$aPage = new Page();
2016-03-05 12:11:13 +01:00
$aPage->init(new \SplFileInfo($path), $language . '.md');
$aPage->header($obj->header());
$aPage->rawMarkdown($obj->rawMarkdown());
$aPage->validate();
$aPage->filter();
$aPage->save();
}
$this->admin->setMessage($this->admin->translate('PLUGIN_ADMIN.SUCCESSFULLY_SWITCHED_LANGUAGE'), 'info');
$this->setRedirect('/' . $language . $uri->route());
return true;
}
/**
* Determine if the user can edit media
*
* @param string $type
2016-07-07 18:55:52 +02:00
*
* @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;
}
2016-03-05 12:11:13 +01:00
return true;
}
/**
* Handles removing a media file
*
* @return bool True if the action was performed
*/
public function taskRemoveMedia()
{
if (!$this->canEditMedia()) {
return false;
}
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
$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) {
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')
];
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
return true;
} else {
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.REMOVE_FAILED')
];
2016-03-05 12:11:13 +01:00
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
return false;
}
}
/**
* 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');
$this->taskRemoveMedia();
if ($type == 'pages') {
$page = $this->admin->page(true, $proute);
$keys = explode('.', preg_replace('/^header./', '', $field));
2016-07-07 18:55:52 +02:00
$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 {
2016-07-07 18:55:52 +02:00
$blueprint_prefix = $type == 'config' ? '' : $type . '.';
$blueprint_name = str_replace('/blueprints', '', str_replace('config/', '', $blueprint));
2016-07-07 18:55:52 +02:00
$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;
}
}
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin->translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')
];
2016-03-05 12:11:13 +01:00
return true;
}
2014-08-05 13:06:38 -07:00
/**
* Prepare and return POST data.
*
* @param array $post
2016-03-05 12:11:13 +01:00
*
2014-08-05 13:06:38 -07:00
* @return array
*/
protected function getPost($post)
2014-08-05 13:06:38 -07:00
{
unset($post['task']);
// Decode JSON encoded fields and merge them to data.
if (isset($post['_json'])) {
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
$post = array_replace_recursive($post, $this->jsonDecode($post['_json']));
2014-08-05 13:06:38 -07:00
unset($post['_json']);
}
2016-03-05 12:11:13 +01:00
2014-08-05 13:06:38 -07:00
return $post;
}
/**
* Recursively JSON decode data.
*
* @param array $data
2016-03-05 12:11:13 +01:00
*
2014-08-05 13:06:38 -07:00
* @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);
}
}
2016-03-05 12:11:13 +01:00
2014-08-05 13:06:38 -07:00
return $data;
}
2015-07-30 12:20:25 +02:00
/**
* Sets the page redirect.
*
* @param string $path The path to redirect to
2016-03-05 12:11:13 +01:00
* @param int $code The HTTP redirect code
2015-07-30 12:20:25 +02:00
*/
2014-12-10 10:23:22 -07:00
protected function setRedirect($path, $code = 303)
{
2014-08-05 13:06:38 -07:00
$this->redirect = $path;
2016-03-05 12:11:13 +01:00
$this->redirectCode = $code;
2014-08-05 13:06:38 -07:00
}
2015-07-30 12:20:25 +02:00
/**
* Gets the configuration data for a given view & post
*
* @return object
*/
protected function prepareData(array $data)
2014-08-05 13:06:38 -07:00
{
$type = trim("{$this->view}/{$this->admin->route}", '/');
$data = $this->admin->data($type, $data);
2014-08-05 13:06:38 -07:00
return $data;
}
2015-07-30 12:20:25 +02:00
/**
* 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;
}
2015-07-30 12:20:25 +02:00
/**
* Prepare a page to be stored: update its folder, name, template, header and content
*
* @param \Grav\Common\Page\Page $page
* @param bool $clean_header
2016-03-05 12:11:13 +01:00
* @param string $language
2015-07-30 12:20:25 +02:00
*/
2016-03-05 12:11:13 +01:00
protected function preparePage(Page $page, $clean_header = false, $language = '')
2014-08-05 13:06:38 -07:00
{
2016-07-07 18:55:52 +02:00
$input = (array)$this->data;
2014-08-05 13:06:38 -07:00
2015-08-14 14:28:24 -06:00
if (isset($input['order'])) {
$order = max(0, ((int)isset($input['order']) && $input['order']) ? $input['order'] : $page->value('order'));
2015-08-14 14:28:24 -06:00
$ordering = $order ? sprintf('%02d.', $order) : '';
2016-03-05 12:11:13 +01:00
$slug = empty($input['folder']) ? $page->value('folder') : (string)$input['folder'];
2015-08-14 14:28:24 -06:00
$page->folder($ordering . $slug);
}
if (isset($input['name']) && !empty($input['name'])) {
2016-03-05 12:11:13 +01:00
$type = (string)strtolower($input['name']);
$name = preg_replace('|.*/|', '', $type);
if ($language) {
$name .= '.' . $language;
} else {
$language = $this->grav['language'];
if ($language->enabled()) {
$name .= '.' . $language->getLanguage();
}
}
$name .= '.md';
2014-10-08 19:55:50 +03:00
$page->name($name);
$page->template($type);
2014-10-08 15:14:46 +03:00
}
2015-07-30 12:20:25 +02:00
// Special case for Expert mode: build the raw, unset content
2014-09-20 15:34:35 -06:00
if (isset($input['frontmatter']) && isset($input['content'])) {
2016-03-05 12:11:13 +01:00
$page->raw("---\n" . (string)$input['frontmatter'] . "\n---\n" . (string)$input['content']);
2014-09-20 15:34:35 -06:00
unset($input['content']);
2014-08-05 13:06:38 -07:00
}
if (isset($input['header'])) {
$header = $input['header'];
2015-08-04 19:28:42 +02:00
2016-03-05 12:11:13 +01:00
foreach ($header as $key => $value) {
if ($key == 'metadata' && is_array($header[$key])) {
2015-08-14 06:41:43 -06:00
foreach ($header['metadata'] as $key2 => $value2) {
2015-08-04 19:28:42 +02:00
if (isset($input['toggleable_header']['metadata'][$key2]) && !$input['toggleable_header']['metadata'][$key2]) {
$header['metadata'][$key2] = '';
}
}
} elseif ($key == 'taxonomy' && is_array($header[$key])) {
2015-08-14 06:41:43 -06:00
foreach ($header[$key] as $taxkey => $taxonomy) {
2015-08-14 14:28:24 -06:00
if (is_array($taxonomy) && count($taxonomy) == 1 && trim($taxonomy[0]) == '') {
2015-08-14 06:41:43 -06:00
unset($header[$key][$taxkey]);
}
}
2015-08-04 19:28:42 +02:00
} else {
if (isset($input['toggleable_header'][$key]) && !$input['toggleable_header'][$key]) {
2015-08-14 06:23:16 -06:00
$header[$key] = null;
2015-08-04 19:28:42 +02:00
}
}
}
if ($clean_header) {
2016-03-05 12:11:13 +01:00
$header = Utils::arrayFilterRecursive($header, function ($k, $v) {
2015-08-03 17:04:48 -06:00
return !(is_null($v) || $v === '');
});
}
2016-03-05 12:11:13 +01:00
$page->header((object)$header);
$page->frontmatter(Yaml::dump((array)$page->header()));
2014-08-05 13:06:38 -07:00
}
2015-07-30 12:20:25 +02:00
// Fill content last because it also renders the output.
2014-08-05 13:06:38 -07:00
if (isset($input['content'])) {
2016-03-05 12:11:13 +01:00
$page->rawMarkdown((string)$input['content']);
2014-08-05 13:06:38 -07:00
}
}
2015-07-30 12:20:25 +02:00
/**
* Checks if the user is allowed to perform the given task with its associated permissions
*
2016-03-05 12:11:13 +01:00
* @param string $task The task to execute
* @param array $permissions The permissions given
*
2015-07-30 12:20:25 +02:00
* @return bool True if authorized. False if not.
*/
2015-09-11 15:00:03 +02:00
protected function authorizeTask($task = '', $permissions = [])
{
2015-09-11 15:00:03 +02:00
if (!$this->admin->authorize($permissions)) {
2016-03-05 12:11:13 +01:00
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;
}
[WIP] Ajax Files Upload (#748) * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Save This improves the Save task tremendously as now there is no longer the need of waiting for the files to finish uploading. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option in the blueprints. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) * Added support for `accept: [‘*’]` to allow any file type * Minor tweaks in the comments and messages * Delete any orphan file when discarding the uploaded files session * Minor optimization * Fixed issue with `_json` elements where nested fields merging would get stored in an unexpected way * Potential fix for wrong order of value in Datetime * Fixed nested fields for files * Fixed tmp streams * Minor cleanup * Update JSON data when removing a file. Implemented task to remove files that haven’t been saved yet, from the flash object session * Ensure temporary files are deleted when removing un-saved files from the flash object session * Fixed wrong reference of HTML file field when clicking on the drop zone area to pick a file * Added JSON template for pages * fix a CSS issue in page order * More CSS fixes * Trigger file field mutation when adding or removing a file * Recompiled JS * Removed twig templates that are no longer needed * Fixed issue with nested header fields in a page, not properly merging data * [internal] Fixed issue with collections not capable of handling both param and dot notations at the same time * Reorganized FileField structure to be more consistent with the other fields * Added support for dynamically created file fields (ie, autoinitialization on new lists items) * Added translationable strings for file uploads errors * Added translasions for all Dropzone available strings * Changed default values
2016-08-29 11:12:09 -07:00
/**
* 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;
}
2014-08-05 13:06:38 -07:00
}