[WORK IN PROGRESS] First draft of a Tools menu with direct install (#990)

* First draft of a Tools menu with direct install

* Basic styling

* Translate GPM messages

* Basic frontend validation

* Fix form action path

* Added lang strings for offical_gpm_only toggle
This commit is contained in:
Flavio Copes
2017-02-22 21:34:21 +01:00
committed by Andy Miller
parent c473a8db84
commit c7256134ba
11 changed files with 199 additions and 54 deletions

View File

@@ -88,6 +88,7 @@ class AdminPlugin extends Plugin
'onShutdown' => ['onShutdown', 1000],
'onFormProcessed' => ['onFormProcessed', 0],
'onAdminDashboard' => ['onAdminDashboard', 0],
'onAdminTools' => ['onAdminTools', 0],
];
}
@@ -725,6 +726,17 @@ class AdminPlugin extends Plugin
return false;
}
/**
* Provide the tools for the Tools page, currently only direct install
*
* @return Event
*/
public function onAdminTools(Event $event)
{
$event['tools'] = array_merge($event['tools'], [$this->grav['language']->translate('PLUGIN_ADMIN.DIRECT_INSTALL')]);
return $event;
}
public function onAdminDashboard()
{
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-maintenance'];

View File

@@ -184,6 +184,18 @@ class Admin
return $configurations;
}
/**
* Return the tools found
*
* @return array
*/
public static function tools()
{
$tools = [];
$event = Grav::instance()->fireEvent('onAdminTools', new Event(['tools' => &$tools]));
return $tools;
}
/**
* Return the languages available in the site
*
@@ -365,8 +377,10 @@ class Admin
*
* @return string
*/
public function translate($args, $languages = null)
public static function translate($args, $languages = null)
{
$grav = Grav::instance();
if (is_array($args)) {
$lookup = array_shift($args);
} else {
@@ -375,7 +389,7 @@ class Admin
}
if (!$languages) {
$languages = [$this->grav['user']->authenticated ? $this->grav['user']->language : 'en'];
$languages = [$grav['user']->authenticated ? $grav['user']->language : 'en'];
} else {
$languages = (array)$languages;
}
@@ -383,25 +397,25 @@ class Admin
if ($lookup) {
if (empty($languages) || reset($languages) == null) {
if ($this->grav['config']->get('system.languages.translations_fallback', true)) {
$languages = $this->grav['language']->getFallbackLanguages();
if ($grav['config']->get('system.languages.translations_fallback', true)) {
$languages = $grav['language']->getFallbackLanguages();
} else {
$languages = (array)$this->grav['language']->getDefault();
$languages = (array)$grav['language']->getDefault();
}
}
}
foreach ((array)$languages as $lang) {
$translation = $this->grav['language']->getTranslation($lang, $lookup);
$translation = $grav['language']->getTranslation($lang, $lookup);
if (!$translation) {
$language = $this->grav['language']->getDefault() ?: 'en';
$translation = $this->grav['language']->getTranslation($language, $lookup);
$language = $grav['language']->getDefault() ?: 'en';
$translation = $grav['language']->getTranslation($language, $lookup);
}
if (!$translation) {
$language = 'en';
$translation = $this->grav['language']->getTranslation($language, $lookup);
$translation = $grav['language']->getTranslation($language, $lookup);
}
if ($translation) {

View File

@@ -2090,4 +2090,30 @@ class AdminController extends AdminBaseController
return $filename . '.md';
}
/**
* Handle direct install.
*
*/
protected function taskDirectInstall()
{
$file_path = '';
if (isset($_FILES['uploaded_file'])) {
$file_path = $_FILES['uploaded_file']['tmp_name'];
} else {
$file_path = $this->data['file_path'];
}
$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');
}
}

View File

@@ -56,15 +56,14 @@ class Gpm
{
$options = array_merge(self::$options, $options);
if (
!Installer::isGravInstance($options['destination'])
|| !Installer::isValidDestination($options['destination'], [Installer::EXISTS, Installer::IS_LINK])
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);
$count = count($packages);
$packages = array_filter(array_map(function ($p) {
return !is_string($p) ? $p instanceof Package ? $p : false : self::GPM()->findPackage($p);
@@ -97,7 +96,7 @@ class Gpm
}
$license = Licenses::get($package->slug);
$local = static::download($package, $license);
$local = static::download($package, $license);
Installer::install($local, $options['destination'],
['install_path' => $package->install_path, 'theme' => $options['theme']]);
@@ -146,14 +145,14 @@ class Gpm
$options = array_merge(self::$options, $options);
$packages = is_array($packages) ? $packages : [$packages];
$count = count($packages);
$count = count($packages);
$packages = array_filter(array_map(function ($p) {
if (is_string($p)) {
$p = strtolower($p);
$p = strtolower($p);
$plugin = static::GPM()->getInstalledPlugin($p);
$p = $plugin ?: static::GPM()->getInstalledTheme($p);
$p = $plugin ?: static::GPM()->getInstalledTheme($p);
}
return $p instanceof Package ? $p : false;
@@ -198,10 +197,15 @@ class Gpm
* Direct install a file
*
* @param $package_file
*
* @return bool
*/
public static function directInstall($package_file)
{
if (!$package_file) {
return Admin::translate('PLUGIN_ADMIN.NO_PACKAGE_NAME');
}
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$tmp_zip = $tmp_dir . '/Grav-' . uniqid();
@@ -216,49 +220,50 @@ class Gpm
$extracted = Installer::unZip($zip, $tmp_source);
if (!$extracted) {
return "Package extraction failed.";
return Admin::translate('PLUGIN_ADMIN.PACKAGE_EXTRACTION_FAILED');
}
$type = GravGPM::getPackageType($extracted);
if (!$type) {
return "Not a valid Grav package";
return Admin::translate('PLUGIN_ADMIN.NOT_VALID_GRAV_PACKAGE');
}
if ($type == 'grav') {
Installer::isValidDestination(GRAV_ROOT . '/system');
if (Installer::IS_LINK === Installer::lastErrorCode()) {
return "Cannot overwrite symlinks";
return Admin::translate('PLUGIN_ADMIN.CANNOT_OVERWRITE_SYMLINKS');
}
Installer::install($zip, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true], $extracted);
Installer::install($zip, GRAV_ROOT,
['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true], $extracted);
} else {
$name = GravGPM::getPackageName($extracted);
if (!$name) {
return "Name could not be determined";
return Admin::translate('PLUGIN_ADMIN.NAME_COULD_NOT_BE_DETERMINED');
}
$install_path = GravGPM::getInstallPath($type, $name);
$is_update = file_exists($install_path);
Installer::isValidDestination(GRAV_ROOT . DS . $install_path);
if (Installer::lastErrorCode() == Installer::IS_LINK) {
return "Cannot overwrite symlinks";
return Admin::translate('PLUGIN_ADMIN.CANNOT_OVERWRITE_SYMLINKS');
}
Installer::install($zip, GRAV_ROOT, ['install_path' => $install_path, 'theme' => (($type == 'theme')), 'is_update' => $is_update], $extracted);
Installer::install($zip, GRAV_ROOT,
['install_path' => $install_path, 'theme' => (($type == 'theme')), 'is_update' => $is_update],
$extracted);
}
Folder::delete($tmp_source);
if(Installer::lastErrorCode()) {
if (Installer::lastErrorCode()) {
return Installer::lastErrorMsg();
}
} else {
return "ZIP package could not be found";
return Admin::translate('PLUGIN_ADMIN.ZIP_PACKAGE_NOT_FOUND');
}
Folder::delete($tmp_zip);
@@ -276,14 +281,11 @@ class Gpm
$query = '';
if ($package->premium) {
$query = \json_encode(array_merge(
$package->premium,
[
$query = \json_encode(array_merge($package->premium, [
'slug' => $package->slug,
'filename' => $package->premium['filename'],
'license_key' => $license
]
));
]));
$query = '?d=' . base64_encode($query);
}
@@ -297,9 +299,7 @@ class Gpm
$tmp_dir = Admin::getTempDir() . '/Grav-' . uniqid();
Folder::mkdir($tmp_dir);
$bad_chars = array_merge(
array_map('chr', range(0, 31)),
array("<", ">", ":", '"', "/", "\\", "|", "?", "*"));
$bad_chars = array_merge(array_map('chr', range(0, 31)), ["<", ">", ":", '"', "/", "\\", "|", "?", "*"]);
$filename = $package->slug . str_replace($bad_chars, "", basename($package->zipball_url));
@@ -341,12 +341,11 @@ class Gpm
}
if (method_exists($upgrader, 'meetsRequirements') && !$upgrader->meetsRequirements()) {
$error = [];
$error = [];
$error[] = '<p>Grav has increased the minimum PHP requirement.<br />';
$error[] = 'You are currently running PHP <strong>' . PHP_VERSION . '</strong>';
$error[] = ', but PHP <strong>' . GRAV_PHP_MIN . '</strong> is required.</p>';
$error[] =
'<p><a href="http://getgrav.org/blog/changing-php-requirements-to-5.5" class="button button-small secondary">Additional information</a></p>';
$error[] = '<p><a href="http://getgrav.org/blog/changing-php-requirements-to-5.5" class="button button-small secondary">Additional information</a></p>';
Installer::setError(implode("\n", $error));
@@ -354,11 +353,10 @@ class Gpm
}
$update = $upgrader->getAssets()['grav-update'];
$tmp = Admin::getTempDir() . '/Grav-' . uniqid();
$file = self::_downloadSelfupgrade($update, $tmp);
$tmp = Admin::getTempDir() . '/Grav-' . uniqid();
$file = self::_downloadSelfupgrade($update, $tmp);
Installer::install($file, GRAV_ROOT,
['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true]);
Installer::install($file, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true]);
$errorCode = Installer::lastErrorCode();

View File

@@ -644,4 +644,14 @@ PLUGIN_ADMIN:
ERROR_REINSTALLING_THE: "Error reinstalling the %s"
PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Package %s reinstalled successfully"
REINSTALLATION_FAILED: "Reinstallation failed"
WARNING_REINSTALL_NOT_LATEST_RELEASE: "The installed version is not the latest release. By clicking Continue, you'll remove the current version and install the latest available release"
WARNING_REINSTALL_NOT_LATEST_RELEASE: "The installed version is not the latest release. By clicking Continue, you'll remove the current version and install the latest available release"
TOOLS: "Tools"
DIRECT_INSTALL: "Direct Install"
NO_PACKAGE_NAME: "Package name not specified"
PACKAGE_EXTRACTION_FAILED: "Package extraction failed"
NOT_VALID_GRAV_PACKAGE: "Not a valid Grav package"
NAME_COULD_NOT_BE_DETERMINED: "Name could not be determined"
CANNOT_OVERWRITE_SYMLINKS: "Cannot overwrite symlinks"
ZIP_PACKAGE_NOT_FOUND: "ZIP package could not be found"
GPM_OFFICIAL_ONLY: "Official GPM Only"
GPM_OFFICIAL_ONLY_HELP: "Only allow direct installs from the official GPM repository only."

7
pages/admin/tools.md Normal file
View File

@@ -0,0 +1,7 @@
---
title: Grav Tools
access:
admin.tools: true
admin.super: true
---

View File

@@ -3980,4 +3980,4 @@ button.toast-close-button {
#admin-main #notifications .badge.alert i, #admin-main #notifications .sidebar-open #admin-sidebar #admin-menu li .badges .alert.updates i, .sidebar-open #admin-sidebar #admin-menu li .badges #admin-main #notifications .alert.updates i, #admin-main #notifications .gpm .alert.gpm-testing i, .gpm #admin-main #notifications .alert.gpm-testing i {
margin-right: 3px; }
/*# sourceMappingURL=../css-compiled/template.css.map */
/*# sourceMappingURL=../css-compiled/template.css.map */

View File

@@ -1086,3 +1086,14 @@ body.sidebar-quickopen #admin-main {
.pointer-events-disabled {
pointer-events: none;
}
// Direct install
.direct-install-content {
padding: 30px;
.button {
margin-top: 10px;
margin-bottom: 50px;
}
}

View File

@@ -1,8 +1,9 @@
{% if authorize(['admin.login', 'admin.super']) %}
<nav id="admin-sidebar" data-quickopen="{{ config.plugins.admin.sidebar.activate == 'hover' ? 'true' : 'false' }}" data-quickopen-delay="{{ config.plugins.admin.sidebar.hover_delay }}">
{% if config.plugins.admin.sidebar.activate != 'hover' %}
<div id="open-handle" data-sidebar-toggle><i class="fa fa-angle-double-right"></i></div>
<div id="open-handle" data-sidebar-toggle><i class="fa fa-angle-double-right"></i></div>
{% endif %}
<div id="admin-logo">
{% include 'partials/logo.html.twig' %}
</div>
@@ -24,9 +25,9 @@
<a href="{{ base_url_relative }}/pages">
<i class="fa fa-fw fa-file-text-o"></i>
<em>{{ "PLUGIN_ADMIN.PAGES"|tu }}</em>
<span class="badges">
<span class="badge count">{{ admin.pagesCount }}</span>
</span>
<span class="badges">
<span class="badge count">{{ admin.pagesCount }}</span>
</span>
</a>
</li>
{% endif %}
@@ -47,7 +48,7 @@
<a href="{{ base_url_relative }}/plugins">
<i class="fa fa-fw fa-plug"></i>
<em>{{ "PLUGIN_ADMIN.PLUGINS"|tu }}</em>
<span class="badges">
<span class="badges">
<span class="badge updates"></span>
<span class="badge count">{{ admin.plugins|length }}</span>
</span>
@@ -59,10 +60,18 @@
<a href="{{ base_url_relative }}/themes">
<i class="fa fa-fw fa-tint"></i>
<em>{{ "PLUGIN_ADMIN.THEMES"|tu }}</em>
<span class="badges">
<span class="badge updates"></span>
<span class="badge count">{{ admin.themes|length }}</span>
</span>
<span class="badges">
<span class="badge updates"></span>
<span class="badge count">{{ admin.themes|length }}</span>
</span>
</a>
</li>
{% endif %}
{% if authorize(['admin.tools', 'admin.super']) %}
<li class="{{ (location == 'tools') ? 'selected' : '' }}">
<a href="{{ base_url_relative }}/tools">
<i class="fa fa-fw fa-briefcase"></i>
<em>{{ "PLUGIN_ADMIN.TOOLS"|tu }}</em>
</a>
</li>
{% endif %}

View File

@@ -0,0 +1,30 @@
<h1>
Direct install of packages
</h1>
<div class="direct-install-content">
<h2>Install a zip package by uploading it</h2>
<form action="{{ base_url_relative }}/tools/task:directInstall" method="post" enctype="multipart/form-data">
<input type="file" name="uploaded_file" id="uploaded_file" required accept="application/zip, application/octet-stream">
<input type="submit" value="Upload and install" name="submit" class="button">
<input type="hidden" name="task" value="directInstall" />
{{ nonce_field('admin-form', 'admin-nonce')|raw }}
</form>
<h2>Install a zip package from a remote location</h2>
<form action="{{ base_url_relative }}/tools/task:directInstall" method="post">
<input type="text" name="file_path" required />
<input type="submit" value="Install" name="submit" class="button">
<input type="hidden" name="task" value="directInstall" />
{{ nonce_field('admin-form', 'admin-nonce')|raw }}
</form>
</div>

View File

@@ -0,0 +1,28 @@
{% extends 'partials/base.html.twig' %}
{% set tools_slug = uri.basename %}
{% if tools_slug == 'tools' %}{% set tools_slug = 'direct-install' %}{% endif %}
{% set title = "PLUGIN_ADMIN.TOOLS"|tu ~ ": " ~ ("PLUGIN_ADMIN." ~ tools_slug|underscorize|upper)|tu %}
{% block titlebar %}
<h1><i class="fa fa-fw fa-wrench"></i> {{ "PLUGIN_ADMIN.TOOLS"|tu }} - {{ ("PLUGIN_ADMIN." ~ tools_slug|underscorize|upper)|tu }}</h1>
{% endblock %}
{% block content_top %}
<ul class="tab-bar">
{% for tool in admin.tools %}
<li {% if tools_slug == tool|hyphenize %}class="active"{% endif %}>
{% if tools_slug == tool %}<span>{% else %}<a href="{{ base_url_relative }}/tools/{{tool|hyphenize}}">{% endif %}
{{ tool|tu|capitalize }}
{% if tools_slug == tool %}</span>{% else %}</a>{% endif %}
</li>
{% endfor %}
</ul>
{% endblock %}
{% block content %}
{% if authorize(['admin.tools', 'admin.super']) %}
{% include 'partials/tools-' ~ tools_slug ~ '.html.twig' %}
{% endif %}
{% endblock %}