Merge branches 'develop' and 'feature/objects' of https://github.com/getgrav/grav into feature/objects

This commit is contained in:
Matias Griese
2017-02-27 12:56:32 +02:00
10 changed files with 236 additions and 198 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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 <cyan>" . $package_file . "</cyan>");
if ($this->isRemote($package_file)) {
$zip = $this->downloadPackage($package_file, $tmp_zip);
if (Response::isRemote($package_file)) {
$this->output->write(" |- Downloading package... 0%");
try {
$zip = GPM::downloadPackage($package_file, $tmp_zip);
} catch (\RuntimeException $e) {
$this->output->writeln('');
$this->output->writeln(" `- <red>ERROR: " . $e->getMessage() . "</red>");
$this->output->writeln('');
exit;
}
if ($zip) {
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... 100%");
$this->output->writeln('');
}
} else {
$zip = $this->copyPackage($package_file, $tmp_zip);
$this->output->write(" |- Copying package... 0%");
$zip = GPM::copyPackage($package_file, $tmp_zip);
if ($zip) {
$this->output->write("\x0D");
$this->output->write(" |- Copying package... 100%");
$this->output->writeln('');
}
}
if (file_exists($zip)) {
@@ -85,7 +104,7 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->writeln(" |- Extracting package... <green>ok</green>");
$type = $this->getPackageType($extracted);
$type = GPM::getPackageType($extracted);
if (!$type) {
$this->output->writeln(" '- <red>ERROR: Not a valid Grav package</red>");
@@ -93,7 +112,7 @@ class DirectInstallCommand extends ConsoleCommand
exit;
}
$blueprint = $this->getBlueprints($extracted);
$blueprint = GPM::getBlueprints($extracted);
if ($blueprint) {
if (isset($blueprint['dependencies'])) {
$depencencies = [];
@@ -137,7 +156,7 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->write(" |- Installing package... ");
Installer::install($zip, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true], $extracted);
} else {
$name = $this->getPackageName($extracted);
$name = GPM::getPackageName($extracted);
if (!$name) {
$this->output->writeln("<red>ERROR: Name could not be determined.</red> Please specify with --name|-n");
@@ -145,7 +164,7 @@ class DirectInstallCommand extends ConsoleCommand
exit;
}
$install_path = $this->getInstallPath($type, $name);
$install_path = GPM::getInstallPath($type, $name);
$is_update = file_exists($install_path);
$this->output->write(" |- Checking destination... ");
@@ -173,7 +192,7 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->write("\x0D");
if(Installer::lastErrorCode()) {
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
$this->output->writeln(" '- <red>" . Installer::lastErrorMsg() . "</red>");
$this->output->writeln('');
} else {
$this->output->writeln(" |- Installing package... <green>ok</green>");
@@ -193,186 +212,4 @@ class DirectInstallCommand extends ConsoleCommand
return true;
}
/**
* Get the install path for a name and a particular type of package
*
* @param $type
* @param $name
* @return string
*/
protected 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;
}
/**
* Try to guess the package name from the source files
*
* @param $source
* @return bool|string
*/
protected function getPackageName($source)
{
foreach (glob($source . "*.yaml") as $filename) {
$name = strtolower(basename($filename, '.yaml'));
if ($name == 'blueprints') {
continue;
}
return $name;
}
return false;
}
/**
* Try to guess the package type from the source files
*
* @param $source
* @return bool|string
*/
protected 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';
}
}
/**
* Determine if this is a local or a remote file
*
* @param $file
* @return bool
*/
protected function isRemote($file)
{
return (bool) filter_var($file, FILTER_VALIDATE_URL);
}
/**
* Find/Parse the blueprint file
*
* @param $source
* @return array|bool
*/
protected 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;
}
/**
* Download the zip package via the URL
*
* @param $package_file
* @param $tmp
* @return null|string
*/
private function downloadPackage($package_file, $tmp)
{
$this->output->write(" |- Downloading package... 0%");
$package = parse_url($package_file);
$filename = basename($package['path']);
$output = Response::get($package_file, [], [$this, 'progress']);
if ($output) {
Folder::mkdir($tmp);
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... 100%");
$this->output->writeln('');
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
*/
private function copyPackage($package_file, $tmp)
{
$this->output->write(" |- Copying package... 0%");
$package_file = realpath($package_file);
if (file_exists($package_file)) {
$filename = basename($package_file);
Folder::mkdir($tmp);
$this->output->write("\x0D");
$this->output->write(" |- Copying package... 100%");
$this->output->writeln('');
copy(realpath($package_file), $tmp . DS . $filename);
return $tmp . DS . $filename;
}
return null;
}
/**
* @param $progress
*/
public function progress($progress)
{
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ",
STR_PAD_LEFT) . '%');
}
}

View File

@@ -675,6 +675,8 @@ class ParsedownTest extends \Codeception\TestCase\Test
{
$this->uri->initializeWithURL('http://testing.dev/item2/item2-2')->init();
$this->assertSame('<p><a href="#something" class="button">Anchor Class</a></p>',
$this->parsedown->text('[Anchor Class](?classes=button#something)'));
$this->assertSame('<p><a href="/item2/item2-3" class="button">Relative Class</a></p>',
$this->parsedown->text('[Relative Class](../item2-3?classes=button)'));
$this->assertSame('<p><a href="/item2/item2-3" id="unique">Relative ID</a></p>',