Merge branch 'release/0.9.4'

This commit is contained in:
Andy Miller
2014-10-31 18:12:28 -06:00
51 changed files with 1986 additions and 649 deletions

0
.gitignore vendored Executable file → Normal file
View File

55
.htaccess Executable file → Normal file
View File

@@ -2,7 +2,24 @@
RewriteEngine On
##
## Begin - Exploits
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
# Block out any script trying to set a PHP GLOBALS variable via URL.
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
# Block out any script trying to modify a _REQUEST variable via URL.
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
# Return 403 Forbidden header and show the content of the root homepage
RewriteRule .* index.php [F]
#
## End - Exploits
## Begin RewriteBase
# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry
# You should change the '/' to your appropriate subfolder. For example if you have
# your Grav install at the root of your site '/' should work, else it might be something
@@ -11,29 +28,29 @@ RewriteEngine On
# RewriteBase /
# Access site
## End - RewriteBase
## Begin - Index
# If the requested path and file is not /index.php and the request
# has not already been internally rewritten to the index.php script
RewriteCond %{REQUEST_URI} !^/index\.php
# and the requested path and file doesn't directly match a physical file
RewriteCond %{REQUEST_FILENAME} !-f
# and the requested path and file doesn't directly match a physical folder
RewriteCond %{REQUEST_FILENAME} !-d
# internally rewrite the request to the index.php script
RewriteRule .* index.php [L]
## End - Index
# Block various user files from being accessed directly
RewriteRule ^user/accounts/(.*)$ error [R=301,L]
RewriteRule ^user/config/(.*)$ error [R=301,L]
RewriteRule ^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ error [R=301,L]
# Block cache/
RewriteRule ^cache/(.*) error [R=301,L]
# Block bin/
RewriteRule ^bin/(.*)$ error [R=301,L]
# Block system/
RewriteRule ^system/(.*)$ error [R=301,L]
# Block vendor/
RewriteRule ^vendor/(.*)$ error [R=301,L]
## Begin - Security
# Block all direct access for these folders
RewriteRule ^(cache|bin|logs)/(.*) error [L]
# Block access to specific file types for these folders
RewriteRule ^(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$ error [L]
## End - Security
</IfModule>
# Prevent file browsing
# Begin - Prevent Browsing
Options -Indexes
# End - Prevent Browsing

View File

@@ -1,3 +1,28 @@
# v0.9.4 beta
## 10/29/2014
1. [](#new)
* New improved Debugbar with messages, timing, config, twig information
* New exception handling system utilizing Whoops
* New logging system utilizing Monolog
* Support for auto-detecting environment configuration
* New version command for CLI
* Integrate Twig dump() calls into Debugbar
2. [](#improved)
* Selfupgrade now clears cache on successful upgrade
* Selfupgrade now supports files without extensions
* Improved error messages when plugin is missing
* Improved security in .htaccess
* Support CSS/JS/Image assets in vendor/system folders via .htaccess
* Add support for system timers
* Improved and optimized configuration loading
* Automatically disable Debugbar on non-HTML pages
* Disable Debugbar by default
3. [](#bugfix)
* More YAML blueprint fixes
* Fix potential double // in assets
* Load debugger as early as possible
# v0.9.3 beta
## 10/09/2014

View File

@@ -28,12 +28,14 @@ if (!file_exists(ROOT_DIR . 'index.php')) {
$grav = Grav::instance(array('loader' => $autoload));
$grav['config']->init();
$grav['streams'];
$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\VersionCommand(),
new \Grav\Console\Gpm\InfoCommand(),
new \Grav\Console\Gpm\InstallCommand(),
new \Grav\Console\Gpm\UpdateCommand(),

View File

@@ -13,7 +13,9 @@
"symfony/console": "~2.5",
"symfony/event-dispatcher": "~2.5",
"doctrine/cache": "~1.3",
"tracy/tracy": "2.3.*@dev",
"maximebf/debugbar": "dev-master",
"filp/whoops": "1.2.*@dev",
"monolog/monolog": "~1.1",
"gregwar/image": "~2.0",
"ircmaxell/password-compat": "1.0.*",
"mrclay/minify": "dev-master",

56
htaccess.txt Normal file
View File

@@ -0,0 +1,56 @@
<IfModule mod_rewrite.c>
RewriteEngine On
## Begin - Exploits
# If you experience problems on your site block out the operations listed below
# This attempts to block the most common type of exploit `attempts` to Grav
#
# Block out any script trying to base64_encode data within the URL.
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR]
# Block out any script that includes a <script> tag in URL.
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
# Block out any script trying to set a PHP GLOBALS variable via URL.
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
# Block out any script trying to modify a _REQUEST variable via URL.
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
# Return 403 Forbidden header and show the content of the root homepage
RewriteRule .* index.php [F]
#
## End - Exploits
## Begin RewriteBase
# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry
# You should change the '/' to your appropriate subfolder. For example if you have
# your Grav install at the root of your site '/' should work, else it might be something
# along the lines of: RewriteBase /<your_sub_folder>
##
# RewriteBase /
## End - RewriteBase
## Begin - Index
# If the requested path and file is not /index.php and the request
# has not already been internally rewritten to the index.php script
RewriteCond %{REQUEST_URI} !^/index\.php
# and the requested path and file doesn't directly match a physical file
RewriteCond %{REQUEST_FILENAME} !-f
# and the requested path and file doesn't directly match a physical folder
RewriteCond %{REQUEST_FILENAME} !-d
# internally rewrite the request to the index.php script
RewriteRule .* index.php [L]
## End - Index
## Begin - Security
# Block all direct access for these folders
RewriteRule ^(cache|bin|logs)/(.*) error [L]
# Block access to specific file types for these folders
RewriteRule ^(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$ error [L]
## End - Security
</IfModule>
# Begin - Prevent Browsing
Options -Indexes
# End - Prevent Browsing

View File

@@ -11,7 +11,6 @@ if (!is_file($autoload)) {
}
use Grav\Common\Grav;
use Grav\Common\Debugger;
// Register the auto-loader.
$loader = require_once $autoload;
@@ -22,13 +21,11 @@ if (!ini_get('date.timezone')) {
$grav = Grav::instance(
array(
'loader' => $loader,
'debugger' => new Debugger(Debugger::PRODUCTION)
'loader' => $loader
)
);
try {
$grav['debugger']->init();
$grav->process();
} catch (\Exception $e) {

0
nginx.conf Executable file → Normal file
View File

View File

@@ -0,0 +1,54 @@
div.phpdebugbar {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.phpdebugbar pre {
padding: 1rem;
}
.phpdebugbar div.phpdebugbar-header > div > * {
padding: 5px 15px;
}
.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * {
padding: 5px 8px;
}
.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn {
background-image: url(grav.png);
}
.phpdebugbar a.phpdebugbar-restore-btn {
width: 13px;
}
.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active {
background: #3DB9EC;
color: #fff;
margin-top: -1px;
padding-top: 6px;
}
.phpdebugbar .phpdebugbar-widgets-toolbar {
padding-left: 5px;
}
.phpdebugbar input[type=text] {
padding: 0;
display: inline;
}
.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
font-size: 12px;
}
ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label {
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
top: 0;
}
.phpdebugbar pre, .phpdebugbar code {
margin: 0;
font-size: 14px;
}

BIN
system/assets/grav.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

110
system/assets/whoops.css Normal file
View File

@@ -0,0 +1,110 @@
body {
background-color: #eee;
}
body header {
background: #349886;
border-left: 8px solid #29796B;
}
body .clipboard {
width: 28px;
height: 28px;
background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcAQMAAABIw03XAAAAA3NCSVQICAjb4U/gAAAABlBMVEX///////9VfPVsAAAAAnRSTlP/AOW3MEoAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAAFnRFWHRDcmVhdGlvbiBUaW1lADEwLzE1LzE0xr/LJAAAADhJREFUCJlj+P///wcGBPGDQR5E8OMi2IEEczOIaAQRHSCioQBGHAAR/7AT/z+DiA8MMALVXhABAJf9Sr5aY+UFAAAAAElFTkSuQmCC);
}
body .exc-title-primary {
color: #1C3631;
text-shadow: none;
}
body .exc-title {
color: #2F5B52;
text-shadow: none;
}
body .data-table-container label {
color: #0082BA;
}
body .frame {
border: 0;
}
body .frames-container {
overflow-y: auto;
overflow-x: hidden;
}
body .active .frame-class {
color: #E3D8E9;
}
body .frame-class {
color: #9055AF;
}
body .frame.active {
border: 0;
box-shadow: none;
background-color: #9055AF;
}
body .frame:not(.active):hover {
background: #e9e9e9;
}
body .frame-file, body .data-table tbody {
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
font-size: 13px;
}
body .frame-code {
background: #305669;
border-left: 8px solid #253A47;
padding: 1rem;
}
body .frame-code .frame-file {
background: #253A47;
color: #eee;
text-shadow: none;
box-shadow: none;
font-family: inherit;
}
body .frame-code .frame-file strong {
color: #fff;
font-weight: normal;
}
body .frame-comments {
background: #283E4D;
box-shadow: none;
}
body .frame-comments.empty:before {
color: #789AAB;
}
body .details-container {
border: 0;
}
body .details {
background-color: #eee;
border-left: 8px solid #ddd;
padding: 1rem;
}
body .code-block {
background: #2C4454;
box-shadow: none;
font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace;
font-size: 13px;
}
body .handler.active {
background: #666;
}

View File

@@ -9,7 +9,17 @@ form:
fields:
username:
type: unset
type: text
size: large
label: Username
readonly: true
email:
type: text
size: large
label: Email
validate:
required: true
password:
type: password
@@ -18,12 +28,7 @@ form:
validate:
required: true
email:
type: text
size: large
label: Email
validate:
required: true
fullname:
type: text

View File

@@ -18,8 +18,3 @@ schemes:
type: ReadOnlyStream
paths:
- user://accounts
data:
type: ReadOnlyStream
paths:
- user://data

View File

@@ -40,12 +40,12 @@ assets: # Configuration for Assets Manager (JS, C
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
errors:
display: true # Display full backtrace-style error page
log: true # Log errors to /logs folder
debugger:
enabled: false # Enable Grav debugger and following settings
mode: detect # Mode tracy Debugger should be set to when enabled: detect|development|production
strict: false # Throw fatal error also on PHP warnings and notices
max_depth: 10 # How many nested levels to display for objects or arrays
log:
enabled: true # Enable logging
twig: true # Enable debugging of Twig templates
shutdown:
close_connection: true # Close the connection before calling onShutdown(). disable for debugging
close_connection: true # Close the connection before calling onShutdown(). false for debugging

View File

@@ -2,7 +2,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '0.9.3');
define('GRAV_VERSION', '0.9.4');
define('DS', '/');
// Directories and Paths

View File

@@ -249,7 +249,7 @@ class Assets
}
if( !array_key_exists($asset, $this->css)) {
$this->css[$asset] = ['asset'=>$asset, 'priority'=>$priority, 'pipeline'=>$pipeline];
$this->css[$asset] = ['asset'=>$asset, 'priority'=>$priority, 'order' => count($this->css), 'pipeline'=>$pipeline];
}
return $this;
@@ -300,7 +300,8 @@ class Assets
}
if( !array_key_exists($asset, $this->js)) {
$this->js[$asset] = ['asset' => $asset, 'priority' => $priority, 'pipeline' => $pipeline];
$this->js[$asset] = ['asset' => $asset, 'priority' => $priority, 'order' => count($this->js), 'pipeline' => $pipeline];
}
return $this;
@@ -317,8 +318,15 @@ class Assets
if( ! $this->css)
return null;
if (self::$grav)
// Sort array by priorities (larger priority first)
usort($this->css, function ($a, $b) {return $a['priority'] - $b['priority'];});
usort($this->css, function ($a, $b) {
if ($a['priority'] == $b['priority']) {
return $b['order'] - $a['order'];
}
return $a['priority'] - $b['priority'];
});
$this->css = array_reverse($this->css);
$attributes = $this->attributes(array_merge([ 'type' => 'text/css', 'rel' => 'stylesheet' ], $attributes));
@@ -363,7 +371,12 @@ class Assets
return null;
// Sort array by priorities (larger priority first)
usort($this->js, function ($a, $b) {return $a['priority'] - $b['priority'];});
usort($this->js, function ($a, $b) {
if ($a['priority'] == $b['priority']) {
return $b['order'] - $a['order'];
}
return $a['priority'] - $b['priority'];
});
$this->js = array_reverse($this->js);
$attributes = $this->attributes(array_merge([ 'type' => 'text/javascript' ], $attributes));
@@ -649,10 +662,10 @@ class Assets
protected function buildLocalLink($asset)
{
try {
return $this->base_url . self::$grav['locator']->findResource($asset, false);
$asset = self::$grav['locator']->findResource($asset, false);
} catch (\Exception $e) {}
return $this->base_url . $asset;
return $this->base_url . ltrim($asset, '/');
}

View File

@@ -80,10 +80,16 @@ class Config extends Data
protected $blueprintLookup;
protected $pluginLookup;
protected $finder;
protected $environment;
protected $messages = [];
public function __construct(array $items = array(), Grav $grav = null)
public function __construct(array $items = array(), Grav $grav = null, $environment = null)
{
$this->grav = $grav ?: Grav::instance();
$this->finder = new ConfigFinder;
$this->environment = $environment ?: 'localhost';
$this->messages[] = 'Environment Name: ' . $this->environment;
if (isset($items['@class'])) {
if ($items['@class'] != get_class($this)) {
@@ -91,7 +97,7 @@ class Config extends Data
}
// Loading pre-compiled configuration.
$this->timestamp = (int) $items['timestamp'];
$this->checksum = (string) $items['checksum'];
$this->checksum = $items['checksum'];
$this->items = (array) $items['data'];
} else {
// Make sure that
@@ -100,6 +106,9 @@ class Config extends Data
}
$items['streams']['schemes'] += $this->streams;
$items = $this->autoDetectEnvironmentConfig($items);
$this->messages[] = $items['streams']['schemes']['config']['prefixes'][''];
parent::__construct($items);
}
$this->check();
@@ -132,6 +141,13 @@ class Config extends Data
}
}
public function debug()
{
foreach ($this->messages as $message) {
$this->grav['debugger']->addMessage($message);
}
}
public function init()
{
/** @var UniformResourceLocator $locator */
@@ -141,13 +157,15 @@ class Config extends Data
$this->blueprintLookup = $locator->findResources('blueprints://config');
$this->pluginLookup = $locator->findResources('plugins://');
$checksum = $this->checksum();
if ($checksum == $this->checksum) {
if (!isset($this->checksum)) {
$this->messages[] = 'No cached configuration, compiling new configuration..';
} elseif ($this->checksum() != $this->checksum) {
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
} else {
$this->messages[] = 'Configuration checksum matches, using cached version.';
return;
}
$this->checksum = $checksum;
/** @var Uri $uri */
$uri = $this->grav['uri'];
@@ -166,45 +184,60 @@ class Config extends Data
$checkSystem = $this->get('system.cache.check.system', true);
if (!$checkBlueprints && !$checkConfig && !$checkSystem) {
$this->messages[] = 'Skip configuration timestamp check.';
return false;
}
// Generate checksum according to the configuration settings.
if (!$checkConfig) {
$this->messages[] = 'Check configuration timestamps from system.yaml files.';
// Just check changes in system.yaml files and ignore all the other files.
$cc = $checkSystem ? $this->detectFile($this->configLookup, 'system') : [];
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
} else {
$this->messages[] = 'Check configuration timestamps from all configuration files.';
// Check changes in all configuration files.
$cc = $this->getConfigFiles($this->configLookup, $this->pluginLookup);
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
}
if ($checkBlueprints) {
$this->messages[] = 'Check blueprint timestamps from all blueprint files.';
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
} else {
$cb = [];
}
$cb = $checkBlueprints ? $this->getBlueprintFiles($this->blueprintLookup, $this->pluginLookup) : [];
return md5(json_encode([$cc, $cb]));
}
protected function autoDetectEnvironmentConfig($items)
{
$environment = $this->environment;
$env_stream = 'user://'.$environment.'/config';
if (file_exists(USER_DIR.$environment.'/config')) {
array_unshift($items['streams']['schemes']['config']['prefixes'][''], $env_stream);
}
return $items;
}
protected function loadCompiledBlueprints($blueprints, $plugins, $filename = null)
{
$checksum = md5(json_encode($blueprints));
$filename = $filename
? CACHE_DIR . 'compiled/blueprints/' . $filename .'.php'
: CACHE_DIR . 'compiled/blueprints/' . $checksum .'.php';
? CACHE_DIR . 'compiled/blueprints/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
if ($file->exists()) {
$cache = $file->exists() ? $file->content() : null;
} else {
$cache = null;
}
$blueprintFiles = $this->getBlueprintFiles($blueprints, $plugins);
$cache = $file->exists() ? $file->content() : null;
$blueprintFiles = $this->finder->locateBlueprintFiles($blueprints, $plugins);
$checksum .= ':'.md5(json_encode($blueprintFiles));
$class = get_class($this);
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| empty($cache['checksum'])
|| empty($cache['$class'])
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
@@ -213,8 +246,8 @@ class Config extends Data
// Load blueprints.
$this->blueprints = new Blueprints;
foreach ($blueprintFiles as $key => $files) {
$this->loadBlueprints($key);
foreach ($blueprintFiles as $files) {
$this->loadBlueprintFiles($files);
}
$cache = [
@@ -226,6 +259,7 @@ class Config extends Data
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$this->messages[] = 'Saving compiled blueprints.';
$file->save($cache);
$file->unlock();
}
@@ -238,23 +272,19 @@ class Config extends Data
{
$checksum = md5(json_encode($configs));
$filename = $filename
? CACHE_DIR . 'compiled/config/' . $filename .'.php'
: CACHE_DIR . 'compiled/config/' . $checksum .'.php';
? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
if ($file->exists()) {
$cache = $file->exists() ? $file->content() : null;
} else {
$cache = null;
}
$configFiles = $this->getConfigFiles($configs, $plugins);
$cache = $file->exists() ? $file->content() : null;
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
$checksum .= ':'.md5(json_encode($configFiles));
$class = get_class($this);
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
@@ -262,18 +292,19 @@ class Config extends Data
$file->lock(false);
// Load configuration.
foreach ($configFiles as $key => $files) {
$this->loadConfig($key);
foreach ($configFiles as $files) {
$this->loadConfigFiles($files);
}
$cache = [
'@class' => $class,
'timestamp' => time(),
'checksum' => $this->checksum,
'checksum' => $this->checksum(),
'data' => $this->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$this->messages[] = 'Saving compiled configuration.';
$file->save($cache);
$file->unlock();
}
@@ -283,16 +314,12 @@ class Config extends Data
}
/**
* Load global blueprints.
* Load blueprints.
*
* @param string $key
* @param array $files
* @param array $files
*/
public function loadBlueprints($key, array $files = null)
public function loadBlueprintFiles(array $files)
{
if (is_null($files)) {
$files = $this->blueprintFiles[$key];
}
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->blueprints->embed($name, $file->content(), '/');
@@ -300,164 +327,15 @@ class Config extends Data
}
/**
* Load global configuration.
* Load configuration.
*
* @param string $key
* @param array $files
* @param array $files
*/
public function loadConfig($key, array $files = null)
public function loadConfigFiles(array $files)
{
if (is_null($files)) {
$files = $this->configFiles[$key];
}
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->join($name, $file->content(), '/');
}
}
/**
* Get all blueprint files (including plugins).
*
* @param array $blueprints
* @param array $plugins
* @return array
*/
protected function getBlueprintFiles(array $blueprints, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectPlugins($folder, true);
}
foreach (array_reverse($blueprints) as $folder) {
$list += $this->detectConfig($folder, true);
}
return $list;
}
/**
* Get all configuration files.
*
* @param array $configs
* @param array $plugins
* @return array
*/
protected function getConfigFiles(array $configs, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectPlugins($folder);
}
foreach (array_reverse($configs) as $folder) {
$list += $this->detectConfig($folder);
}
return $list;
}
/**
* Detects all plugins with a configuration file and returns last modification time.
*
* @param string $lookup Location to look up from.
* @param bool $blueprints
* @return array
* @internal
*/
protected function detectPlugins($lookup = SYSTEM_DIR, $blueprints = false)
{
$find = $blueprints ? 'blueprints.yaml' : '.yaml';
$location = $blueprints ? 'blueprintFiles' : 'configFiles';
$path = trim(Folder::getRelativePath($lookup), '/');
if (isset($this->{$location}[$path])) {
return [$path => $this->{$location}[$path]];
}
$list = [];
if (is_dir($lookup)) {
$iterator = new \DirectoryIterator($lookup);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir() || $directory->isDot()) {
continue;
}
$name = $directory->getBasename();
$filename = "{$path}/{$name}/" . ($find && $find[0] != '.' ? $find : $name . $find);
if (file_exists($filename)) {
$list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
}
}
}
$this->{$location}[$path] = $list;
return [$path => $list];
}
/**
* Detects all plugins with a configuration file and returns last modification time.
*
* @param string $lookup Location to look up from.
* @param bool $blueprints
* @return array
* @internal
*/
protected function detectConfig($lookup = SYSTEM_DIR, $blueprints = false)
{
$location = $blueprints ? 'blueprintFiles' : 'configFiles';
$path = trim(Folder::getRelativePath($lookup), '/');
if (isset($this->{$location}[$path])) {
return [$path => $this->{$location}[$path]];
}
if (is_dir($lookup)) {
// Find all system and user configuration files.
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|',
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
}],
'key' => 'SubPathname'
];
$list = Folder::all($lookup, $options);
} else {
$list = [];
}
$this->{$location}[$path] = $list;
return [$path => $list];
}
/**
* Detects all instances of the file and returns last modification time.
*
* @param array $lookups Locations to look up from.
* @param string $name
* @return array
* @internal
*/
protected function detectFile(array $lookups, $name)
{
$list = [];
$filename = "{$name}.yaml";
foreach ($lookups as $lookup) {
$path = trim(Folder::getRelativePath($lookup), '/');
if (is_file("{$lookup}/{$filename}")) {
$modified = filemtime("{$lookup}/{$filename}");
} else {
$modified = 0;
}
$list[$path] = [$name => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
}
return $list;
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\Filesystem\Folder;
/**
* The Configuration Finder class.
*
* @author RocketTheme
* @license MIT
*/
class ConfigFinder
{
/**
* Get all locations for blueprint files (including plugins).
*
* @param array $blueprints
* @param array $plugins
* @return array
*/
public function locateBlueprintFiles(array $blueprints, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectInFolder($folder, 'blueprints');
}
foreach (array_reverse($blueprints) as $folder) {
$list += $this->detectRecursive($folder);
}
return $list;
}
/**
* Get all locations for configuration files (including plugins).
*
* @param array $configs
* @param array $plugins
* @return array
*/
public function locateConfigFiles(array $configs, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectInFolder($folder);
}
foreach (array_reverse($configs) as $folder) {
$list += $this->detectRecursive($folder);
}
return $list;
}
/**
* Get all locations for a single configuration file.
*
* @param array $folders Locations to look up from.
* @param string $name Filename to be located.
* @return array
*/
public function locateConfigFile(array $folders, $name)
{
$filename = "{$name}.yaml";
$list = [];
foreach ($folders as $folder) {
$path = trim(Folder::getRelativePath($folder), '/');
if (is_file("{$folder}/{$filename}")) {
$modified = filemtime("{$folder}/{$filename}");
} else {
$modified = 0;
}
$list[$path] = [$name => ['file' => "{$path}/{$filename}", 'modified' => $modified]];
}
return $list;
}
/**
* Detects all plugins with a configuration file and returns them with last modification time.
*
* @param string $folder Location to look up from.
* @param string $lookup Filename to be located.
* @return array
* @internal
*/
protected function detectInFolder($folder, $lookup = null)
{
$path = trim(Folder::getRelativePath($folder), '/');
$list = [];
if (is_dir($folder)) {
$iterator = new \DirectoryIterator($folder);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir() || $directory->isDot()) {
continue;
}
$name = $directory->getBasename();
$find = ($lookup ?: $name) . '.yaml';
$filename = "{$path}/{$name}/$find";
if (file_exists($filename)) {
$list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)];
}
}
}
return [$path => $list];
}
/**
* Detects all plugins with a configuration file and returns them with last modification time.
*
* @param string $folder Location to look up from.
* @return array
* @internal
*/
protected function detectRecursive($folder)
{
$path = trim(Folder::getRelativePath($folder), '/');
if (is_dir($folder)) {
// Find all system and user configuration files.
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|',
'value' => function (\RecursiveDirectoryIterator $file) use ($path) {
return ['file' => "{$path}/{$file->getSubPathname()}", 'modified' => $file->getMTime()];
}
],
'key' => 'SubPathname'
];
$list = Folder::all($folder, $options);
} else {
$list = [];
}
return [$path => $list];
}
}

