diff --git a/admin.php b/admin.php index 69272304..71f93f6d 100644 --- a/admin.php +++ b/admin.php @@ -260,13 +260,13 @@ class AdminPlugin extends Plugin ]; echo json_encode([ - "success" => true, + "status" => "success", "payload" => ["resources" => $resources_updates, "grav" => $grav_updates, "installed" => $gpm->countInstalled()] ]); break; } } catch (\Exception $e) { - echo json_encode(["success" => false, "message" => $e->getMessage()]); + echo json_encode(["status" => "error", "message" => $e->getMessage()]); } exit; diff --git a/classes/controller.php b/classes/controller.php index 0b9f5ce0..3aa855e4 100644 --- a/classes/controller.php +++ b/classes/controller.php @@ -259,11 +259,15 @@ class AdminController protected function taskClearCache() { + if (!$this->authoriseTask('clear cache', ['admin.cache', 'admin.super'])) { + return; + } + $results = Cache::clearCache('standard'); if (count($results) > 0) { - $this->admin->json_response = ['success', 'Cache cleared']; + $this->admin->json_response = ['status' => 'success', 'message' => 'Cache cleared']; } else { - $this->admin->json_response = ['error', 'Error clearing cache']; + $this->admin->json_response = ['status' => 'error', 'message' => 'Error clearing cache']; } return true; @@ -271,6 +275,10 @@ class AdminController protected function taskBackup() { + if (!$this->authoriseTask('backup', ['admin.maintenance', 'admin.super'])) { + return; + } + $download = $this->grav['uri']->param('download'); if ($download) { @@ -313,6 +321,10 @@ class AdminController protected function taskFilterPages() { + if (!$this->authoriseTask('filter pages', ['admin.pages', 'admin.super'])) { + return; + } + $data = $this->post; $flags = !empty($data['flags']) ? array_map('strtolower', explode(',', $data['flags'])) : []; @@ -344,16 +356,29 @@ class AdminController } } - $this->admin->json_response = ['success', 'Pages filtered']; + $results = []; + foreach ($collection as $path => $page) { + $results[] = $page->route(); + } + + $this->admin->json_response = [ + 'status' => 'success', + 'message' => 'Pages filtered', + 'results' => $results + ]; $this->admin->collection = $collection; } protected function taskListmedia() { + if (!$this->authoriseTask('list media', ['admin.pages', 'admin.super'])) { + return; + } + $page = $this->admin->page(true); if (!$page) { - $this->admin->json_response = ['error', 'No Page found']; + $this->admin->json_response = ['status' => 'error', 'message' => 'No Page found']; return false; } @@ -361,20 +386,24 @@ class AdminController foreach ($page->media()->all() as $name => $media) { $media_list[$name] = ['url' => $media->cropZoom(150, 100)->url(),'size' => $media->get('size')]; } - $this->admin->media = $media_list; + $this->admin->json_response = ['status' => 'ok', 'results' => $media_list]; return true; } protected function taskAddmedia() { + if (!$this->authoriseTask('add media', ['admin.pages', 'admin.super'])) { + return; + } + $page = $this->admin->page(true); /** @var Config $config */ $config = $this->grav['config']; if (!isset($_FILES['file']['error']) || is_array($_FILES['file']['error'])) { - $this->admin->json_response = ['error', 'Invalid Parameters']; + $this->admin->json_response = ['status' => 'error', 'message' => 'Invalid Parameters']; return; } @@ -383,21 +412,21 @@ class AdminController case UPLOAD_ERR_OK: break; case UPLOAD_ERR_NO_FILE: - $this->admin->json_response = ['error', 'No files sent']; + $this->admin->json_response = ['status' => 'error', 'message' => 'No files sent']; return; case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: - $this->admin->json_response = ['error', 'Exceeded filesize limit.']; + $this->admin->json_response = ['status' => 'error', 'message' => 'Exceeded filesize limit.']; return; default: - $this->admin->json_response = ['error', 'Unkown errors']; + $this->admin->json_response = ['status' => 'error', 'message' => 'Unkown errors']; return; } $grav_limit = $config->get('system.media.upload_limit', 0); // You should also check filesize here. if ($grav_limit > 0 && $_FILES['file']['size'] > grav_limit) { - $this->admin->json_response = ['error', 'Exceeded Grav filesize limit.']; + $this->admin->json_response = ['status' => 'error', 'message' => 'Exceeded Grav filesize limit.']; return; } @@ -408,28 +437,32 @@ class AdminController // If not a supported type, return if (!$config->get("media.{$fileExt}")) { - $this->admin->json_response = ['error', 'Unsupported file type: '.$fileExt]; + $this->admin->json_response = ['status' => 'error', 'message' => 'Unsupported file type: '.$fileExt]; return; } // Upload it if (!move_uploaded_file($_FILES['file']['tmp_name'], sprintf('%s/%s', $page->path(), $_FILES['file']['name']))) { - $this->admin->json_response = ['error', 'Failed to move uploaded file.']; + $this->admin->json_response = ['status' => 'error', 'message' => 'Failed to move uploaded file.']; return; } - $this->admin->json_response = ['success', 'File uploaded successfully']; + $this->admin->json_response = ['status' => 'success', 'message' => 'File uploaded successfully']; return; } protected function taskDelmedia() { + if (!$this->authoriseTask('delete media', ['admin.pages', 'admin.super'])) { + return; + } + $page = $this->admin->page(true); if (!$page) { - $this->admin->json_response = ['error', 'No Page found']; + $this->admin->json_response = ['status' => 'error', 'message' => 'No Page found']; return false; } @@ -439,15 +472,15 @@ class AdminController if (file_exists($targetPath)) { if (unlink($targetPath)) { - $this->admin->json_response = ['success', 'File deleted: '.$filename]; + $this->admin->json_response = ['status' => 'success', 'message' => 'File deleted: '.$filename]; } else { - $this->admin->json_response = ['error', 'File could not be deleted: '.$filename]; + $this->admin->json_response = ['status' => 'error', 'message' => 'File could not be deleted: '.$filename]; } } else { - $this->admin->json_response = ['error', 'File not found: '.$filename]; + $this->admin->json_response = ['status' => 'error', 'message' => 'File not found: '.$filename]; } } else { - $this->admin->json_response = ['error', 'No file found']; + $this->admin->json_response = ['status' => 'error', 'message' => 'No file found']; } return true; } @@ -459,6 +492,10 @@ class AdminController */ public function taskEnable() { + if (!$this->authoriseTask('enable plugin', ['admin.plugins', 'admin.super'])) { + return; + } + if ($this->view != 'plugins') { return false; } @@ -479,6 +516,10 @@ class AdminController */ public function taskDisable() { + if (!$this->authoriseTask('disable plugin', ['admin.plugins', 'admin.super'])) { + return; + } + if ($this->view != 'plugins') { return false; } @@ -499,6 +540,10 @@ class AdminController */ public function taskActivate() { + if (!$this->authoriseTask('activate theme', ['admin.themes', 'admin.super'])) { + return; + } + if ($this->view != 'themes') { return false; } @@ -534,6 +579,11 @@ class AdminController */ public function taskInstall() { + $type = $this->view === 'plugins' ? 'plugins' : 'themes'; + if (!$this->authoriseTask('install ' . $type, ['admin.' . $type, 'admin.super'])) { + return; + } + require_once __DIR__ . '/gpm.php'; $package = $this->route; @@ -561,6 +611,7 @@ class AdminController require_once __DIR__ . '/gpm.php'; $package = $this->route; + $permissions = []; // Update multi mode if (!$package) { @@ -568,10 +619,18 @@ class AdminController if ($this->view === 'plugins' || $this->view === 'update') { $package = $this->admin->gpm()->getUpdatablePlugins(); + $permissions['plugins'] = ['admin.super', 'admin.plugins']; } if ($this->view === 'themes' || $this->view === 'update') { $package = array_merge($package, $this->admin->gpm()->getUpdatableThemes()); + $permissions['themes'] = ['admin.super', 'admin.themes']; + } + } + + foreach ($permissions as $type => $p) { + if (!$this->authoriseTask('update ' . $type , $p)) { + return; } } @@ -580,9 +639,9 @@ class AdminController if ($this->view === 'update') { if ($result) { - $this->admin->json_response = ['success', 'Everything updated']; + $this->admin->json_response = ['status' => 'success', 'message' => 'Everything updated']; } else { - $this->admin->json_response = ['error', 'Updates failed']; + $this->admin->json_response = ['status' => 'error', 'message' => 'Updates failed']; } } else { @@ -605,6 +664,11 @@ class AdminController */ public function taskUninstall() { + $type = $this->view === 'plugins' ? 'plugins' : 'themes'; + if (!$this->authoriseTask('uninstall ' . $type, ['admin.' . $type, 'admin.super'])) { + return; + } + require_once __DIR__ . '/gpm.php'; $package = $this->route; @@ -629,6 +693,10 @@ class AdminController */ public function taskSave() { + if (!$this->authoriseTask('save', $this->dataPermissions())) { + return; + } + $data = $this->post; // Special handler for pages data. @@ -718,6 +786,10 @@ class AdminController */ protected function taskCopy() { + if (!$this->authoriseTask('copy page', ['admin.pages', 'admin.super'])) { + return; + } + // Only applies to pages. if ($this->view != 'pages') { return false; @@ -774,6 +846,10 @@ class AdminController */ protected function taskReorder() { + if (!$this->authoriseTask('reorder pages', ['admin.pages', 'admin.super'])) { + return; + } + // Only applies to pages. if ($this->view != 'pages') { return false; @@ -791,6 +867,10 @@ class AdminController */ protected function taskDelete() { + if (!$this->authoriseTask('delete page', ['admin.pages', 'admin.super'])) { + return; + } + // Only applies to pages. if ($this->view != 'pages') { return false; @@ -869,6 +949,34 @@ class AdminController return $data; } + protected function dataPermissions() + { + $type = $this->view; + $permissions = ['admin.super']; + + switch ($type) { + case 'configuration': + case 'system': + $permissions = ['admin.configuration']; + break; + case 'settings': + case 'site': + $permissions = ['admin.settings']; + break; + case 'plugins': + $permissions = ['admin.plugins']; + break; + case 'themes': + $permissions = ['admin.themes']; + break; + case 'users': + $permissions = ['admin.users']; + break; + } + + return $permissions; + } + protected function preparePage(\Grav\Common\Page\Page $page) { $input = $this->post; @@ -899,4 +1007,19 @@ class AdminController $page->content((string) $input['content']); } } + + protected function authoriseTask($task = '', $permissions = []) + { + if (!$this->admin->authorise($permissions)) { + + if ($this->grav['uri']->extension() === 'json') + $this->admin->json_response = ['status' => 'unauthorized', 'message' => 'You have insufficient permissions for task ' . $task . '.']; + else + $this->admin->setMessage('You have insufficient permissions for task ' . $task . '.'); + + return false; + } + + return true; + } } diff --git a/themes/grav/js/admin-all.js b/themes/grav/js/admin-all.js index 55840082..84bdb1aa 100644 --- a/themes/grav/js/admin-all.js +++ b/themes/grav/js/admin-all.js @@ -58,14 +58,11 @@ $(function () { GravAjax({ dataType: "json", url: url, + toastErrors: true, success: function(result, status) { - if (result.status == 'success') { - toastr.success(result.message); - } else { - toastr.error(result.message); - } + toastr.success(result.message); } - }).complete(function() { + }).always(function() { $('[data-clear-cache]').removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-trash'); }); }); @@ -79,14 +76,11 @@ $(function () { GravAjax({ dataType: "json", url: url, + toastErrors: true, success: function(result, status) { - if (result.status == 'success') { - toastr.success(result.message); - } else { - toastr.error(result.message); - } + toastr.success(result.message); } - }).complete(function() { + }).always(function() { GPMRefresh(); $('[data-maintenance-update]').removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-cloud-download'); }); @@ -120,6 +114,7 @@ $(function () { GravAjax({ dataType: "json", url: url, + toastErrors: true, success: function(result, status) { var toastrBackup = {}; @@ -131,18 +126,14 @@ $(function () { } } - if (result.status == 'success') { - toastr.success(result.message || 'Task completed.'); - } else { - toastr.error(result.message || 'Something went terribly wrong.'); - } + toastr.success(result.message || 'Task completed.'); for (var setting in toastrBackup) { if (toastrBackup.hasOwnProperty(setting)) { toastr.options[setting] = toastrBackup[setting]; } } } - }).complete(function() { + }).always(function() { // Restore button button.removeAttr('disabled'); icon.removeClass('fa-refresh fa-spin').addClass(iconClasses.join(' ')); @@ -158,11 +149,8 @@ $(function () { task: 'GPM', action: 'getUpdates' }, + toastErrors: true, success: function (response) { - if (!response.success) { - throw new Error(response.message); - } - var grav = response.payload.grav, installed = response.payload.installed, resources = response.payload.resources; diff --git a/themes/grav/js/ajax.js b/themes/grav/js/ajax.js index 9be07406..dce71200 100644 --- a/themes/grav/js/ajax.js +++ b/themes/grav/js/ajax.js @@ -5,16 +5,91 @@ $(function(){ settings = typeof settings === 'undefined' ? typeof url === 'string' ? {} : url : settings; settings.url = typeof settings.url === 'undefined' && typeof url === 'string' ? url : settings.url; - var successHandler = typeof settings.success !== 'undefined' ? typeof settings.success === 'function' ? [ settings.success ] : settings.success : []; - successHandler.unshift(root.GravAjax.logoutHandler); - settings.success = successHandler; + var callbacks = { + success: typeof settings.success !== 'undefined' ? typeof settings.success === 'function' ? [ settings.success ] : settings.success : [], + error: typeof settings.error !== 'undefined' ? typeof settings.error === 'function' ? [ settings.error ] : settings.error : [] + }; - return $.ajax(settings); - }; - root.GravAjax.logoutHandler = function (response, status, xhr) { - if (response.status && (response.status === "unauthorized" || response.status === "forbidden")) { - document.location.href = GravAdmin.config.base_url_relative; - throw "Logged out"; + if (settings.toastErrors) { + callbacks.error.push(root.GravAjax.toastErrorHandler); + delete settings.toastErrors; } + + delete settings.success; + delete settings.error; + + var deferred = $.Deferred(), + jqxhr = $.ajax(settings); + + jqxhr.done(function (response, status, xhr) { + + var responseObject = { + response: response, + status: status, + xhr: xhr + }; + + switch (response.status) { + case "unauthenticated": + document.location.href = GravAdmin.config.base_url_relative; + throw "Logged out"; + break; + case "unauthorized": + responseObject.response.message = responseObject.response.message || "Unauthorized."; + root.GravAjax.errorHandler(deferred, callbacks, responseObject); + break; + case "error": + responseObject.response.message = responseObject.response.message || "Unknown error."; + root.GravAjax.errorHandler(deferred, callbacks, responseObject); + break; + case "success": + root.GravAjax.successHandler(deferred, callbacks, responseObject); + break; + default: + responseObject.response.message = responseObject.response.message || "Invalid AJAX response."; + root.GravAjax.errorHandler(deferred, callbacks, responseObject); + break; + } + }); + + jqxhr.fail(function (xhr, status, error) { + var response = { + status: 'error', + message: error + }; + + root.GravAjax.errorHandler(deferred, callbacks, { xhr: xhr, status: status, response: response}); + }); + + return deferred; + + }; + + root.GravAjax.successHandler = function (promise, callbacks, response) { + + callbacks = callbacks.success; + for (var i = 0; i < callbacks.length; i++) { + if (typeof callbacks[i] === 'function') { + callbacks[i](response.response, response.status, response.xhr); + } + } + + promise.resolve(response.response, response.status, response.xhr); + }; + + root.GravAjax.errorHandler = function (promise, callbacks, response) { + + callbacks = callbacks.error; + for (var i = 0; i < callbacks.length; i++) { + if (typeof callbacks[i] === 'function') { + callbacks[i](response.xhr, response.status, response.response.message); + } + } + + promise.reject(response.xhr, response.status, response.response.message); + }; + + root.GravAjax.toastErrorHandler = function (xhr, status, error) { + toastr.error(error); }; }); diff --git a/themes/grav/js/pages-all.js b/themes/grav/js/pages-all.js index a4479e45..2d5bbb9b 100644 --- a/themes/grav/js/pages-all.js +++ b/themes/grav/js/pages-all.js @@ -43,6 +43,7 @@ $(function(){ flags: flags, query: query }, + toastErrors: true, success: function (result, status) { finishFilterPages(result.results); } diff --git a/themes/grav/templates/cache.json.twig b/themes/grav/templates/cache.json.twig index 6774c8cd..ba94f2b1 100644 --- a/themes/grav/templates/cache.json.twig +++ b/themes/grav/templates/cache.json.twig @@ -1,4 +1,5 @@ {% if admin.json_response %} -{% set output = {'status':admin.json_response[0], 'message':admin.json_response[1] } %} -{{ output|json_encode }} +{{ admin.json_response|json_encode }} +{% else %} +{} {% endif %} diff --git a/themes/grav/templates/forms/fields/uploads/uploads.html.twig b/themes/grav/templates/forms/fields/uploads/uploads.html.twig index 1eda63ed..7f058df4 100644 --- a/themes/grav/templates/forms/fields/uploads/uploads.html.twig +++ b/themes/grav/templates/forms/fields/uploads/uploads.html.twig @@ -9,7 +9,7 @@ $(function(){ var URI = $('[data-media-url]').data('media-url'), thisDropzone, modalError = function(args){ - if (args.data.status == 'error'){ + if (args.data.status == 'error' || args.data.status == 'unauthorized'){ if (args.mode == 'addBack'){ // let's add back the file @@ -60,12 +60,20 @@ init: function() { thisDropzone = this; $.get(URI + '/task:listmedia', function(data) { - $.each(data, function(filename, data){ - var mockFile = { name: filename, size: data.size, accepted: true, extras: data }; - thisDropzone.files.push(mockFile); - thisDropzone.options.addedfile.call(thisDropzone, mockFile); - thisDropzone.options.thumbnail.call(thisDropzone, mockFile, data.url); - }); + + $.proxy(modalError, this, { + data: data, + msg: '

An error occurred while trying to list files

'+data.message+'
' + })(); + + if (data.results) { + $.each(data.results, function(filename, data){ + var mockFile = { name: filename, size: data.size, accepted: true, extras: data }; + thisDropzone.files.push(mockFile); + thisDropzone.options.addedfile.call(thisDropzone, mockFile); + thisDropzone.options.thumbnail.call(thisDropzone, mockFile, data.url); + }); + } $('.dz-preview').prop('draggable', 'true'); }); diff --git a/themes/grav/templates/media.json.twig b/themes/grav/templates/media.json.twig index 06461fcb..ba94f2b1 100644 --- a/themes/grav/templates/media.json.twig +++ b/themes/grav/templates/media.json.twig @@ -1,8 +1,5 @@ {% if admin.json_response %} -{% set output = {'status':admin.json_response[0], 'message':admin.json_response[1] } %} -{{ output|json_encode }} +{{ admin.json_response|json_encode }} {% else %} - {% if admin.task == 'listmedia' %} - {{ admin.media|json_encode }} - {% endif %} +{} {% endif %} diff --git a/themes/grav/templates/pages-filter.json.twig b/themes/grav/templates/pages-filter.json.twig index cb7d2501..ba94f2b1 100644 --- a/themes/grav/templates/pages-filter.json.twig +++ b/themes/grav/templates/pages-filter.json.twig @@ -1,5 +1,5 @@ -{"status":{{admin.json_response[0]|json_encode}}, "results":[ -{%- for search_result in admin.collection -%} -{{- search_result.route|json_encode -}}{{ not loop.last ? ',' }} -{%- endfor -%} -]} +{% if admin.json_response %} +{{ admin.json_response|json_encode }} +{% else %} +{} +{% endif %} diff --git a/themes/grav/templates/update.json.twig b/themes/grav/templates/update.json.twig index 6774c8cd..ba94f2b1 100644 --- a/themes/grav/templates/update.json.twig +++ b/themes/grav/templates/update.json.twig @@ -1,4 +1,5 @@ {% if admin.json_response %} -{% set output = {'status':admin.json_response[0], 'message':admin.json_response[1] } %} -{{ output|json_encode }} +{{ admin.json_response|json_encode }} +{% else %} +{} {% endif %}