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

# Conflicts:
#	composer.lock
This commit is contained in:
Matias Griese
2017-04-12 20:58:10 +03:00
33 changed files with 968 additions and 792 deletions

View File

@@ -55,6 +55,7 @@ before_install:
fi
- if [ $TRAVIS_BRANCH != 'develop' ] && [ $TRAVIS_PHP_VERSION == "5.6" ] && [ $TRAVIS_PULL_REQUEST == "false" ]; then
export TRAVIS_TAG=$(curl --fail --user "${GH_API_USER}" -s https://api.github.com/repos/getgrav/grav/releases/latest | grep tag_name | head -n 1 | cut -d '"' -f 4);
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.8 bash)";
go get github.com/aktau/github-release;
git clone --quiet --depth=50 --branch=master https://${BB_TOKEN}bitbucket.org/rockettheme/grav-devtools.git $RT_DEVTOOLS &>/dev/null;
if [ ! -z "$TRAVIS_TAG" ]; then

View File

@@ -1,5 +1,61 @@
# v1.1.18
## 02/xx/2017
# v1.2.2
## 04/11/2017
1. [](#bugfix)
* Fix for redirects breaking [#1420](https://github.com/getgrav/grav/issues/1420)
* Fix issue in direct-install with github-style dependencies [#1405](https://github.com/getgrav/grav/issues/1405)
# v1.2.1
## 04/10/2017
1. [](#improved)
* Added various `ancestor` helper methods in Page and Pages classes [#1362](https://github.com/getgrav/grav/pull/1362)
* Added new `parents` field and switched Page blueprints to use this
* Added `isajaxrequest()` Twig function [#1400](https://github.com/getgrav/grav/issues/1400)
* Added ability to inline CSS and JS code via Asset manager [#1377](https://github.com/getgrav/grav/pull/1377)
* Add query string in lighttpd default config [#1393](https://github.com/getgrav/grav/issues/1393)
* Add `--all-yes` and `--destination` options for `bin/gpm direct-install` [#1397](https://github.com/getgrav/grav/pull/1397)
1. [](#bugfix)
* Fix for direct-install of plugins with `languages.yaml` [#1396](https://github.com/getgrav/grav/issues/1396)
* When determining language from HTTP_ACCEPT_LANGUAGE, also try base language only [#1402](https://github.com/getgrav/grav/issues/1402)
* Fixed a bad method signature causing warning when running tests on `GPMTest` object
# v1.2.0
## 03/31/2017
1. [](#new)
* Added file upload for user avatar in user/admin blueprint
1. [](#improved)
* Analysis fixes
* Switched to stable composer lib versions
# v1.2.0-rc.3
## 03/22/2017
1. [](#new)
* Refactored Page re-ordering to handle all siblings at once
* Added `language_codes` to Twig init to allow for easy language name/code/native-name lookup
1. [](#improved)
* Added an _Admin Overrides_ section with option to choose the order of children in Pages Management
1. [](#bugfix)
* Fixed loading issues with improperly named themes (use old broken method first) [#1373](https://github.com/getgrav/grav/issues/1373)
* Simplified modular/twig processing logic and fixed an issue with system process config [#1351](https://github.com/getgrav/grav/issues/1351)
* Cleanup package files via GPM install to make them more windows-friendly [#1361](https://github.com/getgrav/grav/pull/1361)
* Fix for page-level debugger override changing the option site-wide
* Allow `url()` twig function to pass-through external links
# v1.2.0-rc.2
## 03/17/2017
1. [](#improved)
* Updated vendor libraries to latest
* Added the ability to disable debugger on a per-page basis with `debugger: false` in page frontmatter
1. [](#bugfix)
* Fixed an issue with theme inheritance and hyphenated base themes [#1353](https://github.com/getgrav/grav/issues/1353)
* Fixed an issue when trying to use an `@2x` derivative on a non-image media file [#1341](https://github.com/getgrav/grav/issues/1341)
# v1.2.0-rc.1
## 03/13/2017
1. [](#new)
* Added default setting to only allow `direct-installs` from official GPM. Can be configured in `system.yaml`
@@ -7,6 +63,7 @@
* Added optional parameter to `|markdown(false)` filter to toggle block/line processing (default|true = `block`)
* Added new `Page::folderExists()` method
1. [](#improved)
* `Twig::evaluate()` now takes current environment and context into account
* Genericized `direct-install` so it can be called via Admin plugin
1. [](#bugfix)
* Fixed a minor bug in Number validation [#1329](https://github.com/getgrav/grav/issues/1329)

View File

@@ -110,7 +110,7 @@ Good pull requests - patches, improvements, new features - are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.
**Please ask first** in Gitter or in the Forum before embarking on any significant pull request (e.g.
**Please ask first** in [Slack](https://getgrav.org/slack) or in the Forum before embarking on any significant pull request (e.g.
implementing features, refactoring code..),
otherwise you risk spending a lot of time working on something that the
project's developers might not want to merge into the project.

View File

@@ -8,7 +8,7 @@
"require": {
"php": ">=5.5.9",
"twig/twig": "~1.24",
"erusev/parsedown": "dev-master as 1.6.0",
"erusev/parsedown": "~1.6",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~2.8",
"symfony/console": "~2.8",
@@ -20,7 +20,7 @@
"filp/whoops": "~2.0",
"matthiasmullie/minify": "^1.3",
"monolog/monolog": "~1.0",
"gregwar/image": "dev-master#72568cfbeb77515278f2ccb386fc344e874b7ae8",
"gregwar/image": "~2.0",
"donatj/phpuseragentparser": "~0.3",
"pimple/pimple": "~3.0",
"rockettheme/toolbox": "~1.0",

441
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ form:
default_lang:
type: text
label: PLUGIN_ADMIN.SITE_DEFAULT_LANG
size: vsmall
size: x-small
placeholder: PLUGIN_ADMIN.SITE_DEFAULT_LANG_PLACEHOLDER
help: PLUGIN_ADMIN.SITE_DEFAULT_LANG_HELP

View File

@@ -102,7 +102,7 @@ form:
pages.order.by:
type: select
size: long
size: large
classes: fancy
label: PLUGIN_ADMIN.DEFAULT_ORDERING
help: PLUGIN_ADMIN.DEFAULT_ORDERING_HELP
@@ -155,6 +155,7 @@ form:
pages.append_url_extension:
type: text
size: x-small
placeholder: "e.g. .html"
label: PLUGIN_ADMIN.APPEND_URL_EXT
help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
@@ -856,13 +857,12 @@ form:
fields:
images.default_image_quality:
type: text
type: range
append: '%'
label: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY
help: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY_HELP
classes: x-small
validate:
type: number
min: 1
max: 100

View File

@@ -117,17 +117,6 @@ form:
title: PLUGIN_ADMIN.SETTINGS
underline: true
ordering:
type: toggle
label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
@@ -135,13 +124,9 @@ form:
rule: slug
route:
type: select
type: parents
label: PLUGIN_ADMIN.PARENT
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
name:
type: select
@@ -165,9 +150,20 @@ form:
title: PLUGIN_ADMIN.ORDERING
underline: true
ordering:
type: toggle
label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
order:
type: order
label: PLUGIN_ADMIN.PAGE_ORDER
label: PLUGIN_ADMIN.SORTABLE_PAGES
sitemap:
overrides:
@@ -291,6 +287,18 @@ form:
validate:
type: bool
header.debugger:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.DEBUGGER
help: PLUGIN_ADMIN.DEBUGGER_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
header.template:
type: text
toggleable: true
@@ -302,6 +310,25 @@ form:
toggleable: true
help: PLUGIN_ADMIN.APPEND_URL_EXT_HELP
admin_only:
type: section
title: PLUGIN_ADMIN.ADMIN_SPECIFIC_OVERRIDES
underline: true
fields:
header.admin.children_display_order:
type: select
label: PLUGIN_ADMIN.ADMIN_CHILDREN_DISPLAY_ORDER
help: PLUGIN_ADMIN.ADMIN_CHILDREN_DISPLAY_ORDER_HELP
toggleable: true
classes: fancy
default: 'collection'
options:
'default': 'Ordered by Folder name (default)'
'collection': 'Ordered by Collection definition'
header.order_by:
type: hidden

View File

@@ -1,54 +0,0 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
section:
type: section
title: PLUGIN_ADMIN.ADD_MODULAR_CONTENT
title:
type: text
label: PLUGIN_ADMIN.PAGE_TITLE
validate:
required: true
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
validate:
rule: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PAGE
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
validate:
required: true
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.MODULAR_TEMPLATE
help: PLUGIN_ADMIN.PAGE_FILE_HELP
default: default
data-options@: '\Grav\Common\Page\Pages::modularTypes'
validate:
required: true
modular:
type: hidden
default: 1
validate:
type: bool
blueprint:
type: blueprint

View File

@@ -1,106 +0,0 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: PLUGIN_ADMIN.CONTENT
fields:
frontmatter:
classes: frontmatter
type: editor
label: PLUGIN_ADMIN.FRONTMATTER
autofocus: true
codemirror:
mode: 'yaml'
indentUnit: 4
autofocus: true
indentWithTabs: false
lineNumbers: true
styleActiveLine: true
gutters: ['CodeMirror-lint-markers']
lint: true
content:
type: markdown
uploads:
type: pagemedia
label: PLUGIN_ADMIN.PAGE_MEDIA
options:
type: tab
title: PLUGIN_ADMIN.OPTIONS
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
ordering:
type: toggle
label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
folder:
type: text
label: PLUGIN_ADMIN.FILENAME
validate:
rule: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PARENT
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
options:
'': PLUGIN_ADMIN.DEFAULT_OPTION_SELECT
validate:
required: true
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.MODULAR_TEMPLATE
default: default
data-options@: '\Grav\Common\Page\Pages::modularTypes'
validate:
required: true
column2:
type: column
fields:
order:
type: order
label: PLUGIN_ADMIN.ORDERING
blueprint:
type: blueprint

View File

@@ -1,11 +0,0 @@
form:
validation: loose
fields:
route:
type: select
label: PLUGIN_ADMIN.PARENT
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT

View File

@@ -1,66 +0,0 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
section:
type: section
title: PLUGIN_ADMIN.ADD_PAGE
title:
type: text
label: PLUGIN_ADMIN.PAGE_TITLE
help: PLUGIN_ADMIN.PAGE_TITLE_HELP
validate:
required: true
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
rule: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PARENT_PAGE
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
validate:
required: true
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.PAGE_FILE
help: PLUGIN_ADMIN.PAGE_FILE_HELP
data-options@: '\Grav\Common\Page\Pages::types'
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageName'
validate:
required: true
visible:
type: toggle
label: PLUGIN_ADMIN.VISIBLE
help: PLUGIN_ADMIN.VISIBLE_HELP
highlight: ''
default: ''
options:
'': Auto
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
required: true
blueprint:
type: blueprint

View File

@@ -1,35 +0,0 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
section:
type: section
title: PLUGIN_ADMIN.ADD_FOLDER
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
rule: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PARENT_PAGE
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\Admin\Admin::getLastPageRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
validate:
required: true
blueprint:
type: blueprint

View File

@@ -1,105 +0,0 @@
rules:
slug:
pattern: '[a-zA-Zа-яA-Я0-9_\-]+'
min: 1
max: 200
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: PLUGIN_ADMIN.CONTENT
fields:
frontmatter:
classes: frontmatter
type: editor
label: PLUGIN_ADMIN.FRONTMATTER
autofocus: true
codemirror:
mode: 'yaml'
indentUnit: 4
autofocus: true
indentWithTabs: false
lineNumbers: true
styleActiveLine: true
gutters: ['CodeMirror-lint-markers']
lint: true
content:
type: markdown
uploads:
type: pagemedia
label: PLUGIN_ADMIN.PAGE_MEDIA
options:
type: tab
title: PLUGIN_ADMIN.OPTIONS
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
ordering:
type: toggle
label: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX
help: PLUGIN_ADMIN.FOLDER_NUMERIC_PREFIX_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
folder:
type: text
label: PLUGIN_ADMIN.FOLDER_NAME
help: PLUGIN_ADMIN.FOLDER_NAME_HELP
validate:
rule: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PARENT
classes: fancy
data-options@: '\Grav\Common\Page\Pages::parentsRawRoutes'
data-default@: '\Grav\Plugin\Admin\Admin::rawRoute'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.DISPLAY_TEMPLATE
help: PLUGIN_ADMIN.DISPLAY_TEMPLATE_HELP
default: default
data-options@: '\Grav\Common\Page\Pages::types'
validate:
required: true
column2:
type: column
fields:
order:
type: order
label: PLUGIN_ADMIN.ORDERING
blueprint:
type: blueprint

View File

@@ -8,6 +8,13 @@ form:
type: userinfo
size: large
avatar:
type: file
size: large
destination: 'user://accounts/avatars'
multiple: false
random_name: true
content:
type: section
title: PLUGIN_ADMIN.ACCOUNT

View File

@@ -8,7 +8,7 @@
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.1.17');
define('GRAV_VERSION', '1.2.2');
define('GRAV_TESTING', false);
define('DS', '/');

View File

@@ -255,28 +255,32 @@ class Assets
}
/**
* Add a CSS asset.
* Add an asset to its assembly.
*
* It checks for duplicates.
* You may add more than one asset passing an array as argument.
* The third argument may alternatively contain an array of options which take precedence over positional
* arguments.
*
* @param mixed $asset
* @param int $priority the priority, bigger comes first
* @param bool $pipeline false if this should not be pipelined
* @param null $group
* @param array $assembly the array assembling the assets
* @param mixed $asset
* @param int $priority the priority, bigger comes first
* @param bool $pipeline false if this should not be pipelined
* @param string $loading how the asset is loaded (async/defer/inline, for CSS: only inline)
* @param string $group name of the group
*
* @return $this
*/
public function addCss($asset, $priority = null, $pipeline = true, $group = null)
public function addTo(&$assembly, $asset, $priority = null, $pipeline = true, $loading = null, $group = null)
{
if (is_array($asset)) {
foreach ($asset as $a) {
$this->addCss($a, $priority, $pipeline, $group);
$this->addTo($assembly, $a, $priority, $pipeline, $loading, $group);
}
return $this;
} elseif (isset($this->collections[$asset])) {
$this->addCss($this->collections[$asset], $priority, $pipeline, $group);
$this->addTo($assembly, $this->collections[$asset], $priority, $pipeline, $loading, $group);
return $this;
}
@@ -297,15 +301,16 @@ class Assets
'asset' => $asset,
'remote' => $remote,
'priority' => intval($priority ?: 10),
'order' => count($this->css),
'order' => count($assembly),
'pipeline' => (bool) $pipeline,
'loading' => $loading ?: '',
'group' => $group ?: 'head',
'modified' => $modified
];
// check for dynamic array and merge with defaults
if (func_num_args() > 1) {
$dynamic_arg = func_get_arg(1);
if (func_num_args() > 2) {
$dynamic_arg = func_get_arg(2);
if (is_array($dynamic_arg)) {
$data = array_merge($data, $dynamic_arg);
}
@@ -313,17 +318,40 @@ class Assets
$key = md5($asset);
if ($asset) {
$this->css[$key] = $data;
$assembly[$key] = $data;
}
return $this;
}
/**
* Add a CSS asset.
*
* It checks for duplicates.
* You may add more than one asset passing an array as argument.
* The second argument may alternatively contain an array of options which take precedence over positional
* arguments.
*
* @param mixed $asset
* @param int $priority the priority, bigger comes first
* @param bool $pipeline false if this should not be pipelined
* @param string $group
* @param string $loading how the asset is loaded (async/defer/inline, for CSS: only inline)
*
* @return $this
*/
public function addCss($asset, $priority = null, $pipeline = true, $group = null, $loading = null)
{
return $this->addTo($this->css, $asset, $priority, $pipeline, $loading, $group);
}
/**
* Add a JavaScript asset.
*
* It checks for duplicates.
* You may add more than one asset passing an array as argument.
* The second argument may alternatively contain an array of options which take precedence over positional
* arguments.
*
* @param mixed $asset
* @param int $priority the priority, bigger comes first
@@ -335,55 +363,7 @@ class Assets
*/
public function addJs($asset, $priority = null, $pipeline = true, $loading = null, $group = null)
{
if (is_array($asset)) {
foreach ($asset as $a) {
$this->addJs($a, $priority, $pipeline, $loading, $group);
}
return $this;
} elseif (isset($this->collections[$asset])) {
$this->addJs($this->collections[$asset], $priority, $pipeline, $loading, $group);
return $this;
}
$modified = false;
$remote = $this->isRemoteLink($asset);
if (!$remote) {
$modified = $this->getLastModificationTime($asset);
$asset = $this->buildLocalLink($asset);
}
// Check for existence
if ($asset === false) {
return $this;
}
$data = [
'asset' => $asset,
'remote' => $remote ,
'priority' => intval($priority ?: 10),
'order' => count($this->js),
'pipeline' => (bool) $pipeline,
'loading' => $loading ?: '',
'group' => $group ?: 'head',
'modified' => $modified
];
// check for dynamic array and merge with defaults
if (func_num_args() > 1) {
$dynamic_arg = func_get_arg(1);
if (is_array($dynamic_arg)) {
$data = array_merge($data, $dynamic_arg);
}
}
$key = md5($asset);
if ($asset) {
$this->js[$key] = $data;
}
return $this;
return $this->addTo($this->js, $asset, $priority, $pipeline, $loading, $group);
}
/**
@@ -547,32 +527,54 @@ class Assets
$this->css = array_reverse($this->css);
$this->inline_css = array_reverse($this->inline_css);
$inlineGroup = array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline';
$attributes = $this->attributes(array_merge(['type' => 'text/css', 'rel' => 'stylesheet'], $attributes));
$output = '';
$inline_css = '';
if ($this->css_pipeline) {
$pipeline_result = $this->pipelineCss($group);
$pipeline_html = '<link href="' . $pipeline_result . '"' . $attributes . ' />' . "\n";
$pipeline_result = $this->pipelineCss($group, !$inlineGroup);
$pipeline_html = ($inlineGroup ? '' : '<link href="' . $pipeline_result . '"' . $attributes . ' />' . "\n");
if ($this->css_pipeline_before_excludes && $pipeline_result) {
$output .= $pipeline_html;
if ($inlineGroup) {
$inline_css .= $pipeline_result;
}
else {
$output .= $pipeline_html;
}
}
foreach ($this->css_no_pipeline as $file) {
if ($group && $file['group'] == $group) {
$media = isset($file['media']) ? sprintf(' media="%s"', $file['media']) : '';
$output .= '<link href="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . $media . ' />' . "\n";
if ($file['loading'] === 'inline') {
$inline_css .= $this->gatherLinks([$file], CSS_ASSET) . "\n";
}
else {
$media = isset($file['media']) ? sprintf(' media="%s"', $file['media']) : '';
$output .= '<link href="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . $media . ' />' . "\n";
}
}
}
if (!$this->css_pipeline_before_excludes && $pipeline_result) {
$output .= $pipeline_html;
if ($inlineGroup) {
$inline_css .= $pipeline_result;
}
else {
$output .= $pipeline_html;
}
}
} else {
foreach ($this->css as $file) {
if ($group && $file['group'] == $group) {
$media = isset($file['media']) ? sprintf(' media="%s"', $file['media']) : '';
$output .= '<link href="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . $media . ' />' . "\n";
if ($inlineGroup || $file['loading'] === 'inline') {
$inline_css .= $this->gatherLinks([$file], CSS_ASSET) . "\n";
}
else {
$media = isset($file['media']) ? sprintf(' media="%s"', $file['media']) : '';
$output .= '<link href="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . $media . ' />' . "\n";
}
}
}
}
@@ -626,30 +628,52 @@ class Assets
$this->js = array_reverse($this->js);
$this->inline_js = array_reverse($this->inline_js);
$inlineGroup = array_key_exists('loading', $attributes) && $attributes['loading'] === 'inline';
$attributes = $this->attributes(array_merge(['type' => 'text/javascript'], $attributes));
$output = '';
$inline_js = '';
if ($this->js_pipeline) {
$pipeline_result = $this->pipelineJs($group);
$pipeline_html = '<script src="' . $pipeline_result . '"' . $attributes . ' ></script>' . "\n";
$pipeline_result = $this->pipelineJs($group, !$inlineGroup);
$pipeline_html = ($inlineGroup ? '' : '<script src="' . $pipeline_result . '"' . $attributes . ' ></script>' . "\n");
if ($this->js_pipeline_before_excludes && $pipeline_result) {
$output .= $pipeline_html;
if ($inlineGroup) {
$inline_js .= $pipeline_result;
}
else {
$output .= $pipeline_html;
}
}
foreach ($this->js_no_pipeline as $file) {
if ($group && $file['group'] == $group) {
$output .= '<script src="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
if ($file['loading'] === 'inline') {
$inline_js .= $this->gatherLinks([$file], JS_ASSET) . "\n";
}
else {
$output .= '<script src="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
}
}
}
if (!$this->js_pipeline_before_excludes && $pipeline_result) {
$output .= $pipeline_html;
if ($inlineGroup) {
$inline_js .= $pipeline_result;
}
else {
$output .= $pipeline_html;
}
}
} else {
foreach ($this->js as $file) {
if ($group && $file['group'] == $group) {
$output .= '<script src="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
if ($inlineGroup || $file['loading'] === 'inline') {
$inline_js .= $this->gatherLinks([$file], JS_ASSET) . "\n";
}
else {
$output .= '<script src="' . $file['asset'] . $this->getTimestamp($file) . '"' . $attributes . ' ' . $file['loading'] . '></script>' . "\n";
}
}
}
}
@@ -672,10 +696,11 @@ class Assets
* Minify and concatenate CSS
*
* @param string $group
* @param bool $returnURL true if pipeline should return the URL, otherwise the content
*
* @return bool|string
* @return bool|string URL or generated content if available, else false
*/
protected function pipelineCss($group = 'head')
protected function pipelineCss($group = 'head', $returnURL = true)
{
// temporary list of assets to pipeline
$temp_css = [];
@@ -695,9 +720,14 @@ class Assets
$this->css_no_pipeline = json_decode(file_get_contents($this->assets_dir . $inline_file), true);
}
// If pipeline exist return it
// If pipeline exist return its URL or content
if (file_exists($this->assets_dir . $file)) {
return $relative_path . $this->getTimestamp();
if ($returnURL) {
return $relative_path . $this->getTimestamp();
}
else {
return file_get_contents($this->assets_dir . $file) . "\n";
}
}
// Remove any non-pipeline files
@@ -744,7 +774,12 @@ class Assets
if (strlen(trim($buffer)) > 0) {
file_put_contents($this->assets_dir . $file, $buffer);
return $relative_path . $this->getTimestamp();
if ($returnURL) {
return $relative_path . $this->getTimestamp();
}
else {
return $buffer . "\n";
}
} else {
return false;
}
@@ -754,10 +789,11 @@ class Assets
* Minify and concatenate JS files.
*
* @param string $group
* @param bool $returnURL true if pipeline should return the URL, otherwise the content
*
* @return string
* @return bool|string URL or generated content if available, else false
*/
protected function pipelineJs($group = 'head')
protected function pipelineJs($group = 'head', $returnURL = true)
{
// temporary list of assets to pipeline
$temp_js = [];
@@ -777,9 +813,14 @@ class Assets
$this->js_no_pipeline = json_decode(file_get_contents($this->assets_dir . $inline_file), true);
}
// If pipeline exist return it
// If pipeline exist return its URL or content
if (file_exists($this->assets_dir . $file)) {
return $relative_path . $this->getTimestamp();
if ($returnURL) {
return $relative_path . $this->getTimestamp();
}
else {
return file_get_contents($this->assets_dir . $file) . "\n";
}
}
// Remove any non-pipeline files
@@ -816,7 +857,12 @@ class Assets
if (strlen(trim($buffer)) > 0) {
file_put_contents($this->assets_dir . $file, $buffer);
return $relative_path . $this->getTimestamp();
if ($returnURL) {
return $relative_path . $this->getTimestamp();
}
else {
return $buffer . "\n";
}
} else {
return false;
}
@@ -1255,13 +1301,8 @@ class Assets
$old_url = $matches[2];
// ensure this is not a data url
if (strpos($old_url, 'data:') === 0) {
return $matches[0];
}
// ensure this is not a remote url
if ($this->isRemoteLink($old_url)) {
// Ensure link is not rooted to webserver, a data URL, or to a remote host
if (Utils::startsWith($old_url, '/') || Utils::startsWith($old_url, 'data:') || $this->isRemoteLink($old_url)) {
return $matches[0];
}

View File

@@ -599,9 +599,11 @@ class GPM extends Iterator
*/
public static function getPackageName($source)
{
$ignore_yaml_files = ['blueprints', 'languages'];
foreach (glob($source . "*.yaml") as $filename) {
$name = strtolower(basename($filename, '.yaml'));
if ($name == 'blueprints') {
if (in_array($name, $ignore_yaml_files)) {
continue;
}
return $name;

View File

@@ -203,6 +203,15 @@ class Language
}
}
// repeat if not found, try base language only - fixes Safari sending the language code always
// with a locale (e.g. it-it or fr-fr)
foreach ($preferred as $lang) {
$lang = substr($lang, 0, 2);
if ($this->validate($lang)) {
$this->active = $lang;
break;
}
}
}
}
}

View File

@@ -141,8 +141,12 @@ class Media extends AbstractMedia
foreach ($types['alternative'] as $altMedium) {
if ($altMedium['file'] != $medium) {
$ratio = $altMedium['file']->get('width') / $medium->get('width');
$medium->addAlternative($ratio, $altMedium['file']);
$altWidth = $altMedium['file']->get('width');
$medWidth = $medium->get('width');
if ($altWidth && $medWidth) {
$ratio = $altWidth / $medWidth;
$medium->addAlternative($ratio, $altMedium['file']);
}
}
}
}

View File

@@ -86,6 +86,7 @@ class Page
protected $hide_home_route;
protected $ssl;
protected $template_format;
protected $debugger;
/**
* @var Page Unmodified (original) version of the page. Used for copying and moving the page.
@@ -424,6 +425,9 @@ class Page
if (isset($this->header->template_format)) {
$this->template_format = $this->header->template_format;
}
if (isset($this->header->debugger)) {
$this->debugger = (bool)$this->header->debugger;
}
}
return $this->header;
@@ -566,7 +570,8 @@ class Page
$process_markdown = $this->shouldProcess('markdown');
$process_twig = $this->shouldProcess('twig');
$process_twig = $this->shouldProcess('twig') || $this->modularTwig() ;
$cache_enable = isset($this->header->cache_enable) ? $this->header->cache_enable : $config->get('system.cache.enabled',
true);
$twig_first = isset($this->header->twig_first) ? $this->header->twig_first : $config->get('system.pages.twig_first',
@@ -799,6 +804,9 @@ class Page
if ($name == 'folder') {
return preg_replace(PAGE_ORDER_PREFIX_REGEX, '', $this->folder);
}
if ($name == 'slug') {
return $this->slug();
}
if ($name == 'name') {
$language = $this->language() ? '.' . $this->language() : '';
$name_val = str_replace($language . '.md', '', $this->name());
@@ -882,12 +890,12 @@ class Page
/**
* Save page if there's a file assigned to it.
*
* @param bool $reorder Internal use.
* @param bool|mixed $reorder Internal use.
*/
public function save($reorder = true)
{
// Perform move, copy or reordering if needed.
$this->doRelocation($reorder);
// Perform move, copy [or reordering] if needed.
$this->doRelocation();
$file = $this->file();
if ($file) {
@@ -896,6 +904,13 @@ class Page
$file->markdown($this->raw_content);
$file->save();
}
// Perform reorder if required
if ($reorder && is_array($reorder)) {
$this->doReorder($reorder);
}
$this->_original = null;
}
/**
@@ -1111,7 +1126,7 @@ class Page
*/
public function childType()
{
return isset($this->header->child_type) ? (string)$this->header->child_type : 'default';
return isset($this->header->child_type) ? (string)$this->header->child_type : '';
}
/**
@@ -1373,6 +1388,20 @@ class Page
return $this->process;
}
/**
* Returns the state of the debugger override etting for this page
*
* @return mixed
*/
public function debugger()
{
if (isset($this->debugger) && $this->debugger === false) {
return false;
} else {
return true;
}
}
/**
* Function to merge page metadata tags and build an array of Metadata objects
* that can then be rendered in the page.
@@ -2027,7 +2056,6 @@ class Page
if ($var !== null) {
$this->modular_twig = (bool)$var;
if ($var) {
$this->process['twig'] = true;
$this->visible(false);
// some routable logic
if (empty($this->header->routable)) {
@@ -2254,6 +2282,74 @@ class Page
}
}
/**
* Helper method to return an ancestor page.
*
* @param string $url The url of the page
* @param bool $lookup Name of the parent folder
*
* @return \Grav\Common\Page\Page page you were looking for if it exists
*/
public function ancestor($lookup = null)
{
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
return $pages->ancestor($this->route, $lookup);
}
/**
* Helper method to return an ancestor page to inherit from. The current
* page object is returned.
*
* @param string $field Name of the parent folder
*
* @return Page
*/
public function inherited($field)
{
list($inherited, $currentParams) = $this->getInheritedParams($field);
$this->modifyHeader($field, $currentParams);
return $inherited;
}
/**
* Helper method to return an ancestor field only to inherit from. The
* first occurrence of an ancestor field will be returned if at all.
*
* @param string $field Name of the parent folder
*
* @return array
*/
public function inheritedField($field)
{
list($inherited, $currentParams) = $this->getInheritedParams($field);
return $currentParams;
}
/**
* Method that contains shared logic for inherited() and inheritedField()
*
* @param string $field Name of the parent folder
*
* @return array
*/
protected function getInheritedParams($field)
{
$pages = Grav::instance()['pages'];
/** @var Pages $pages */
$inherited = $pages->inherited($this->route, $field);
$inheritedParams = (array) $inherited->value('header.' . $field);
$currentParams = (array) $this->value('header.' . $field);
if($inheritedParams && is_array($inheritedParams)) {
$currentParams = array_replace_recursive($inheritedParams, $currentParams);
}
return [$inherited, $currentParams];
}
/**
* Helper method to return a page.
*
@@ -2588,65 +2684,62 @@ class Page
}
/**
* Moves or copies the page in filesystem.
* Reorders all siblings according to a defined order
*
* @internal
*
* @param bool $reorder
*
* @throws Exception
* @param $new_order
*/
protected function doRelocation($reorder)
protected function doReorder($new_order)
{
if (!$this->_original) {
return;
}
// Do reordering.
if ($reorder && $this->order() != $this->_original->order()) {
/** @var Pages $pages */
$pages = Grav::instance()['pages'];
$pages = Grav::instance()['pages'];
$pages->init();
$parent = $this->parent();
$this->_original->path($this->path());
// Extract visible children from the parent page.
$list = [];
/** @var Page $page */
foreach ($parent->children()->visible() as $page) {
if ($page->order()) {
$list[$page->slug] = $page->path();
}
}
$siblings = $this->parent()->children();
$siblings->order('slug', 'asc', $new_order);
// If page was moved, take it out of the list.
if ($this->_action == 'move') {
unset($list[$this->slug()]);
}
$counter = 0;
$list = array_values($list);
// Reorder all moved pages.
foreach ($siblings as $slug => $page) {
$order = intval(trim($page->order(),'.'));
$counter++;
// Then add it back to the new location (if needed).
if ($this->order()) {
array_splice($list, min($this->order() - 1, count($list)), 0, [$this->path()]);
}
// Reorder all moved pages.
foreach ($list as $order => $path) {
if ($path == $this->path()) {
if ($order) {
if ($page->path() == $this->path() && $this->folderExists()) {
// Handle current page; we do want to change ordering number, but nothing else.
$this->order($order + 1);
$this->order($counter);
$this->save(false);
} else {
// Handle all the other pages.
$page = $pages->get($path);
if ($page && $page->exists() && !$page->_action && $page->order() != $order + 1) {
$page = $page->move($parent);
$page->order($order + 1);
$page = $pages->get($page->path());
if ($page && $page->folderExists() && !$page->_action) {
$page = $page->move($this->parent());
$page->order($counter);
$page->save(false);
}
}
}
}
}
/**
* Moves or copies the page in filesystem.
*
* @internal
*
* @throws Exception
*/
protected function doRelocation()
{
if (!$this->_original) {
return;
}
if (is_dir($this->_original->path())) {
if ($this->_action == 'move') {
Folder::move($this->_original->path(), $this->path());
@@ -2662,7 +2755,6 @@ class Page
}
}
$this->_original = null;
}
protected function setPublishState()

View File

@@ -291,36 +291,86 @@ class Pages
return new Collection($children, [], $this);
}
/**
* Get a page ancestor.
*
* @param string $route The relative URL of the page
* @param string $path The relative path of the ancestor folder
*
* @return Page|null
*/
public function ancestor($route, $path = null)
{
if (!is_null($path)) {
$page = $this->dispatch($route, true);
if ($page->path() == $path) {
return $page;
} elseif (!$page->parent()->root()) {
return $this->ancestor($page->parent()->route(), $path);
}
}
return null;
}
/**
* Get a page ancestor trait.
*
* @param string $route The relative route of the page
* @param string $field The field name of the ancestor to query for
*
* @return Page|null
*/
public function inherited($route, $field = null)
{
if (!is_null($field)) {
$page = $this->dispatch($route, true);
$ancestorField = $page->parent()->value('header.' . $field);
if ($ancestorField != null) {
return $page->parent();
} elseif (!$page->parent()->root()) {
return $this->inherited($page->parent()->route(), $field);
}
}
return null;
}
/**
* alias method to return find a page.
*
* @param string $url The relative URL of the page
* @param string $route The relative URL of the page
* @param bool $all
*
* @return Page|null
*/
public function find($url, $all = false)
public function find($route, $all = false)
{
return $this->dispatch($url, $all, false);
return $this->dispatch($route, $all, false);
}
/**
* Dispatch URI to a page.
*
* @param string $url The relative URL of the page
* @param string $route The relative URL of the page
* @param bool $all
*
* @param bool $redirect
* @return Page|null
* @throws \Exception
*/
public function dispatch($url, $all = false, $redirect = true)
public function dispatch($route, $all = false, $redirect = true)
{
// Fetch page if there's a defined route to it.
$page = isset($this->routes[$url]) ? $this->get($this->routes[$url]) : null;
$page = isset($this->routes[$route]) ? $this->get($this->routes[$route]) : null;
// Try without trailing slash
if (!$page && Utils::endsWith($url, '/')) {
$page = isset($this->routes[rtrim($url, '/')]) ? $this->get($this->routes[rtrim($url, '/')]) : null;
if (!$page && Utils::endsWith($route, '/')) {
$page = isset($this->routes[rtrim($route, '/')]) ? $this->get($this->routes[rtrim($route, '/')]) : null;
}
// Are we in the admin? this is important!
@@ -340,13 +390,13 @@ class Pages
$config = $this->grav['config'];
// See if route matches one in the site configuration
$route = $config->get("site.routes.{$url}");
if ($route) {
$page = $this->dispatch($route, $all);
$site_route = $config->get("site.routes.{$route}");
if ($site_route) {
$page = $this->dispatch($site_route, $all);
} else {
// Try Regex style redirects
$uri = $this->grav['uri'];
$source_url = $url;
$source_url = $route;
$extension = $uri->extension();
if (isset($extension) && !Utils::endsWith($uri->url(), $extension)) {
$source_url.= '.' . $extension;

View File

@@ -39,6 +39,11 @@ class PageServiceProvider implements ServiceProviderInterface
/** @var Language $language */
$language = $c['language'];
// some debugger override logic
if ($page->debugger() === false) {
Grav::instance()['debugger']->enabled(false);
}
if ($c['config']->get('system.force_ssl')) {
if (!isset($_SERVER['HTTPS']) || $_SERVER["HTTPS"] != "on") {
$url = "https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];

View File

@@ -311,17 +311,23 @@ class Themes extends Iterator
*/
protected function autoloadTheme($class)
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$prefix = "Grav\\Theme";
if (false !== strpos($class, $prefix)) {
// Remove prefix from class
$class = substr($class, strlen($prefix));
// Replace namespace tokens to directory separators
// Try Old style theme classes
$path = strtolower(ltrim(preg_replace('#\\\|_(?!.+\\\)#', '/', $class), '/'));
$file = $locator->findResource("themes://{$path}/{$path}.php");
$file = $this->grav['locator']->findResource("themes://{$path}/{$path}.php");
// Load class
if (file_exists($file)) {
return include_once($file);
}
// Replace namespace tokens to directory separators
$path = $this->grav['inflector']->hyphenize(ltrim($class,"\\"));
$file = $this->grav['locator']->findResource("themes://{$path}/{$path}.php");
// Load class
if (file_exists($file)) {

View File

@@ -11,6 +11,7 @@ namespace Grav\Common\Twig;
use Grav\Common\Grav;
use Grav\Common\Config\Config;
use Grav\Common\Language\Language;
use Grav\Common\Language\LanguageCodes;
use Grav\Common\Page\Page;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Event\Event;
@@ -173,7 +174,7 @@ class Twig
'theme_dir' => $locator->findResource('theme://'),
'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false),
'html_lang' => $this->grav['language']->getActive() ?: $config->get('site.default_lang', 'en'),
'language_codes' => new LanguageCodes,
];
}
}
@@ -229,25 +230,22 @@ class Twig
$twig_vars['header'] = $item->header();
$local_twig = clone($this->twig);
$modular_twig = $item->modularTwig();
$process_twig = isset($item->header()->process['twig']) ? $item->header()->process['twig'] : false;
$output = '';
try {
// Process Modular Twig
if ($modular_twig) {
if ($item->modularTwig()) {
$twig_vars['content'] = $content;
$template = $item->template() . TEMPLATE_EXT;
$output = $content = $local_twig->render($template, $twig_vars);
}
// Process in-page Twig
if (!$modular_twig || ($modular_twig && $process_twig)) {
if ($item->shouldProcess('twig')) {
$name = '@Page:' . $item->path();
$this->setTemplate($name, $content);
$output = $local_twig->render($name, $twig_vars);
}
} catch (\Twig_Error_Loader $e) {
throw new \RuntimeException($e->getRawMessage(), 404, $e);
}

View File

@@ -104,7 +104,8 @@ class TwigExtension extends \Twig_Extension
new \Twig_simpleFunction('authorize', [$this, 'authorize']),
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateFunc']),
new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
@@ -118,6 +119,7 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
new \Twig_SimpleFunction('redirect_me', [$this, 'redirectFunc']),
new \Twig_SimpleFunction('range', [$this, 'rangeFunc']),
new \Twig_SimpleFunction('isajaxrequest', [$this, 'isAjaxFunc']),
];
}
@@ -569,15 +571,22 @@ class TwigExtension extends \Twig_Extension
$domain = true;
}
if (Grav::instance()['uri']->isExternal($input)) {
return $input;
}
if (strpos((string)$input, '://')) {
$input = ltrim((string)$input, '/');
if (Utils::contains((string)$input, '://')) {
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
// Get relative path to the resource (or false if not found).
$resource = $locator->findResource((string)$input, false);
$resource = $locator->findResource($input, false);
} else {
$resource = (string)$input;
$resource = $input;
}
/** @var Uri $uri */
@@ -587,22 +596,51 @@ class TwigExtension extends \Twig_Extension
}
/**
* Evaluate a string
* This function will evaluate Twig $twig through the $environment, and return its results.
*
* @example {{ evaluate('grav.language.getLanguage') }}
*
* @param string $input String to be evaluated
*
* @return string Returns the evaluated string
* @param \Twig_Environment $environment
* @param array $context
* @param string $twig
* @return mixed
*/
public function evaluateFunc($input)
{
if (!$input) { //prevent an obscure Twig error if $input is not set
$input = '""';
}
return $this->grav['twig']->processString("{{ $input }}");
public function evaluateTwigFunc( \Twig_Environment $environment, $context, $twig ) {
$loader = $environment->getLoader( );
$parsed = $this->parseString( $environment, $context, $twig );
$environment->setLoader( $loader );
return $parsed;
}
/**
* This function will evaluate a $string through the $environment, and return its results.
*
* @param \Twig_Environment $environment
* @param $context
* @param $string
* @return mixed
*/
public function evaluateStringFunc(\Twig_Environment $environment, $context, $string )
{
$parsed = $this->evaluateTwigFunc($environment, $context, "{{ $string }}");
return $parsed;
}
/**
* Sets the parser for the environment to Twig_Loader_String, and parsed the string $string.
*
* @param \Twig_Environment $environment
* @param array $context
* @param string $string
* @return string
*/
protected function parseString( \Twig_Environment $environment, $context, $string ) {
$environment->setLoader( new \Twig_Loader_String( ) );
return $environment->render( $string, $context );
}
/**
* Based on Twig_Extension_Debug / twig_var_dump
* (c) 2011 Fabien Potencier
@@ -855,4 +893,17 @@ class TwigExtension extends \Twig_Extension
{
return range($start, $end, $step);
}
/**
* Check if HTTP_X_REQUESTED_WITH has been set to xmlhttprequest,
* in which case we may unsafely assume ajax. Non critical use only.
*
* @return true if HTTP_X_REQUESTED_WITH exists and has been set to xmlhttprequest
*/
public function isAjaxFunc()
{
return (
!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
}
}

View File

@@ -239,4 +239,20 @@ class User extends Data
{
return $this->authorize($action);
}
/**
* Return the User's avatar URL
*
* @return string
*/
public function avatarUrl()
{
if ($this->avatar) {
$avatar = $this->avatar;
$avatar = array_shift($avatar);
return Grav::instance()['base_url'] . '/' . $avatar['path'];
} else {
return 'https://www.gravatar.com/avatar/' . md5($this->email);
}
}
}

View File

@@ -15,6 +15,7 @@ use Grav\Common\GPM\Installer;
use Grav\Common\GPM\Response;
use Grav\Console\ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class DirectInstallCommand extends ConsoleCommand
@@ -31,7 +32,20 @@ class DirectInstallCommand extends ConsoleCommand
->addArgument(
'package-file',
InputArgument::REQUIRED,
'The local location or remote URL to an installable package file'
'Installable package local <path> or remote <URL>. Can install specific version'
)
->addOption(
'all-yes',
'y',
InputOption::VALUE_NONE,
'Assumes yes (or best approach) instead of prompting'
)
->addOption(
'destination',
'd',
InputOption::VALUE_OPTIONAL,
'The destination where the package should be installed at. By default this would be where the grav instance has been launched from',
GRAV_ROOT
)
->setDescription("Installs Grav, plugin, or theme directly from a file or a URL")
->setHelp('The <info>direct-install</info> command installs Grav, plugin, or theme directly from a file or a URL');
@@ -42,12 +56,26 @@ class DirectInstallCommand extends ConsoleCommand
*/
protected function serve()
{
// Making sure the destination is usable
$this->destination = realpath($this->input->getOption('destination'));
if (
!Installer::isGravInstance($this->destination) ||
!Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK])
) {
$this->output->writeln("<red>ERROR</red>: " . Installer::lastErrorMsg());
exit;
}
$this->all_yes = $this->input->getOption('all-yes');
$package_file = $this->input->getArgument('package-file');
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Are you sure you want to direct-install <cyan>'.$package_file.'</cyan> [y|N] ', false);
$answer = $helper->ask($this->input, $this->output, $question);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("exiting...");
@@ -72,7 +100,7 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->writeln('');
exit;
}
if ($zip) {
$this->output->write("\x0D");
$this->output->write(" |- Downloading package... 100%");
@@ -97,6 +125,8 @@ class DirectInstallCommand extends ConsoleCommand
if (!$extracted) {
$this->output->write("\x0D");
$this->output->writeln(" |- Extracting package... <red>failed</red>");
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
@@ -109,6 +139,8 @@ class DirectInstallCommand extends ConsoleCommand
if (!$type) {
$this->output->writeln(" '- <red>ERROR: Not a valid Grav package</red>");
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
@@ -117,22 +149,27 @@ class DirectInstallCommand extends ConsoleCommand
if (isset($blueprint['dependencies'])) {
$depencencies = [];
foreach ($blueprint['dependencies'] as $dependency) {
if (is_array($dependency) && isset($dependency['name'])) {
$depencencies[] = $dependency['name'];
if (is_array($dependency)){
if (isset($dependency['name'])) {
$depencencies[] = $dependency['name'];
}
if (isset($dependency['github'])) {
$depencencies[] = $dependency['github'];
}
} else {
$depencencies[] = $dependency;
$depencencies[] = $dependency;
}
}
$this->output->writeln(" |- Dependencies found... <cyan>[" . implode(',', $depencencies) . "]</cyan>");
$question = new ConfirmationQuestion(" | '- Dependencies will not be satisfied. Continue ? [y|N] ", false);
$answer = $helper->ask($this->input, $this->output, $question);
$answer = $this->all_yes ? true : $helper->ask($this->input, $this->output, $question);
if (!$answer) {
$this->output->writeln("exiting...");
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
}
@@ -147,6 +184,8 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
$this->output->writeln(" '- <red>ERROR: symlinks found...</red> <yellow>" . GRAV_ROOT."</yellow>");
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
@@ -161,6 +200,8 @@ class DirectInstallCommand extends ConsoleCommand
if (!$name) {
$this->output->writeln("<red>ERROR: Name could not be determined.</red> Please specify with --name|-n");
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
}
@@ -175,6 +216,8 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->writeln(" |- Checking destination... <yellow>symbolic link</yellow>");
$this->output->writeln(" '- <red>ERROR: symlink found...</red> <yellow>" . GRAV_ROOT . DS . $install_path . '</yellow>');
$this->output->writeln('');
Folder::delete($tmp_source);
Folder::delete($tmp_zip);
exit;
} else {
@@ -184,7 +227,16 @@ class DirectInstallCommand extends ConsoleCommand
$this->output->write(" |- Installing package... ");
Installer::install($zip, GRAV_ROOT, ['install_path' => $install_path, 'theme' => (($type == 'theme')), 'is_update' => $is_update], $extracted);
Installer::install(
$zip,
$this->destination,
$options = [
'install_path' => $install_path,
'theme' => (($type == 'theme')),
'is_update' => $is_update
],
$extracted
);
}
Folder::delete($tmp_source);

View File

@@ -80,7 +80,7 @@ class InstallCommand extends ConsoleCommand
->addArgument(
'package',
InputArgument::IS_ARRAY | InputArgument::REQUIRED,
'The package(s) that are desired to be installed. Use the "index" command for a list of packages'
'Package(s) to install. Use "bin/gpm index" to list packages. Use "bin/gpm direct-install" to install a specific version'
)
->setDescription("Performs the installation of plugins and themes")
->setHelp('The <info>install</info> command allows to install plugins and themes');
@@ -563,6 +563,7 @@ class InstallCommand extends ConsoleCommand
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
$this->tmp = $tmp_dir . '/Grav-' . uniqid();
$filename = $package->slug . basename($package->zipball_url);
$filename = preg_replace('/[\\\\\/:"*?&<>|]+/mi', '-', $filename);
$query = '';
if ($package->premium) {

View File

@@ -0,0 +1,40 @@
<?php
use Codeception\Util\Fixtures;
use Grav\Common\Grav;
use Grav\Console\Gpm\DirectInstallCommand;
/**
* Class DirectInstallCommandTest
*/
class DirectInstallCommandTest extends \Codeception\TestCase\Test
{
/** @var Grav $grav */
protected $grav;
/** @var DirectInstallCommand */
protected $directInstall;
protected function _before()
{
$this->grav = Fixtures::get('grav');
$this->directInstallCommand = new DirectInstallCommand();
}
}
/**
* Why this test file is empty
*
* Wasn't able to call a symfony\console. Kept having $output problem.
* symfony console \NullOutput didn't cut it.
*
* We would also need to Mock tests since downloading packages would
* make tests slow and unreliable. But it's not worth the time ATM.
*
* Look at Gpm/InstallCommandTest.php
*
* For the full story: https://git.io/vSlI3
*/

View File

@@ -41,6 +41,7 @@ class AssetsTest extends \Codeception\TestCase\Test
'priority' => 10,
'order' => 0,
'pipeline' => true,
'loading' => '',
'group' => 'head',
'modified' => false
], reset($array));
@@ -56,6 +57,7 @@ class AssetsTest extends \Codeception\TestCase\Test
'priority' => 10,
'order' => 0,
'pipeline' => true,
'loading' => '',
'group' => 'head',
'modified' => false
], reset($array));
@@ -73,6 +75,7 @@ class AssetsTest extends \Codeception\TestCase\Test
'priority' => 10,
'order' => 0,
'pipeline' => true,
'loading' => '',
'group' => 'head',
'modified' => false
], reset($array));
@@ -90,6 +93,7 @@ class AssetsTest extends \Codeception\TestCase\Test
'priority' => 10,
'order' => 0,
'pipeline' => true,
'loading' => '',
'group' => 'head',
'modified' => false
], reset($array));
@@ -133,6 +137,7 @@ class AssetsTest extends \Codeception\TestCase\Test
'priority' => 10,
'order' => 0,
'pipeline' => true,
'loading' => '',
'group' => 'footer',
'modified' => false
], reset($array));
@@ -192,6 +197,22 @@ class AssetsTest extends \Codeception\TestCase\Test
'modified' => false
], reset($array));
//Test inline
$this->assets->reset();
$this->assets->addJs('/system/assets/jquery/jquery-2.x.min.js', null, true, 'inline', null);
$js = $this->assets->js();
$this->assertContains('jQuery Foundation', $js);
$this->assets->reset();
$this->assets->addCss('/system/assets/debugger.css', null, true, null, 'inline');
$css = $this->assets->css();
$this->assertContains('div.phpdebugbar', $css);
$this->assets->reset();
$this->assets->addCss('https://fonts.googleapis.com/css?family=Roboto', null, true, null, 'inline');
$css = $this->assets->css();
$this->assertContains('font-family: \'Roboto\';', $css);
//Test adding media queries
$this->assets->reset();
$this->assets->add('test.css', ['media' => 'only screen and (min-width: 640px)']);
@@ -336,6 +357,24 @@ class AssetsTest extends \Codeception\TestCase\Test
$this->assertContains($this->assets->getTimestamp(), $css);
}
public function testInlinePipeline()
{
$this->assets->reset();
//File not existing. Pipeline searches for that file without reaching it. Output is empty.
$this->assets->add('test.css', null, true);
$this->assets->setCssPipeline(true);
$css = $this->assets->css('head', ['loading' => 'inline']);
$this->assertSame('', $css);
//Add a core Grav CSS file, which is found. Pipeline will now return its content.
$this->assets->addCss('https://fonts.googleapis.com/css?family=Roboto', null, true);
$this->assets->add('/system/assets/debugger.css', null, true);
$css = $this->assets->css('head', ['loading' => 'inline']);
$this->assertContains('font-family:\'Roboto\';', $css);
$this->assertContains('div.phpdebugbar', $css);
}
public function testAddAsyncJs()
{
$this->assets->reset();

View File

@@ -11,7 +11,7 @@ class GpmStub extends GPM
{
public $data;
public function findPackage($packageName)
public function findPackage($packageName, $ignore_exception = false)
{
if (isset($this->data[$packageName])) {
return $this->data[$packageName];
@@ -320,4 +320,4 @@ class GpmTest extends \Codeception\TestCase\Test
$this->assertSame(null, $this->gpm->calculateVersionNumberFromDependencyVersion('*'));
$this->assertSame('2.0.2', $this->gpm->calculateVersionNumberFromDependencyVersion('2.0.2'));
}
}
}

View File

@@ -23,7 +23,7 @@ $HTTP["querystring"] =~ "_REQUEST(=|\[|\%[0-9A-Z])" {
#REROUTING TO THE INDEX PAGE
url.rewrite-if-not-file = (
"^/grav_path/(.*)$" => "/grav_path/index.php"
"^/grav_path/(.*)$" => "/grav_path/index.php$1"
)
#IMPROVING SECURITY