diff --git a/CHANGELOG.md b/CHANGELOG.md
index c2996b161..620be3db5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+# v1.1.18
+## 02/xx/2017
+
+1. [](#new)
+ * Added default setting to only allow `direct-installs` from official GPM. Can be configured in `system.yaml`
+ * Added a new `Utils::isValidUrl()` method
+1. [](#improved)
+ * Genericized `direct-install` so it can be called via Admin plugin
+1. [](#bugfix)
+ * Fixed a minor bug in Number validation [#1329](https://github.com/getgrav/grav/issues/1329)
+ * Fixed exception when trying to find user account and there is no `user://accounts` folder
+
# v1.1.17
## 02/17/2017
diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml
index 6aa0d6012..03f6b898e 100644
--- a/system/blueprints/config/system.yaml
+++ b/system/blueprints/config/system.yaml
@@ -1057,6 +1057,19 @@ form:
fopen: PLUGIN_ADMIN.FOPEN
curl: PLUGIN_ADMIN.CURL
+ gpm.official_gpm_only:
+ type: toggle
+ label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY
+ highlight: auto
+ help: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY_HELP
+ highlight: 1
+ options:
+ 1: PLUGIN_ADMIN.YES
+ 0: PLUGIN_ADMIN.NO
+ default: true
+ validate:
+ type: bool
+
gpm.verify_peer:
type: toggle
label: PLUGIN_ADMIN.GPM_VERIFY_PEER
diff --git a/system/config/system.yaml b/system/config/system.yaml
index 7d4de30e0..99628151c 100644
--- a/system/config/system.yaml
+++ b/system/config/system.yaml
@@ -138,3 +138,4 @@ gpm:
proxy_url: # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
method: 'auto' # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
verify_peer: true # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
+ official_gpm_only: true # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php
index b597cd642..fd886b017 100644
--- a/system/src/Grav/Common/Data/Validation.php
+++ b/system/src/Grav/Common/Data/Validation.php
@@ -339,7 +339,7 @@ class Validation
protected static function filterNumber($value, array $params, array $field)
{
- return (int) $value;
+ return self::validateFloat($value, $params) ? (float) $value : (int) $value;
}
protected static function filterDateTime($value, array $params, array $field)
@@ -585,7 +585,7 @@ class Validation
$values[$key] = array_map('trim', explode(',', $value));
} else {
$values[$key] = trim($value);
- }
+ }
}
}
diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php
index 857dd8fc0..584adbdd7 100644
--- a/system/src/Grav/Common/GPM/GPM.php
+++ b/system/src/Grav/Common/GPM/GPM.php
@@ -9,6 +9,7 @@
namespace Grav\Common\GPM;
use Grav\Common\Grav;
+use Grav\Common\Filesystem\Folder;
use Grav\Common\Inflector;
use Grav\Common\Iterator;
use Grav\Common\Utils;
@@ -499,6 +500,151 @@ class GPM extends Iterator
return false;
}
+ /**
+ * Download the zip package via the URL
+ *
+ * @param $package_file
+ * @param $tmp
+ * @return null|string
+ */
+ public static function downloadPackage($package_file, $tmp)
+ {
+ $package = parse_url($package_file);
+ $filename = basename($package['path']);
+
+ if (Grav::instance()['config']->get('system.gpm.official_gpm_only') && $package['host'] !== 'getgrav.org') {
+ throw new \RuntimeException("Only offical GPM URLs are allowed. You can modify this behavior in the System configuration.");
+ }
+
+ $output = Response::get($package_file, []);
+
+ if ($output) {
+ Folder::mkdir($tmp);
+ file_put_contents($tmp . DS . $filename, $output);
+ return $tmp . DS . $filename;
+ }
+
+ return null;
+ }
+
+ /**
+ * Copy the local zip package to tmp
+ *
+ * @param $package_file
+ * @param $tmp
+ * @return null|string
+ */
+ public static function copyPackage($package_file, $tmp)
+ {
+ $package_file = realpath($package_file);
+
+ if (file_exists($package_file)) {
+ $filename = basename($package_file);
+ Folder::mkdir($tmp);
+ copy(realpath($package_file), $tmp . DS . $filename);
+ return $tmp . DS . $filename;
+ }
+
+ return null;
+ }
+
+ /**
+ * Try to guess the package type from the source files
+ *
+ * @param $source
+ * @return bool|string
+ */
+ public static function getPackageType($source)
+ {
+ $plugin_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Plugin/m';
+ $theme_regex = '/^class\\s{1,}[a-zA-Z0-9]{1,}\\s{1,}extends.+Theme/m';
+
+ if (
+ file_exists($source . 'system/defines.php') &&
+ file_exists($source . 'system/config/system.yaml')
+ ) {
+ return 'grav';
+ } else {
+ // must have a blueprint
+ if (!file_exists($source . 'blueprints.yaml')) {
+ return false;
+ }
+
+ // either theme or plugin
+ $name = basename($source);
+ if (Utils::contains($name, 'theme')) {
+ return 'theme';
+ } elseif (Utils::contains($name, 'plugin')) {
+ return 'plugin';
+ }
+ foreach (glob($source . "*.php") as $filename) {
+ $contents = file_get_contents($filename);
+ if (preg_match($theme_regex, $contents)) {
+ return 'theme';
+ } elseif (preg_match($plugin_regex, $contents)) {
+ return 'plugin';
+ }
+ }
+
+ // Assume it's a theme
+ return 'theme';
+ }
+ }
+
+ /**
+ * Try to guess the package name from the source files
+ *
+ * @param $source
+ * @return bool|string
+ */
+ public static function getPackageName($source)
+ {
+ foreach (glob($source . "*.yaml") as $filename) {
+ $name = strtolower(basename($filename, '.yaml'));
+ if ($name == 'blueprints') {
+ continue;
+ }
+ return $name;
+ }
+ return false;
+ }
+
+ /**
+ * Find/Parse the blueprint file
+ *
+ * @param $source
+ * @return array|bool
+ */
+ public static function getBlueprints($source)
+ {
+ $blueprint_file = $source . 'blueprints.yaml';
+ if (!file_exists($blueprint_file)) {
+ return false;
+ }
+
+ $blueprint = (array)Yaml::parse(file_get_contents($blueprint_file));
+ return $blueprint;
+ }
+
+ /**
+ * Get the install path for a name and a particular type of package
+ *
+ * @param $type
+ * @param $name
+ * @return string
+ */
+ public static function getInstallPath($type, $name)
+ {
+ $locator = Grav::instance()['locator'];
+
+ if ($type == 'theme') {
+ $install_path = $locator->findResource('themes://', false) . DS . $name;
+ } else {
+ $install_path = $locator->findResource('plugins://', false) . DS . $name;
+ }
+ return $install_path;
+ }
+
/**
* Searches for a list of Packages in the repository
* @param array $searches An array of either slugs or names
diff --git a/system/src/Grav/Common/GPM/Response.php b/system/src/Grav/Common/GPM/Response.php
index 6b3036ff7..f046352ac 100644
--- a/system/src/Grav/Common/GPM/Response.php
+++ b/system/src/Grav/Common/GPM/Response.php
@@ -189,6 +189,17 @@ class Response
return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
}
+ /**
+ * Is this a remote file or not
+ *
+ * @param $file
+ * @return bool
+ */
+ public static function isRemote($file)
+ {
+ return (bool) filter_var($file, FILTER_VALIDATE_URL);
+ }
+
/**
* Progress normalized for cURL and Fopen
* Accepts a variable length of arguments passed in by stream method
diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php
index dd8308db6..e1383688c 100644
--- a/system/src/Grav/Common/Uri.php
+++ b/system/src/Grav/Common/Uri.php
@@ -727,7 +727,7 @@ class Uri
*
* @return boolean is eternal state
*/
- public function isExternal($url)
+ public static function isExternal($url)
{
if (Utils::startsWith($url, 'http')) {
return true;
@@ -1086,4 +1086,20 @@ class Uri
return $urlWithNonce;
}
+
+ /**
+ * Is the passed in URL a valid URL?
+ *
+ * @param $url
+ * @return bool
+ */
+ public static function isValidUrl($url)
+ {
+ $regex = '/^(?:(https?|ftp|telnet):)?\/\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?/';
+ if (preg_match($regex, $url)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
}
diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php
index fcc8f6c43..d5c108ff8 100644
--- a/system/src/Grav/Common/User/User.php
+++ b/system/src/Grav/Common/User/User.php
@@ -57,14 +57,14 @@ class User extends Data
/**
* Find a user by username, email, etc
*
- * @param $query the query to search for
+ * @param string $query the query to search for
* @param array $fields the fields to search
* @return User
*/
public static function find($query, $fields = ['username', 'email'])
{
$account_dir = Grav::instance()['locator']->findResource('account://');
- $files = array_diff(scandir($account_dir), ['.', '..']);
+ $files = $account_dir ? array_diff(scandir($account_dir), ['.', '..']) : [];
// Try with username first, you never know!
if (in_array('username', $fields)) {
@@ -100,7 +100,7 @@ class User extends Data
public static function remove($username)
{
$file_path = Grav::instance()['locator']->findResource('account://' . $username . YAML_EXT);
- if (file_exists($file_path) && unlink($file_path)) {
+ if ($file_path && unlink($file_path)) {
return true;
}
diff --git a/system/src/Grav/Console/Gpm/DirectInstallCommand.php b/system/src/Grav/Console/Gpm/DirectInstallCommand.php
index e23712e9f..202840289 100644
--- a/system/src/Grav/Console/Gpm/DirectInstallCommand.php
+++ b/system/src/Grav/Console/Gpm/DirectInstallCommand.php
@@ -9,14 +9,13 @@
namespace Grav\Console\Gpm;
use Grav\Common\Grav;
-use Grav\Common\Utils;
use Grav\Common\Filesystem\Folder;
+use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Question\ConfirmationQuestion;
-use Symfony\Component\Yaml\Yaml;
class DirectInstallCommand extends ConsoleCommand
{
@@ -63,10 +62,30 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->writeln("Preparing to install