diff --git a/classes/controller.php b/classes/controller.php index 1dcc33cc..8502d46b 100644 --- a/classes/controller.php +++ b/classes/controller.php @@ -336,10 +336,91 @@ class AdminController */ public function taskInstall() { - $mode = $this->view === 'plugins' ? 'plugin' : 'theme'; + require_once __DIR__ . '/gpm.php'; + $package = $this->route; - $this->admin->setMessage("Actual installation is not hooked up to GPM yet."); + $result = \Grav\Plugin\Admin\Gpm::install($package, []); + + if ($result) { + $this->admin->setMessage("Installation successful."); + } else { + $this->admin->setMessage("Installation failed."); + } + + $this->post = array('_redirect' => $this->view . '/' . $this->route); + + return true; + } + + /** + * Handles updating plugins and themes + * + * @return bool True is the action was performed + */ + public function taskUpdate() + { + require_once __DIR__ . '/gpm.php'; + + $package = $this->route; + + // Update multi mode + if (!$package) { + $package = []; + + if ($this->view === 'plugins' || $this->view === 'update') { + $package = $this->admin->gpm()->getUpdatablePlugins(); + } + + if ($this->view === 'themes' || $this->view === 'update') { + $package = array_merge($package, $this->admin->gpm()->getUpdatableThemes()); + } + } + + $result = \Grav\Plugin\Admin\Gpm::update($package, []); + + if ($this->view === 'update') { + + if ($result) { + $this->admin->json_response = ['success', 'Everything updated']; + } else { + $this->admin->json_response = ['error', 'Updates failed']; + } + + } else { + if ($result) { + $this->admin->setMessage("Installation successful."); + } else { + $this->admin->setMessage("Installation failed."); + } + + $this->post = array('_redirect' => $this->view . '/' . $this->route); + } + + return true; + } + + /** + * Handles uninstalling plugins and themes + * + * @return bool True is the action was performed + */ + public function taskUninstall() + { + require_once __DIR__ . '/gpm.php'; + + $package = $this->route; + + $result = \Grav\Plugin\Admin\Gpm::uninstall($package, []); + + if ($result) { + $this->admin->setMessage("Uninstall successful."); + } else { + $this->admin->setMessage("Uninstall failed."); + } + + $this->post = array('_redirect' => $this->view); + return true; } diff --git a/classes/gpm.php b/classes/gpm.php new file mode 100644 index 00000000..f83e559c --- /dev/null +++ b/classes/gpm.php @@ -0,0 +1,159 @@ + GRAV_ROOT, + 'overwrite' => true, + 'ignore_symlinks' => true, + 'skip_invalid' => true, + 'install_deps' => true + ]; + + public static function install($packages, $options) + { + $options = array_merge(self::$options, $options); + + if ( + !Installer::isGravInstance($options['destination']) || + !Installer::isValidDestination($options['destination'], [Installer::EXISTS, Installer::IS_LINK]) + ) { + return false; + } + + $packages = is_array($packages) ? $packages : [ $packages ]; + $count = count($packages); + + $packages = array_filter(array_map(function ($p) { + return !is_string($p) ? $p instanceof Package ? $p : false : self::GPM()->findPackage($p); + }, $packages)); + + if (!$options['skip_invalid'] && $count !== count($packages)) { + return false; + } + + foreach ($packages as $package) { + if (isset($package->dependencies) && $options['install_deps']) { + $result = static::install($package->dependencies, $options); + + if (!$result) { + return false; + } + } + + // Check destination + Installer::isValidDestination($options['destination'] . DS . $package->install_path); + + if (Installer::lastErrorCode() === Installer::EXISTS && !$options['overwrite']) { + return false; + } + + if (Installer::lastErrorCode() === Installer::IS_LINK && !$options['ignore_symlinks']) { + return false; + } + + $local = static::download($package); + + Installer::install($local, $options['destination'], ['install_path' => $package->install_path]); + Folder::delete(dirname($local)); + + $errorCode = Installer::lastErrorCode(); + + if (Installer::lastErrorCode() & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)) { + return false; + } + } + + return true; + } + + public static function update($packages, $options) + { + $options['overwrite'] = true; + return static::install($packages, $options); + } + + public static function uninstall($packages, $options) + { + $options = array_merge(self::$options, $options); + + $packages = is_array($packages) ? $packages : [ $packages ]; + $count = count($packages); + + $packages = array_filter(array_map(function ($p) { + + if (is_string($p)) { + $p = strtolower($p); + $plugin = static::GPM()->getInstalledPlugin($p); + $p = $plugin ?: static::GPM()->getInstalledTheme($p); + } + + return $p instanceof Package ? $p : false; + + }, $packages)); + + if (!$options['skip_invalid'] && $count !== count($packages)) { + return false; + } + + foreach ($packages as $package) { + + $location = self::getGrav()['locator']->findResource($package->package_type . '://' . $package->slug); + + // Check destination + Installer::isValidDestination($location); + + if (Installer::lastErrorCode() === Installer::IS_LINK && !$options['ignore_symlinks']) { + return false; + } + + Installer::uninstall($location); + + $errorCode = Installer::lastErrorCode(); + if ($errorCode && $errorCode !== Installer::IS_LINK && $errorCode !== Installer::EXISTS) { + return false; + } + } + + return true; + } + + private static function download($package) + { + $contents = Response::get($package->zipball_url, []); + + $cache_dir = self::getGrav()['locator']->findResource('cache://', true); + $cache_dir = $cache_dir . DS . 'tmp/Grav-' . uniqid(); + Folder::mkdir($cache_dir); + + $filename = $package->slug . basename($package->zipball_url); + + file_put_contents($cache_dir . DS . $filename, $contents); + + return $cache_dir . DS . $filename; + } +} \ No newline at end of file diff --git a/pages/admin/update.md b/pages/admin/update.md new file mode 100644 index 00000000..b5437c46 --- /dev/null +++ b/pages/admin/update.md @@ -0,0 +1,7 @@ +--- +title: Cache + +access: + admin.maintenance: true + admin.super: true +--- diff --git a/themes/grav/js/admin-all.js b/themes/grav/js/admin-all.js index 9addce9e..11eae69e 100644 --- a/themes/grav/js/admin-all.js +++ b/themes/grav/js/admin-all.js @@ -87,114 +87,137 @@ $(function () { }); }); - ajaxRequest({ - dataType: "JSON", - url: window.location.href, - method: "POST", - data: { - task: 'GPM', - action: 'getUpdates' - }, - success: function (response) { - if (!response.success) { - throw new Error(response.message); - } + // Cache Clear + $('[data-maintenance-update]').on('click', function(e) { - var grav = response.payload.grav, - installed = response.payload.installed, - resources = response.payload.resources; + $(this).attr('disabled','disabled').find('> .fa').removeClass('fa-cloud-download').addClass('fa-refresh fa-spin'); + var url = $(this).data('maintenanceUpdate'); - //console.log(grav, resources); - - // grav updatable - if (grav.isUpdatable) { - var icon = ' '; - content = 'Grav v{available} is now available! (Current: v{version}) ', - button = ''; - - content = jQuery.substitute(content, {available: grav.available, version: grav.version}); - $('[data-gpm-grav]').addClass('grav').html('
' + icon + content + button + '
'); - } - - // dashboard - if ($('.updates-chart').length) { - var missing = (resources.total + (grav.isUpdatable ? 1 : 0)) * 100 / (installed + (grav.isUpdatable ? 1 : 0)), - updated = 100 - missing; - UpdatesChart.update({series: [updated, missing]}); - } - - if (resources.total > 0) { - var length, - icon = '', - content = '{updates} of your {type} have an update available', - button = '', - plugins = $('.grav-update.plugins'), - themes = $('.grav-update.themes'), - sidebar = {plugins: $('#admin-menu a[href$="/plugins"]'), themes: $('#admin-menu a[href$="/themes"]')}; - - // sidebar - if (sidebar.plugins.length || sidebar.themes.length) { - var length, badges; - if (sidebar.plugins.length && (length = Object.keys(resources.plugins).length)) { - badges = sidebar.plugins.find('.badges'); - badges.addClass('with-updates'); - badges.find('.badge.updates').text(length); - } - - if (sidebar.themes.length && (length = Object.keys(resources.themes).length)) { - badges = sidebar.themes.find('.badges'); - badges.addClass('with-updates'); - badges.find('.badge.updates').text(length); - } - } - - // list page - if (plugins[0] && (length = Object.keys(resources.plugins).length)) { - content = jQuery.substitute(content, {updates: length, type: 'plugins'}); - button = jQuery.substitute(button, {Type: 'All Plugins'}); - plugins.html('' + icon + content + button + '
'); - - var plugin, url; - $.each(resources.plugins, function (key, value) { - plugin = $('[data-gpm-plugin="' + key + '"] .gpm-name'); - url = plugin.find('a'); - plugin.append('Update available!'); - - }); - } - - if (themes[0] && (length = Object.keys(resources.themes).length)) { - content = jQuery.substitute(content, {updates: length, type: 'themes'}); - button = jQuery.substitute(button, {Type: 'All Themes'}); - themes.html('' + icon + content + button + '
'); - - var theme, url; - $.each(resources.themes, function (key, value) { - theme = $('[data-gpm-theme="' + key + '"]'); - url = theme.find('.gpm-name a'); - theme.append(''); - }); - } - - // details page - var type = 'plugin', - details = $('.grav-update.plugin')[0]; - - if (!details) { - details = $('.grav-update.theme')[0]; - type = 'theme'; - } - - if (details){ - var slug = $('[data-gpm-' + type + ']').data('gpm-' + type), - Type = type.charAt(0).toUpperCase() + type.substring(1); - - content = 'v{available} of this ' + type + ' is now available!'; - content = jQuery.substitute(content, {available: resources[type + 's'][slug].available}); - button = jQuery.substitute(button, {Type: Type}); - $(details).html('' + icon + content + button + '
'); + ajaxRequest({ + dataType: "json", + url: url, + success: function(result, status) { + if (result.status == 'success') { + toastr.success(result.message); + } else { + toastr.error(result.message); } } - } + }).complete(function() { + GPMRefresh(); + $('[data-maintenance-update]').removeAttr('disabled').find('> .fa').removeClass('fa-refresh fa-spin').addClass('fa-cloud-download'); + }); }); + + var GPMRefresh = function () { + ajaxRequest({ + dataType: "JSON", + url: window.location.href, + method: "POST", + data: { + task: 'GPM', + action: 'getUpdates' + }, + success: function (response) { + if (!response.success) { + throw new Error(response.message); + } + + var grav = response.payload.grav, + installed = response.payload.installed, + resources = response.payload.resources; + + // grav updatable + if (grav.isUpdatable) { + var icon = ' '; + content = 'Grav v{available} is now available! (Current: v{version}) ', + button = ''; + + content = jQuery.substitute(content, {available: grav.available, version: grav.version}); + $('[data-gpm-grav]').addClass('grav').html('' + icon + content + button + '
'); + } + + // dashboard + if ($('.updates-chart').length) { + var missing = (resources.total + (grav.isUpdatable ? 1 : 0)) * 100 / (installed + (grav.isUpdatable ? 1 : 0)), + updated = 100 - missing; + UpdatesChart.update({series: [updated, missing]}); + } + + if (resources.total > 0) { + var length, + icon = '', + content = '{updates} of your {type} have an update available', + button = 'Update {Type}', + plugins = $('.grav-update.plugins'), + themes = $('.grav-update.themes'), + sidebar = {plugins: $('#admin-menu a[href$="/plugins"]'), themes: $('#admin-menu a[href$="/themes"]')}; + + // sidebar + if (sidebar.plugins.length || sidebar.themes.length) { + var length, badges; + if (sidebar.plugins.length && (length = Object.keys(resources.plugins).length)) { + badges = sidebar.plugins.find('.badges'); + badges.addClass('with-updates'); + badges.find('.badge.updates').text(length); + } + + if (sidebar.themes.length && (length = Object.keys(resources.themes).length)) { + badges = sidebar.themes.find('.badges'); + badges.addClass('with-updates'); + badges.find('.badge.updates').text(length); + } + } + + // list page + if (plugins[0] && (length = Object.keys(resources.plugins).length)) { + content = jQuery.substitute(content, {updates: length, type: 'plugins'}); + button = jQuery.substitute(button, {Type: 'All Plugins', location: GravAdmin.config.base_url_relative + '/plugins'}); + plugins.html('' + icon + content + button + '
'); + + var plugin, url; + $.each(resources.plugins, function (key, value) { + plugin = $('[data-gpm-plugin="' + key + '"] .gpm-name'); + url = plugin.find('a'); + plugin.append('Update available!'); + + }); + } + + if (themes[0] && (length = Object.keys(resources.themes).length)) { + content = jQuery.substitute(content, {updates: length, type: 'themes'}); + button = jQuery.substitute(button, {Type: 'All Themes', location: GravAdmin.config.base_url_relative + '/themes'}); + themes.html('' + icon + content + button + '
'); + + var theme, url; + $.each(resources.themes, function (key, value) { + theme = $('[data-gpm-theme="' + key + '"]'); + url = theme.find('.gpm-name a'); + theme.append(''); + }); + } + + // details page + var type = 'plugin', + details = $('.grav-update.plugin')[0]; + + if (!details) { + details = $('.grav-update.theme')[0]; + type = 'theme'; + } + + if (details){ + var slug = $('[data-gpm-' + type + ']').data('gpm-' + type), + Type = type.charAt(0).toUpperCase() + type.substring(1); + + content = 'v{available} of this ' + type + ' is now available!'; + content = jQuery.substitute(content, {available: resources[type + 's'][slug].available}); + button = jQuery.substitute(button, {Type: Type, location: GravAdmin.config.base_url_relative + '/' + type + 's/' + slug}); + $(details).html('' + icon + content + button + '
'); + } + } + } + }); + }; + GPMRefresh(); }); diff --git a/themes/grav/templates/dashboard.html.twig b/themes/grav/templates/dashboard.html.twig index 583b8f82..439f2ea8 100644 --- a/themes/grav/templates/dashboard.html.twig +++ b/themes/grav/templates/dashboard.html.twig @@ -47,7 +47,7 @@ diff --git a/themes/grav/templates/partials/plugins-details.html.twig b/themes/grav/templates/partials/plugins-details.html.twig index 1cc9cdcb..58cb7140 100644 --- a/themes/grav/templates/partials/plugins-details.html.twig +++ b/themes/grav/templates/partials/plugins-details.html.twig @@ -85,7 +85,7 @@ {% else %}