[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], 'onShutdown' => ['onShutdown', 1000],
'onFormProcessed' => ['onFormProcessed', 0], 'onFormProcessed' => ['onFormProcessed', 0],
'onAdminDashboard' => ['onAdminDashboard', 0], 'onAdminDashboard' => ['onAdminDashboard', 0],
'onAdminTools' => ['onAdminTools', 0],
]; ];
} }
@@ -725,6 +726,17 @@ class AdminPlugin extends Plugin
return false; 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() public function onAdminDashboard()
{ {
$this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-maintenance']; $this->grav['twig']->plugins_hooked_dashboard_widgets_top[] = ['template' => 'dashboard-maintenance'];

View File

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

View File

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

View File

@@ -645,3 +645,13 @@ PLUGIN_ADMIN:
PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Package %s reinstalled successfully" PACKAGE_X_REINSTALLED_SUCCESSFULLY: "Package %s reinstalled successfully"
REINSTALLATION_FAILED: "Reinstallation failed" 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

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

View File

@@ -3,6 +3,7 @@
{% if config.plugins.admin.sidebar.activate != 'hover' %} {% 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 %} {% endif %}
<div id="admin-logo"> <div id="admin-logo">
{% include 'partials/logo.html.twig' %} {% include 'partials/logo.html.twig' %}
</div> </div>
@@ -66,6 +67,14 @@
</a> </a>
</li> </li>
{% endif %} {% 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 %}
{% include 'nav-pro.html.twig' ignore missing %} {% include 'nav-pro.html.twig' ignore missing %}

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 %}