View File

@@ -1,7 +1,10 @@
<?php
namespace Grav\Common;
use \Tracy\Debugger as TracyDebugger;
use DebugBar\Bridge\Twig\TraceableTwigEnvironment;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
//use \Tracy\Debugger as TracyDebugger;
/**
* Class Debugger
@@ -9,70 +12,110 @@ use \Tracy\Debugger as TracyDebugger;
*/
class Debugger
{
const PRODUCTION = TracyDebugger::PRODUCTION;
const DEVELOPMENT = TracyDebugger::DEVELOPMENT;
const DETECT = TracyDebugger::DETECT;
protected $grav;
protected $debugbar;
protected $renderer;
protected $enabled;
public function __construct($mode = self::PRODUCTION)
public function __construct()
{
// Start the timer and enable debugger in production mode as we do not have system configuration yet.
// Debugger catches all errors and logs them, for example if the script doesn't have write permissions.
TracyDebugger::timer();
TracyDebugger::enable($mode, is_dir(LOG_DIR) ? LOG_DIR : null);
$this->debugbar = new StandardDebugBar();
$this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
}
public function init()
{
$grav = Grav::instance();
/** @var Config $config */
$config = $grav['config'];
TracyDebugger::$logDirectory = $config->get('system.debugger.log.enabled') ? LOG_DIR : null;
TracyDebugger::$maxDepth = $config->get('system.debugger.max_depth');
// Switch debugger into development mode if configured
if ($config->get('system.debugger.enabled')) {
if ($config->get('system.debugger.strict')) {
TracyDebugger::$strictMode = true;
}
$mode = $config->get('system.debugger.mode');
if (function_exists('ini_set')) {
ini_set('display_errors', !($mode === 'production'));
}
if ($mode === 'detect') {
TracyDebugger::$productionMode = self::DETECT;
} elseif ($mode === 'production') {
TracyDebugger::$productionMode = self::PRODUCTION;
} else {
TracyDebugger::$productionMode = self::DEVELOPMENT;
}
$this->grav = Grav::instance();
if ($this->enabled()) {
$this->debugbar->addCollector(new \DebugBar\DataCollector\ConfigCollector((array)$this->grav['config']->get('system')));
}
return $this;
}
/**
* Log a message.
*
* @param string $message
*/
public function log($message)
public function enabled($state = null)
{
if (TracyDebugger::$logDirectory) {
TracyDebugger::log(sprintf($message, TracyDebugger::timer() * 1000));
if (isset($state)) {
$this->enabled = $state;
} else {
if (!isset($this->enabled)) {
$this->enabled = $this->grav['config']->get('system.debugger.enabled');
}
}
return $this->enabled;
}
public static function dump($var)
public function addAssets()
{
TracyDebugger::dump($var);
if ($this->enabled()) {
$assets = $this->grav['assets'];
$this->renderer = $this->debugbar->getJavascriptRenderer();
$this->renderer->setIncludeVendors(false);
// Get the required CSS files
list($css_files, $js_files) = $this->renderer->getAssets(null, JavascriptRenderer::RELATIVE_URL);
foreach ($css_files as $css) {
$assets->addCss($css);
}
$assets->addCss('/system/assets/debugger.css');
foreach ($js_files as $js) {
$assets->addJs($js);
}
}
return $this;
}
public static function barDump($var, $title = NULL, array $options = NULL)
public function addCollector($collector)
{
TracyDebugger::barDump($var, $title, $options);
$this->debugbar->addCollector($collector);
return $this;
}
public function getCollector($collector)
{
return $this->debugbar->getCollector($collector);
}
public function render()
{
if ($this->enabled()) {
echo $this->renderer->render();
}
return $this;
}
public function sendDataInHeaders()
{
$this->debugbar->sendDataInHeaders();
return $this;
}
public function startTimer($name, $desription = null)
{
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
$this->debugbar['time']->startMeasure($name, $desription);
}
return $this;
}
public function stopTimer($name)
{
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
$this->debugbar['time']->stopMeasure($name);
}
return $this;
}
public function addMessage($message, $label = 'info', $isString = true)
{
if ($this->enabled()) {
$this->debugbar['messages']->addMessage($message, $label, $isString);
}
return $this;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Grav\Common\Errors;
use Grav\Common\Grav;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\HandlerInterface;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Run;
/**
* Class Debugger
* @package Grav\Common
*/
class Errors extends \Whoops\Run
{
public function pushHandler($handler, $key = null)
{
if (is_callable($handler)) {
$handler = new CallbackHandler($handler);
}
if (!$handler instanceof HandlerInterface) {
throw new InvalidArgumentException(
"Argument to " . __METHOD__ . " must be a callable, or instance of"
. "Whoops\\Handler\\HandlerInterface"
);
}
// Store with key if provided
if ($key) {
$this->handlerStack[$key] = $handler;
} else {
$this->handlerStack[] = $handler;
}
return $this;
}
public function resetHandlers()
{
$grav = Grav::instance();
$config = $grav['config']->get('system.errors');
if (isset($config['display']) && !$config['display']) {
unset($this->handlerStack['pretty']);
unset($this->handlerStack['text']);
unset($this->handlerStack['json']);
$this->handlerStack = array('simple' => new SimplePageHandler()) + $this->handlerStack;
}
if (isset($config['log']) && !$config['log']) {
unset($this->handlerStack['log']);
}
}
}

View File

@@ -0,0 +1,51 @@
html, body {
height: 100%
}
body {
margin:0 3rem;
padding:0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1.5rem;
line-height: 1.4;
display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
display: -moz-box; /* OLD - Firefox 19- (buggy but mostly works) */
display: -ms-flexbox; /* TWEENER - IE 10 */
display: -webkit-flex; /* NEW - Chrome */
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
}
.container {
margin: 0rem;
max-width: 600px;
padding-bottom:5rem;
}
header {
color: #000;
font-size: 4rem;
letter-spacing: 2px;
line-height: 1.1;
margin-bottom: 2rem;
}
p {
font-family: Optima, Segoe, "Segoe UI", Candara, Calibri, Arial, sans-serif;
color: #666;
}
h5 {
font-weight: normal;
color: #999;
font-size: 1rem;
}
h6 {
font-weight: normal;
color: #999;
}
code {
font-weight: bold;
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Layout template file for Whoops's pretty error output.
*/
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Whoops there was an error!</title>
<style><?php echo $stylesheet ?></style>
</head>
<body>
<div class="container">
<div class="details">
<header>
Server Error
</header>
<p>We're sorry! The server has encountered an internal error and was unable to complete your request.
Please contact the system administrator for more information.</p>
<h6>For further details please review your <code>logs/</code> folder, or enable displaying of errors in your system configuration.</h6>
<h6>Error Code: <b><?php echo $code ?></b></h6>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,91 @@
<?php
namespace Grav\Common\Errors;
use Whoops\Handler\Handler;
use Whoops\Util\Misc;
use Whoops\Util\TemplateHelper;
class SimplePageHandler extends Handler
{
private $searchPaths = array();
private $resourceCache = array();
public function __construct()
{
// Add the default, local resource search path:
$this->searchPaths[] = __DIR__ . "/Resources";
}
/**
* @return int|null
*/
public function handle()
{
$exception = $this->getException();
$inspector = $this->getInspector();
$run = $this->getRun();
$helper = new TemplateHelper();
$templateFile = $this->getResource("layout.html.php");
$cssFile = $this->getResource("error.css");
$code = $inspector->getException()->getCode();
if ($inspector->getException() instanceof \ErrorException) {
$code = Misc::translateErrorCode($code);
}
$vars = array(
"stylesheet" => file_get_contents($cssFile),
"code" => $code,
);
$helper->setVariables($vars);
$helper->render($templateFile);
return Handler::QUIT;
}
protected function getResource($resource)
{
// If the resource was found before, we can speed things up
// by caching its absolute, resolved path:
if (isset($this->resourceCache[$resource])) {
return $this->resourceCache[$resource];
}
// Search through available search paths, until we find the
// resource we're after:
foreach ($this->searchPaths as $path) {
$fullPath = $path . "/$resource";
if (is_file($fullPath)) {
// Cache the result:
$this->resourceCache[$resource] = $fullPath;
return $fullPath;
}
}
// If we got this far, nothing was found.
throw new RuntimeException(
"Could not find resource '$resource' in any resource paths."
. "(searched: " . join(", ", $this->searchPaths). ")"
);
}
public function addResourcePath($path)
{
if (!is_dir($path)) {
throw new InvalidArgumentException(
"'$path' is not a valid directory"
);
}
array_unshift($this->searchPaths, $path);
}
public function getResourcePaths()
{
return $this->searchPaths;
}
}

View File

@@ -27,6 +27,11 @@ trait CompiledFile
$key = md5($this->filename);
$file = PhpFile::instance(CACHE_DIR . "/compiled/files/{$key}{$this->extension}.php");
$modified = $this->modified();
if (!$modified) {
return $this->decode($this->raw());
}
$class = get_class($this);
$cache = $file->exists() ? $file->content() : null;

View File

@@ -7,13 +7,13 @@ class GPM extends Iterator
{
/**
* Local installed Packages
* @var Packages
* @var Local\Packages
*/
private $installed;
/**
* Remote available Packages
* @var Packages
* @var Remote\Packages
*/
private $repository;
@@ -24,7 +24,7 @@ class GPM extends Iterator
/**
* Internal cache
* @var Iterator
* @var
*/
protected $cache;
@@ -63,7 +63,7 @@ class GPM extends Iterator
/**
* Return the instance of a specific Plugin
* @param string $slug The slug of the Plugin
* @return Package The instance of the Plugin
* @return Local\Package The instance of the Plugin
*/
public function getInstalledPlugin($slug)
{
@@ -92,7 +92,7 @@ class GPM extends Iterator
/**
* Return the instance of a specific Theme
* @param string $slug The slug of the Theme
* @return Package The instance of the Theme
* @return Local\Package The instance of the Theme
*/
public function getInstalledTheme($slug)
{
@@ -204,7 +204,7 @@ class GPM extends Iterator
*/
public function isPluginUpdatable($plugin)
{
return array_key_exists($plugin, $this->getUpdatablePlugins());
return array_key_exists($plugin, (array) $this->getUpdatablePlugins());
}
/**
@@ -249,7 +249,7 @@ class GPM extends Iterator
*/
public function isThemeUpdatable($theme)
{
return array_key_exists($theme, $this->getUpdatableThemes());
return array_key_exists($theme, (array) $this->getUpdatableThemes());
}
/**
@@ -303,7 +303,7 @@ class GPM extends Iterator
/**
* 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
* @return Remote\Package Package if found, FALSE if not
*/
public function findPackage($search)
{

View File

@@ -150,10 +150,8 @@ class Installer
}
}
} else {
if (is_file($path)) {
@unlink($path);
@copy($tmp . DS . $filename, $path);
}
@unlink($path);
@copy($tmp . DS . $filename, $path);
}
}
}

View File

@@ -3,6 +3,8 @@ namespace Grav\Common;
use Grav\Common\Page\Pages;
use Grav\Common\Service\ConfigServiceProvider;
use Grav\Common\Service\ErrorServiceProvider;
use Grav\Common\Service\LoggerServiceProvider;
use Grav\Common\Service\StreamsServiceProvider;
use RocketTheme\Toolbox\DI\Container;
use RocketTheme\Toolbox\Event\Event;
@@ -15,9 +17,7 @@ use Grav\Common\Page\Medium;
* @author Andy Miller
* @link http://www.rockettheme.com
* @license http://opensource.org/licenses/MIT
* @version 0.8.0
*
* Originally based on Pico by Gilbert Pellegrom - http://pico.dev7studios.com
* Influenced by Pico, Stacey, Kirby, PieCrust and other great platforms...
*/
class Grav extends Container
@@ -55,6 +55,13 @@ class Grav extends Container
$container['grav'] = $container;
$container['debugger'] = new Debugger();
$container['debugger']->startTimer('_init', 'Initialize');
$container->register(new LoggerServiceProvider);
$container->register(new ErrorServiceProvider);
$container['uri'] = function ($c) {
return new Uri($c);
};
@@ -138,6 +145,8 @@ class Grav extends Container
$container->register(new StreamsServiceProvider);
$container->register(new ConfigServiceProvider);
$container['debugger']->stopTimer('_init');
return $container;
}
@@ -146,16 +155,30 @@ class Grav extends Container
// Use output buffering to prevent headers from being sent too early.
ob_start();
/** @var Debugger $debugger */
$debugger = $this['debugger'];
// Initialize configuration.
$debugger->startTimer('_config', 'Configuration');
$this['config']->init();
$this['errors']->resetHandlers();
$debugger->init();
$this['config']->debug();
$debugger->stopTimer('_config');
$debugger->startTimer('streams', 'Streams');
$this['streams'];
$debugger->stopTimer('streams');
$debugger->startTimer('plugins', 'Plugins');
$this['plugins']->init();
$this->fireEvent('onPluginsInitialized');
$debugger->stopTimer('plugins');
$debugger->startTimer('themes', 'Themes');
$this['themes']->init();
$this->fireEvent('onThemeInitialized');
$debugger->stopTimer('themes');
$task = $this['task'];
if ($task) {
@@ -163,25 +186,31 @@ class Grav extends Container
}
$this['assets']->init();
$this->fireEvent('onAssetsInitialized');
$debugger->startTimer('twig', 'Twig');
$this['twig']->init();
$this['pages']->init();
$debugger->stopTimer('twig');
$debugger->startTimer('pages', 'Pages');
$this['pages']->init();
$this->fireEvent('onPagesInitialized');
$debugger->stopTimer('pages');
$this->fireEvent('onPageInitialized');
// Process whole page as required
$this->output = $this['output'];
$debugger->addAssets();
// Process whole page as required
$debugger->startTimer('render', 'Render');
$this->output = $this['output'];
$this->fireEvent('onOutputGenerated');
$debugger->stopTimer('render');
// Set the header type
$this->header();
echo $this->output;
$debugger->render();
$this->fireEvent('onOutputRendered');
@@ -235,9 +264,14 @@ class Grav extends Container
*/
public function header()
{
/** @var Uri $uri */
$uri = $this['uri'];
header('Content-type: ' . $this->mime($uri->extension()));
$extension = $this['uri']->extension();
header('Content-type: ' . $this->mime($extension));
// Set debugger data in headers
if (!($extension == null || $extension == 'html')) {
$this['debugger']->enabled(false);
// $this['debugger']->sendDataInHeaders();
}
}
/**

View File

@@ -62,7 +62,7 @@ class Pages
/**
* Constructor
*
* @params Grav $c
* @param Grav $c
*/
public function __construct(Grav $c)
{
@@ -391,8 +391,6 @@ class Pages
/** @var Taxonomy $taxonomy */
$taxonomy = $this->grav['taxonomy'];
$last_modified = 0;
// how should we check for last modified? Default is by file
switch (strtolower($config->get('system.cache.check.method', 'file'))) {
case 'none':
@@ -432,7 +430,7 @@ class Pages
* Recursive function to load & build page relationships.
*
* @param string $directory
* @param null $parent
* @param Page|null $parent
* @return Page
* @throws \RuntimeException
* @internal

View File

@@ -23,6 +23,8 @@ class Plugin implements EventSubscriberInterface
*/
protected $config;
protected $active = true;
/**
* By default assign all methods as listeners using the default priority.
*
@@ -53,6 +55,14 @@ class Plugin implements EventSubscriberInterface
$this->config = $config;
}
public function isAdmin()
{
if (isset($this->grav['admin'])) {
return true;
}
return false;
}
/**
* @param array $events
*/

View File

@@ -46,7 +46,7 @@ class Plugins extends Iterator
$filePath = $this->grav['locator']('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT);
if (!is_file($filePath)) {
throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found!", $filePath, $plugin));
throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin));
}
require_once $filePath;
@@ -54,7 +54,7 @@ class Plugins extends Iterator
$pluginClass = 'Grav\\Plugin\\'.ucfirst($plugin).'Plugin';
if (!class_exists($pluginClass)) {
throw new \RuntimeException(sprintf("Plugin '%s' class not found!", $plugin));
throw new \RuntimeException(sprintf("Plugin '%s' class not found! Try reinstalling this plugin.", $plugin));
}
$instance = new $pluginClass($this->grav, $config);

View File

@@ -16,6 +16,8 @@ use RocketTheme\Toolbox\Blueprints\Blueprints;
*/
class ConfigServiceProvider implements ServiceProviderInterface
{
private $environment;
public function register(Container $container)
{
$self = $this;
@@ -31,11 +33,12 @@ class ConfigServiceProvider implements ServiceProviderInterface
public function loadMasterConfig(Container $container)
{
$file = CACHE_DIR . 'compiled/config/master.php';
$environment = $this->getEnvironment();
$file = CACHE_DIR . 'compiled/config/master-'.$environment.'.php';
$data = is_file($file) ? (array) include $file : [];
if ($data) {
try {
$config = new Config($data, $container);
$config = new Config($data, $container, $environment);
} catch (\Exception $e) {
}
}
@@ -43,7 +46,7 @@ class ConfigServiceProvider implements ServiceProviderInterface
if (!isset($config)) {
$file = GRAV_ROOT . '/setup.php';
$data = is_file($file) ? (array) include $file : [];
$config = new Config($data, $container);
$config = new Config($data, $container, $environment);
}
return $config;
@@ -51,9 +54,27 @@ class ConfigServiceProvider implements ServiceProviderInterface
public function loadMasterBlueprints(Container $container)
{
$file = CACHE_DIR . 'compiled/blueprints/master.php';
$environment = $this->getEnvironment();
$file = CACHE_DIR . 'compiled/blueprints/master-'.$environment.'.php';
$data = is_file($file) ? (array) include $file : [];
return new Blueprints($data, $container);
}
public function getEnvironment()
{
if (!$this->environment) {
$address = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '::1';
// check for localhost variations
if ($address == '::1' || $address == '127.0.0.1') {
$hostname = 'localhost';
} else {
$hostname = gethostname();
}
$this->environment = $hostname;
}
return $this->environment;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Grav\Common\Service;
use Grav\Common\Errors\Errors;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Handler\PlainTextHandler;
use Whoops\Run;
class ErrorServiceProvider implements ServiceProviderInterface
{
public function register(Container $container)
{
// Setup Whoops-based error handler
$errors = new Errors;
$error_page = new PrettyPageHandler;
$error_page->setPageTitle('Crikey! There was an error...');
$error_page->setEditor('sublime');
$error_page->addResourcePath(GRAV_ROOT . '/system/assets');
$error_page->addCustomCss('whoops.css');
$json_page = new JsonResponseHandler;
$json_page->onlyForAjaxRequests(true);
$errors->pushHandler($error_page, 'pretty');
$errors->pushHandler(new PlainTextHandler, 'text');
$errors->pushHandler($json_page, 'json');
$logger = $container['log'];
$errors->pushHandler(function ($exception, $inspector, $run) use($logger) {
$logger->addCritical($exception->getMessage(). ' - Trace: '. $exception->getTraceAsString());
}, 'log');
$errors->register();
$container['errors'] = $errors;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Grav\Common\Service;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use \Monolog\Logger;
use \Monolog\Handler\StreamHandler;
use \Monolog\Handler\RotatingFileHandler;
class LoggerServiceProvider implements ServiceProviderInterface
{
public function register(Container $container)
{
$log = new Logger('grav');
$log_file = LOG_DIR.'grav.log';
$log_days = 14;
// $log->pushHandler(new RotatingFileHandler($log_file, $log_days, Logger::WARNING));
$log->pushHandler(new StreamHandler($log_file, Logger::WARNING));
$container['log'] = $log;
}
}

View File

@@ -7,9 +7,12 @@ use RocketTheme\Toolbox\DI\ServiceProviderInterface;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\StreamWrapper\ReadOnlyStream;
use RocketTheme\Toolbox\StreamWrapper\Stream;
use RocketTheme\Toolbox\StreamWrapper\StreamBuilder;
class StreamsServiceProvider implements ServiceProviderInterface
{
protected $schemes = [];
public function register(Container $container)
{
$self = $this;
@@ -20,23 +23,23 @@ class StreamsServiceProvider implements ServiceProviderInterface
return $locator;
};
$container['streams'] = function($c) use ($self) {
$locator = $c['locator'];
// Set locator to both streams.
Stream::setLocator($locator);
ReadOnlyStream::setLocator($locator);
return new StreamBuilder($this->schemes);
};
}
protected function init(Container $container, UniformResourceLocator $locator)
{
/** @var Config $config */
$config = $container['config'];
$schemes = $config->get('streams.schemes');
if (!$schemes) {
return;
}
// Set locator to both streams.
Stream::setLocator($locator);
ReadOnlyStream::setLocator($locator);
$registered = stream_get_wrappers();
$schemes = (array) $config->get('streams.schemes', []);
foreach ($schemes as $scheme => $config) {
if (isset($config['paths'])) {
@@ -48,17 +51,12 @@ class StreamsServiceProvider implements ServiceProviderInterface
}
}
if (in_array($scheme, $registered)) {
stream_wrapper_unregister($scheme);
}
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
if ($type[0] != '\\') {
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
}
if (!stream_wrapper_register($scheme, $type)) {
throw new \InvalidArgumentException("Stream '{$type}' could not be initialized.");
}
$this->schemes[$scheme] = $type;
}
}
}

View File

@@ -70,6 +70,7 @@ class Twig
$config = $this->grav['config'];
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$debugger = $this->grav['debugger'];
$this->twig_paths = $locator->findResources('theme://templates');
$this->grav->fireEvent('onTwigTemplatePaths');
@@ -84,6 +85,11 @@ class Twig
}
$this->twig = new \Twig_Environment($loader_chain, $params);
if ($debugger->enabled() && $config->get('system.debugger.twig')) {
$this->twig = new \DebugBar\Bridge\Twig\TraceableTwigEnvironment($this->twig);
$collector = new \DebugBar\Bridge\Twig\TwigCollector($this->twig);
$debugger->addCollector($collector);
}
$this->grav->fireEvent('onTwigInitialized');
// set default date format if set in config
@@ -95,6 +101,8 @@ class Twig
$this->twig->addExtension(new \Twig_Extension_Debug());
}
$this->twig->addExtension(new TwigExtension());
$this->grav->fireEvent('onTwigExtensions');
$baseUrlAbsolute = $config->get('system.base_url_absolute');
@@ -117,7 +125,6 @@ class Twig
'taxonomy' => $this->grav['taxonomy'],
'browser' => $this->grav['browser'],
);
}
}
@@ -170,15 +177,17 @@ class Twig
$twig_vars['media'] = $item->media();
$twig_vars['header'] = $item->header();
$local_twig = clone($this->twig);
// Get Twig template layout
if ($item->modularTwig()) {
$twig_vars['content'] = $content;
$template = $item->template() . TEMPLATE_EXT;
$output = $this->twig->render($template, $twig_vars);
$output = $local_twig->render($template, $twig_vars);
} else {
$name = '@Page:' . $item->path();
$this->setTemplate($name, $content);
$output = $this->twig->render($name, $twig_vars);
$output = $local_twig->render($name, $twig_vars);
}
return $output;
@@ -231,7 +240,7 @@ class Twig
try {
$output = $this->twig->render($template, $twig_vars);
} catch (\Twig_Error_Loader $e) {
throw new \RuntimeException('Resource not found.', 404, $e);
throw new \RuntimeException('Twig template not found: '.$template, 404, $e);
}
return $output;

View File

@@ -12,6 +12,15 @@ use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
*/
class TwigExtension extends \Twig_Extension
{
protected $grav;
protected $debugger;
public function __construct()
{
$this->grav = Grav::instance();
$this->debugger = isset($this->grav['debugger']) ? $this->grav['debugger'] : null;
}
/**
* Returns extension name.
*
@@ -29,15 +38,15 @@ class TwigExtension extends \Twig_Extension
*/
public function getFilters()
{
return array(
new \Twig_SimpleFilter('fieldName', array($this,'fieldNameFilter')),
new \Twig_SimpleFilter('safe_email', array($this,'safeEmailFilter')),
new \Twig_SimpleFilter('randomize', array($this,'randomizeFilter')),
new \Twig_SimpleFilter('truncate', array($this,'truncateFilter')),
new \Twig_SimpleFilter('*ize', array($this,'inflectorFilter')),
new \Twig_SimpleFilter('md5', array($this,'md5Filter')),
new \Twig_SimpleFilter('sort_by_key', array($this,'sortByKeyFilter')),
);
return [
new \Twig_SimpleFilter('fieldName', [$this,'fieldNameFilter']),
new \Twig_SimpleFilter('safe_email', [$this,'safeEmailFilter']),
new \Twig_SimpleFilter('randomize', [$this,'randomizeFilter']),
new \Twig_SimpleFilter('truncate', [$this,'truncateFilter']),
new \Twig_SimpleFilter('*ize', [$this,'inflectorFilter']),
new \Twig_SimpleFilter('md5', [$this,'md5Filter']),
new \Twig_SimpleFilter('sort_by_key',[$this,'sortByKeyFilter']),
];
}
/**
@@ -47,10 +56,12 @@ class TwigExtension extends \Twig_Extension
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('repeat', array($this, 'repeatFunc')),
new \Twig_SimpleFunction('url', array($this, 'urlFunc'))
);
return [
new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
];
}
/**
@@ -125,7 +136,7 @@ class TwigExtension extends \Twig_Extension
$original = iterator_to_array($original, false);
}
$sorted = array();
$sorted = [];
$random = array_slice($original, $offset);
shuffle($random);
@@ -165,10 +176,10 @@ class TwigExtension extends \Twig_Extension
if (in_array(
$action,
array('titleize','camelize','underscorize','hyphenize', 'humanize','ordinalize','monthize')
['titleize','camelize','underscorize','hyphenize', 'humanize','ordinalize','monthize']
)) {
return Inflector::$action($data);
} elseif (in_array($action, array('pluralize','singularize'))) {
} elseif (in_array($action, ['pluralize','singularize'])) {
if ($count) {
return Inflector::$action($data, $count);
} else {
@@ -225,12 +236,13 @@ class TwigExtension extends \Twig_Extension
/**
* Sorts a collection by key
*
* @param string $input
* @param string $filter
* @param string $direction
* @param array $input
* @param string $filter
* @param array|int $direction
*
* @return string
*/
public function sortByKeyFilter($input, $filter, $direction = SORT_ASC)
public function sortByKeyFilter(array $input, $filter, $direction = SORT_ASC)
{
$output = [];
@@ -246,4 +258,39 @@ class TwigExtension extends \Twig_Extension
return $input;
}
/**
* Based on Twig_Extension_Debug / twig_var_dump
* (c) 2011 Fabien Potencier
*
* @param \Twig_Environment $env
* @param $context
*/
public function dump(\Twig_Environment $env, $context)
{
if (!$env->isDebug() || !$this->debugger) {
return;
}
$count = func_num_args();
if (2 === $count) {
$data = [];
foreach ($context as $key => $value) {
if (is_object($value)) {
if (method_exists($value, 'toArray')) {
$data[$key] = $value->toArray();
} else {
$data[$key] = "Object (" . get_class($value) . ")";
}
} else {
$data[$key] = $value;
}
}
$this->debugger->addMessage($data, 'debug');
} else {
for ($i = 2; $i < $count; $i++) {
$this->debugger->addMessage(func_get_arg($i), 'debug');
}
}
}
}

View File

@@ -2,70 +2,98 @@
namespace Grav\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\ProgressBar;
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\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Yaml\Yaml;
class BackupCommand extends Command {
/**
* Class BackupCommand
* @package Grav\Console\Cli
*/
class BackupCommand extends Command
{
/**
* @var
*/
protected $source;
/**
* @var
*/
protected $progress;
protected function configure() {
/**
*
*/
protected function configure()
{
$this
->setName("backup")
->addArgument(
'destination',
InputArgument::OPTIONAL,
'Where to store the backup'
->setName("backup")
->addArgument(
'destination',
InputArgument::OPTIONAL,
'Where to store the backup'
)
->setDescription("Creates a backup of the Grav instance")
->setHelp('The <info>backup</info> creates a zipped backup. Optionally can be saved in a different destination.');
)
->setDescription("Creates a backup of the Grav instance")
->setHelp('The <info>backup</info> creates a zipped backup. Optionally can be saved in a different destination.');
$this->source = getcwd();
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
$this->progress = new ProgressBar($output);
$this->progress->setFormat('Archiving <cyan>%current%</cyan> files [<green>%bar%</green>] %elapsed:6s% %memory:6s%');
$this->progress = new ProgressBar($output);
$this->progress->setFormat('Archiving <cyan>%current%</cyan> files [<green>%bar%</green>] %elapsed:6s% %memory:6s%');
$name = basename($this->source);
$dir = dirname($this->source);
$date = date('YmdHis', time());
$filename = $name . '-' . $date . '.zip';
$name = basename($this->source);
$dir = dirname($this->source);
$date = date('YmdHis', time());
$filename = $name . '-' . $date . '.zip';
$destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : ROOT_DIR;
$destination = rtrim($destination, DS) . DS . $filename;
$destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : ROOT_DIR;
$destination = rtrim($destination, DS) . DS . $filename;
$output->writeln('');
$output->writeln('Creating new Backup "'.$destination.'"');
$this->progress->start();
$output->writeln('');
$output->writeln('Creating new Backup "' . $destination . '"');
$this->progress->start();
$zip = new \ZipArchive();
$zip->open($destination, \ZipArchive::CREATE);
$zip->addEmptyDir($name);
$zip = new \ZipArchive();
$zip->open($destination, \ZipArchive::CREATE);
$zip->addEmptyDir($name);
$this->folderToZip($this->source, $zip, strlen($dir.DS), $this->progress);
$zip->close();
$this->progress->finish();
$output->writeln('');
$output->writeln('');
$this->folderToZip($this->source, $zip, strlen($dir . DS), $this->progress);
$zip->close();
$this->progress->finish();
$output->writeln('');
$output->writeln('');
}
private static function folderToZip($folder, &$zipFile, $exclusiveLength, $progress) {
/**
* @param $folder
* @param $zipFile
* @param $exclusiveLength
* @param $progress
*/
private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, ProgressBar $progress)
{
$handle = opendir($folder);
while (false !== $f = readdir($handle)) {
if ($f != '.' && $f != '..') {

View File

@@ -3,14 +3,22 @@ namespace Grav\Console\Cli;
use Grav\Common\Filesystem\Folder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
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\Formatter\OutputFormatterStyle;
class CleanCommand extends Command {
/**
* Class CleanCommand
* @package Grav\Console\Cli
*/
class CleanCommand extends Command
{
/**
* @var array
*/
protected $paths_to_remove = [
'user/plugins/email/vendor/swiftmailer/swiftmailer/.travis.yml',
'user/plugins/email/vendor/swiftmailer/swiftmailer/build.xml',
@@ -51,6 +59,16 @@ class CleanCommand extends Command {
'vendor/erusev/parsedown-extra/.travis.yml',
'vendor/erusev/parsedown-extra/.git',
'vendor/erusev/parsedown-extra/test',
'vendor/filp/whoops/composer.json',
'vendor/filp/whoops/docs',
'vendor/filp/whoops/examples',
'vendor/filp/whoops/tests',
'vendor/filp/whoops/.git',
'vendor/filp/whoops/.gitignore',
'vendor/filp/whoops/.scrutinizer.yml',
'vendor/filp/whoops/.travis.yml',
'vendor/filp/whoops/phpunit.xml.dist',
'vendor/filp/whoops/src/deprecated',
'vendor/gregwar/image/Gregwar/Image/composer.json',
'vendor/gregwar/image/Gregwar/Image/phpunit.xml',
'vendor/gregwar/image/Gregwar/Image/.gitignore',
@@ -68,6 +86,14 @@ class CleanCommand extends Command {
'vendor/ircmaxell/password-compat/version-test.php',
'vendor/ircmaxell/password-compat/.travis.yml',
'vendor/ircmaxell/password-compat/test',
'vendor/maximebf/debugbar/bower.json',
'vendor/maximebf/debugbar/composer.json',
'vendor/maximebf/debugbar/.bowerrc',
'vendor/maximebf/debugbar/src/Debugbar/Resources/vendor',
'vendor/monolog/monolog/composer.json',
'vendor/monolog/monolog/doc',
'vendor/monolog/monolog/phpunit.xml.dist',
'vendor/monolog/monolog/tests',
'vendor/mrclay/minify/.editorconfig',
'vendor/mrclay/minify/.git',
'vendor/mrclay/minify/.gitignore',
@@ -89,6 +115,8 @@ class CleanCommand extends Command {
'vendor/pimple/pimple/ext',
'vendor/pimple/pimple/phpunit.xml.dist',
'vendor/pimple/pimple/src/Pimple/Tests',
'vendor/psr/log/composer.json',
'vendor/psr/log/.gitignore',
'vendor/rockettheme/toolbox/.git',
'vendor/rockettheme/toolbox/.gitignore',
'vendor/rockettheme/toolbox/.scrutinizer.yml',
@@ -128,13 +156,23 @@ class CleanCommand extends Command {
'vendor/twig/twig/test',
];
protected function configure() {
/**
*
*/
protected function configure()
{
$this
->setName("clean")
->setDescription("Handles cleaning chores for Grav distribution")
->setHelp('The <info>clean</info> clean extraneous folders and data');
->setName("clean")
->setDescription("Handles cleaning chores for Grav distribution")
->setHelp('The <info>clean</info> clean extraneous folders and data');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -151,14 +189,17 @@ class CleanCommand extends Command {
}
// loops over the array of paths and deletes the files/folders
private function cleanPaths($output)
/**
* @param OutputInterface $output
*/
private function cleanPaths(OutputInterface $output)
{
$output->writeln('');
$output->writeln('<red>DELETING</red>');
$anything = false;
foreach($this->paths_to_remove as $path) {
foreach ($this->paths_to_remove as $path) {
$path = ROOT_DIR . $path;
if (is_dir($path) && @Folder::delete($path)) {

View File

@@ -3,15 +3,23 @@ namespace Grav\Console\Cli;
use Grav\Common\Filesystem\Folder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
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\Formatter\OutputFormatterStyle;
use \Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Yaml;
class ClearCacheCommand extends Command {
/**
* Class ClearCacheCommand
* @package Grav\Console\Cli
*/
class ClearCacheCommand extends Command
{
/**
* @var array
*/
protected $standard_remove = [
'cache/twig/',
'cache/doctrine/',
@@ -21,20 +29,33 @@ class ClearCacheCommand extends Command {
'assets/',
];
/**
* @var array
*/
protected $all_remove = [
'cache/',
'images/',
'assets/'
];
protected function configure() {
/**
*
*/
protected function configure()
{
$this
->setName("clear-cache")
->setDescription("Clears Grav cache")
->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all')
->setHelp('The <info>clear-cache</info> deletes all cache files');
->setName("clear-cache")
->setDescription("Clears Grav cache")
->addOption('all', null, InputOption::VALUE_NONE, 'If set will remove all')
->setHelp('The <info>clear-cache</info> deletes all cache files');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Create a red output option
@@ -47,7 +68,11 @@ class ClearCacheCommand extends Command {
}
// loops over the array of paths and deletes the files/folders
private function cleanPaths($input, $output)
/**
* @param InputInterface $input
* @param OutputInterface $output
*/
private function cleanPaths(InputInterface $input, OutputInterface $output)
{
$output->writeln('');
$output->writeln('<magenta>Clearing cache</magenta>');
@@ -63,24 +88,29 @@ class ClearCacheCommand extends Command {
$remove_paths = $this->standard_remove;
}
foreach($remove_paths as $path) {
foreach ($remove_paths as $path) {
$files = glob(ROOT_DIR . $path . '*');
foreach ($files as $file) {
if (is_file($file)) {
if (@unlink($file)) $anything = true;
}
elseif (is_dir($file)) {
if (@Folder::delete($file)) $anything = true;
if (@unlink($file)) {
$anything = true;
}
} elseif (is_dir($file)) {
if (@Folder::delete($file)) {
$anything = true;
}
}
}
if ($anything) $output->writeln('<red>Cleared: </red>' . $path . '*');
if ($anything) {
$output->writeln('<red>Cleared: </red>' . $path . '*');
}
}
if (file_exists($user_config)) {
touch ($user_config);
touch($user_config);
$output->writeln('');
$output->writeln('<red>Touched: </red>' . $user_config);
$output->writeln('');

View File

@@ -2,39 +2,66 @@
namespace Grav\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
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\Formatter\OutputFormatterStyle;
use \Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Yaml;
class InstallCommand extends Command {
/**
* Class InstallCommand
* @package Grav\Console\Cli
*/
class InstallCommand extends Command
{
/**
* @var
*/
protected $config;
/**
* @var
*/
protected $local_config;
/**
* @var
*/
protected $destination;
/**
* @var
*/
protected $user_path;
protected function configure() {
/**
*
*/
protected function configure()
{
$this
->setName("install")
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the required bits'
)
->addArgument(
'destination',
InputArgument::OPTIONAL,
'Where to install the required bits (default to current project)'
->setName("install")
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the required bits'
)
->addArgument(
'destination',
InputArgument::OPTIONAL,
'Where to install the required bits (default to current project)'
)
->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');
)
->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');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -44,7 +71,7 @@ class InstallCommand extends Command {
// fix trailing slash
$this->destination = rtrim($this->destination, DS) . DS;
$this->user_path = $this->destination . USER_PATH;
$this->user_path = $this->destination . USER_PATH;
// Create a red output option
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
@@ -60,39 +87,42 @@ class InstallCommand extends Command {
// Look for dependencies file in ROOT and USER dir
if (file_exists($this->user_path . $dependencies_file)) {
$this->config = Yaml::parse($this->user_path . $dependencies_file);
} elseif (file_exists($this->destination . $dependencies_file )) {
} elseif (file_exists($this->destination . $dependencies_file)) {
$this->config = Yaml::parse($this->destination . $dependencies_file);
} else {
$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')) {
// Updates composer first
$output->writeln("\nInstalling vendor dependencies");
$output->writeln(system('php bin/composer.phar --working-dir="'.$this->destination.'" --no-interaction update'));
$this->gitclone($output);
} else {
$this->symlink($output);
}
} else {
$output->writeln('<red>ERROR</red> invalid YAML in '. $dependencies_file);
$output->writeln('<red>ERROR</red> invalid YAML in ' . $dependencies_file);
}
}
// loops over the array of paths and deletes the files/folders
private function gitclone($output)
/**
* @param OutputInterface $output
*/
private function gitclone(OutputInterface $output)
{
$output->writeln('');
$output->writeln('<green>Cloning Bits</green>');
$output->writeln('============');
$output->writeln('');
foreach($this->config['git'] as $repo => $data) {
foreach ($this->config['git'] as $repo => $data) {
$path = $this->destination . DS . $data['path'];
if (!file_exists($path)) {
exec('cd ' . $this->destination . ' && git clone -b ' . $data['branch'] . ' ' . $data['url'] . ' ' . $data['path']);
@@ -107,7 +137,10 @@ class InstallCommand extends Command {
}
// loops over the array of paths and deletes the files/folders
private function symlink($output)
/**
* @param OutputInterface $output
*/
private function symlink(OutputInterface $output)
{
$output->writeln('');
$output->writeln('<green>Symlinking Bits</green>');
@@ -121,13 +154,13 @@ class InstallCommand extends Command {
}
exec('cd ' . $this->destination);
foreach($this->config['links'] as $repo => $data) {
$from = $this->local_config[$data['scm'].'_repos'] . $data['src'];
foreach ($this->config['links'] as $repo => $data) {
$from = $this->local_config[$data['scm'] . '_repos'] . $data['src'];
$to = $this->destination . $data['path'];
if (file_exists($from)) {
if (!file_exists($to)) {
symlink ($from, $to);
symlink($from, $to);
$output->writeln('<green>SUCCESS</green> symlinked <magenta>' . $data['src'] . '</magenta> -> <cyan>' . $data['path'] . '</cyan>');
$output->writeln('');
} else {

View File

@@ -2,51 +2,66 @@
namespace Grav\Console\Cli;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
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\Formatter\OutputFormatterStyle;
use \Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Yaml;
class NewProjectCommand extends Command {
/**
* Class NewProjectCommand
* @package Grav\Console\Cli
*/
class NewProjectCommand extends Command
{
protected function configure() {
/**
*
*/
protected function configure()
{
$this
->setName("new-project")
->addArgument(
'destination',
InputArgument::REQUIRED,
'The destination directory of your new Grav project'
)
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the required bits'
)
->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.");
->setName("new-project")
->addArgument(
'destination',
InputArgument::REQUIRED,
'The destination directory of your new Grav project'
)
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the required bits'
)
->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.");
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$sandboxCommand = $this->getApplication()->find('sandbox');
$sandboxCommand = $this->getApplication()->find('sandbox');
$installCommand = $this->getApplication()->find('install');
$sandboxArguments = new ArrayInput(array(
'command' => 'sandbox',
'destination' => $input->getArgument('destination'),
'-s' => $input->getOption('symlink')
));
'command' => 'sandbox',
'destination' => $input->getArgument('destination'),
'-s' => $input->getOption('symlink')
));
$installArguments = new ArrayInput(array(
'command' => 'install',
'destination' => $input->getArgument('destination'),
'-s' => $input->getOption('symlink')
));
'command' => 'install',
'destination' => $input->getArgument('destination'),
'-s' => $input->getOption('symlink')
));
$sandboxCommand->run($sandboxArguments, $output);
$installCommand->run($installArguments, $output);

View File

@@ -3,98 +3,154 @@ namespace Grav\Console\Cli;
use Grav\Common\Filesystem\Folder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
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\Formatter\OutputFormatterStyle;
/**
* Class SandboxCommand
* @package Grav\Console\Cli
*/
class SandboxCommand extends Command
{
protected $directories = array('/cache',
'/logs',
'/images',
'/assets',
'/user/accounts',
'/user/config',
'/user/pages',
'/user/data',
'/user/plugins',
'/user/themes',
);
/**
* @var array
*/
protected $directories = array(
'/cache',
'/logs',
'/images',
'/assets',
'/user/accounts',
'/user/config',
'/user/pages',
'/user/data',
'/user/plugins',
'/user/themes',
);
protected $files = array('/.dependencies',
'/.htaccess',
'/user/config/site.yaml',
'/user/config/system.yaml',
);
/**
* @var array
*/
protected $files = array(
'/.dependencies',
'/.htaccess',
'/nginx.conf',
'/web.config',
'/user/config/site.yaml',
'/user/config/system.yaml',
);
protected $mappings = array('/index.php' => '/index.php',
'/composer.json' => '/composer.json',
'/bin' => '/bin',
'/system' => '/system'
);
/**
* @var array
*/
protected $mappings = array(
'/.editorconfig' => '/.editorconfig',
'/.gitignore' => '/.gitignore',
'/CHANGELOG.md' => '/CHANGELOG.md',
'/LICENSE' => '/LICENSE',
'/README.md' => '/README.md',
'/index.php' => '/index.php',
'/composer.json' => '/composer.json',
'/bin' => '/bin',
'/system' => '/system',
'/vendor' => '/vendor',
);
/**
* @var string
*/
protected $default_file = "---\ntitle: HomePage\n---\n# HomePage\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque porttitor eu felis sed ornare. Sed a mauris venenatis, pulvinar velit vel, dictum enim. Phasellus ac rutrum velit. Nunc lorem purus, hendrerit sit amet augue aliquet, iaculis ultricies nisl. Suspendisse tincidunt euismod risus, quis feugiat arcu tincidunt eget. Nulla eros mi, commodo vel ipsum vel, aliquet congue odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque velit orci, laoreet at adipiscing eu, interdum quis nibh. Nunc a accumsan purus.";
/**
* @var
*/
protected $source;
/**
* @var
*/
protected $destination;
/**
* @var InputInterface $input
*/
protected $input;
/**
* @var OutputInterface $output
*/
protected $output;
/**
*
*/
protected function configure()
{
$this
->setName('sandbox')
->setDescription('Setup of a base Grav system in your webroot, good for development, playing around or starting fresh')
->addArgument(
'destination',
InputArgument::REQUIRED,
'The destination directory to symlink into'
)
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the base grav system'
)
->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");
->setName('sandbox')
->setDescription('Setup of a base Grav system in your webroot, good for development, playing around or starting fresh')
->addArgument(
'destination',
InputArgument::REQUIRED,
'The destination directory to symlink into'
)
->addOption(
'symlink',
's',
InputOption::VALUE_NONE,
'Symlink the base grav system'
)
->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();
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->destination = $input->getArgument('destination');
$this->input = $input;
$this->output = $output;
// Create a red output option
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
$this->output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
$this->output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
$this->output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
// Symlink the Core Stuff
if ($input->getOption('symlink')) {
// Create Some core stuff if it doesn't exist
$this->createDirectories($output);
$this->createDirectories();
// Loop through the symlink mappings and create the symlinks
$this->symlink($output);
$this->symlink();
// Copy the Core STuff
// Copy the Core STuff
} else {
// Create Some core stuff if it doesn't exist
$this->createDirectories($output);
$this->createDirectories();
// Loop through the symlink mappings and copy what otherwise would be symlinks
$this->copy($output);
$this->copy();
}
$this->pages($output);
$this->initFiles($output);
$this->perms($output);
$this->pages();
$this->initFiles();
$this->perms();
}
private function createDirectories($output)
/**
*
*/
private function createDirectories()
{
$output->writeln('');
$output->writeln('<comment>Creating Directories</comment>');
$this->output->writeln('');
$this->output->writeln('<comment>Creating Directories</comment>');
$dirs_created = false;
if (!file_exists($this->destination)) {
@@ -104,50 +160,56 @@ class SandboxCommand extends Command
foreach ($this->directories as $dir) {
if (!file_exists($this->destination . $dir)) {
$dirs_created = true;
$output->writeln(' <cyan>' . $dir . '</cyan>');
$this->output->writeln(' <cyan>' . $dir . '</cyan>');
mkdir($this->destination . $dir, 0777, true);
}
}
if (!$dirs_created) {
$output->writeln(' <red>Directories already exist</red>');
$this->output->writeln(' <red>Directories already exist</red>');
}
}
private function copy($output)
/**
*
*/
private function copy()
{
$output->writeln('');
$output->writeln('<comment>Copying Files</comment>');
$this->output->writeln('');
$this->output->writeln('<comment>Copying Files</comment>');
foreach ($this->mappings as $source => $target) {
if ((int) $source == $source) {
if ((int)$source == $source) {
$source = $target;
}
$from = $this->source . $source;
$to = $this->destination . $target;
$output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
$this->output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
$this->rcopy($from, $to);
}
}
private function symlink($output)
/**
*
*/
private function symlink()
{
$output->writeln('');
$output->writeln('<comment>Resetting Symbolic Links</comment>');
$this->output->writeln('');
$this->output->writeln('<comment>Resetting Symbolic Links</comment>');
foreach ($this->mappings as $source => $target) {
if ((int) $source == $source) {
if ((int)$source == $source) {
$source = $target;
}
$from = $this->source . $source;
$to = $this->destination . $target;
$output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
$this->output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
if (is_dir($to)) {
@Folder::delete($to);
@@ -158,17 +220,20 @@ class SandboxCommand extends Command
}
}
private function initFiles($output)
/**
*
*/
private function initFiles()
{
$this->check($output);
$this->check($this->output);
$output->writeln('');
$output->writeln('<comment>File Initializing</comment>');
$this->output->writeln('');
$this->output->writeln('<comment>File Initializing</comment>');
$files_init = false;
// Copy files if they do not exist
foreach ($this->files as $source => $target) {
if ((int) $source == $source) {
foreach ($this->files as $source => $target) {
if ((int)$source == $source) {
$source = $target;
}
@@ -178,21 +243,24 @@ class SandboxCommand extends Command
if (!file_exists($to)) {
$files_init = true;
copy($from, $to);
$output->writeln(' <cyan>'.$target.'</cyan> <comment>-></comment> Created');
$this->output->writeln(' <cyan>' . $target . '</cyan> <comment>-></comment> Created');
}
}
if (!$files_init) {
$output->writeln(' <red>Files already exist</red>');
$this->output->writeln(' <red>Files already exist</red>');
}
}
private function pages($output)
/**
*
*/
private function pages()
{
$output->writeln('');
$output->writeln('<comment>Pages Initializing</comment>');
$this->output->writeln('');
$this->output->writeln('<comment>Pages Initializing</comment>');
// get pages files and initialize if no pages exist
$pages_dir = $this->destination . '/user/pages';
@@ -201,70 +269,83 @@ class SandboxCommand extends Command
if (count($pages_files) == 0) {
$destination = $this->source . '/user/pages';
$this->rcopy($destination, $pages_dir);
$output->writeln(' <cyan>'.$destination.'</cyan> <comment>-></comment> Created');
$this->output->writeln(' <cyan>' . $destination . '</cyan> <comment>-></comment> Created');
}
}
private function perms($output)
/**
*
*/
private function perms()
{
$output->writeln('');
$output->writeln('<comment>Permisions Initializing</comment>');
$this->output->writeln('');
$this->output->writeln('<comment>Permisions Initializing</comment>');
$dir_perms = 0755;
$binaries = glob($this->destination . DS .'bin' . DS . '*');
$binaries = glob($this->destination . DS . 'bin' . DS . '*');
foreach($binaries as $bin) {
foreach ($binaries as $bin) {
chmod($bin, $dir_perms);
$output->writeln(' <cyan>bin/' . basename($bin) . '</cyan> permissions reset to '. decoct($dir_perms));
$this->output->writeln(' <cyan>bin/' . basename($bin) . '</cyan> permissions reset to ' . decoct($dir_perms));
}
$output->writeln("");
$this->output->writeln("");
}
private function check($output)
/**
*
*/
private function check()
{
$success = true;
if (!file_exists($this->destination)) {
$output->writeln(' file: <red>$this->destination</red> does not exist!');
$this->output->writeln(' file: <red>$this->destination</red> does not exist!');
$success = false;
}
foreach ($this->directories as $dir) {
if (!file_exists($this->destination . $dir)) {
$output->writeln(' directory: <red>' . $dir . '</red> does not exist!');
$this->output->writeln(' directory: <red>' . $dir . '</red> does not exist!');
$success = false;
}
}
foreach ($this->mappings as $target => $link) {
if (!file_exists($this->destination . $target)) {
$output->writeln(' mappings: <red>' . $target . '</red> does not exist!');
$this->output->writeln(' mappings: <red>' . $target . '</red> does not exist!');
$success = false;
}
}
if (!$success) {
$output->writeln('');
$output->writeln('<comment>install should be run with --symlink|--s to symlink first</comment>');
$this->output->writeln('');
$this->output->writeln('<comment>install should be run with --symlink|--s to symlink first</comment>');
exit;
}
}
private function rcopy($src, $dest){
/**
* @param $src
* @param $dest
*
* @return bool
*/
private function rcopy($src, $dest)
{
// If the src is not a directory do a simple file copy
if(!is_dir($src)) {
if (!is_dir($src)) {
copy($src, $dest);
return true;
}
// If the destination directory does not exist create it
if(!is_dir($dest)) {
if(!mkdir($dest)) {
// If the destination directory could not be created stop processing
if (!is_dir($dest)) {
if (!mkdir($dest)) {
// If the destination directory could not be created stop processing
return false;
}
}
@@ -272,11 +353,13 @@ class SandboxCommand extends Command
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
/** @var \DirectoryIterator $f */
foreach($i as $f) {
if($f->isFile()) {
foreach ($i as $f) {
if ($f->isFile()) {
copy($f->getRealPath(), "$dest/" . $f->getFilename());
} else if(!$f->isDot() && $f->isDir()) {
$this->rcopy($f->getRealPath(), "$dest/$f");
} else {
if (!$f->isDot() && $f->isDir()) {
$this->rcopy($f->getRealPath(), "$dest/$f");
}
}
}
return true;

View File

@@ -2,20 +2,38 @@
namespace Grav\Console;
use Grav\Common\GravTrait;
use Grav\Console\Cli\ClearCacheCommand;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class ConsoleTrait
* @package Grav\Console
*/
trait ConsoleTrait
{
use GravTrait;
/**
* @var
*/
protected $argv;
/* @var InputInterface $output */
protected $input;
/* @var OutputInterface $output */
protected $output;
/**
* Set colors style definition for the formatter.
*
* @param InputInterface $input
* @param OutputInterface $output
*/
public function setupConsole($input, $output)
public function setupConsole(InputInterface $input, OutputInterface $output)
{
if (self::$grav) {
self::$grav['config']->set('system.cache.driver', 'default');
@@ -35,7 +53,10 @@ trait ConsoleTrait
$this->output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold')));
}
private function isGravInstance($path)
/**
* @param $path
*/
public function isGravInstance($path)
{
if (!file_exists($path)) {
$this->output->writeln('');
@@ -61,4 +82,21 @@ trait ConsoleTrait
exit;
}
}
/**
* @param array $all
*
* @return int
* @throws \Exception
*/
public function clearCache($all = [])
{
if ($all) {
$all = ['--all' => true];
}
$command = new ClearCacheCommand();
$input = new ArrayInput($all);
return $command->run($input, $this->output);
}
}

View File

@@ -8,13 +8,26 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class IndexCommand
* @package Grav\Console\Gpm
*/
class IndexCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $gpm;
/**
*
*/
protected function configure()
{
$this
@@ -29,11 +42,17 @@ class IndexCommand extends Command
->setHelp('The <info>index</info> command lists the plugins and themes available for installation');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$this->gpm = new GPM($this->input->getOption('force'));
$this->gpm = new GPM($this->input->getOption('force'));
$this->data = $this->gpm->getRepository();
@@ -45,8 +64,8 @@ class IndexCommand extends Command
$index = 0;
foreach ($packages as $slug => $package) {
$this->output->writeln(
// index
str_pad($index+++1, 2, '0', STR_PAD_LEFT) . ". " .
// index
str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " .
// package name
"<cyan>" . str_pad($package->name, 15) . "</cyan> " .
// slug
@@ -67,17 +86,22 @@ class IndexCommand extends Command
$this->output->writeln('');
}
/**
* @param $package
*
* @return string
*/
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));
$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);
$local = $this->gpm->{'getInstalled' . $type}($package->slug);
if (!$installed || !$updatable) {
$version = $installed ? $local->version : $package->version;
$version = $installed ? $local->version : $package->version;
$installed = !$installed ? ' (<magenta>not installed</magenta>)' : ' (<cyan>installed</cyan>)';
return str_pad(" [v<green>" . $version . "</green>]", 35) . $installed;
@@ -86,8 +110,10 @@ class IndexCommand extends Command
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;
return str_pad(" [v<red>" . $package->version . "</red> <cyan>➜</cyan> v<green>" . $package->available . "</green>]",
61) . $installed;
}
return '';
}
}

View File

@@ -9,13 +9,26 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class InfoCommand
* @package Grav\Console\Gpm
*/
class InfoCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $gpm;
/**
*
*/
protected function configure()
{
$this
@@ -35,6 +48,12 @@ class InfoCommand extends Command
->setHelp('The <info>info</info> shows more informations about a package');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
@@ -63,9 +82,22 @@ class InfoCommand extends Command
$packageURL = '<' . $foundPackage->author->url . '>';
}
$this->output->writeln("<green>".str_pad("Author", 12).":</green> " . $foundPackage->author->name . ' <' . $foundPackage->author->email . '> '.$packageURL);
$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) {
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;
@@ -80,7 +112,7 @@ class InfoCommand extends Command
}
$name = str_pad($name, 12);
$this->output->writeln("<green>".$name.":</green> " . $data);
$this->output->writeln("<green>" . $name . ":</green> " . $data);
}
}

View File

@@ -13,16 +13,38 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
/**
* Class InstallCommand
* @package Grav\Console\Gpm
*/
class InstallCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $gpm;
/**
* @var
*/
protected $destination;
/**
* @var
*/
protected $file;
/**
* @var
*/
protected $tmp;
/**
*
*/
protected function configure()
{
$this
@@ -48,21 +70,27 @@ class InstallCommand extends Command
)
->addArgument(
'package',
InputArgument::IS_ARRAY|InputArgument::REQUIRED,
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');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$this->gpm = new GPM($this->input->getOption('force'));
$this->gpm = new GPM($this->input->getOption('force'));
$this->destination = realpath($this->input->getOption('destination'));
$packages = array_map('strtolower', $this->input->getArgument('package'));
$packages = array_map('strtolower', $this->input->getArgument('package'));
$this->data = $this->gpm->findPackages($packages);
if (
@@ -82,7 +110,8 @@ class InstallCommand extends Command
}
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>");
$this->output->writeln("These packages were not found on Grav: <red>" . implode('</red>, <red>',
$this->data['not_found']) . "</red>");
}
unset($this->data['not_found']);
@@ -114,13 +143,21 @@ class InstallCommand extends Command
}
}
}
// clear cache after successful upgrade
$this->clearCache();
}
/**
* @param $package
*
* @return string
*/
private function downloadPackage($package)
{
$this->tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid();
$filename = $package->slug . basename($package->zipball_url);
$output = Response::get($package->zipball_url, [], [$this, 'progress']);
$output = Response::get($package->zipball_url, [], [$this, 'progress']);
Folder::mkdir($this->tmp);
@@ -133,10 +170,15 @@ class InstallCommand extends Command
return $this->tmp . DS . $filename;
}
/**
* @param $package
*
* @return bool
*/
private function checkDestination($package)
{
$questionHelper = $this->getHelper('question');
$skipPrompt = $this->input->getOption('all-yes');
$skipPrompt = $this->input->getOption('all-yes');
Installer::isValidDestination($this->destination . DS . $package->install_path);
@@ -145,8 +187,9 @@ class InstallCommand extends Command
$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);
$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>");
@@ -166,8 +209,9 @@ class InstallCommand extends Command
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);
$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>");
@@ -182,17 +226,22 @@ class InstallCommand extends Command
return true;
}
/**
* @param $package
*
* @return bool
*/
private function installPackage($package)
{
$installer = Installer::install($this->file, $this->destination, ['install_path' => $package->install_path]);
$errorCode = Installer::lastErrorCode();
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());
$this->output->writeln(" | '- " . Installer::lastErrorMsg());
return false;
}
@@ -204,9 +253,13 @@ class InstallCommand extends Command
return true;
}
/**
* @param $progress
*/
public function progress($progress)
{
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ", STR_PAD_LEFT) . '%');
$this->output->write(" |- Downloading package... " . str_pad($progress['percent'], 5, " ",
STR_PAD_LEFT) . '%');
}
}

View File

@@ -2,9 +2,9 @@
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\Common\GPM\Upgrader;
use Grav\Console\ConsoleTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@@ -12,16 +12,46 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
/**
* Class SelfupgradeCommand
* @package Grav\Console\Gpm
*/
class SelfupgradeCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $extensions;
/**
* @var
*/
protected $updatable;
/**
* @var
*/
protected $file;
/**
* @var array
*/
protected $types = array('plugins', 'themes');
/**
* @var
*/
private $tmp;
/**
* @var
*/
private $upgrader;
/**
*
*/
protected function configure()
{
$this
@@ -43,14 +73,20 @@ class SelfupgradeCommand extends Command
->setHelp('The <info>update</info> command updates plugins and themes when a new version is available');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
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'};
$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()) {
@@ -59,14 +95,15 @@ class SelfupgradeCommand extends Command
}
$questionHelper = $this->getHelper('question');
$skipPrompt = $this->input->getOption('all-yes');
$skipPrompt = $this->input->getOption('all-yes');
$this->output->writeln("Grav v<cyan>$remote</cyan> is now available [release date: $release].");
$this->output->writeln("You are currently using v<cyan>".GRAV_VERSION."</cyan>.");
$this->output->writeln("You are currently using v<cyan>" . GRAV_VERSION . "</cyan>.");
if (!$skipPrompt) {
$question = new ConfirmationQuestion("Would you like to read the changelog before proceeding? [y|N] ", false);
$answer = $questionHelper->ask($this->input, $this->output, $question);
$question = new ConfirmationQuestion("Would you like to read the changelog before proceeding? [y|N] ",
false);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if ($answer) {
$changelog = $this->upgrader->getChangelog(GRAV_VERSION);
@@ -74,8 +111,8 @@ class SelfupgradeCommand extends Command
$this->output->writeln("");
foreach ($changelog as $version => $log) {
$title = $version . ' [' . $log->date . ']';
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function($match){
return "\n".ucfirst($match[1]).":";
$content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) {
return "\n" . ucfirst($match[1]) . ":";
}, $log->content);
$this->output->writeln($title);
@@ -89,7 +126,7 @@ class SelfupgradeCommand extends Command
}
$question = new ConfirmationQuestion("Would you like to upgrade now? [y|N] ", false);
$answer = $questionHelper->ask($this->input, $this->output, $question);
$answer = $questionHelper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("Aborting...");
@@ -114,12 +151,20 @@ class SelfupgradeCommand extends Command
$this->output->writeln(" '- <green>Success!</green> ");
$this->output->writeln('');
}
// clear cache after successful upgrade
$this->clearCache('all');
}
/**
* @param $package
*
* @return string
*/
private function download($package)
{
$this->tmp = CACHE_DIR . DS . 'tmp/Grav-' . uniqid();
$output = Response::get($package->download, [], [$this, 'progress']);
$output = Response::get($package->download, [], [$this, 'progress']);
Folder::mkdir($this->tmp);
@@ -132,17 +177,21 @@ class SelfupgradeCommand extends Command
return $this->tmp . DS . $package->name;
}
/**
* @return bool
*/
private function upgrade()
{
$installer = Installer::install($this->file, GRAV_ROOT, ['sophisticated' => true, 'overwrite' => true, 'ignore_symlinks' => true]);
$errorCode = Installer::lastErrorCode();
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());
$this->output->writeln(" | '- " . Installer::lastErrorMsg());
return false;
}
@@ -154,17 +203,27 @@ class SelfupgradeCommand extends Command
return true;
}
/**
* @param $progress
*/
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) . '%');
$this->output->write(" |- Downloading upgrade [" . $this->formatBytes($progress["filesize"]) . "]... " . str_pad($progress['percent'],
5, " ", STR_PAD_LEFT) . '%');
}
/**
* @param $size
* @param int $precision
*
* @return string
*/
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)];
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[(int)floor($base)];
}
}

View File

@@ -12,17 +12,46 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
/**
* Class UpdateCommand
* @package Grav\Console\Gpm
*/
class UpdateCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
protected $data;
/**
* @var
*/
protected $extensions;
/**
* @var
*/
protected $updatable;
/**
* @var
*/
protected $destination;
/**
* @var
*/
protected $file;
/**
* @var array
*/
protected $types = array('plugins', 'themes');
/**
* @var GPM $gpm
*/
protected $gpm;
/**
*
*/
protected function configure()
{
$this
@@ -42,18 +71,24 @@ class UpdateCommand extends Command
)
->addArgument(
'package',
InputArgument::IS_ARRAY|InputArgument::OPTIONAL,
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');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$this->gpm = new GPM($this->input->getOption('force'));
$this->gpm = new GPM($this->input->getOption('force'));
$this->destination = realpath($this->input->getOption('destination'));
if (!Installer::isGravInstance($this->destination)) {
@@ -61,7 +96,7 @@ class UpdateCommand extends Command
exit;
}
$this->data = $this->gpm->getUpdatable();
$this->data = $this->gpm->getUpdatable();
$onlyPackages = array_map('strtolower', $this->input->getArgument('package'));
if (!$this->data['total']) {
@@ -90,7 +125,7 @@ class UpdateCommand extends Command
}
$this->output->writeln(
// index
// index
str_pad($index++ + 1, 2, '0', STR_PAD_LEFT) . ". " .
// name
"<cyan>" . str_pad($package->name, 15) . "</cyan> " .
@@ -104,8 +139,8 @@ class UpdateCommand extends Command
// 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);
$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...");
@@ -115,24 +150,32 @@ class UpdateCommand extends Command
// 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
));
$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;
}
// clear cache after successful upgrade
$this->clearCache();
}
/**
* @param $onlyPackages
*
* @return array
*/
private function userInputPackages($onlyPackages)
{
$found = ['total' => 0];
$found = ['total' => 0];
$ignore = [];
if (!count($onlyPackages)) {
@@ -156,15 +199,17 @@ class UpdateCommand extends Command
$list = array_keys($list);
if ($found['total'] !== $this->data['total']) {
$this->output->write(", only <magenta>".$found['total']."</magenta> will be updated");
$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>");
$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>");
$this->output->writeln("Packages not found or not requiring updates: <red>" . implode('</red>, <red>',
$ignore) . "</red>");
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Grav\Console\Gpm;
use Grav\Common\GPM\GPM;
use Grav\Common\GPM\Upgrader;
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 VersionCommand
* @package Grav\Console\Gpm
*/
class VersionCommand extends Command
{
use ConsoleTrait;
/**
* @var
*/
protected $gpm;
/**
*
*/
protected function configure()
{
$this
->setName("version")
->addOption(
'force',
'f',
InputOption::VALUE_NONE,
'Force re-fetching the data from remote'
)
->addArgument(
'package',
InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
'The package or packages that is desired to know the version of. By default and if not specified this would be grav'
)
->setDescription("Shows the version of an installed package. If available also shows pending updates.")
->setHelp('The <info>version</info> command displays the current version of a package installed and, if available, the available version of pending updates');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->setupConsole($input, $output);
$this->gpm = new GPM($this->input->getOption('force'));
$packages = $this->input->getArgument('package');
if (!count($packages)) {
$packages = ['grav'];
}
foreach ($packages as $package) {
$package = strtolower($package);
$name = null;
$version = null;
$updatable = false;
if ($package == 'grav') {
$name = 'Grav';
$version = GRAV_VERSION;
$upgrader = new Upgrader();
if ($upgrader->isUpgradable()) {
$updatable = ' [upgradable: v<green>' . $upgrader->getRemoteVersion() . '</green>]';
}
} else {
if ($installed = $this->gpm->findPackage($package)) {
$name = $installed->name;
$version = $installed->version;
if ($this->gpm->isUpdatable($package)) {
$updatable = ' [updatable: v<green>' . $installed->available . '</green>]';
}
}
}
$updatable = $updatable ?: '';
if ($installed || $package == 'grav') {
$this->output->writeln('You are running <white>' . $name . '</white> v<cyan>' . $version . '</cyan>' . $updatable);
} else {
$this->output->writeln('Package <red>' . $package . '</red> not found');
}
}
}
}

View File

@@ -28,10 +28,12 @@ assets:
js_pipeline: false
js_minify: true
errors:
display: true
log: true
debugger:
enabled: true
strict: false
max_depth: 10
log:
enabled: false
timing: false
enabled: false
twig: true
shutdown:
close_connection: true

0
web.config Executable file → Normal file
View File