Files
Grav-Admin-Plugin/classes/plugin/AdminController.php

2817 lines
89 KiB
PHP
Raw Normal View History

2014-08-05 13:06:38 -07:00
<?php
namespace Grav\Plugin\Admin;
2014-08-05 13:06:38 -07:00
2018-09-10 21:41:02 -06:00
use Grav\Common\Backup\Backups;
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;
2017-02-17 16:01:14 -07:00
use Grav\Common\GPM\GPM as GravGPM;
use Grav\Common\GPM\Installer;
use Grav\Common\Grav;
2014-08-05 13:06:38 -07:00
use Grav\Common\Data;
2020-04-30 13:27:10 -06:00
use Grav\Common\Helpers\Excerpts;
use Grav\Common\Language\Language;
use Grav\Common\Page\Interfaces\PageInterface;
2016-07-18 15:42:38 -06:00
use Grav\Common\Page\Media;
use Grav\Common\Page\Medium\ImageMedium;
2018-05-10 10:14:18 +03:00
use Grav\Common\Page\Medium\Medium;
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;
2018-09-30 17:44:50 -06:00
use Grav\Common\Security;
use Grav\Common\User\Interfaces\UserCollectionInterface;
use Grav\Common\User\User;
2015-04-27 13:37:22 +02:00
use Grav\Common\Utils;
2019-06-03 13:17:16 +03:00
use Grav\Framework\Psr7\Response;
2019-08-23 12:54:41 +03:00
use Grav\Framework\RequestHandler\Exception\RequestException;
use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
2018-08-22 12:45:39 -06:00
use Grav\Common\Yaml;
2019-02-08 17:17:05 -07:00
use PicoFeed\Parser\MalformedXmlException;
2019-08-23 12:54:41 +03:00
use Psr\Http\Message\ResponseInterface;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use Twig\Loader\FilesystemLoader;
2018-08-22 12:45:39 -06:00
2014-08-05 13:06:38 -07:00
2016-02-12 10:16:28 +01:00
/**
* Class AdminController
*
2016-02-12 10:16:28 +01:00
* @package Grav\Plugin
*/
class AdminController extends AdminBaseController
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 initialize(Grav $grav = null, $view = null, $task = null, $route = null, $post = null)
2014-08-05 13:06:38 -07:00
{
$this->grav = $grav;
2014-08-05 13:06:38 -07:00
$this->view = $view;
2018-12-05 08:20:38 +02:00
$this->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);
}
$this->post = $this->getPost($post);
2014-08-05 13:06:38 -07:00
$this->route = $route;
$this->admin = $this->grav['admin'];
$this->grav->fireEvent('onAdminControllerInit', new Event(['controller' => &$this]));
2014-08-05 13:06:38 -07:00
}
// GENERAL TASKS
/**
* Keep alive
*/
protected function taskKeepAlive()
{
$response = new Response(200);
2019-08-23 12:54:41 +03:00
$this->close($response);
}
/**
* Clear the cache.
*
* @return bool True if the action was performed.
*/
protected function taskClearCache()
{
if (!$this->authorizeTask('clear cache', ['admin.cache', 'admin.super', 'admin.maintenance'])) {
return false;
}
// get optional cleartype param
$clear_type = $this->grav['uri']->param('cleartype');
if ($clear_type) {
$clear = $clear_type;
} else {
$clear = 'standard';
}
if ($clear === 'purge') {
$msg = Cache::purgeJob();
$this->admin->json_response = [
'status' => 'success',
'message' => $msg,
];
} else {
$results = Cache::clearCache($clear);
if (count($results) > 0) {
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin::translate('PLUGIN_ADMIN.CACHE_CLEARED') . ' <br />' . $this->admin::translate('PLUGIN_ADMIN.METHOD') . ': ' . $clear . ''
];
} else {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.ERROR_CLEARING_CACHE')
];
}
}
return true;
}
/**
* Handles form and saves the input data if its valid.
*
* @return bool True if the action was performed.
*/
public function taskSave()
{
if (!$this->authorizeTask('save', $this->dataPermissions())) {
return false;
}
$this->grav['twig']->twig_vars['current_form_data'] = (array)$this->data;
switch ($this->view) {
case 'pages':
return $this->taskSavePage();
case 'user':
return $this->taskSaveUser();
default:
return $this->taskSaveDefault();
}
}
protected function taskSaveDefault()
{
// Handle standard data types.
$obj = $this->prepareData((array)$this->data);
try {
$obj->validate();
} catch (\Exception $e) {
$this->admin->setMessage($e->getMessage(), 'error');
return false;
}
$obj->filter(false, true);
$obj = $this->storeFiles($obj);
if ($obj) {
// Event to manipulate data before saving the object
$this->grav->fireEvent('onAdminSave', new Event(['object' => &$obj]));
$obj->save();
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
$this->grav->fireEvent('onAdminAfterSave', new Event(['object' => $obj]));
}
// Force configuration reload.
/** @var Config $config */
$config = $this->grav['config'];
$config->reload();
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
return true;
}
// LOGIN & USER TASKS
/**
* Handle login.
*
* @return bool True if the action was performed.
*/
protected function taskLogin()
{
$this->admin->authenticate($this->data, $this->post);
return true;
}
/**
* @return bool True if the action was performed.
*/
protected function taskTwofa()
{
$this->admin->twoFa($this->data, $this->post);
return true;
}
/**
* Handle logout.
*
* @return bool True if the action was performed.
*/
protected function taskLogout()
{
$this->admin->logout($this->data, $this->post);
return true;
}
/**
* @return bool
*/
public function taskRegenerate2FASecret()
{
if (!$this->authorizeTask('regenerate 2FA Secret', ['admin.login'])) {
return false;
}
try {
/** @var User $user */
2018-05-10 11:51:27 +03:00
$user = $this->grav['user'];
/** @var TwoFactorAuth $twoFa */
$twoFa = $this->grav['login']->twoFactorAuth();
2018-05-10 12:13:04 +03:00
$secret = $twoFa->createSecret();
$image = $twoFa->getQrImageData($user->username, $secret);
$user->set('twofa_secret', $secret);
2018-05-10 11:51:27 +03:00
2019-07-01 20:43:53 +03:00
// TODO: data user can also use save, but please test it before removing this code.
if ($user instanceof \Grav\Common\User\DataUser\User) {
// Save secret into the user file.
$file = $user->file();
if ($file->exists()) {
$content = (array)$file->content();
$content['twofa_secret'] = $secret;
$file->save($content);
$file->free();
}
} else {
$user->save();
}
2018-05-10 11:51:27 +03:00
$this->admin->json_response = ['status' => 'success', 'image' => $image, 'secret' => preg_replace('|(\w{4})|', '\\1 ', $secret)];
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false;
}
return true;
}
2014-08-05 13:06:38 -07:00
/**
* Handle the reset password action.
2015-07-30 12:20:25 +02:00
*
* @return bool True if the action was performed.
2014-08-05 13:06:38 -07:00
*/
public function taskReset()
2014-08-05 13:06:38 -07:00
{
$data = $this->data;
if (isset($data['password'])) {
/** @var UserCollectionInterface $users */
$users = $this->grav['accounts'];
$username = isset($data['username']) ? strip_tags(strtolower($data['username'])) : null;
$user = $username ? $users->load($username) : null;
2018-12-05 08:20:38 +02:00
$password = $data['password'] ?? null;
$token = $data['token'] ?? null;
if ($user && $user->exists() && !empty($user->get('reset'))) {
list($good_token, $expire) = explode('::', $user->get('reset'));
if ($good_token === $token) {
if (time() > $expire) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_LINK_EXPIRED'), 'error');
$this->setRedirect('/forgot');
return true;
}
$user->undef('hashed_password');
$user->undef('reset');
$user->set('password', $password);
$user->save();
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_PASSWORD_RESET'), 'info');
$this->setRedirect('/');
return true;
2015-11-17 11:56:21 +01:00
}
2015-11-10 17:53:09 +01:00
}
2015-11-06 15:32:26 +01:00
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
$this->setRedirect('/forgot');
return true;
}
$user = $this->grav['uri']->param('user');
$token = $this->grav['uri']->param('token');
if (empty($user) || empty($token)) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_INVALID_LINK'), 'error');
$this->setRedirect('/forgot');
return true;
}
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.RESET_NEW_PASSWORD'), 'info');
$this->admin->forgot = ['username' => $user, 'token' => $token];
return true;
}
/**
* Handle the email password recovery procedure.
*
* @return bool True if the action was performed.
*/
protected function taskForgot()
{
$param_sep = $this->grav['config']->get('system.param_sep', ':');
$post = $this->post;
$data = $this->data;
$login = $this->grav['login'];
/** @var UserCollectionInterface $users */
$users = $this->grav['accounts'];
$username = isset($data['username']) ? strip_tags(strtolower($data['username'])) : '';
$user = !empty($username) ? $users->load($username) : null;
if (!isset($this->grav['Email'])) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
$this->setRedirect($post['redirect']);
return true;
}
if (!$user || !$user->exists()) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
'info');
$this->setRedirect($post['redirect']);
return true;
}
if (empty($user->email)) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
'info');
$this->setRedirect($post['redirect']);
return true;
}
$count = $this->grav['config']->get('plugins.login.max_pw_resets_count', 0);
$interval =$this->grav['config']->get('plugins.login.max_pw_resets_interval', 2);
if ($login->isUserRateLimited($user, 'pw_resets', $count, $interval)) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate(['PLUGIN_LOGIN.FORGOT_CANNOT_RESET_IT_IS_BLOCKED', $user->email, $interval]), 'error');
$this->setRedirect($post['redirect']);
return true;
}
$token = md5(uniqid(mt_rand(), true));
$expire = time() + 604800; // next week
$user->set('reset', $token . '::' . $expire);
$user->save();
$author = $this->grav['config']->get('site.author.name', '');
$fullname = $user->fullname ?: $username;
$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');
$sitename = $this->grav['config']->get('site.title', 'Website');
$from = $this->grav['config']->get('plugins.email.from');
if (empty($from)) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error');
$this->setRedirect($post['redirect']);
return true;
}
$to = $user->email;
2018-12-05 08:20:38 +02:00
$subject = $this->admin::translate(['PLUGIN_ADMIN.FORGOT_EMAIL_SUBJECT', $sitename]);
$content = $this->admin::translate([
'PLUGIN_ADMIN.FORGOT_EMAIL_BODY',
$fullname,
$reset_link,
$author,
$sitename
]);
$body = $this->grav['twig']->processTemplate('email/base.html.twig', ['content' => $content]);
$message = $this->grav['Email']->message($subject, $body, 'text/html')->setFrom($from)->setTo($to);
$sent = $this->grav['Email']->send($message);
if ($sent < 1) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_FAILED_TO_EMAIL'), 'error');
} else {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'),
'info');
2014-08-05 13:06:38 -07:00
}
2016-03-05 12:11:13 +01:00
$this->setRedirect('/');
return true;
2014-08-05 13:06:38 -07:00
}
2015-07-30 12:20:25 +02:00
/**
* Save user account.
*
* Called by more general save task.
*
* @return bool
2015-07-30 12:20:25 +02:00
*/
protected function taskSaveUser()
2014-08-05 13:06:38 -07:00
{
/** @var UserCollectionInterface $users */
$users = $this->grav['accounts'];
$user = $users->load($this->admin->route);
if (!$this->admin->authorize(['admin.super', 'admin.users'])) {
// no user file or not admin.super or admin.users
if ($user->username !== $this->grav['user']->username) {
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' save.','error');
return false;
}
2014-08-05 13:06:38 -07:00
}
/** @var Data\Blueprint $blueprint */
$blueprint = $user->blueprints();
$data = $blueprint->processForm($this->admin->cleanUserPost((array)$this->data));
$data = new Data\Data($data, $blueprint);
try {
$data->validate();
$data->filter();
} catch (\Exception $e) {
$this->admin->setMessage($e->getMessage(), 'error');
return false;
}
$user->update($data->toArray());
2014-08-05 13:06:38 -07:00
$user = $this->storeFiles($user);
if ($user) {
// Event to manipulate data before saving the object
$this->grav->fireEvent('onAdminSave', new Event(['object' => &$user]));
$user->save();
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
$this->grav->fireEvent('onAdminAfterSave', new Event(['object' => $user]));
}
if ($user->username === $this->grav['user']->username) {
/** @var UserCollectionInterface $users */
$users = $this->grav['accounts'];
//Editing current user. Reload user object
$this->grav['user']->undef('avatar');
$this->grav['user']->merge($users->load($this->admin->route)->toArray());
}
return true;
2014-08-05 13:06:38 -07:00
}
// DASHBOARD TASKS
/**
* Get Notifications
*
*/
protected function taskGetNotifications()
2016-01-10 17:17:04 +01:00
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
$this->sendJsonResponse(['status' => 'error', 'message' => 'unauthorized']);
}
// do we need to force a reload
$refresh = $this->data['refresh'] === 'true';
$filter = $this->data['filter'] ?? '';
$filter_types = !empty($filter) ? array_map('trim', explode(',', $filter)) : [];
try {
$notifications = $this->admin->getNotifications($refresh);
$notification_data = [];
foreach ($notifications as $type => $type_notifications) {
if ($filter_types && in_array($type, $filter_types, true)) {
$twig_template = 'partials/notification-' . $type . '-block.html.twig';
$notification_data[$type] = $this->grav['twig']->processTemplate($twig_template, ['notifications' => $type_notifications]);
}
}
$json_response = [
'status' => 'success',
'notifications' => $notification_data
];
} catch (\Exception $e) {
$json_response = ['status' => 'error', 'message' => $e->getMessage()];
}
$this->sendJsonResponse($json_response);
}
2014-08-05 13:06:38 -07:00
/**
* Hide notifications.
2014-08-05 13:06:38 -07:00
*
* @return bool True if the action was performed.
*/
protected function taskHideNotification()
2014-08-05 13:06:38 -07:00
{
if (!$this->authorizeTask('hide notification', ['admin.login'])) {
return false;
}
2014-08-05 13:06:38 -07:00
$notification_id = $this->grav['uri']->param('notification_id');
if (!$notification_id) {
$this->admin->json_response = [
'status' => 'error'
];
return false;
}
2014-08-05 13:06:38 -07:00
$filename = $this->grav['locator']->findResource('user://data/notifications/' . $this->grav['user']->username . YAML_EXT,
true, true);
$file = CompiledYamlFile::instance($filename);
$data = (array)$file->content();
$data[] = $notification_id;
$file->save($data);
$this->admin->json_response = [
'status' => 'success'
];
2014-08-05 13:06:38 -07:00
return true;
}
/**
* Get Newsfeeds
*/
protected function taskGetNewsFeed()
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
$this->sendJsonResponse(['status' => 'error', 'message' => 'unauthorized']);
}
$refresh = $this->data['refresh'] === 'true' ? true : false;
try {
$feed = $this->admin->getFeed($refresh);
$feed_data = $this->grav['twig']->processTemplate('partials/feed-block.html.twig', ['feed' => $feed]);
$json_response = [
'status' => 'success',
'feed_data' => $feed_data
];
} catch (MalformedXmlException $e) {
$json_response = ['status' => 'error', 'message' => $e->getMessage()];
}
$this->sendJsonResponse($json_response);
}
// BACKUP TASKS
/**
* Handle the backup action
*
* @return bool True if the action was performed.
*/
protected function taskBackup()
{
$param_sep = $this->grav['config']->get('system.param_sep', ':');
if (!$this->authorizeTask('backup', ['admin.maintenance', 'admin.super'])) {
return false;
}
$download = $this->grav['uri']->param('download');
try {
if ($download) {
$file = base64_decode(urldecode($download));
$backups_root_dir = $this->grav['locator']->findResource('backup://', true);
if (0 !== strpos($file, $backups_root_dir)) {
$response = new Response(401);
2019-08-23 12:54:41 +03:00
$this->close($response);
}
Utils::download($file, true);
}
$id = $this->grav['uri']->param('id', 0);
$backup = Backups::backup($id);
} catch (\Exception $e) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.AN_ERROR_OCCURRED') . '. ' . $e->getMessage()
];
return true;
}
$download = urlencode(base64_encode($backup));
$url = rtrim($this->grav['uri']->rootUrl(false), '/') . '/' . trim($this->admin->base,
'/') . '/task' . $param_sep . 'backup/download' . $param_sep . $download . '/admin-nonce' . $param_sep . Utils::getNonce('admin-form');
$this->admin->json_response = [
'status' => 'success',
'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>',
'toastr' => [
'timeOut' => 0,
'extendedTimeOut' => 0,
'closeButton' => true
]
];
return true;
}
/**
* Handle delete backup action
*
* @return bool
*/
protected function taskBackupDelete()
{
$param_sep = $this->grav['config']->get('system.param_sep', ':');
if (!$this->authorizeTask('backup', ['admin.maintenance', 'admin.super'])) {
return false;
}
$backup = $this->grav['uri']->param('backup', null);
if (null !== $backup) {
$file = base64_decode(urldecode($backup));
$backups_root_dir = $this->grav['locator']->findResource('backup://', true);
$backup_path = $backups_root_dir . '/' . $file;
if (file_exists($backup_path)) {
unlink($backup_path);
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin::translate('PLUGIN_ADMIN.BACKUP_DELETED'),
'toastr' => [
'closeButton' => true
]
];
} else {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.BACKUP_NOT_FOUND'),
];
}
}
return true;
}
// PLUGIN / THEME TASKS
/**
* Enable a plugin.
*
* Route: /plugins
*
* @return bool True if the action was performed.
*/
public function taskEnable()
{
if (!$this->authorizeTask('enable plugin', ['admin.plugins', 'admin.super'])) {
return false;
}
if ($this->view !== 'plugins') {
return false;
}
// Filter value and save it.
$this->post = ['enabled' => true];
$obj = $this->prepareData($this->post);
$obj->save();
$this->post = ['_redirect' => 'plugins'];
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_ENABLED_PLUGIN'), 'info');
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
return true;
}
/**
* Disable a plugin.
*
* Route: /plugins
*
* @return bool True if the action was performed.
*/
public function taskDisable()
{
if (!$this->authorizeTask('disable plugin', ['admin.plugins', 'admin.super'])) {
return false;
}
if ($this->view !== 'plugins') {
return false;
}
// Filter value and save it.
$this->post = ['enabled' => false];
$obj = $this->prepareData($this->post);
$obj->save();
$this->post = ['_redirect' => 'plugins'];
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_DISABLED_PLUGIN'), 'info');
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
return true;
}
/**
* Set the default theme.
*
* Route: /themes
*
* @return bool True if the action was performed.
*/
public function taskActivate()
{
if (!$this->authorizeTask('activate theme', ['admin.themes', 'admin.super'])) {
return false;
}
if ($this->view !== 'themes') {
return false;
}
$this->post = ['_redirect' => 'themes'];
// Make sure theme exists (throws exception)
$name = $this->route;
$this->grav['themes']->get($name);
// Store system configuration.
$system = $this->admin->data('config/system');
$system->set('pages.theme', $name);
$system->save();
// Force configuration reload and save.
/** @var Config $config */
$config = $this->grav['config'];
$config->reload()->save();
$config->set('system.pages.theme', $name);
2016-07-07 18:55:52 +02:00
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_CHANGED_THEME'), 'info');
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
return true;
}
// INSTALL & UPGRADE
/**
* Handles updating Grav
*
* @return bool False if user has no permissions.
*/
public function taskUpdategrav()
{
if (!$this->authorizeTask('install grav', ['admin.super'])) {
return false;
}
$gpm = Gpm::GPM();
$version = $gpm->grav->getVersion();
$result = Gpm::selfupgrade();
if ($result) {
$json_response = [
'status' => 'success',
'type' => 'updategrav',
'version' => $version,
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.GRAV_WAS_SUCCESSFULLY_UPDATED_TO') . ' ' . $version
];
} else {
$json_response = [
'status' => 'error',
'type' => 'updategrav',
'version' => GRAV_VERSION,
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.GRAV_UPDATE_FAILED') . ' <br>' . Installer::lastErrorMsg()
];
}
$this->sendJsonResponse($json_response);
return true;
}
/**
* Handles uninstalling plugins and themes
*
* Route: /plugins
* Route: /themes
*
* @deprecated
*
* @return bool True if the action was performed
*/
public function taskUninstall()
{
$type = $this->view === 'plugins' ? 'plugins' : 'themes';
if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) {
return false;
}
$package = $this->route;
$result = Gpm::uninstall($package, []);
if ($result) {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL'), 'info');
} else {
2018-12-05 08:20:38 +02:00
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.UNINSTALL_FAILED'), 'error');
}
$this->post = ['_redirect' => $this->view];
return true;
}
/**
* Toggle the gpm.releases setting
*/
protected function taskGpmRelease()
{
2020-02-03 13:52:58 +02:00
if (!$this->authorizeTask('configuration', ['admin.configuration.system', '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];
return true;
}
/**
* Get update status from GPM
*/
protected function taskGetUpdates()
{
if (!$this->authorizeTask('dashboard', ['admin.login', 'admin.super'])) {
return false;
}
$data = $this->post;
$flush = !empty($data['flush']);
if (isset($this->grav['session'])) {
$this->grav['session']->close();
}
try {
$gpm = new GravGPM($flush);
$resources_updates = $gpm->getUpdatable();
foreach ($resources_updates as $key => $update) {
if (!is_iterable($update)) {
continue;
}
foreach ($update as $slug => $item) {
$resources_updates[$key][$slug] = $item;
}
}
if ($gpm->grav !== null) {
$grav_updates = [
'isUpdatable' => $gpm->grav->isUpdatable(),
'assets' => $gpm->grav->getAssets(),
'version' => GRAV_VERSION,
'available' => $gpm->grav->getVersion(),
'date' => $gpm->grav->getDate(),
'isSymlink' => $gpm->grav->isSymlink()
];
$this->admin->json_response = [
'status' => 'success',
'payload' => [
'resources' => $resources_updates,
'grav' => $grav_updates,
'installed' => $gpm->countInstalled(),
'flushed' => $flush
]
];
} else {
$this->admin->json_response = ['status' => 'error', 'message' => 'Cannot connect to the GPM'];
return false;
}
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false;
}
return true;
}
/**
* Handle getting a new package dependencies needed to be installed
*
* @return bool
*/
protected function taskGetPackagesDependencies()
{
$data = $this->post;
$packages = isset($data['packages']) ? explode(',', $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()];
return false;
}
$this->admin->json_response = ['status' => 'success', 'dependencies' => $dependencies];
return true;
}
protected function taskInstallDependenciesOfPackages()
{
$data = $this->post;
$packages = isset($data['packages']) ? explode(',', $data['packages']) : '';
$packages = (array)$packages;
$type = $data['type'] ?? '';
if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
return false;
}
try {
$dependencies = $this->admin->getDependenciesNeededToInstall($packages);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false;
}
$result = Gpm::install(array_keys($dependencies), ['theme' => $type === 'theme']);
if ($result) {
$this->admin->json_response = ['status' => 'success', 'message' => 'Dependencies installed successfully'];
} else {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.INSTALLATION_FAILED')
];
}
2018-09-30 15:26:19 -06:00
return true;
}
2018-09-29 21:18:45 -06:00
protected function taskInstallPackage($reinstall = false)
{
$data = $this->post;
$package = $data['package'] ?? '';
$type = $data['type'] ?? '';
if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
return false;
}
2017-04-14 11:24:48 -06:00
try {
$result = Gpm::install($package, ['theme' => $type === 'theme']);
} catch (\Exception $e) {
$this->admin->json_response = ['status' => 'error', 'message' => $e->getMessage()];
return false;
}
if ($result) {
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin::translate(is_string($result) ? $result : sprintf($this->admin::translate($reinstall ?: 'PLUGIN_ADMIN.PACKAGE_X_REINSTALLED_SUCCESSFULLY',
null), $package))
];
} else {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate($reinstall ?: 'PLUGIN_ADMIN.INSTALLATION_FAILED')
];
}
2016-07-07 18:55:52 +02:00
return true;
}
2017-03-21 15:35:33 -06:00
/**
* Handle removing a package
*/
protected function taskRemovePackage()
{
$data = $this->post;
$package = $data['package'] ?? '';
$type = $data['type'] ?? '';
2020-02-04 11:22:27 +02:00
$result = false;
if (!$this->authorizeTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) {
$json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
$this->sendJsonResponse($json_response, 403);
}
2016-07-07 18:55:52 +02:00
//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) {
$message = 'The installed packages <cyan>' . implode('</cyan>, <cyan>',
$dependent_packages) . '</cyan> depends on this package. Please remove those first.';
} else {
$message = 'The installed package <cyan>' . implode('</cyan>, <cyan>',
$dependent_packages) . '</cyan> depends on this package. Please remove it first.';
}
$json_response = ['status' => 'error', 'message' => $message];
$this->sendJsonResponse($json_response, 200);
}
2020-02-04 11:22:27 +02:00
$dependencies = false;
try {
$dependencies = $this->admin->dependenciesThatCanBeRemovedWhenRemoving($package);
$result = Gpm::uninstall($package, []);
} catch (\Exception $e) {
$json_response = ['status' => 'error', 'message' => $e->getMessage()];
$this->sendJsonResponse($json_response, 200);
}
if ($result) {
$json_response = [
'status' => 'success',
'dependencies' => $dependencies,
'message' => $this->admin::translate(is_string($result) ? $result : 'PLUGIN_ADMIN.UNINSTALL_SUCCESSFUL')
];
$this->sendJsonResponse($json_response, 200);
}
$json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.UNINSTALL_FAILED')
];
$this->sendJsonResponse($json_response, 200);
}
/**
* Handle reinstalling a package
*/
protected function taskReinstallPackage()
{
$data = $this->post;
$slug = $data['slug'] ?? '';
$type = $data['type'] ?? '';
$package_name = $data['package_name'] ?? '';
$current_version = $data['current_version'] ?? '';
if (!$this->authorizeTask('install ' . $type, ['admin.' . $type, 'admin.super'])) {
$json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK')
];
$this->sendJsonResponse($json_response, 403);
}
$url = "https://getgrav.org/download/{$type}s/$slug/$current_version";
$result = Gpm::directInstall($url);
if ($result === true) {
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin::translate(sprintf($this->admin::translate('PLUGIN_ADMIN.PACKAGE_X_REINSTALLED_SUCCESSFULLY',
null), $package_name))
];
} else {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.REINSTALLATION_FAILED')
];
}
}
2015-07-30 12:20:25 +02:00
/**
* Handle direct install.
2015-07-30 12:20:25 +02:00
*/
protected function taskDirectInstall()
2015-04-27 13:37:22 +02:00
{
if (!$this->authorizeTask('install', ['admin.super'])) {
return false;
}
$file_path = $this->data['file_path'] ?? null;
if (isset($_FILES['uploaded_file'])) {
// Check $_FILES['file']['error'] value.
switch ($_FILES['uploaded_file']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.NO_FILES_SENT'), 'error');
return false;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT'), 'error');
return false;
case UPLOAD_ERR_NO_TMP_DIR:
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR'), 'error');
return false;
default:
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.UNKNOWN_ERRORS'), 'error');
return false;
}
$file_name = $_FILES['uploaded_file']['name'];
$file_path = $_FILES['uploaded_file']['tmp_name'];
2014-09-22 15:49:53 -06:00
// Handle bad filenames.
if (!Utils::checkFilename($file_name)) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')
];
2016-03-05 12:11:13 +01:00
return false;
}
2014-09-22 17:13:19 -06:00
}
$result = Gpm::directInstall($file_path);
if ($result === true) {
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INSTALLATION_SUCCESSFUL'), 'info');
} else {
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INSTALLATION_FAILED') . ': ' . $result,
'error');
}
$this->setRedirect('/tools');
2014-09-22 15:49:53 -06:00
return true;
}
// PAGE TASKS
/**
* 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())) {
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
$data = (array)$this->data;
2016-03-05 12:11:13 +01:00
if ($data['route'] === '' || $data['route'] === '/') {
$path = $this->grav['locator']->findResource('page://');
} else {
2019-08-23 20:45:51 +03:00
$pages = $this->admin::enablePages();
$page = $pages->find($data['route']);
if (!$page) {
return false;
}
$path = $page->path();
}
2016-03-05 12:11:13 +01:00
$orderOfNewFolder = static::getNextOrderInFolder($path);
$new_path = $path . '/' . $orderOfNewFolder . '.' . $data['folder'];
Folder::create($new_path);
Cache::clearCache('invalidate');
2016-03-05 12:11:13 +01:00
$this->grav->fireEvent('onAdminAfterSaveAs', new Event(['path' => $new_path]));
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
2014-09-22 16:35:11 -06:00
$this->setRedirect($this->admin->getAdminRoute("/{$this->view}")->toString());
2016-03-05 12:11:13 +01:00
return true;
}
protected function taskSavePage()
{
$reorder = true;
$data = (array)$this->data;
$this->grav['twig']->twig_vars['current_form_data'] = $data;
2019-08-23 20:45:51 +03:00
$pages = $this->admin::enablePages();
// Find new parent page in order to build the path.
$route = $data['route'] ?? dirname($this->admin->route);
/** @var PageInterface $obj */
$obj = $this->admin->page(true);
if (!isset($data['folder']) || !$data['folder']) {
$data['folder'] = $obj->slug();
$this->data['folder'] = $obj->slug();
2019-02-08 17:17:05 -07:00
}
// Ensure route is prefixed with a forward slash.
$route = '/' . ltrim($route, '/');
// Check for valid frontmatter
if (isset($data['frontmatter']) && !$this->checkValidFrontmatter($data['frontmatter'])) {
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INVALID_FRONTMATTER_COULD_NOT_SAVE'),
'error');
return false;
2019-02-08 17:17:05 -07:00
}
// XSS Checks for page content
$xss_whitelist = $this->grav['config']->get('security.xss_whitelist', 'admin.super');
if (!$this->admin->authorize($xss_whitelist)) {
$check_what = ['header' => $data['header'] ?? '', 'frontmatter' => $data['frontmatter'] ?? '', 'content' => $data['content'] ?? ''];
$results = Security::detectXssFromArray($check_what);
if (!empty($results)) {
$this->admin->setMessage('<i class="fa fa-ban"></i> ' . $this->admin::translate('PLUGIN_ADMIN.XSS_ONSAVE_ISSUE'),
'error');
return false;
}
}
$parent = $route && $route !== '/' && $route !== '.' && $route !== '/.' ? $pages->dispatch($route, true) : $pages->root();
$original_order = (int)trim($obj->order(), '.');
2019-02-08 17:17:05 -07:00
try {
// 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());
$obj->validate();
2017-02-26 19:36:01 +01:00
} catch (\Exception $e) {
$this->admin->setMessage($e->getMessage(), 'error');
return false;
}
$obj->filter();
2014-10-07 15:32:07 -06:00
// rename folder based on visible
if ($original_order === 1000) {
// increment order to force reshuffle
$obj->order($original_order + 1);
}
if (isset($data['order']) && !empty($data['order'])) {
$reorder = explode(',', $data['order']);
}
// add or remove numeric prefix based on ordering value
if (isset($data['ordering'])) {
if ($data['ordering'] && !$obj->order()) {
$obj->order(static::getNextOrderInFolder($obj->parent()->path()));
$reorder = false;
} elseif (!$data['ordering'] && $obj->order()) {
$obj->folder($obj->slug());
}
}
$obj = $this->storeFiles($obj);
if ($obj) {
// Event to manipulate data before saving the object
$this->grav->fireEvent('onAdminSave', new Event(['object' => &$obj]));
2019-06-14 12:19:31 +03:00
$obj->save($reorder);
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SAVED'), 'info');
$this->grav->fireEvent('onAdminAfterSave', new Event(['object' => $obj]));
}
if (method_exists($obj, 'unsetRouteSlug')) {
$obj->unsetRouteSlug();
}
$multilang = $this->isMultilang();
if ($multilang && !$obj->language()) {
$obj->language($this->admin->getLanguage());
}
$admin_route = $this->admin->base;
$route = $obj->rawRoute();
$redirect_url = ($multilang ? '/' . $obj->language() : '') . $admin_route . '/' . $this->view . $route;
$this->setRedirect($redirect_url);
return true;
}
2016-03-05 12:11:13 +01:00
/**
* Save page as a new copy.
*
* Route: /pages
*
* @return bool True if the action was performed.
* @throws \RuntimeException
*/
protected function taskCopy()
{
if (!$this->authorizeTask('copy page', ['admin.pages', 'admin.super'])) {
return false;
}
// Only applies to pages.
if ($this->view !== 'pages') {
return false;
}
try {
2019-08-23 20:45:51 +03:00
$pages = $this->admin::enablePages();
// 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();
// Make a copy of the current page and fill the updated information into it.
$page = $original_page->copy($parent);
$order = 0;
if ($page->order()) {
$order = $this->getNextOrderInFolder($page->parent()->path());
}
// Make sure the header is loaded in case content was set through raw() (expert mode)
$page->header();
if ($page->order()) {
$page->order($order);
}
$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 progressive number 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();
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();
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
$this->grav->fireEvent('onAdminAfterSave', new Event(['page' => $page]));
// Enqueue message and redirect to new location.
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_COPIED'), 'info');
$this->setRedirect($redirect);
} catch (\Exception $e) {
throw new \RuntimeException('Copying page failed on error: ' . $e->getMessage());
}
return true;
}
2015-07-30 12:20:25 +02:00
/**
* Reorder pages.
2015-07-30 12:20:25 +02:00
*
* Route: /pages
*
* @return bool True if the action was performed.
2015-07-30 12:20:25 +02:00
*/
protected function taskReorder()
2014-09-22 17:13:19 -06:00
{
if (!$this->authorizeTask('reorder pages', ['admin.pages', 'admin.super'])) {
return false;
}
// Only applies to pages.
if ($this->view !== 'pages') {
2016-01-21 09:46:38 +02:00
return false;
}
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.REORDERING_WAS_SUCCESSFUL'), 'info');
2014-09-22 17:27:48 -06:00
return true;
}
/**
* Delete page.
*
* Route: /pages
*
* @return bool True if the action was performed.
* @throws \RuntimeException
*/
protected function taskDelete()
{
if (!$this->authorizeTask('delete page', ['admin.pages', 'admin.super'])) {
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
// Only applies to pages.
if ($this->view !== 'pages') {
return false;
2014-09-22 17:27:48 -06:00
}
2015-07-30 12:20:25 +02:00
try {
$page = $this->admin->page();
2016-03-05 12:11:13 +01:00
if (count($page->translatedLanguages()) > 1) {
$page->file()->delete();
} else {
Folder::delete($page->path());
}
$this->grav->fireEvent('onAdminAfterDelete', new Event(['page' => $page]));
Cache::clearCache('invalidate');
// Set redirect to pages list.
$redirect = 'pages';
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_DELETED'), 'info');
$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.
*
* Route: /pages
*/
protected function taskSwitchlanguage()
{
if (!$this->authorizeTask('switch language', ['admin.pages', 'admin.super'])) {
return false;
2015-08-06 15:16:22 +02:00
}
$data = (array)$this->data;
$language = $data['lang'] ?? $this->grav['uri']->param('lang');
if (isset($data['redirect'])) {
$redirect = '/pages/' . $data['redirect'];
} else {
$redirect = '/pages';
}
2015-04-13 21:37:12 +02:00
if ($language) {
$this->grav['session']->admin_lang = $language ?: 'en';
2015-04-13 21:37:12 +02:00
}
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SWITCHED_LANGUAGE'), 'info');
$this->setRedirect($this->admin->getAdminRoute($redirect)->toString());
2015-04-13 21:37:12 +02:00
return true;
}
/**
* Save the current page in a different language. Automatically switches to that language.
2016-01-15 12:57:09 +01:00
*
* @return bool True if the action was performed.
2016-01-15 12:57:09 +01:00
*/
protected function taskSaveas()
2016-01-15 12:57:09 +01:00
{
if (!$this->authorizeTask('save', $this->dataPermissions())) {
return false;
}
2016-01-15 12:57:09 +01:00
/** @var Language $language */
$language = $this->grav['language'];
2015-04-13 21:37:12 +02:00
$data = (array)$this->data;
$lang = $data['lang'] ?? null;
if ($lang) {
$this->grav['session']->admin_lang = $lang ?: 'en';
}
$uri = $this->grav['uri'];
$obj = $this->admin->page($uri->route());
$this->preparePage($obj, false, $lang);
$file = $obj->file();
if ($file) {
$filename = $this->determineFilenameIncludingLanguage($obj->name(), $lang);
2016-01-15 12:57:09 +01:00
$path = $obj->path() . DS . $filename;
$aFile = File::instance($path);
$aFile->save();
2018-09-11 16:12:46 -06:00
$aPage = new Page();
$aPage->init(new \SplFileInfo($path), $lang . '.md');
$aPage->header($obj->header());
$aPage->rawMarkdown($obj->rawMarkdown());
$aPage->template($obj->template());
$aPage->validate();
$aPage->filter();
2018-09-11 16:12:46 -06:00
$this->grav->fireEvent('onAdminSave', new Event(['page' => &$aPage]));
2019-06-14 12:19:31 +03:00
$aPage->save();
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
$this->grav->fireEvent('onAdminAfterSave', new Event(['page' => $aPage]));
2018-09-11 16:12:46 -06:00
}
$this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.SUCCESSFULLY_SWITCHED_LANGUAGE'), 'info');
// TODO: better multilanguage support needed.
$this->setRedirect($language->getLanguageURLPrefix($lang) . $uri->route());
2018-09-11 16:12:46 -06:00
2016-01-15 12:57:09 +01:00
return true;
}
2019-06-02 14:44:06 -06:00
/**
* Continue to the new page.
2019-06-02 14:44:06 -06:00
*
* @return bool True if the action was performed.
2019-06-02 14:44:06 -06:00
*/
public function taskContinue()
2019-06-02 14:44:06 -06:00
{
$data = (array)$this->data;
2019-06-02 14:44:06 -06:00
if ($this->view === 'users') {
$username = strip_tags(strtolower($data['username']));
$this->setRedirect("{$this->view}/{$username}");
return true;
}
2019-06-02 14:44:06 -06:00
if ($this->view === 'groups') {
$this->setRedirect("{$this->view}/{$data['groupname']}");
return true;
}
if ($this->view !== 'pages') {
return false;
}
2019-05-28 19:53:10 +02:00
$route = $data['route'] !== '/' ? $data['route'] : '';
2020-03-03 15:01:17 +02:00
$folder = $data['folder'] ?? null;
$title = $data['title'] ?? null;
// Handle @slugify-{field} value, automatically slugifies the specified field
2020-03-03 15:01:17 +02:00
if (null !== $folder && 0 === strpos($folder, '@slugify-')) {
$folder = \Grav\Plugin\Admin\Utils::slug($data[substr($folder, 9)] ?? '');
}
if (!$folder) {
$folder = \Grav\Plugin\Admin\Utils::slug($title) ?: '';
2019-05-28 19:53:10 +02:00
}
$folder = ltrim($folder, '_');
if (!empty($data['modular'])) {
$folder = '_' . $folder;
}
2020-03-03 15:01:17 +02:00
$data['folder'] = $folder;
2020-03-03 15:01:17 +02:00
$path = $route . '/' . $folder;
$this->admin->session()->{$path} = $data;
2019-06-02 14:44:06 -06:00
// Store the name and route of a page, to be used pre-filled defaults of the form in the future
$this->admin->session()->lastPageName = $data['name'];
$this->admin->session()->lastPageRoute = $data['route'];
2019-06-13 09:55:39 -06:00
$this->setRedirect("{$this->view}/" . ltrim($path, '/'));
return true;
}
2019-05-28 19:53:10 +02:00
/**
* $data['route'] = $this->grav['uri']->param('route');
* $data['sortby'] = $this->grav['uri']->param('sortby', null);
* $data['filters'] = $this->grav['uri']->param('filters', null);
* $data['page'] $this->grav['uri']->param('page', true);
* $data['base'] = $this->grav['uri']->param('base');
* $initial = (bool) $this->grav['uri']->param('initial');
*
2019-08-23 12:54:41 +03:00
* @return ResponseInterface
* @throws RequestException
*/
2019-08-23 12:54:41 +03:00
protected function taskGetLevelListing(): ResponseInterface
{
2019-08-23 12:54:41 +03:00
$this->checkTaskAuthorization('save', $this->dataPermissions());
2019-05-28 19:53:10 +02:00
2019-08-23 12:54:41 +03:00
$request = $this->getRequest();
$data = $request->getParsedBody();
if (!isset($data['field'])) {
throw new RequestException($request, 'Bad Request', 400);
}
// Base64 decode the route
$data['route'] = isset($data['route']) ? base64_decode($data['route']) : null;
2019-08-23 12:54:41 +03:00
$initial = $data['initial'] ?? null;
if ($initial) {
$data['leaf_route'] = $data['route'];
$data['route'] = null;
$data['level'] = 1;
2019-05-28 19:53:10 +02:00
}
2019-08-23 12:58:56 +03:00
[$status, $message, $response,] = $this->getLevelListing($data);
2019-08-23 12:54:41 +03:00
$json = [
'status' => $status,
'message' => $this->admin::translate($message ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED'),
'data' => array_values($response)
];
2019-08-23 12:54:41 +03:00
return $this->createJsonResponse($json, 200);
2019-05-28 19:53:10 +02:00
}
2017-03-05 17:45:10 -07:00
protected function taskGetChildTypes()
{
if (!$this->authorizeTask('get childtypes', ['admin.pages', 'admin.super'])) {
2018-05-10 10:14:18 +03:00
return false;
2017-03-05 17:45:10 -07:00
}
$data = $this->post;
2018-12-05 08:20:38 +02:00
$rawroute = $data['rawroute'] ?? null;
2017-03-05 17:45:10 -07:00
if ($rawroute) {
2019-08-23 20:45:51 +03:00
$pages = $this->admin::enablePages();
/** @var PageInterface $page */
$page = $pages->dispatch($rawroute);
2017-03-05 17:45:10 -07:00
if ($page) {
$child_type = $page->childType();
if ($child_type !== '') {
2017-03-05 17:45:10 -07:00
$this->admin->json_response = [
'status' => 'success',
'child_type' => $child_type
];
return true;
}
}
}
$this->admin->json_response = [
'status' => 'success',
'child_type' => '',
// 'message' => $this->admin::translate('PLUGIN_ADMIN.NO_CHILD_TYPE')
2017-03-05 17:45:10 -07:00
];
return true;
}
2016-02-12 10:16:28 +01:00
/**
* Handles filtering the page by modular/visible/routable in the pages list.
*/
protected function taskFilterPages()
{
if (!$this->authorizeTask('filter pages', ['admin.pages', 'admin.super'])) {
return;
}
$data = $this->post;
[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
$flags = !empty($data['flags']) ? array_map('strtolower', explode(',', $data['flags'])) : [];
$queries = !empty($data['query']) ? explode(',', $data['query']) : [];
2019-08-23 20:45:51 +03:00
$pages = $this->admin::enablePages();
/** @var Collection $collection */
$collection = $pages->all();
if (count($flags)) {
// Filter by state
$pageStates = [
'modular',
'nonmodular',
'visible',
'nonvisible',
'routable',
'nonroutable',
'published',
'nonpublished'
[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 (count(array_intersect($pageStates, $flags)) > 0) {
2018-05-10 10:14:18 +03:00
if (in_array('modular', $flags, true)) {
$collection = $collection->modular();
}
[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
2018-05-10 10:14:18 +03:00
if (in_array('nonmodular', $flags, true)) {
$collection = $collection->nonModular();
}
[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
2018-05-10 10:14:18 +03:00
if (in_array('visible', $flags, true)) {
$collection = $collection->visible();
}
[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
2018-05-10 10:14:18 +03:00
if (in_array('nonvisible', $flags, true)) {
$collection = $collection->nonVisible();
}
[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
2018-05-10 10:14:18 +03:00
if (in_array('routable', $flags, true)) {
$collection = $collection->routable();
}
[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
2018-05-10 10:14:18 +03:00
if (in_array('nonroutable', $flags, true)) {
$collection = $collection->nonRoutable();
}
[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
2018-05-10 10:14:18 +03:00
if (in_array('published', $flags, true)) {
$collection = $collection->published();
}
[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
2018-05-10 10:14:18 +03:00
if (in_array('nonpublished', $flags, true)) {
$collection = $collection->nonPublished();
}
[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
}
foreach ($pageStates as $pageState) {
2018-05-10 10:14:18 +03:00
if (($pageState = array_search($pageState, $flags, true)) !== false) {
unset($flags[$pageState]);
}
[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
}
// Filter by page type
2018-05-10 10:14:18 +03:00
if ($flags) {
$types = [];
[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
$pageTypes = array_keys(Pages::pageTypes());
foreach ($pageTypes as $pageType) {
2018-12-05 08:20:38 +02:00
if (($pageKey = array_search($pageType, $flags, true)) !== false) {
$types[] = $pageType;
unset($flags[$pageKey]);
}
}
[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 (count($types)) {
$collection = $collection->ofOneOfTheseTypes($types);
}
}
[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
// Filter by page type
2018-05-10 10:14:18 +03:00
if ($flags) {
$accessLevels = $flags;
$collection = $collection->ofOneOfTheseAccessLevels($accessLevels);
}
[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 (!empty($queries)) {
foreach ($collection as $page) {
foreach ($queries as $query) {
$query = trim($query);
2018-12-05 08:20:38 +02:00
if (stripos($page->getRawContent(), $query) === false
&& stripos($page->title(), $query) === false
&& stripos($page->folder(), $query) === false
&& stripos($page->slug(), \Grav\Plugin\Admin\Utils::slug($query)) === false
) {
$collection->remove($page);
}
}
[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
}
}
$results = [];
foreach ($collection as $path => $page) {
$results[] = $page->route();
}
[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',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.PAGES_FILTERED'),
'results' => $results
[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->collection = $collection;
[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 the page Markdown
*
* @return bool True if the action was performed.
*/
protected function taskProcessMarkdown()
{
if (!$this->authorizeTask('process markdown', ['admin.pages', 'admin.super'])) {
return false;
}
try {
$page = $this->admin->page(true);
if (!$page) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
return false;
}
$this->preparePage($page, true);
$page->header();
$page->templateFormat('html');
// Add theme template paths to Twig loader
$template_paths = $this->grav['locator']->findResources('theme://templates');
$this->grav['twig']->twig->getLoader()->addLoader(new FilesystemLoader($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()];
return false;
}
return true;
}
// MEDIA TASKS
[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
/**
* Determines the file types allowed to be uploaded
[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 bool True if the action was performed.
*/
protected function taskListmedia()
[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('list media', ['admin.pages', 'admin.super'])) {
[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;
}
$media = $this->getMedia();
if (!$media) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
[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;
[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
}
$media_list = [];
2018-05-10 10:14:18 +03:00
/**
* @var string $name
* @var Medium|ImageMedium $medium
2018-05-10 10:14:18 +03:00
*/
foreach ($media->all() as $name => $medium) {
$metadata = [];
$img_metadata = $medium->metadata();
if ($img_metadata) {
$metadata = $img_metadata;
}
// Get original name
/** @var ImageMedium $source */
$source = method_exists($medium, 'higherQualityAlternative') ? $medium->higherQualityAlternative() : null;
2018-12-05 08:20:38 +02:00
$media_list[$name] = [
'url' => $medium->display($medium->get('extension') === 'svg' ? 'source' : 'thumbnail')->cropZoom(400, 300)->url(),
'size' => $medium->get('size'),
'metadata' => $metadata,
'original' => $source ? $source->get('filename') : null
2018-12-05 08:20:38 +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
}
$this->admin->json_response = ['status' => 'success', 'results' => $media_list];
[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;
}
2014-08-05 13:06:38 -07:00
/**
* Handles adding a media file to a page
2014-08-05 13:06:38 -07:00
*
* @return bool True if the action was performed.
*/
protected function taskAddmedia()
2014-08-05 13:06:38 -07:00
{
if (!$this->authorizeTask('add media', ['admin.pages', 'admin.super'])) {
2016-01-21 09:46:38 +02:00
return false;
}
/** @var Config $config */
2016-01-21 09:46:38 +02:00
$config = $this->grav['config'];
if (empty($_FILES)) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.EXCEEDED_POSTMAX_LIMIT')
];
return false;
}
if (!isset($_FILES['file']['error']) || is_array($_FILES['file']['error'])) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.INVALID_PARAMETERS')
];
return false;
}
// Check $_FILES['file']['error'] value.
switch ($_FILES['file']['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.NO_FILES_SENT')
];
2016-03-05 12:11:13 +01:00
return false;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.EXCEEDED_FILESIZE_LIMIT')
];
2016-07-07 18:55:52 +02:00
return false;
case UPLOAD_ERR_NO_TMP_DIR:
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.UPLOAD_ERR_NO_TMP_DIR')
];
2016-07-07 18:55:52 +02:00
return false;
default:
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.UNKNOWN_ERRORS')
];
return false;
2015-08-05 18:10:15 -06:00
}
2018-10-04 15:42:59 +03:00
$filename = $_FILES['file']['name'];
// Handle bad filenames.
if (!Utils::checkFilename($filename)) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD'),
2018-10-04 15:42:59 +03:00
$filename, 'Bad filename')
];
return false;
}
// You should also check filesize here.
$grav_limit = Utils::getUploadLimit();
if ($grav_limit > 0 && $_FILES['file']['size'] > $grav_limit) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')
];
[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;
[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
}
2014-08-05 13:06:38 -07:00
// Check extension
2018-10-04 15:42:59 +03:00
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
2014-10-07 12:12:21 +03:00
// If not a supported type, return
2018-10-04 15:42:59 +03:00
if (!$extension || !$config->get("media.types.{$extension}")) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.UNSUPPORTED_FILE_TYPE') . ': ' . $extension
];
return false;
}
2015-12-29 19:31:02 +01:00
$media = $this->getMedia();
if (!$media) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
return false;
}
2018-05-22 10:58:35 +03:00
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$path = $media->getPath();
2018-05-22 10:58:35 +03:00
if ($locator->isStream($path)) {
$path = $locator->findResource($path, true, true);
}
2019-09-03 12:18:20 -06:00
// Special Sanitization for SVG
if (Utils::contains($extension, 'svg', false)) {
Security::sanitizeSVG($_FILES['file']['tmp_name']);
}
// Upload it
2018-12-05 08:20:38 +02:00
if (!move_uploaded_file($_FILES['file']['tmp_name'], sprintf('%s/%s', $path, $filename))) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.FAILED_TO_MOVE_UPLOADED_FILE')
];
2015-12-29 19:31:02 +01:00
return false;
2014-08-05 13:06:38 -07:00
}
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
// Add metadata if needed
$include_metadata = Grav::instance()['config']->get('system.media.auto_metadata_exif', false);
2018-10-04 15:42:59 +03:00
$basename = str_replace(['@3x', '@2x'], '', pathinfo($filename, PATHINFO_BASENAME));
$metadata = [];
2018-10-04 15:42:59 +03:00
if ($include_metadata && isset($media[$basename])) {
$img_metadata = $media[$basename]->metadata();
if ($img_metadata) {
$metadata = $img_metadata;
}
}
2017-05-12 12:54:08 -06:00
$page = $this->admin->page(true);
if ($page) {
$this->grav->fireEvent('onAdminAfterAddMedia', new Event(['page' => $page]));
}
$this->admin->json_response = [
'status' => 'success',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_UPLOADED_SUCCESSFULLY'),
'metadata' => $metadata,
];
2014-08-05 13:06:38 -07:00
return true;
}
2020-04-19 18:14:12 -06:00
protected function taskCompileScss()
2020-04-18 17:45:11 -06:00
{
if (!$this->authorizeTask('compile scss', ['admin.pages', 'admin.super'])) {
return false;
}
2020-04-19 18:14:12 -06:00
$preview = $this->data['preview'] ?? false;
2020-04-20 09:57:44 -06:00
$data = ['color_scheme' => $this->data['whitelabel']['color_scheme'] ?? null];
$output_file = $preview ? 'admin-preset.css' : 'admin-preset__tmp.css';
2020-04-18 17:45:11 -06:00
$options = [
'input' => 'plugin://admin/themes/grav/scss/preset.scss',
'output' => 'asset://' .$output_file
];
[$compile_status, $msg] = $this->grav['admin-whitelabel']->compilePresetScss($data, $options);
2020-04-18 17:45:11 -06:00
$json_response = [
2020-04-19 18:14:12 -06:00
'status' => $compile_status ? 'success' : 'error',
'message' => ($preview ? 'Preview ' : 'SCSS ') . $msg,
2020-04-18 17:45:11 -06:00
'files' => [
'color_scheme' => Utils::url($options['output'])
2020-04-18 17:45:11 -06:00
]
];
echo json_encode($json_response);
exit;
}
protected function taskExportScss()
{
if (!$this->authorizeTask('compile scss', ['admin.pages', 'admin.super'])) {
return false;
}
$data = ['color_scheme' => $this->data['whitelabel']['color_scheme'] ?? null];
$name = $this->data['whitelabel']['color_scheme']['name'] ?? 'theme';
//todo slugify name
$location = 'asset://' . $name . '.yaml';
[$status, $msg] = $this->grav['admin-whitelabel']->exportPresetScsss($data, $location);
$json_response = [
'status' => 'success' ,
'message' => 'Theme Export Ready',
'files' => [
'download' => Utils::url($location)
]
];
echo json_encode($json_response);
exit;
}
2014-08-05 13:06:38 -07:00
/**
* Handles deleting a media file from a page
2014-08-05 13:06:38 -07:00
*
* @return bool True if the action was performed.
*/
protected function taskDelmedia()
2014-08-05 13:06:38 -07:00
{
if (!$this->authorizeTask('delete media', ['admin.pages', 'admin.super'])) {
return false;
}
$media = $this->getMedia();
if (!$media) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.NO_PAGE_FOUND')
];
return false;
2014-10-10 15:25:07 +03:00
}
$filename = !empty($this->post['filename']) ? $this->post['filename'] : null;
2016-03-05 12:11:13 +01:00
2018-10-04 15:42:59 +03:00
// Handle bad filenames.
if (!Utils::checkFilename($filename)) {
$filename = null;
}
if (!$filename) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.NO_FILE_FOUND')
];
2015-10-20 16:36:39 +02:00
2014-08-05 13:06:38 -07:00
return false;
}
2018-05-23 13:01:58 +03:00
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$targetPath = $media->getPath() . '/' . $filename;
2018-05-23 13:01:58 +03:00
if ($locator->isStream($targetPath)) {
$targetPath = $locator->findResource($targetPath, true, true);
}
2017-02-26 19:36:01 +01:00
$fileParts = pathinfo($filename);
$found = false;
if (file_exists($targetPath)) {
2017-02-26 19:36:01 +01:00
$found = true;
$result = unlink($targetPath);
if (!$result) {
$this->admin->json_response = [
'status' => 'error',
2018-12-05 08:20:38 +02:00
'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename
];
return false;
}
2014-10-10 11:57:57 +03:00
}
2014-08-05 13:06:38 -07:00
// Remove Extra Files
foreach (scandir($media->getPath(), SCANDIR_SORT_NONE) as $file) {
if (preg_match("/{$fileParts['filename']}@\d+x\.{$fileParts['extension']}(?:\.meta\.yaml)?$|{$filename}\.meta\.yaml$/", $file)) {
$targetPath = $media->getPath() . '/' . $file;
if ($locator->isStream($targetPath)) {
$targetPath = $locator->findResource($targetPath, true, true);
}
$result = unlink($targetPath);
if (!$result) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_COULD_NOT_BE_DELETED') . ': ' . $filename
];
return false;
}
$found = true;
}
}
2019-06-14 12:19:31 +03:00
Cache::clearCache('invalidate');
if (!$found) {
$this->admin->json_response = [
'status' => 'error',
'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_NOT_FOUND') . ': ' . $filename
];
return false;
}
$page = $this->admin->page(true);
if ($page) {
$this->grav->fireEvent('onAdminAfterDelMedia', new Event(['page' => $page]));
}
$this->admin->json_response = [
'status' => 'success',
'message' => $this->admin::translate('PLUGIN_ADMIN.FILE_DELETED') . ': ' . $filename
];
return true;
}
2019-08-23 12:54:41 +03:00
protected function getLevelListing($data)
{
// Valid types are dir|file|link
$default_filters = ['type'=> ['root', 'dir'], 'name' => null, 'extension' => null];
2019-08-23 20:45:51 +03:00
$pages = $this->admin::enablePages();
$page_instances = $pages->instances();
$is_page = $data['page'] ?? true;
$route = $data['route'] ?? null;
$leaf_route = $data['leaf_route'] ?? null;
$sortby = $data['sortby'] ?? 'filename';
$order = $data['order'] ?? SORT_ASC;
$initial = $data['initial'] ?? null;
$filters = isset($data['filters']) ? $default_filters + json_decode($data['filters']) : $default_filters;
$filter_type = (array) $filters['type'];
$status = 'error';
$msg = null;
$response = [];
$children = null;
$sub_route = null;
$extra = null;
$root = false;
// Handle leaf_route
2019-08-23 20:45:51 +03:00
if ($leaf_route && $route !== $leaf_route) {
$nodes = explode('/', $leaf_route);
2019-08-23 20:45:51 +03:00
$sub_route = '/' . implode('/', array_slice($nodes, 1, $data['level']++));
$data['route'] = $sub_route;
2019-08-23 12:58:56 +03:00
[$status, $msg, $children, $extra] = $this->getLevelListing($data);
}
// Handle no route, assume page tree root
if (!$route) {
$is_page = false;
$route = $this->grav['locator']->findResource('page://', true);
$root = true;
}
if ($is_page) {
// Try the path
/** @var PageInterface $page */
$page = $pages->get(GRAV_ROOT . $route);
// Try a real route (like homepage)
if (is_null($page)) {
$page = $pages->find($route);
}
$path = $page ? $page->path() : null;
} else {
// Try a physical path
if (!Utils::startsWith($route, GRAV_ROOT)) {
$try_path = GRAV_ROOT . $route;
} else {
$try_path = $route;
}
$path = file_exists($try_path) ? $try_path : null;
}
$blueprintsData = $this->admin->page(true);
if (null !== $blueprintsData) {
if (method_exists($blueprintsData, 'blueprints')) {
$settings = $blueprintsData->blueprints()->schema()->getProperty($data['field']);
} elseif (method_exists($blueprintsData, 'getBlueprint')) {
$settings = $blueprintsData->getBlueprint()->schema()->getProperty($data['field']);
}
$filters = array_merge([], $filters, ($settings['filters'] ?? []));
$filter_type = $filters['type'] ?? $filter_type;
}
if ($path) {
/** @var \SplFileInfo $fileInfo */
$status = 'success';
$msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
foreach (new \DirectoryIterator($path) as $fileInfo) {
$fileName = $fileInfo->getFilename();
2019-06-24 11:22:38 -06:00
$filePath = str_replace('\\', '/', $fileInfo->getPathname());
if (($fileInfo->isDot() && $fileName !== '.' && $initial) || (Utils::startsWith($fileName, '.') && strlen($fileName) > 1)) {
continue;
}
if ($fileInfo->isDot()) {
if ($root) {
$payload = [
'name' => '<root>',
'value' => '',
'item-key' => '',
'filename' => '.',
'extension' => '',
'type' => 'root',
'modified' => $fileInfo->getMTime(),
'size' => 0,
2020-03-06 17:10:32 -07:00
'has-children' => false
];
} else {
continue;
}
} else {
2019-06-24 11:22:38 -06:00
$file_page = $page_instances[$filePath] ?? null;
$file_path = Utils::replaceFirstOccurrence(GRAV_ROOT, '', $filePath);
$type = $fileInfo->getType();
$child_path = $file_page ? GRAV_ROOT . $file_page->path() : $filePath;
$has_children = Folder::hasChildren($child_path);
$payload = [
'name' => $file_page ? $file_page->title() : $fileName,
'value' => $file_page ? $file_page->rawRoute() : $file_path,
'item-key' => basename($file_page ? $file_page->route() : $file_path),
'filename' => $fileName,
'extension' => $type === 'dir' ? '' : $fileInfo->getExtension(),
'type' => $type,
'modified' => $fileInfo->getMTime(),
'size' => $fileInfo->getSize(),
'symlink' => false,
2020-03-06 17:10:32 -07:00
'has-children' => $has_children
];
}
2019-06-24 11:22:38 -06:00
// Fix for symlink
if ($payload['type'] === 'link') {
$payload['symlink'] = true;
$physical_path = $fileInfo->getRealPath();
$payload['type'] = is_dir($physical_path) ? 'dir' : 'file';
2019-06-24 11:22:38 -06:00
}
// filter types
if ($filters['type']) {
if (!in_array($payload['type'], $filter_type)) {
continue;
}
}
// Simple filter for name or extension
if (($filters['name'] && Utils::contains($payload['basename'], $filters['name'])) ||
($filters['extension'] && Utils::contains($payload['extension'], $filters['extension']))) {
continue;
}
// Add children if any
2019-08-23 20:45:51 +03:00
if ($filePath === $extra && is_array($children)) {
$payload['children'] = array_values($children);
}
$response[] = $payload;
}
} else {
$msg = 'PLUGIN_ADMIN.PAGE_ROUTE_NOT_FOUND';
}
// Sorting
$response = Utils::sortArrayByKey($response, $sortby, $order);
$temp_array = [];
foreach ($response as $index => $item) {
$temp_array[$item['type']][$index] = $item;
}
$sorted = Utils::sortArrayByArray($temp_array, $filter_type);
$response = Utils::arrayFlatten($sorted);
2014-08-05 13:06:38 -07:00
return [$status, $this->admin::translate($msg ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED'), $response, $path];
2014-08-05 13:06:38 -07:00
}
/**
* @return Media
*/
protected function getMedia()
{
$this->uri = $this->uri ?? $this->grav['uri'];
$uri = $this->uri->post('uri');
$order = $this->uri->post('order') ?: null;
2016-07-07 18:55:52 +02:00
if ($uri) {
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
2016-07-07 18:55:52 +02:00
$media_path = $locator->isStream($uri) ? $uri : null;
} else {
$page = $this->admin->page(true);
$media_path = $page ? $page->path() : null;
}
if ($order) {
$order = array_map('trim', explode(',', $order));
}
2016-07-07 18:55:52 +02:00
return $media_path ? new Media($media_path, $order) : null;
}
2016-07-07 18:55:52 +02:00
/**
* Gets the configuration data for a given view & post
*
* @param array $data
*
* @return object
*/
protected function prepareData(array $data)
{
$type = trim("{$this->view}/{$this->admin->route}", '/');
$data = $this->admin->data($type, $data);
return $data;
}
/**
* Prepare a page to be stored: update its folder, name, template, header and content
*
* @param PageInterface $page
* @param bool $clean_header
* @param string $languageCode
*/
protected function preparePage(PageInterface $page, $clean_header = false, $languageCode = '')
{
$input = (array)$this->data;
2019-08-23 20:45:51 +03:00
$this->admin::enablePages();
2018-05-10 10:14:18 +03:00
if (isset($input['folder']) && $input['folder'] !== $page->value('folder')) {
$order = $page->value('order');
$ordering = $order ? sprintf('%02d.', $order) : '';
$page->folder($ordering . $input['folder']);
}
if (isset($input['name']) && !empty($input['name'])) {
2018-05-10 10:14:18 +03:00
$type = strtolower($input['name']);
$page->template($type);
$name = preg_replace('|.*/|', '', $type);
/** @var Language $language */
$language = $this->grav['language'];
if ($language->enabled()) {
$languageCode = $languageCode ?: $language->getLanguage();
if ($languageCode) {
$isDefault = $languageCode === $language->getDefault();
$includeLang = !$isDefault || (bool)$this->grav['config']->get('system.languages.include_default_lang_file_extension', true);
if (!$includeLang) {
// Check if the language specific file exists; use it if it does.
$includeLang = file_exists("{$page->path()}/{$name}.{$languageCode}.md");
}
// Keep existing .md file if we're updating default language, otherwise always append the language.
if ($includeLang) {
$name .= '.' . $languageCode;
}
}
}
$name .= '.md';
$page->name($name);
}
// Special case for Expert mode: build the raw, unset content
2018-05-10 10:14:18 +03:00
if (isset($input['frontmatter'], $input['content'])) {
$page->raw("---\n" . (string)$input['frontmatter'] . "\n---\n" . (string)$input['content']);
unset($input['content']);
// Handle header normally
} elseif (isset($input['header'])) {
$header = $input['header'];
foreach ($header as $key => $value) {
2018-05-10 10:14:18 +03:00
if ($key === 'metadata' && is_array($header[$key])) {
foreach ($header['metadata'] as $key2 => $value2) {
if (isset($input['toggleable_header']['metadata'][$key2]) && !$input['toggleable_header']['metadata'][$key2]) {
$header['metadata'][$key2] = '';
}
}
2018-05-10 10:14:18 +03:00
} elseif ($key === 'taxonomy' && is_array($header[$key])) {
foreach ($header[$key] as $taxkey => $taxonomy) {
2018-12-05 08:20:38 +02:00
if (is_array($taxonomy) && \count($taxonomy) === 1 && trim($taxonomy[0]) === '') {
unset($header[$key][$taxkey]);
}
}
} else {
if (isset($input['toggleable_header'][$key]) && !$input['toggleable_header'][$key]) {
$header[$key] = null;
}
}
}
if ($clean_header) {
$header = Utils::arrayFilterRecursive($header, function ($k, $v) {
return !(null === $v || $v === '');
});
}
$page->header((object)$header);
$page->frontmatter(Yaml::dump((array)$page->header(), 20));
}
// Fill content last because it also renders the output.
if (isset($input['content'])) {
$page->rawMarkdown((string)$input['content']);
}
}
/**
* Find the first available $item ('slug' | 'folder') for a page
* Used when copying a page, to determine the first available slot
*
* @param string $item
* @param PageInterface $page
*
* @return string The first available slot
*/
protected function findFirstAvailable($item, PageInterface $page)
{
$parent = $page->parent();
if (!$parent || !$parent->children()) {
2018-12-05 08:20:38 +02:00
return $page->{$item}();
}
$withoutPrefix = function ($string) {
$match = preg_split('/^[0-9]+\./u', $string, 2, PREG_SPLIT_DELIM_CAPTURE);
2018-12-05 08:20:38 +02:00
return $match[1] ?? $match[0];
};
$withoutPostfix = function ($string) {
$match = preg_split('/-(\d+)$/', $string, 2, PREG_SPLIT_DELIM_CAPTURE);
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);
return $append;
};*/
$highest = 1;
$siblings = $parent->children();
$findCorrectAppendedNumber = function ($item, $page_item, $highest) use (
$siblings,
&$findCorrectAppendedNumber,
&$withoutPrefix
) {
foreach ($siblings as $sibling) {
2018-12-05 08:20:38 +02:00
if ($withoutPrefix($sibling->{$item}()) == ($highest === 1 ? $page_item : $page_item . '-' . $highest)) {
$highest = $findCorrectAppendedNumber($item, $page_item, $highest + 1);
2014-08-05 13:06:38 -07:00
return $highest;
}
}
return $highest;
};
$base = $withoutPrefix($withoutPostfix($page->$item()));
2014-08-05 13:06:38 -07:00
$return = $base;
$highest = $findCorrectAppendedNumber($item, $base, $highest);
if ($highest > 1) {
$return .= '-' . $highest;
2014-08-05 13:06:38 -07:00
}
return $return;
2014-08-05 13:06:38 -07:00
}
/**
* @param string $frontmatter
2014-08-05 13:06:38 -07:00
*
* @return bool
2014-08-05 13:06:38 -07:00
*/
public function checkValidFrontmatter($frontmatter)
2014-08-05 13:06:38 -07:00
{
try {
Yaml::parse($frontmatter);
} catch (\RuntimeException $e) {
2014-08-05 13:06:38 -07:00
return false;
}
return true;
}
/**
* The what should be the new filename when saving as a new language
2014-08-05 13:06:38 -07:00
*
* @param string $current_filename the current file name, including .md. Example: default.en.md
* @param string $language The new language it will be saved as. Example: 'it' or 'en-GB'.
*
* @return string The new filename. Example: 'default.it'
*/
public function determineFilenameIncludingLanguage($current_filename, $language)
2016-01-10 17:17:04 +01:00
{
$ext = '.md';
$filename = substr($current_filename, 0, -strlen($ext));
$languages_enabled = $this->grav['config']->get('system.languages.supported', []);
2015-08-21 18:01:20 +02:00
$parts = explode('.', trim($filename, '.'));
$lang = array_pop($parts);
2015-08-21 18:01:20 +02:00
if ($lang === $language) {
return $filename . $ext;
2015-08-21 18:01:20 +02:00
}
if (in_array($lang, $languages_enabled, true)) {
$filename = implode('.', $parts);
}
return $filename . '.' . $language . $ext;
}
/**
* Get the next available ordering number in a folder
*
* @param string $path
*
* @return string the correct order string to prepend
*/
public static function getNextOrderInFolder($path)
{
$files = Folder::all($path, ['recursive' => false]);
$highestOrder = 0;
foreach ($files as $file) {
preg_match(PAGE_ORDER_PREFIX_REGEX, $file, $order);
if (isset($order[0])) {
$theOrder = (int)trim($order[0], '.');
} else {
$theOrder = 0;
}
if ($theOrder >= $highestOrder) {
$highestOrder = $theOrder;
2018-10-04 15:42:59 +03:00
}
}
$orderOfNewFolder = $highestOrder + 1;
if ($orderOfNewFolder < 10) {
$orderOfNewFolder = '0' . $orderOfNewFolder;
}
return $orderOfNewFolder;
}
2020-04-30 13:27:10 -06:00
protected function taskConvertUrls(): ResponseInterface
{
$request = $this->getRequest();
$data = $request->getParsedBody();
$converted_links = [];
$converted_images = [];
$status = 'success';
$message = 'All links converted';
$data['route'] = isset($data['route']) ? base64_decode($data['route']) : null;
$data['data'] = json_decode($data['data'] ?? '{}', true);
// use the route if passed, else use current page in admin as reference
$page_route = $data['route'] ?? $this->admin->page(true);
/** @var PageInterface */
$pages = $this->admin::enablePages();
$page = $pages->find($page_route);
if (!$page) {
throw new RequestException($request,'Page Not Found', 404);
}
if (!isset($data['data'])) {
throw new RequestException($request, 'Bad Request', 400);
}
foreach ($data['data']['a'] ?? [] as $link) {
$converted_links[$link] = Excerpts::processLinkHtml($link, $page);
}
foreach ($data['data']['img'] ?? [] as $image) {
$converted_images[$image] = Excerpts::processImageHtml($image, $page);
}
$json = [
'status' => $status,
'message' => $message,
'data' => ['links' => $converted_links, 'images' => $converted_images]
];
return $this->createJsonResponse($json, 200);
}
2014-08-05 13:06:38 -07:00
}