Merge branch 'develop' of https://github.com/getgrav/grav into develop

This commit is contained in:
Matias Griese
2014-10-07 11:02:18 +03:00
45 changed files with 2282 additions and 118 deletions

2
.gitignore vendored
View File

@@ -1,7 +1,7 @@
# Composer
composer.lock
.composer
vendor
vendor/
# Sass
.sass-cache

View File

@@ -1 +0,0 @@
0.9.2

Binary file not shown.

42
bin/gpm Executable file
View 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();

View File

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

View File

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

View File

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

View File

@@ -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(), '/');
}
}

View File

@@ -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(), '/');
}
}

View File

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

View File

@@ -3,7 +3,7 @@ namespace Grav\Common\File;
use RocketTheme\Toolbox\File\YamlFile;
class CompiledYaml extends YamlFile
class CompiledYamlFile extends YamlFile
{
use CompiledFile;
}

View File

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

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

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

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

View 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();
}
}

View 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]);
}
}

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

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

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

View 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(), '<');
}
}

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

View 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]);
}
}

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

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

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

View 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(), "<");
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<?php
namespace Grav\Console;
namespace Grav\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;

View File

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

View File

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

View File

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

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

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

View 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('');
}
}

View 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) . '%');
}
}

View 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)];
}
}

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