mirror of
https://github.com/getgrav/grav.git
synced 2026-05-07 22:06:43 +02:00
Merge branch 'develop' of https://github.com/getgrav/grav into develop
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,7 +1,7 @@
|
||||
# Composer
|
||||
composer.lock
|
||||
.composer
|
||||
vendor
|
||||
vendor/
|
||||
|
||||
# Sass
|
||||
.sass-cache
|
||||
|
||||
Binary file not shown.
42
bin/gpm
Executable file
42
bin/gpm
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
define('GRAV_CLI', true);
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
}
|
||||
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
echo "Preparing to install vendor dependencies...\n\n";
|
||||
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Grav\Common\Grav;
|
||||
|
||||
$autoload = require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
if (!file_exists(ROOT_DIR . 'index.php')) {
|
||||
exit('FATAL: Must be run from ROOT directory of Grav!');
|
||||
}
|
||||
|
||||
$grav = Grav::instance(array('loader' => $autoload));
|
||||
$grav['config']->init();
|
||||
$grav['plugins']->init();
|
||||
$grav['themes']->init();
|
||||
|
||||
$app = new Application('Grav Package Manager', GRAV_VERSION);
|
||||
$app->addCommands(array(
|
||||
new \Grav\Console\Gpm\IndexCommand(),
|
||||
new \Grav\Console\Gpm\InfoCommand(),
|
||||
new \Grav\Console\Gpm\InstallCommand(),
|
||||
new \Grav\Console\Gpm\UpdateCommand(),
|
||||
new \Grav\Console\Gpm\SelfupgradeCommand(),
|
||||
));
|
||||
$app->run();
|
||||
15
bin/grav
15
bin/grav
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
define('GRAV_CLI', true);
|
||||
|
||||
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
|
||||
@@ -8,7 +9,7 @@ if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
|
||||
if (!file_exists(__DIR__ . '/../vendor')){
|
||||
// Before we can even start, we need to run composer first
|
||||
echo "Preparing to install vendor dependencies...\n\n";
|
||||
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install -o');
|
||||
echo system('php bin/composer.phar --working-dir="'.__DIR__.'/../" --no-interaction install');
|
||||
echo "\n\n";
|
||||
}
|
||||
|
||||
@@ -26,11 +27,11 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
|
||||
|
||||
$app = new Application('Grav CLI Application', '0.1.0');
|
||||
$app->addCommands(array(
|
||||
new Grav\Console\InstallCommand(),
|
||||
new Grav\Console\SetupCommand(),
|
||||
new Grav\Console\CleanCommand(),
|
||||
new Grav\Console\ClearCacheCommand(),
|
||||
new Grav\Console\BackupCommand(),
|
||||
new Grav\Console\NewProjectCommand(),
|
||||
new Grav\Console\Cli\InstallCommand(),
|
||||
new Grav\Console\Cli\SandboxCommand(),
|
||||
new Grav\Console\Cli\CleanCommand(),
|
||||
new Grav\Console\Cli\ClearCacheCommand(),
|
||||
new Grav\Console\Cli\BackupCommand(),
|
||||
new Grav\Console\Cli\NewProjectCommand(),
|
||||
));
|
||||
$app->run();
|
||||
|
||||
@@ -233,6 +233,8 @@ form:
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.css_minify:
|
||||
type: toggle
|
||||
@@ -241,6 +243,8 @@ form:
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.css_minify_windows:
|
||||
type: toggle
|
||||
@@ -249,6 +253,8 @@ form:
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.css_rewrite:
|
||||
type: toggle
|
||||
@@ -257,6 +263,8 @@ form:
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.js_pipeline:
|
||||
type: toggle
|
||||
@@ -265,6 +273,8 @@ form:
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
assets.js_minify:
|
||||
type: toggle
|
||||
@@ -273,6 +283,8 @@ form:
|
||||
options:
|
||||
1: Yes
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger:
|
||||
type: section
|
||||
@@ -340,7 +352,7 @@ form:
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.shutdown.close_conection:
|
||||
debugger.shutdown.close_connection:
|
||||
type: toggle
|
||||
label: Shutdown Close Connection
|
||||
highlight: 1
|
||||
@@ -349,5 +361,3 @@ form:
|
||||
0: No
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
|
||||
|
||||
@@ -7,24 +7,19 @@ schemes:
|
||||
image:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user/images
|
||||
|
||||
user:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user
|
||||
- user://images
|
||||
|
||||
page:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user/pages
|
||||
- user://pages
|
||||
|
||||
account:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user/accounts
|
||||
- user://accounts
|
||||
|
||||
data:
|
||||
type: ReadOnlyStream
|
||||
paths:
|
||||
- user/data
|
||||
- user://data
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYaml;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
@@ -101,7 +101,7 @@ class Blueprints extends BaseBlueprints
|
||||
$files = $this->files[$key];
|
||||
}
|
||||
foreach ($files as $name => $item) {
|
||||
$file = CompiledYaml::instance($item['file']);
|
||||
$file = CompiledYamlFile::instance($item['file']);
|
||||
$this->blueprints->embed($name, $file->content(), '/');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common\Config;
|
||||
|
||||
use Grav\Common\File\CompiledYaml;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Uri;
|
||||
@@ -21,34 +21,40 @@ class Config extends Data
|
||||
{
|
||||
protected $grav;
|
||||
protected $streams = [
|
||||
'user' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user'],
|
||||
]
|
||||
],
|
||||
'blueprints' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user/blueprints', 'system/blueprints'],
|
||||
'' => ['user://blueprints', 'system/blueprints'],
|
||||
]
|
||||
],
|
||||
'config' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user/config', 'system/config'],
|
||||
'' => ['user://config', 'system/config'],
|
||||
]
|
||||
],
|
||||
'plugins' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user/plugins'],
|
||||
'' => ['user://plugins'],
|
||||
]
|
||||
],
|
||||
'plugin' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user/plugins'],
|
||||
'' => ['user://plugins'],
|
||||
]
|
||||
],
|
||||
'themes' => [
|
||||
'type' => 'ReadOnlyStream',
|
||||
'prefixes' => [
|
||||
'' => ['user/themes'],
|
||||
'' => ['user://themes'],
|
||||
]
|
||||
],
|
||||
'cache' => [
|
||||
@@ -101,6 +107,7 @@ class Config extends Data
|
||||
|
||||
public function reload()
|
||||
{
|
||||
$this->check();
|
||||
$this->init();
|
||||
|
||||
return $this;
|
||||
@@ -282,7 +289,7 @@ class Config extends Data
|
||||
$files = $this->blueprintFiles[$key];
|
||||
}
|
||||
foreach ($files as $name => $item) {
|
||||
$file = CompiledYaml::instance($item['file']);
|
||||
$file = CompiledYamlFile::instance($item['file']);
|
||||
$this->blueprints->embed($name, $file->content(), '/');
|
||||
}
|
||||
}
|
||||
@@ -299,7 +306,7 @@ class Config extends Data
|
||||
$files = $this->configFiles[$key];
|
||||
}
|
||||
foreach ($files as $name => $item) {
|
||||
$file = CompiledYaml::instance($item['file']);
|
||||
$file = CompiledYamlFile::instance($item['file']);
|
||||
$this->join($name, $file->content(), '/');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ class Blueprint
|
||||
public function fields()
|
||||
{
|
||||
if (!isset($this->fields)) {
|
||||
$this->fields = [];
|
||||
$this->embed('', $this->items);
|
||||
}
|
||||
|
||||
@@ -241,7 +242,7 @@ class Blueprint
|
||||
$this->fields();
|
||||
$prefix = $name ? strtr($name, $separator, '.') . '.' : '';
|
||||
$params = array_intersect_key($this->filter, $value);
|
||||
$this->parseFormFields($value['form']['fields'], $params, $prefix);
|
||||
$this->parseFormFields($value['form']['fields'], $params, $prefix, $this->fields);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,12 +361,14 @@ class Blueprint
|
||||
* @param array $fields
|
||||
* @param array $params
|
||||
* @param string $prefix
|
||||
* @param array $current
|
||||
* @internal
|
||||
*/
|
||||
protected function parseFormFields(array &$fields, $params, $prefix)
|
||||
protected function parseFormFields(array &$fields, $params, $prefix, array &$current)
|
||||
{
|
||||
// Go though all the fields in current level.
|
||||
foreach ($fields as $key => &$field) {
|
||||
$current[$key] = &$field;
|
||||
// Set name from the array key.
|
||||
$field['name'] = $prefix . $key;
|
||||
$field += $params;
|
||||
@@ -373,7 +376,7 @@ class Blueprint
|
||||
if (isset($field['fields'])) {
|
||||
// Recursively get all the nested fields.
|
||||
$newParams = array_intersect_key($this->filter, $field);
|
||||
$this->parseFormFields($field['fields'], $newParams, $prefix);
|
||||
$this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']);
|
||||
} else {
|
||||
// Add rule.
|
||||
$this->rules[$prefix . $key] = &$field;
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Grav\Common\File;
|
||||
|
||||
use RocketTheme\Toolbox\File\YamlFile;
|
||||
|
||||
class CompiledYaml extends YamlFile
|
||||
class CompiledYamlFile extends YamlFile
|
||||
{
|
||||
use CompiledFile;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively find the last modified time under given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $path
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModifiedFolder($path)
|
||||
@@ -29,6 +29,7 @@ abstract class Folder
|
||||
$last_modified = $dir_modified;
|
||||
}
|
||||
}
|
||||
|
||||
return $last_modified;
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively find the last modified time under given path by file.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $path
|
||||
* @return int
|
||||
*/
|
||||
public static function lastModifiedFile($path)
|
||||
@@ -67,15 +68,15 @@ abstract class Folder
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $last_modified;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return recursive list of all files and directories under given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $params
|
||||
* @param string $path
|
||||
* @param array $params
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
@@ -124,14 +125,15 @@ abstract class Folder
|
||||
$results[] = $filePath;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively copy directory in filesystem.
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function copy($source, $target)
|
||||
@@ -175,8 +177,8 @@ abstract class Folder
|
||||
/**
|
||||
* Move directory in filesystem.
|
||||
*
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @param string $source
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function move($source, $target)
|
||||
@@ -204,7 +206,7 @@ abstract class Folder
|
||||
/**
|
||||
* Recursively delete directory from filesystem.
|
||||
*
|
||||
* @param string $target
|
||||
* @param string $target
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public static function delete($target)
|
||||
@@ -224,6 +226,25 @@ abstract class Folder
|
||||
@touch(dirname($target));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
public static function mkdir($folder)
|
||||
{
|
||||
if (is_dir($folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$success = @mkdir($folder, 0777, true);
|
||||
|
||||
if (!$success) {
|
||||
$error = error_get_last();
|
||||
throw new \RuntimeException($error['message']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @return bool
|
||||
@@ -245,34 +266,16 @@ abstract class Folder
|
||||
|
||||
return @rmdir($folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected static function mkdir($folder)
|
||||
{
|
||||
if (is_dir($folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$success = @mkdir($folder, 0777, true);
|
||||
|
||||
if (!$success) {
|
||||
$error = error_get_last();
|
||||
throw new \RuntimeException($error['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GravRecursiveFilterIterator extends \RecursiveFilterIterator {
|
||||
|
||||
class GravRecursiveFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
public static $FILTERS = array(
|
||||
'.', '..', '.DS_Store'
|
||||
);
|
||||
|
||||
public function accept() {
|
||||
public function accept()
|
||||
{
|
||||
return !in_array(
|
||||
$this->current()->getFilename(),
|
||||
self::$FILTERS,
|
||||
|
||||
364
system/src/Grav/Common/GPM/GPM.php
Normal file
364
system/src/Grav/Common/GPM/GPM.php
Normal file
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class GPM extends Iterator
|
||||
{
|
||||
/**
|
||||
* Local installed Packages
|
||||
* @var Packages
|
||||
*/
|
||||
private $installed;
|
||||
|
||||
/**
|
||||
* Remote available Packages
|
||||
* @var Packages
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
/**
|
||||
* @var Remote\Grav
|
||||
*/
|
||||
public $grav;
|
||||
|
||||
/**
|
||||
* Internal cache
|
||||
* @var Iterator
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Creates a new GPM instance with Local and Remote packages available
|
||||
* @param boolean $refresh Applies to Remote Packages only and forces a refetch of data
|
||||
* @param callable $callback Either a function or callback in array notation
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
$this->installed = new Local\Packages();
|
||||
$this->repository = new Remote\Packages($refresh, $callback);
|
||||
$this->grav = new Remote\Grav($refresh, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Locally installed packages
|
||||
* @return Iterator The installed packages
|
||||
*/
|
||||
public function getInstalled()
|
||||
{
|
||||
return $this->installed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of locally installed packages
|
||||
* @return integer Amount of installed packages
|
||||
*/
|
||||
public function countInstalled()
|
||||
{
|
||||
$installed = $this->getInstalled();
|
||||
|
||||
return count($installed['plugins']) + count($installed['themes']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance of a specific Plugin
|
||||
* @param string $slug The slug of the Plugin
|
||||
* @return Package The instance of the Plugin
|
||||
*/
|
||||
public function getInstalledPlugin($slug)
|
||||
{
|
||||
return $this->installed['plugins'][$slug];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Locally installed plugins
|
||||
* @return Iterator The installed plugins
|
||||
*/
|
||||
public function getInstalledPlugins()
|
||||
{
|
||||
return $this->installed['plugins'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Plugin is installed
|
||||
* @param string $slug The slug of the Plugin
|
||||
* @return boolean True if the Plugin has been installed. False otherwise
|
||||
*/
|
||||
public function isPluginInstalled($slug)
|
||||
{
|
||||
return isset($this->installed['plugins'][$slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance of a specific Theme
|
||||
* @param string $slug The slug of the Theme
|
||||
* @return Package The instance of the Theme
|
||||
*/
|
||||
public function getInstalledTheme($slug)
|
||||
{
|
||||
return $this->installed['themes'][$slug];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Locally installed themes
|
||||
* @return Iterator The installed themes
|
||||
*/
|
||||
public function getInstalledThemes()
|
||||
{
|
||||
return $this->installed['themes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Theme is installed
|
||||
* @param string $slug The slug of the Theme
|
||||
* @return boolean True if the Theme has been installed. False otherwise
|
||||
*/
|
||||
public function isThemeInstalled($slug)
|
||||
{
|
||||
return isset($this->installed['themes'][$slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of updates available
|
||||
* @return integer Amount of available updates
|
||||
*/
|
||||
public function countUpdates()
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
$count += count($this->getUpdatablePlugins());
|
||||
$count += count($this->getUpdatableThemes());
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Plugins and Themes that can be updated.
|
||||
* Plugins and Themes are extended with the `available` property that relies to the remote version
|
||||
* @return array Array of updatable Plugins and Themes.
|
||||
* Format: ['total' => int, 'plugins' => array, 'themes' => array]
|
||||
*/
|
||||
public function getUpdatable()
|
||||
{
|
||||
$plugins = $this->getUpdatablePlugins();
|
||||
$themes = $this->getUpdatableThemes();
|
||||
|
||||
$items = [
|
||||
'total' => count($plugins)+count($themes),
|
||||
'plugins' => $plugins,
|
||||
'themes' => $themes
|
||||
];
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Plugins that can be updated.
|
||||
* The Plugins are extended with the `available` property that relies to the remote version
|
||||
* @return Iterator Array of updatable Plugins
|
||||
*/
|
||||
public function getUpdatablePlugins()
|
||||
{
|
||||
$items = [];
|
||||
$repository = $this->repository['plugins'];
|
||||
|
||||
// local cache to speed things up
|
||||
if (isset($this->cache[__METHOD__])) {
|
||||
return $this->cache[__METHOD__];
|
||||
}
|
||||
|
||||
foreach ($this->installed['plugins'] as $slug => $plugin) {
|
||||
if (!isset($repository[$slug])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$local_version = $plugin->version ? $plugin->version : 'Unknown';
|
||||
$remote_version = $repository[$slug]->version;
|
||||
|
||||
if (version_compare($local_version, $remote_version) < 0) {
|
||||
$repository[$slug]->available = $remote_version;
|
||||
$repository[$slug]->version = $local_version;
|
||||
$items[$slug] = $repository[$slug];
|
||||
}
|
||||
}
|
||||
|
||||
$this->cache[__METHOD__] = $items;
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a Plugin or Theme is updatable
|
||||
* @param string $slug The slug of the package
|
||||
* @return boolean True if updatable. False otherwise or if not found
|
||||
*/
|
||||
public function isUpdatable($slug)
|
||||
{
|
||||
return $this->isPluginUpdatable($slug) || $this->isThemeUpdatable($slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Plugin is updatable
|
||||
* @param string $plugin The slug of the Plugin
|
||||
* @return boolean True if the Plugin is updatable. False otherwise
|
||||
*/
|
||||
public function isPluginUpdatable($plugin)
|
||||
{
|
||||
return array_key_exists($plugin, $this->getUpdatablePlugins());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of Themes that can be updated.
|
||||
* The Themes are extended with the `available` property that relies to the remote version
|
||||
* @return Iterator Array of updatable Themes
|
||||
*/
|
||||
public function getUpdatableThemes()
|
||||
{
|
||||
$items = [];
|
||||
$repository = $this->repository['themes'];
|
||||
|
||||
// local cache to speed things up
|
||||
if (isset($this->cache[__METHOD__])) {
|
||||
return $this->cache[__METHOD__];
|
||||
}
|
||||
|
||||
foreach ($this->installed['themes'] as $slug => $plugin) {
|
||||
if (!isset($repository[$slug])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$local_version = $plugin->version ? $plugin->version : 'Unknown';
|
||||
$remote_version = $repository[$slug]->version;
|
||||
|
||||
if (version_compare($local_version, $remote_version) < 0) {
|
||||
$repository[$slug]->available = $remote_version;
|
||||
$repository[$slug]->version = $local_version;
|
||||
$items[$slug] = $repository[$slug];
|
||||
}
|
||||
}
|
||||
|
||||
$this->cache[__METHOD__] = $items;
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a Theme is Updatable
|
||||
* @param string $theme The slug of the Theme
|
||||
* @return boolean True if the Theme is updatable. False otherwise
|
||||
*/
|
||||
public function isThemeUpdatable($theme)
|
||||
{
|
||||
return array_key_exists($theme, $this->getUpdatableThemes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Plugin from the repository
|
||||
* @param string $slug The slug of the Plugin
|
||||
* @return mixed Package if found, NULL if not
|
||||
*/
|
||||
public function getRepositoryPlugin($slug)
|
||||
{
|
||||
return @$this->repository['plugins'][$slug];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Plugins available in the repository
|
||||
* @return Iterator The Plugins remotely available
|
||||
*/
|
||||
public function getRepositoryPlugins()
|
||||
{
|
||||
return $this->repository['plugins'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Theme from the repository
|
||||
* @param string $slug The slug of the Theme
|
||||
* @return mixed Package if found, NULL if not
|
||||
*/
|
||||
public function getRepositoryTheme($slug)
|
||||
{
|
||||
return @$this->repository['themes'][$slug];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Themes available in the repository
|
||||
* @return Iterator The Themes remotely available
|
||||
*/
|
||||
public function getRepositoryThemes()
|
||||
{
|
||||
return $this->repository['themes'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Plugins and Themes available in the repository
|
||||
* @return array Array of available Plugins and Themes
|
||||
* Format: ['plugins' => array, 'themes' => array]
|
||||
*/
|
||||
public function getRepository()
|
||||
{
|
||||
return $this->repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a Package in the repository
|
||||
* @param string $search Can be either the slug or the name
|
||||
* @return Package Package if found, FALSE if not
|
||||
*/
|
||||
public function findPackage($search)
|
||||
{
|
||||
$search = strtolower($search);
|
||||
if ($found = $this->getRepositoryTheme($search)) {
|
||||
return $found;
|
||||
}
|
||||
|
||||
if ($found = $this->getRepositoryPlugin($search)) {
|
||||
return $found;
|
||||
}
|
||||
|
||||
foreach ($this->getRepositoryThemes() as $slug => $theme) {
|
||||
if ($search == $slug || $search == $theme->name) {
|
||||
return $theme;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getRepositoryPlugins() as $slug => $plugin) {
|
||||
if ($search == $slug || $search == $plugin->name) {
|
||||
return $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of Plugins and Themes available in the repository
|
||||
* @return array Array of available Plugins and Themes
|
||||
* Format: ['plugins' => array, 'themes' => array]
|
||||
*/
|
||||
/**
|
||||
* Searches for a list of Packages in the repository
|
||||
* @param array $searches An array of either slugs or names
|
||||
* @return array Array of found Packages
|
||||
* Format: ['total' => int, 'not_found' => array, <found-slugs>]
|
||||
*/
|
||||
public function findPackages($searches = [])
|
||||
{
|
||||
$packages = ['total' => 0, 'not_found' => []];
|
||||
|
||||
foreach ($searches as $search) {
|
||||
if ($found = $this->findPackage($search)) {
|
||||
if (!isset($packages[$found->package_type])) {
|
||||
$packages[$found->package_type] = [];
|
||||
}
|
||||
|
||||
$packages[$found->package_type][$found->slug] = $found;
|
||||
$packages['total']++;
|
||||
} else {
|
||||
$packages['not_found'][] = $search;
|
||||
}
|
||||
}
|
||||
|
||||
return $packages;
|
||||
}
|
||||
}
|
||||
269
system/src/Grav/Common/GPM/Installer.php
Normal file
269
system/src/Grav/Common/GPM/Installer.php
Normal file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
class Installer
|
||||
{
|
||||
/** @const No error */
|
||||
const OK = 0;
|
||||
/** @const Target already exists */
|
||||
const EXISTS = 1;
|
||||
/** @const Target is a symbolic link */
|
||||
const IS_LINK = 2;
|
||||
/** @const Target doesn't exist */
|
||||
const NOT_FOUND = 4;
|
||||
/** @const Target is not a directory */
|
||||
const NOT_DIRECTORY = 8;
|
||||
/** @const Target is not a Grav instance */
|
||||
const NOT_GRAV_ROOT = 16;
|
||||
/** @const Error while trying to open the ZIP package */
|
||||
const ZIP_OPEN_ERROR = 32;
|
||||
/** @const Error while trying to extract the ZIP package */
|
||||
const ZIP_EXTRACT_ERROR = 64;
|
||||
|
||||
/**
|
||||
* Destination folder on which validation checks are applied
|
||||
* @var string
|
||||
*/
|
||||
protected static $target;
|
||||
|
||||
/**
|
||||
* Error Code
|
||||
* @var integer
|
||||
*/
|
||||
protected static $error = 0;
|
||||
|
||||
/**
|
||||
* Default options for the install
|
||||
* @var array
|
||||
*/
|
||||
protected static $options = [
|
||||
'overwrite' => true,
|
||||
'ignore_symlinks' => true,
|
||||
'sophisticated' => false,
|
||||
'install_path' => '',
|
||||
'exclude_checks' => [self::EXISTS, self::NOT_FOUND, self::IS_LINK]
|
||||
];
|
||||
|
||||
/**
|
||||
* Installs a given package to a given destination.
|
||||
*
|
||||
* @param string $package The local path to the ZIP package
|
||||
* @param string $destination The local path to the Grav Instance
|
||||
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
|
||||
*
|
||||
* @return boolean True if everything went fine, False otherwise.
|
||||
*/
|
||||
public static function install($package, $destination, $options = [])
|
||||
{
|
||||
$destination = rtrim($destination, DS);
|
||||
$options = array_merge(self::$options, $options);
|
||||
$install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS);
|
||||
|
||||
if (!self::isGravInstance($destination) || !self::isValidDestination($install_path, $options['exclude_checks'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
|
||||
self::lastErrorCode() == self::EXISTS && !$options['overwrite']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($package);
|
||||
$tmp = sys_get_temp_dir() . DS . 'Grav-' . uniqid();
|
||||
|
||||
if ($archive !== true) {
|
||||
self::$error = self::ZIP_OPEN_ERROR;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Folder::mkdir($tmp);
|
||||
|
||||
$unzip = $zip->extractTo($tmp);
|
||||
|
||||
if (!$unzip) {
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
|
||||
$zip->close();
|
||||
Folder::delete($tmp);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!$options['sophisticated']) {
|
||||
self::nonSophisticatedInstall($zip, $install_path, $tmp);
|
||||
} else {
|
||||
self::sophisticatedInstall($zip, $install_path, $tmp);
|
||||
}
|
||||
|
||||
Folder::delete($tmp);
|
||||
$zip->close();
|
||||
|
||||
self::$error = self::OK;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public static function nonSophisticatedInstall($zip, $install_path, $tmp)
|
||||
{
|
||||
$container = $zip->getNameIndex(0); // TODO: better way of determining if zip has container folder
|
||||
if (file_exists($install_path)) {
|
||||
Folder::delete($install_path);
|
||||
}
|
||||
|
||||
Folder::move($tmp . DS . $container, $install_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function sophisticatedInstall($zip, $install_path, $tmp)
|
||||
{
|
||||
for ($i = 0, $l = $zip->numFiles; $i < $l; $i++) {
|
||||
$filename = $zip->getNameIndex($i);
|
||||
$fileinfo = pathinfo($filename);
|
||||
$depth = count(explode(DS, rtrim($filename, '/')));
|
||||
|
||||
if ($depth > 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $install_path . DS . $fileinfo['basename'];
|
||||
|
||||
if (is_link($path)) {
|
||||
continue;
|
||||
} else {
|
||||
if (is_dir($path)) {
|
||||
Folder::delete($path);
|
||||
Folder::move($tmp . DS . $filename, $path);
|
||||
} else {
|
||||
if (is_file($path)) {
|
||||
@unlink($path);
|
||||
@copy($tmp . DS . $filename, $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a set of checks on the destination and sets the Error if any
|
||||
*
|
||||
* @param string $destination The directory to run validations at
|
||||
* @param array $exclude An array of constants to exclude from the validation
|
||||
*
|
||||
* @return boolean True if validation passed. False otherwise
|
||||
*/
|
||||
public static function isValidDestination($destination, $exclude = [])
|
||||
{
|
||||
self::$error = 0;
|
||||
self::$target = $destination;
|
||||
|
||||
if (is_link($destination)) {
|
||||
self::$error = self::IS_LINK;
|
||||
} elseif (file_exists($destination)) {
|
||||
self::$error = self::EXISTS;
|
||||
} elseif (!file_exists($destination)) {
|
||||
self::$error = self::NOT_FOUND;
|
||||
} elseif (!is_dir($destination)) {
|
||||
self::$error = self::NOT_DIRECTORY;
|
||||
}
|
||||
|
||||
if (count($exclude) && in_array(self::$error, $exclude)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !(self::$error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the given path is a Grav Instance
|
||||
*
|
||||
* @param string $target The local path to the Grav Instance
|
||||
*
|
||||
* @return boolean True if is a Grav Instance. False otherwise
|
||||
*/
|
||||
public static function isGravInstance($target)
|
||||
{
|
||||
self::$error = 0;
|
||||
self::$target = $target;
|
||||
|
||||
if (
|
||||
!file_exists($target . DS . 'index.php') ||
|
||||
!file_exists($target . DS . 'bin') ||
|
||||
!file_exists($target . DS . 'user') ||
|
||||
!file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml')
|
||||
) {
|
||||
self::$error = self::NOT_GRAV_ROOT;
|
||||
}
|
||||
|
||||
return !self::$error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error occurred in a string message format
|
||||
* @return string The message of the last error
|
||||
*/
|
||||
public static function lastErrorMsg()
|
||||
{
|
||||
$msg = 'Unknown Error';
|
||||
|
||||
switch (self::$error) {
|
||||
case 0:
|
||||
$msg = 'No Error';
|
||||
break;
|
||||
|
||||
case self::EXISTS:
|
||||
$msg = 'The target path "' . self::$target . '" already exists';
|
||||
break;
|
||||
|
||||
case self::IS_LINK:
|
||||
$msg = 'The target path "' . self::$target . '" is a symbolic link';
|
||||
break;
|
||||
|
||||
case self::NOT_FOUND:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to exist';
|
||||
break;
|
||||
|
||||
case self::NOT_DIRECTORY:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to be a folder';
|
||||
break;
|
||||
|
||||
case self::NOT_GRAV_ROOT:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to be a Grav instance';
|
||||
break;
|
||||
|
||||
case self::ZIP_OPEN_ERROR:
|
||||
$msg = 'Unable to open the package file';
|
||||
break;
|
||||
|
||||
case self::ZIP_EXTRACT_ERROR:
|
||||
$msg = 'An error occurred while extracting the package';
|
||||
break;
|
||||
|
||||
default:
|
||||
return 'Unknown error';
|
||||
break;
|
||||
}
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code of the occurred error
|
||||
* @return integer The code of the last error
|
||||
*/
|
||||
public static function lastErrorCode()
|
||||
{
|
||||
return self::$error;
|
||||
}
|
||||
}
|
||||
32
system/src/Grav/Common/GPM/Local/Collection.php
Normal file
32
system/src/Grav/Common/GPM/Local/Collection.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class Collection extends Iterator
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
86
system/src/Grav/Common/GPM/Local/Package.php
Normal file
86
system/src/Grav/Common/GPM/Local/Package.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
/**
|
||||
* Class Package
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Package
|
||||
{
|
||||
/**
|
||||
* @var Data
|
||||
*/
|
||||
protected $data;
|
||||
/**
|
||||
* @var \Grav\Common\Data\Blueprint
|
||||
*/
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @param Data $package
|
||||
* @param bool $package_type
|
||||
*/
|
||||
public function __construct(Data $package, $package_type = false)
|
||||
{
|
||||
$this->data = $package;
|
||||
$this->blueprints = $this->data->blueprints();
|
||||
|
||||
if ($package_type) {
|
||||
$html_description = \Parsedown::instance()->line($this->blueprints->get('description'));
|
||||
$this->blueprints->set('package_type', $package_type);
|
||||
$this->blueprints->set('description_html', $html_description);
|
||||
$this->blueprints->set('description_plain', strip_tags($html_description));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->data['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->blueprints->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return $this->blueprints->toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->blueprints->toArray();
|
||||
}
|
||||
}
|
||||
28
system/src/Grav/Common/GPM/Local/Packages.php
Normal file
28
system/src/Grav/Common/GPM/Local/Packages.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class Packages extends Iterator
|
||||
{
|
||||
private $plugins;
|
||||
private $themes;
|
||||
protected static $cache;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[__METHOD__])) {
|
||||
self::$cache[__METHOD__] = [
|
||||
'plugins' => new Plugins(),
|
||||
'themes' => new Themes()
|
||||
];
|
||||
}
|
||||
|
||||
$this->plugins = self::$cache[__METHOD__]['plugins'];
|
||||
$this->themes = self::$cache[__METHOD__]['themes'];
|
||||
|
||||
$this->append(['plugins' => $this->plugins]);
|
||||
$this->append(['themes' => $this->themes]);
|
||||
}
|
||||
}
|
||||
26
system/src/Grav/Common/GPM/Local/Plugins.php
Normal file
26
system/src/Grav/Common/GPM/Local/Plugins.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
/**
|
||||
* Class Plugins
|
||||
* @package Grav\Common\GPM\Local
|
||||
*/
|
||||
class Plugins extends Collection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $type = 'plugins';
|
||||
|
||||
/**
|
||||
* Local Plugins Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$grav = self::$grav;
|
||||
|
||||
foreach ($grav['plugins']->all() as $name => $data) {
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
system/src/Grav/Common/GPM/Local/Themes.php
Normal file
15
system/src/Grav/Common/GPM/Local/Themes.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
class Themes extends Collection
|
||||
{
|
||||
private $type = 'themes';
|
||||
public function __construct()
|
||||
{
|
||||
$grav = self::$grav;
|
||||
|
||||
foreach ($grav['themes']->all() as $name => $data) {
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
system/src/Grav/Common/GPM/Remote/Collection.php
Normal file
64
system/src/Grav/Common/GPM/Remote/Collection.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class Collection extends Iterator {
|
||||
use GravTrait;
|
||||
|
||||
/**
|
||||
* The cached data previously fetched
|
||||
* @var string
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* The lifetime to store the entry in seconds
|
||||
* @var integer
|
||||
*/
|
||||
private $lifetime = 86400;
|
||||
private $repository;
|
||||
|
||||
private $plugins, $themes;
|
||||
|
||||
public function __construct($repository = null) {
|
||||
if ($repository == null) {
|
||||
throw new \RuntimeException("A repository is required for storing the cache");
|
||||
}
|
||||
|
||||
$this->repository = $repository;
|
||||
$this->raw = self::$grav['cache']->fetch(md5($this->repository));
|
||||
}
|
||||
|
||||
public function toJson() {
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $theme) {
|
||||
$items[$name] = $theme->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function fetch($refresh = false, $callback = null) {
|
||||
if (!$this->raw || $refresh) {
|
||||
$response = Response::get($this->repository, [], $callback);
|
||||
$this->raw = $response;
|
||||
self::$grav['cache']->save(md5($this->repository), $this->raw, $this->lifetime);
|
||||
}
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
}
|
||||
60
system/src/Grav/Common/GPM/Remote/Grav.php
Normal file
60
system/src/Grav/Common/GPM/Remote/Grav.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Grav extends Collection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/grav.json';
|
||||
private $data;
|
||||
|
||||
private $version;
|
||||
private $date;
|
||||
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository);
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
$this->data = json_decode($this->raw);
|
||||
|
||||
$this->version = @$this->data->version ?: '-';
|
||||
$this->date = @$this->data->date ?: '-';
|
||||
|
||||
$this->data = $this->data->assets;
|
||||
|
||||
foreach ($this->data as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of assets associated to the latest version of Grav
|
||||
* @return array list of assets
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest version of Grav available remotely
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the release date of the latest Grav
|
||||
* @return string
|
||||
*/
|
||||
public function getDate()
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function isUpdatable()
|
||||
{
|
||||
return version_compare(GRAV_VERSION, $this->getVersion(), '<');
|
||||
}
|
||||
}
|
||||
32
system/src/Grav/Common/GPM/Remote/Package.php
Normal file
32
system/src/Grav/Common/GPM/Remote/Package.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Package {
|
||||
public function __construct($package, $package_type = false) {
|
||||
$this->data = $package;
|
||||
if ($package_type) {
|
||||
$this->data->package_type = $package_type;
|
||||
}
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
return $this->data->$key;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
public function toJson() {
|
||||
return json_encode($this->data);
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
}
|
||||
28
system/src/Grav/Common/GPM/Remote/Packages.php
Normal file
28
system/src/Grav/Common/GPM/Remote/Packages.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class Packages extends Iterator
|
||||
{
|
||||
private $plugins;
|
||||
private $themes;
|
||||
protected static $cache;
|
||||
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[__METHOD__])) {
|
||||
self::$cache[__METHOD__] = [
|
||||
'plugins' => new Plugins($refresh, $callback),
|
||||
'themes' => new Themes($refresh, $callback)
|
||||
];
|
||||
}
|
||||
|
||||
$this->plugins = self::$cache[__METHOD__]['plugins']->toArray();
|
||||
$this->themes = self::$cache[__METHOD__]['themes']->toArray();
|
||||
|
||||
$this->append(['plugins' => $this->plugins]);
|
||||
$this->append(['themes' => $this->themes]);
|
||||
}
|
||||
}
|
||||
21
system/src/Grav/Common/GPM/Remote/Plugins.php
Normal file
21
system/src/Grav/Common/GPM/Remote/Plugins.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Plugins extends Collection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/plugins.json';
|
||||
private $type = 'plugins';
|
||||
private $data;
|
||||
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository);
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
$this->data = json_decode($this->raw);
|
||||
|
||||
foreach ($this->data as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
system/src/Grav/Common/GPM/Remote/Themes.php
Normal file
21
system/src/Grav/Common/GPM/Remote/Themes.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Themes extends Collection
|
||||
{
|
||||
private $repository = 'http://getgrav.org/downloads/themes.json';
|
||||
private $type = 'themes';
|
||||
private $data;
|
||||
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository);
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
$this->data = json_decode($this->raw);
|
||||
|
||||
foreach ($this->data as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
220
system/src/Grav/Common/GPM/Response.php
Normal file
220
system/src/Grav/Common/GPM/Response.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* The callback for the progress
|
||||
* @var callable Either a function or callback in array notation
|
||||
*/
|
||||
public static $callback = null;
|
||||
|
||||
/**
|
||||
* Which method to use for HTTP calls, can be 'curl', 'fopen' or 'auto'. Auto is default and fopen is the preferred method
|
||||
* @var string
|
||||
*/
|
||||
private static $method = 'auto';
|
||||
|
||||
/**
|
||||
* Default parameters for `curl` and `fopen`
|
||||
* @var array
|
||||
*/
|
||||
private static $defaults = [
|
||||
|
||||
'curl' => [
|
||||
CURLOPT_REFERER => 'Grav GPM',
|
||||
CURLOPT_USERAGENT => 'Grav GPM',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_HEADER => false,
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
//CURLOPT_NOPROGRESS => false,
|
||||
//CURLOPT_PROGRESSFUNCTION => [$this, 'progress']
|
||||
],
|
||||
'fopen' => [
|
||||
'method' => 'GET',
|
||||
'user_agent' => 'Grav GPM',
|
||||
'max_redirects' => 5,
|
||||
'follow_location' => 1,
|
||||
'timeout' => 15,
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
//'notification' => [$this, 'progress']
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets the preferred method to use for making HTTP calls.
|
||||
* @param string $method Default is `auto`
|
||||
*/
|
||||
public static function setMethod($method = 'auto')
|
||||
{
|
||||
if (!in_array($method, ['auto', 'curl', 'fopen'])) {
|
||||
$method = 'auto';
|
||||
}
|
||||
|
||||
self::$method = $method;
|
||||
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to the URL by using the preferred method
|
||||
* @param string $uri URL to call
|
||||
* @param array $options An array of parameters for both `curl` and `fopen`
|
||||
* @return string The response of the request
|
||||
*/
|
||||
public static function get($uri = '', $options = [], $callback = null)
|
||||
{
|
||||
if (!self::isCurlAvailable() && !self::isFopenAvailable()) {
|
||||
throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
|
||||
}
|
||||
|
||||
$options = array_replace_recursive(self::$defaults, $options);
|
||||
$method = 'get' . ucfirst(strtolower(self::$method));
|
||||
|
||||
self::$callback = $callback;
|
||||
|
||||
return static::$method($uri, $options, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress normalized for cURL and Fopen
|
||||
* @param args Variable length of arguments passed in by stream method
|
||||
* @return array Normalized array with useful data.
|
||||
* Format: ['code' => int|false, 'filesize' => bytes, 'transferred' => bytes, 'percent' => int]
|
||||
*/
|
||||
public static function progress()
|
||||
{
|
||||
static $filesize = null;
|
||||
|
||||
$args = func_get_args();
|
||||
$isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) == 'curl';
|
||||
|
||||
$notification_code = !$isCurlResource ? $args[0] : false;
|
||||
$bytes_transferred = $isCurlResource ? $args[2] : $args[4];
|
||||
|
||||
if ($isCurlResource) {
|
||||
$filesize = $args[1];
|
||||
} elseif ($notification_code == STREAM_NOTIFY_FILE_SIZE_IS) {
|
||||
$filesize = $args[5];
|
||||
}
|
||||
|
||||
if ($bytes_transferred > 0) {
|
||||
if ($notification_code == STREAM_NOTIFY_PROGRESS|STREAM_NOTIFY_COMPLETED || $isCurlResource) {
|
||||
|
||||
$progress = [
|
||||
'code' => $notification_code,
|
||||
'filesize' => $filesize,
|
||||
'transferred' => $bytes_transferred,
|
||||
'percent' => $filesize <= 0 ? '-' : round(($bytes_transferred * 100) / $filesize, 1)
|
||||
];
|
||||
|
||||
if (self::$callback !== null) {
|
||||
call_user_func_array(self::$callback, [$progress]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cURL is available
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isCurlAvailable()
|
||||
{
|
||||
return function_exists('curl_version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the remote fopen request is enabled in PHP
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isFopenAvailable()
|
||||
{
|
||||
return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically picks the preferred method
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getAuto()
|
||||
{
|
||||
if (self::isFopenAvailable()) {
|
||||
return self::getFopen(func_get_args());
|
||||
}
|
||||
|
||||
if (self::isCurlAvailable()) {
|
||||
return self::getCurl(func_get_args());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a HTTP request via cURL
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getCurl()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$uri = $args[0];
|
||||
$options = $args[1];
|
||||
$callback = $args[2];
|
||||
|
||||
$ch = curl_init($uri);
|
||||
curl_setopt_array($ch, $options['curl']);
|
||||
|
||||
if ($callback) {
|
||||
curl_setopt_array(
|
||||
$ch,
|
||||
[
|
||||
CURLOPT_NOPROGRESS => false,
|
||||
CURLOPT_PROGRESSFUNCTION => ['self', 'progress']
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if ($errno = curl_errno($ch)) {
|
||||
$error_message = curl_strerror($errno);
|
||||
throw new \RuntimeException("cURL error ({$errno}):\n {$error_message}");
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a HTTP request via fopen
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getFopen()
|
||||
{
|
||||
if (count($args = func_get_args()) == 1) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
$uri = $args[0];
|
||||
$options = $args[1];
|
||||
$callback = $args[2];
|
||||
|
||||
if ($callback) {
|
||||
$options['fopen']['notification'] = ['self', 'progress'];
|
||||
}
|
||||
|
||||
$stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
|
||||
$content = @file_get_contents($uri, false, $stream);
|
||||
|
||||
if ($content === false) {
|
||||
throw new \RuntimeException("Error while trying to download '$uri'");
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
75
system/src/Grav/Common/GPM/Upgrader.php
Normal file
75
system/src/Grav/Common/GPM/Upgrader.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\GPM\Installer;
|
||||
|
||||
class Upgrader
|
||||
{
|
||||
/**
|
||||
* Remote details about latest Grav version
|
||||
* @var Packages
|
||||
*/
|
||||
private $remote;
|
||||
|
||||
/**
|
||||
* Internal cache
|
||||
* @var Iterator
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Creates a new GPM instance with Local and Remote packages available
|
||||
* @param boolean $refresh Applies to Remote Packages only and forces a refetch of data
|
||||
* @param callable $callback Either a function or callback in array notation
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
$this->remote = new Remote\Grav($refresh, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the release date of the latest version of Grav
|
||||
* @return string
|
||||
*/
|
||||
public function getReleaseDate()
|
||||
{
|
||||
return $this->remote->getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the installed Grav
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalVersion()
|
||||
{
|
||||
return GRAV_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the remotely available Grav
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteVersion()
|
||||
{
|
||||
return $this->remote->getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of assets available to download remotely
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->remote->getAssets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the currently installed Grav is upgradable to a newer version
|
||||
* @return boolean True if it's upgradable, False otherwise.
|
||||
*/
|
||||
public function isUpgradable()
|
||||
{
|
||||
return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), "<");
|
||||
}
|
||||
}
|
||||
@@ -100,8 +100,9 @@ class Grav extends Container
|
||||
$page = $c['pages']->dispatch($path_parts['dirname']);
|
||||
if ($page) {
|
||||
$media = $page->media()->all();
|
||||
if (isset($media[$path_parts['basename']])) {
|
||||
$medium = $media[$path_parts['basename']];
|
||||
$media_file = urldecode($path_parts['basename']);
|
||||
if (isset($media[$media_file])) {
|
||||
$medium = $media[$media_file];
|
||||
|
||||
// loop through actions for the image and call them
|
||||
foreach ($c['uri']->query(null,true) as $action => $params) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Iterator as ArrayIterator;
|
||||
use RocketTheme\Toolbox\ArrayTraits\Constructor;
|
||||
@@ -18,7 +19,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $items;
|
||||
protected $items = [];
|
||||
|
||||
/**
|
||||
* Convert function calls for the existing keys into their values.
|
||||
@@ -112,7 +113,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
shuffle($keys);
|
||||
|
||||
$new = array();
|
||||
foreach($keys as $key) {
|
||||
foreach ($keys as $key) {
|
||||
$new[$key] = $this->items[$key];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Gantry\Component\Filesystem\Folder;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\GravTrait;
|
||||
use Grav\Common\Utils;
|
||||
|
||||
@@ -463,13 +463,15 @@ class Pages
|
||||
if ($config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
|
||||
}
|
||||
} else {
|
||||
$date = $file->getMTime();
|
||||
if ($date > $last_modified) {
|
||||
$last_modified = $date;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the last modified if it's newer than already found
|
||||
$date = $file->getMTime();
|
||||
if ($date > $last_modified) {
|
||||
$last_modified = $date;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Override the modified and ID so that it takes the latest change into account
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\File\CompiledYaml;
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Data\Data;
|
||||
use RocketTheme\Toolbox\Event\EventDispatcher;
|
||||
@@ -74,7 +74,7 @@ class Themes extends Iterator
|
||||
/**
|
||||
* Get theme configuration or throw exception if it cannot be found.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $name
|
||||
* @return Data
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
@@ -96,11 +96,11 @@ class Themes extends Iterator
|
||||
}
|
||||
|
||||
// Load default configuration.
|
||||
$file = CompiledYaml::instance("themes://{$name}/{$name}" . YAML_EXT);
|
||||
$file = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT);
|
||||
$obj = new Data($file->content(), $blueprint);
|
||||
|
||||
// Override with user configuration.
|
||||
$file = CompiledYaml::instance("user://config/themes/{$name}" . YAML_EXT);
|
||||
$file = CompiledYamlFile::instance("user://config/themes/{$name}" . YAML_EXT);
|
||||
$obj->merge($file->content());
|
||||
|
||||
// Save configuration always to user/config.
|
||||
@@ -146,7 +146,7 @@ class Themes extends Iterator
|
||||
$class = new $className($grav, $config, $name);
|
||||
}
|
||||
}
|
||||
} elseif (!$locator('theme://')) {
|
||||
} elseif (!$locator('theme://') && !defined('GRAV_CLI')) {
|
||||
exit("Theme '$name' does not exist, unable to display page.");
|
||||
}
|
||||
|
||||
@@ -162,7 +162,8 @@ class Themes extends Iterator
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function configure() {
|
||||
public function configure()
|
||||
{
|
||||
$name = $this->current();
|
||||
$config = $this->config;
|
||||
|
||||
@@ -204,7 +205,7 @@ class Themes extends Iterator
|
||||
|
||||
protected function loadConfiguration($name, Config $config)
|
||||
{
|
||||
$themeConfig = CompiledYaml::instance("themes://{$name}/{$name}" . YAML_EXT)->content();
|
||||
$themeConfig = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT)->content();
|
||||
|
||||
$config->merge(['themes' => [$name => $themeConfig]]);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,10 @@ class Uri
|
||||
{
|
||||
|
||||
$base = 'http://';
|
||||
$uri = $_SERVER['REQUEST_URI'];
|
||||
$name = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost';
|
||||
$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : 80;
|
||||
$uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
|
||||
|
||||
$root_path = rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/');
|
||||
|
||||
|
||||
@@ -36,15 +39,15 @@ class Uri
|
||||
$base = (@$_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
|
||||
}
|
||||
|
||||
$base .= $_SERVER['SERVER_NAME'];
|
||||
$base .= $name;
|
||||
|
||||
if ($_SERVER['SERVER_PORT'] != '80' && $_SERVER['SERVER_PORT'] != '443') {
|
||||
$base .= ":".$_SERVER['SERVER_PORT'];
|
||||
if ($port != '80' && $port != '443') {
|
||||
$base .= ":".$port;
|
||||
}
|
||||
|
||||
// check if userdir in the path and workaround PHP bug with PHP_SELF
|
||||
if (strpos($_SERVER['REQUEST_URI'], '/~') !== false && strpos($_SERVER['PHP_SELF'], '/~') === false) {
|
||||
$root_path = substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], '/', 1)) . $root_path;
|
||||
if (strpos($uri, '/~') !== false && strpos($_SERVER['PHP_SELF'], '/~') === false) {
|
||||
$root_path = substr($uri, 0, strpos($uri, '/', 1)) . $root_path;
|
||||
}
|
||||
|
||||
$this->base = $base;
|
||||
@@ -130,7 +133,7 @@ class Uri
|
||||
*/
|
||||
public function route($absolute = false, $domain = false)
|
||||
{
|
||||
return ($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths);
|
||||
return urldecode(($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -25,7 +25,7 @@ class BackupCommand extends Command {
|
||||
|
||||
)
|
||||
->setDescription("Creates a backup of the Grav instance")
|
||||
->setHelp('The <info>backup</info> creates a zipped backup');
|
||||
->setHelp('The <info>backup</info> creates a zipped backup. Optionally can be saved in a different destination.');
|
||||
|
||||
|
||||
$this->source = getcwd();
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -10,8 +10,6 @@ use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
|
||||
class CleanCommand extends Command {
|
||||
|
||||
protected $destination_dir = 'distribution';
|
||||
|
||||
protected $paths_to_remove = [
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/.travis.yml',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/build.xml',
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -31,8 +31,8 @@ class InstallCommand extends Command {
|
||||
'Where to install the required bits (default to current project)'
|
||||
|
||||
)
|
||||
->setDescription("Handles cloning and symlinking for Grav")
|
||||
->setHelp('The <info>install</info> provides clone and symlink installation chores');
|
||||
->setDescription("Installs the dependencies needed by Grav. Optionally can create symbolic links")
|
||||
->setHelp('The <info>install</info> command installs the dependencies needed by Grav. Optionally can create symbolic links');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
@@ -54,8 +54,7 @@ class InstallCommand extends Command {
|
||||
|
||||
if (file_exists($local_config_file)) {
|
||||
$this->local_config = Yaml::parse($local_config_file);
|
||||
$output->writeln('');
|
||||
$output->writeln('read local config from <cyan>' . $local_config_file . '</cyan>');
|
||||
$output->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');
|
||||
}
|
||||
|
||||
// Look for dependencies file in ROOT and USER dir
|
||||
@@ -67,6 +66,10 @@ class InstallCommand extends Command {
|
||||
$output->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');
|
||||
}
|
||||
|
||||
// Updates composer first
|
||||
$output->writeln("\nInstalling vendor dependencies");
|
||||
$output->writeln(system('php bin/composer.phar --working-dir="'.$this->destination.'" --no-interaction update'));
|
||||
|
||||
// If yaml config, process
|
||||
if ($this->config) {
|
||||
if (!$input->getOption('symlink')) {
|
||||
@@ -114,7 +117,7 @@ class InstallCommand extends Command {
|
||||
if (!$this->local_config) {
|
||||
$output->writeln('<red>No local configuration available, aborting...</red>');
|
||||
$output->writeln('');
|
||||
exit;
|
||||
return;
|
||||
}
|
||||
|
||||
exec('cd ' . $this->destination);
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
@@ -26,18 +26,18 @@ class NewProjectCommand extends Command {
|
||||
InputOption::VALUE_NONE,
|
||||
'Symlink the required bits'
|
||||
)
|
||||
->setDescription("Creates a new Grav project with all the dependencies included")
|
||||
->setHelp('The <info>new</info> command provides clone and symlink installation chores');
|
||||
->setDescription("Creates a new Grav project with all the dependencies installed")
|
||||
->setHelp("The <info>new-project</info> command is a combination of the `setup` and `install` commands.\nCreates a new Grav instance and performs the installation of all the required dependencies.");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
|
||||
$setupCommand = $this->getApplication()->find('setup');
|
||||
$sandboxCommand = $this->getApplication()->find('sandbox');
|
||||
$installCommand = $this->getApplication()->find('install');
|
||||
|
||||
$setupArguments = new ArrayInput(array(
|
||||
'command' => 'setup',
|
||||
$sandboxArguments = new ArrayInput(array(
|
||||
'command' => 'sandbox',
|
||||
'destination' => $input->getArgument('destination'),
|
||||
'-s' => $input->getOption('symlink')
|
||||
));
|
||||
@@ -48,7 +48,7 @@ class NewProjectCommand extends Command {
|
||||
'-s' => $input->getOption('symlink')
|
||||
));
|
||||
|
||||
$setupCommand->run($setupArguments, $output);
|
||||
$sandboxCommand->run($sandboxArguments, $output);
|
||||
$installCommand->run($installArguments, $output);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
namespace Grav\Console\Cli;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -8,7 +8,7 @@ use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
|
||||
class SetupCommand extends Command
|
||||
class SandboxCommand extends Command
|
||||
{
|
||||
protected $directories = array('/cache',
|
||||
'/logs',
|
||||
@@ -42,8 +42,8 @@ class SetupCommand extends Command
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('setup')
|
||||
->setDescription('Setup of a base Grav system in your webroot')
|
||||
->setName('sandbox')
|
||||
->setDescription('Setup of a base Grav system in your webroot, good for development, playing around or starting fresh')
|
||||
->addArgument(
|
||||
'destination',
|
||||
InputArgument::REQUIRED,
|
||||
@@ -55,10 +55,7 @@ class SetupCommand extends Command
|
||||
InputOption::VALUE_NONE,
|
||||
'Symlink the base grav system'
|
||||
)
|
||||
->setHelp(<<<EOT
|
||||
The <info>setup</info> command help create a development environment that uses symbolic links to link the core of grav to the git cloned repository
|
||||
EOT
|
||||
);
|
||||
->setHelp("The <info>sandbox</info> command help create a development environment that can optionally use symbolic links to link the core of grav to the git cloned repository.\nGood for development, playing around or starting fresh");
|
||||
$this->source = getcwd();
|
||||
}
|
||||
|
||||
64
system/src/Grav/Console/ConsoleTrait.php
Normal file
64
system/src/Grav/Console/ConsoleTrait.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
|
||||
use Grav\Common\GravTrait;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
|
||||
trait ConsoleTrait
|
||||
{
|
||||
use GravTrait;
|
||||
|
||||
protected $argv;
|
||||
protected $input;
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* Set colors style definition for the formatter.
|
||||
*/
|
||||
public function setupConsole($input, $output)
|
||||
{
|
||||
if (self::$grav) {
|
||||
self::$grav['config']->set('system.cache.driver', 'default');
|
||||
}
|
||||
|
||||
$this->argv = $_SERVER['argv'][0];
|
||||
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
|
||||
$this->output->getFormatter()->setStyle('normal', new OutputFormatterStyle('white'));
|
||||
$this->output->getFormatter()->setStyle('yellow', new OutputFormatterStyle('yellow', null, array('bold')));
|
||||
$this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold')));
|
||||
$this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan', null, array('bold')));
|
||||
$this->output->getFormatter()->setStyle('green', new OutputFormatterStyle('green', null, array('bold')));
|
||||
$this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta', null, array('bold')));
|
||||
$this->output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
|
||||
}
|
||||
|
||||
private function isGravInstance($path)
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln("<red>ERROR</red>: Destination doesn't exist:");
|
||||
$this->output->writeln(" <white>$path</white>");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln("<red>ERROR</red>: Destination chosen to install is not a directory:");
|
||||
$this->output->writeln(" <white>$path</white>");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!file_exists($path . DS . 'index.php') || !file_exists($path . DS . '.dependencies') || !file_exists($path . DS . 'system' . DS . 'config' . DS . 'system.yaml')) {
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln("<red>ERROR</red>: Destination chosen to install does not appear to be a Grav instance:");
|
||||
$this->output->writeln(" <white>$path</white>");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
system/src/Grav/Console/Gpm/IndexCommand.php
Normal file
93
system/src/Grav/Console/Gpm/IndexCommand.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace Grav\Console\Gpm;
|
||||
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class IndexCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
protected $data;
|
||||
protected $gpm;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("index")
|
||||
->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
'Force re-fetching the data from remote'
|
||||
)
|
||||
->setDescription("Lists the plugins and themes available for installation")
|
||||
->setHelp('The <info>index</info> command lists the plugins and themes available for installation');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
|
||||
$this->gpm = new GPM($this->input->getOption('force'));
|
||||
|
||||
$this->data = $this->gpm->getRepository();
|
||||
|
||||
$this->output->writeln('');
|
||||
|
||||
foreach ($this->data as $type => $packages) {
|
||||
$this->output->writeln("<green>" . ucfirst($type) . "</green> [ " . count($packages) . " ]");
|
||||
|
||||
$index = 0;
|
||||
foreach ($packages as $slug => $package) {
|
||||
$this->output->writeln(
|
||||
// index
|
||||
str_pad($index+++1, 2, '0', STR_PAD_LEFT) . ". " .
|
||||
// package name
|
||||
"<cyan>" . str_pad($package->name, 15) . "</cyan> " .
|
||||
// slug
|
||||
"[" . str_pad($slug, 15, ' ', STR_PAD_BOTH) . "] " .
|
||||
// version details
|
||||
$this->versionDetails($package)
|
||||
);
|
||||
}
|
||||
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
$this->output->writeln('You can either get more informations about a package by typing:');
|
||||
$this->output->writeln(' <green>' . $this->argv . ' info <cyan><package></cyan></green>');
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('Or you can install a package by typing:');
|
||||
$this->output->writeln(' <green>' . $this->argv . ' install <cyan><package></cyan></green>');
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
private function versionDetails($package)
|
||||
{
|
||||
$list = $this->gpm->{'getUpdatable' . ucfirst($package->package_type)}();
|
||||
$package = isset($list[$package->slug]) ? $list[$package->slug] : $package;
|
||||
$type = ucfirst(preg_replace("/s$/", '', $package->package_type));
|
||||
$updatable = $this->gpm->{'is' . $type . 'Updatable'}($package->slug);
|
||||
$installed = $this->gpm->{'is' . $type . 'Installed'}($package->slug);
|
||||
$local = $this->gpm->{'getInstalled' . $type}($package->slug);
|
||||
|
||||
if (!$installed || !$updatable) {
|
||||
$version = $installed ? $local->version : $package->version;
|
||||
$installed = !$installed ? ' (<magenta>not installed</magenta>)' : ' (<cyan>installed</cyan>)';
|
||||
|
||||
return str_pad(" [v<green>" . $version . "</green>]", 35) . $installed;
|
||||
}
|
||||
|
||||
if ($updatable) {
|
||||
$installed = !$installed ? ' (<magenta>not installed</magenta>)' : ' (<cyan>installed</cyan>)';
|
||||
|
||||
return str_pad(" [v<red>" . $package->version . "</red> <cyan>➜</cyan> v<green>" . $package->available . "</green>]", 61) . $installed;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
93
system/src/Grav/Console/Gpm/InfoCommand.php
Normal file
93
system/src/Grav/Console/Gpm/InfoCommand.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace Grav\Console\Gpm;
|
||||
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class InfoCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
protected $data;
|
||||
protected $gpm;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("info")
|
||||
->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
'Force fetching the new data remotely'
|
||||
)
|
||||
->addArgument(
|
||||
'package',
|
||||
InputArgument::REQUIRED,
|
||||
'The package of which more informations are desired. Use the "index" command for a list of packages'
|
||||
)
|
||||
->setDescription("Shows more informations about a package")
|
||||
->setHelp('The <info>info</info> shows more informations about a package');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
$this->gpm = new GPM($this->input->getOption('force'));
|
||||
|
||||
$foundPackage = $this->gpm->findPackage($input->getArgument('package'));
|
||||
|
||||
if (!$foundPackage) {
|
||||
$this->output->writeln("The package <cyan>'" . $input->getArgument('package') . "'</cyan> was not found in the Grav repository.");
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln("You can list all the available packages by typing:");
|
||||
$this->output->writeln(" <green>" . $this->argv . " index</green>");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->writeln("Found package <cyan>'" . $input->getArgument('package') . "'</cyan> under the '<green>" . ucfirst($foundPackage->package_type) . "</green>' section");
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln("<cyan>" . $foundPackage->name . "</cyan> [" . $foundPackage->slug . "]");
|
||||
$this->output->writeln(str_repeat('-', strlen($foundPackage->name) + strlen($foundPackage->slug) + 3));
|
||||
$this->output->writeln("<white>" . strip_tags($foundPackage->description_plain) . "</white>");
|
||||
$this->output->writeln('');
|
||||
|
||||
$packageURL = '';
|
||||
if (isset($foundPackage->author->url)) {
|
||||
$packageURL = '<' . $foundPackage->author->url . '>';
|
||||
}
|
||||
|
||||
$this->output->writeln("<green>".str_pad("Author", 12).":</green> " . $foundPackage->author->name . ' <' . $foundPackage->author->email . '> '.$packageURL);
|
||||
|
||||
foreach (array('version', 'keywords', 'date', 'homepage', 'demo', 'docs', 'guide', 'repository', 'bugs', 'zipball_url', 'license') as $info) {
|
||||
if (isset($foundPackage->$info)) {
|
||||
$name = ucfirst($info);
|
||||
$data = $foundPackage->$info;
|
||||
|
||||
if ($info == 'zipball_url') {
|
||||
$name = "Download";
|
||||
}
|
||||
|
||||
if ($info == 'date') {
|
||||
$name = "Last Update";
|
||||
$data = date('D, j M Y, H:i:s, P ', strtotime('2014-09-16T00:07:16Z'));
|
||||
}
|
||||
|
||||
$name = str_pad($name, 12);
|
||||
$this->output->writeln("<green>".$name.":</green> " . $data);
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln("You can install this package by typing:");
|
||||
$this->output->writeln(" <green>" . $this->argv . " install</green> <cyan>" . $foundPackage->slug . "</cyan>");
|
||||
$this->output->writeln('');
|
||||
|
||||
}
|
||||
}
|
||||
212
system/src/Grav/Console/Gpm/InstallCommand.php
Normal file
212
system/src/Grav/Console/Gpm/InstallCommand.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
namespace Grav\Console\Gpm;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
protected $data;
|
||||
protected $gpm;
|
||||
protected $destination;
|
||||
protected $file;
|
||||
protected $tmp;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("install")
|
||||
->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
'Force re-fetching the data from remote'
|
||||
)
|
||||
->addOption(
|
||||
'all-yes',
|
||||
'y',
|
||||
InputOption::VALUE_NONE,
|
||||
'Assumes yes (or best approach) instead of prompting'
|
||||
)
|
||||
->addOption(
|
||||
'destination',
|
||||
'd',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The destination where the package should be installed at. By default this would be where the grav instance has been launched from',
|
||||
GRAV_ROOT
|
||||
)
|
||||
->addArgument(
|
||||
'package',
|
||||
InputArgument::IS_ARRAY|InputArgument::REQUIRED,
|
||||
'The package of which more informations are desired. Use the "index" command for a list of packages'
|
||||
)
|
||||
->setDescription("Performs the installation of plugins and themes")
|
||||
->setHelp('The <info>install</info> command allows to install plugins and themes');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
|
||||
$this->gpm = new GPM($this->input->getOption('force'));
|
||||
$this->destination = realpath($this->input->getOption('destination'));
|
||||
|
||||
$packages = array_map('strtolower', $this->input->getArgument('package'));
|
||||
$this->data = $this->gpm->findPackages($packages);
|
||||
|
||||
if (
|
||||
!Installer::isGravInstance($this->destination) ||
|
||||
!Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
|
||||
) {
|
||||
$this->output->writeln("<red>ERROR</red>: " . Installer::lastErrorMsg());
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->writeln('');
|
||||
|
||||
if (!$this->data['total']) {
|
||||
$this->output->writeln("Nothing to install.");
|
||||
$this->output->writeln('');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (count($this->data['not_found'])) {
|
||||
$this->output->writeln("These packages were not found on Grav: <red>" . implode('</red>, <red>', $this->data['not_found']) . "</red>");
|
||||
}
|
||||
|
||||
unset($this->data['not_found']);
|
||||
unset($this->data['total']);
|
||||
|
||||
foreach ($this->data as $data) {
|
||||
foreach ($data as $package) {
|
||||
$this->output->writeln("Preparing to install <cyan>" . $package->name . "</cyan> [v" . $package->version . "]");
|
||||
|
||||
$this->output->write(" |- Downloading package... 0%");
|
||||
$this->file = $this->downloadPackage($package);
|
||||
|
||||
$this->output->write(" |- Checking destination... ");
|
||||
$checks = $this->checkDestination($package);
|
||||
|
||||
if (!$checks) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->write(" |- Installing package... ");
|
||||
$installation = $this->installPackage($package);
|
||||
if (!$installation) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->writeln(" '- <green>Success!</green> ");
|
||||
$this->output->writeln('');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function downloadPackage($package)
|
||||
{
|
||||
$this->tmp = sys_get_temp_dir() . DS . 'Grav-' . uniqid();
|
||||
$filename = $package->slug . basename($package->zipball_url);
|
||||
$output = Response::get($package->zipball_url, [], [$this, 'progress']);
|
||||
|
||||
Folder::mkdir($this->tmp);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Downloading package... 100%");
|
||||
$this->output->writeln('');
|
||||
|
||||
file_put_contents($this->tmp . DS . $filename, $output);
|
||||
|
||||
return $this->tmp . DS . $filename;
|
||||
}
|
||||
|
||||
private function checkDestination($package)
|
||||
{
|
||||
$questionHelper = $this->getHelper('question');
|
||||
$skipPrompt = $this->input->getOption('all-yes');
|
||||
|
||||
Installer::isValidDestination($this->destination . DS . $package->install_path);
|
||||
|
||||
if (Installer::lastErrorCode() == Installer::EXISTS) {
|
||||
if (!$skipPrompt) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>exists</yellow>");
|
||||
|
||||
$question = new ConfirmationQuestion(" | '- The package has been detected as installed already, do you want to overwrite it? [y|N] ", false);
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln(" | '- <red>You decided to not overwrite the already installed package.</red>");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Installer::lastErrorCode() == Installer::IS_LINK) {
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
|
||||
|
||||
if ($skipPrompt) {
|
||||
$this->output->writeln(" | '- <yellow>Skipped automatically.</yellow>");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$question = new ConfirmationQuestion(" | '- Destination has been detected as symlink, delete symbolic link first? [y|N] ", false);
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln(" | '- <red>You decided to not delete the symlink automatically.</red>");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->writeln(" |- Checking destination... <green>ok</green>");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function installPackage($package)
|
||||
{
|
||||
$installer = Installer::install($this->file, $this->destination, ['install_path' => $package->install_path]);
|
||||
$errorCode = Installer::lastErrorCode();
|
||||
Folder::delete($this->tmp);
|
||||
|
||||
if ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)) {
|
||||
$this->output->write("\x0D");
|
||||
// extra white spaces to clear out the buffer properly
|
||||
$this->output->writeln(" |- Installing package... <red>error</red> ");
|
||||
$this->output->writeln(" | '- " . $installer->lastErrorMsg());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->output->write("\x0D");
|
||||
// extra white spaces to clear out the buffer properly
|
||||
$this->output->writeln(" |- Installing package... <green>ok</green> ");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function progress($progress)
|
||||
{
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ", STR_PAD_LEFT) . '%');
|
||||
}
|
||||
}
|
||||
122
system/src/Grav/Console/Gpm/SelfupgradeCommand.php
Normal file
122
system/src/Grav/Console/Gpm/SelfupgradeCommand.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
namespace Grav\Console\Gpm;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\GPM\Upgrader;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Common\GPM\Response;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class SelfupgradeCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
protected $data;
|
||||
protected $extensions;
|
||||
protected $updatable;
|
||||
protected $file;
|
||||
protected $types = array('plugins', 'themes');
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("self-upgrade")
|
||||
->setAliases(['selfupgrade'])
|
||||
->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
'Force re-fetching the data from remote'
|
||||
)
|
||||
->setDescription("Detects and performs an update of plugins and themes when available")
|
||||
->setHelp('The <info>update</info> command updates plugins and themes when a new version is available');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
$this->upgrader = new Upgrader($this->input->getOption('force'));
|
||||
|
||||
$local = $this->upgrader->getLocalVersion();
|
||||
$remote = $this->upgrader->getRemoteVersion();
|
||||
$update = $this->upgrader->getAssets()->{'grav-update'};
|
||||
$release = strftime('%c', strtotime($this->upgrader->getReleaseDate()));
|
||||
|
||||
if (!$this->upgrader->isUpgradable()) {
|
||||
$this->output->writeln("You are already running the latest version of Grav (v" . $local . ") released on " . $release);
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->writeln("Preparing to upgrade Grav to v<cyan>" . $remote . "</cyan> [release date: " . $release . "]");
|
||||
|
||||
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($update->size) . "]... 0%");
|
||||
$this->file = $this->download($update);
|
||||
|
||||
$this->output->write(" |- Installing upgrade... ");
|
||||
$installation = $this->upgrade();
|
||||
|
||||
if (!$installation) {
|
||||
$this->output->writeln(" '- <red>Installation failed or aborted.</red>");
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
$this->output->writeln(" '- <green>Success!</green> ");
|
||||
$this->output->writeln('');
|
||||
}
|
||||
}
|
||||
|
||||
private function download($package)
|
||||
{
|
||||
$this->tmp = sys_get_temp_dir() . DS . 'Grav-' . uniqid();
|
||||
$output = Response::get($package->download, [], [$this, 'progress']);
|
||||
|
||||
Folder::mkdir($this->tmp);
|
||||
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($package->size) . "]... 100%");
|
||||
$this->output->writeln('');
|
||||
|
||||
file_put_contents($this->tmp . DS . $package->name, $output);
|
||||
|
||||
return $this->tmp . DS . $package->name;
|
||||
}
|
||||
|
||||
private function upgrade()
|
||||
{
|
||||
$installer = Installer::install($this->file, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true]);
|
||||
$errorCode = Installer::lastErrorCode();
|
||||
Folder::delete($this->tmp);
|
||||
|
||||
if ($errorCode & (Installer::ZIP_OPEN_ERROR | Installer::ZIP_EXTRACT_ERROR)) {
|
||||
$this->output->write("\x0D");
|
||||
// extra white spaces to clear out the buffer properly
|
||||
$this->output->writeln(" |- Installing upgrade... <red>error</red> ");
|
||||
$this->output->writeln(" | '- " . $installer->lastErrorMsg());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->output->write("\x0D");
|
||||
// extra white spaces to clear out the buffer properly
|
||||
$this->output->writeln(" |- Installing upgrade... <green>ok</green> ");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function progress($progress)
|
||||
{
|
||||
$this->output->write("\x0D");
|
||||
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($progress["filesize"]) . "]... " . str_pad($progress['percent'], 5, " ", STR_PAD_LEFT) . '%');
|
||||
}
|
||||
|
||||
public function formatBytes($size, $precision = 2)
|
||||
{
|
||||
$base = log($size) / log(1024);
|
||||
$suffixes = array('', 'k', 'M', 'G', 'T');
|
||||
|
||||
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
|
||||
}
|
||||
}
|
||||
173
system/src/Grav/Console/Gpm/UpdateCommand.php
Normal file
173
system/src/Grav/Console/Gpm/UpdateCommand.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
namespace Grav\Console\Gpm;
|
||||
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Console\ConsoleTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
class UpdateCommand extends Command
|
||||
{
|
||||
use ConsoleTrait;
|
||||
|
||||
protected $data;
|
||||
protected $extensions;
|
||||
protected $updatable;
|
||||
protected $destination;
|
||||
protected $file;
|
||||
protected $types = array('plugins', 'themes');
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName("update")
|
||||
->addOption(
|
||||
'force',
|
||||
'f',
|
||||
InputOption::VALUE_NONE,
|
||||
'Force re-fetching the data from remote'
|
||||
)
|
||||
->addOption(
|
||||
'destination',
|
||||
'd',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The grav instance location where the updates should be applied to. By default this would be where the grav cli has been launched from',
|
||||
GRAV_ROOT
|
||||
)
|
||||
->addArgument(
|
||||
'package',
|
||||
InputArgument::IS_ARRAY|InputArgument::OPTIONAL,
|
||||
'The package or packages that is desired to update. By default all available updates will be applied.'
|
||||
)
|
||||
->setDescription("Detects and performs an update of plugins and themes when available")
|
||||
->setHelp('The <info>update</info> command updates plugins and themes when a new version is available');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setupConsole($input, $output);
|
||||
|
||||
$this->gpm = new GPM($this->input->getOption('force'));
|
||||
$this->destination = realpath($this->input->getOption('destination'));
|
||||
|
||||
if (!Installer::isGravInstance($this->destination)) {
|
||||
$this->output->writeln("<red>ERROR</red>: " . Installer::lastErrorMsg());
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->data = $this->gpm->getUpdatable();
|
||||
$onlyPackages = array_map('strtolower', $this->input->getArgument('package'));
|
||||
|
||||
if (!$this->data['total']) {
|
||||
$this->output->writeln("Nothing to update.");
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->output->write("Found <green>" . $this->gpm->countInstalled() . "</green> extensions installed of which <magenta>" . $this->data['total'] . "</magenta> need updating");
|
||||
|
||||
$limitTo = $this->userInputPackages($onlyPackages);
|
||||
|
||||
$this->output->writeln('');
|
||||
|
||||
unset($this->data['total']);
|
||||
unset($limitTo['total']);
|
||||
|
||||
|
||||
// updates review
|
||||
$slugs = [];
|
||||
|
||||
$index = 0;
|
||||
foreach ($this->data as $packages) {
|
||||
foreach ($packages as $slug => $package) {
|
||||
if (count($limitTo) && !array_key_exists($slug, $limitTo)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->output->writeln(
|
||||
// index
|
||||
str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " .
|
||||
// name
|
||||
"<cyan>" . str_pad($package->name, 15) . "</cyan> " .
|
||||
// version
|
||||
"[v<magenta>" . $package->version . "</magenta> ➜ v<green>" . $package->available . "</green>]"
|
||||
);
|
||||
$slugs[] = $slug;
|
||||
}
|
||||
}
|
||||
|
||||
// prompt to continue
|
||||
$this->output->writeln("");
|
||||
$questionHelper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion("Continue with the update process? [Y|n] ", true);
|
||||
$answer = $questionHelper->ask($this->input, $this->output, $question);
|
||||
|
||||
if (!$answer) {
|
||||
$this->output->writeln("Update aborted. Exiting...");
|
||||
exit;
|
||||
}
|
||||
|
||||
// finally update
|
||||
$installCommand = $this->getApplication()->find('install');
|
||||
|
||||
$args = new ArrayInput(array(
|
||||
'command' => 'install',
|
||||
'package' => $slugs,
|
||||
'-f' => $this->input->getOption('force'),
|
||||
'-d' => $this->destination,
|
||||
'-y' => true
|
||||
));
|
||||
$commandExec = $installCommand->run($args, $this->output);
|
||||
|
||||
if ($commandExec != 0) {
|
||||
$this->output->writeln("<red>Error:</red> An error occured while trying to install the extensions");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
private function userInputPackages($onlyPackages)
|
||||
{
|
||||
$found = ['total' => 0];
|
||||
$ignore = [];
|
||||
|
||||
if (!count($onlyPackages)) {
|
||||
$this->output->writeln('');
|
||||
} else {
|
||||
foreach ($onlyPackages as $onlyPackage) {
|
||||
$find = $this->gpm->findPackage($onlyPackage);
|
||||
|
||||
if (!$find || !$this->gpm->isUpdatable($find->slug)) {
|
||||
$name = isset($find->slug) ? $find->slug : $onlyPackage;
|
||||
$ignore[$name] = $name;
|
||||
} else {
|
||||
$found[$find->slug] = $find;
|
||||
$found['total']++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found['total']) {
|
||||
$list = $found;
|
||||
unset($list['total']);
|
||||
$list = array_keys($list);
|
||||
|
||||
if ($found['total'] !== $this->data['total']) {
|
||||
$this->output->write(", only <magenta>".$found['total']."</magenta> will be updated");
|
||||
}
|
||||
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln("Limiting updates for only <cyan>".implode('</cyan>, <cyan>', $list)."</cyan>");
|
||||
}
|
||||
|
||||
if (count($ignore)) {
|
||||
$this->output->writeln("Packages not found or not requiring updates: <red>".implode('</red>, <red>', $ignore)."</red>");
|
||||
}
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user