1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/grav_ynh.git synced 2024-09-03 19:16:01 +02:00

Add sources

This commit is contained in:
magikcypress 2015-11-11 18:23:02 +01:00
parent 0d4f8fd17b
commit d8aabe9663
1844 changed files with 201002 additions and 3 deletions

View file

@ -4,4 +4,5 @@
### Configuration
1. Start url https://domaine.tld/grav
1. Start url https://domaine.tld/grav

View file

@ -28,7 +28,7 @@ db_user=grav
# Copy files to the right place
final_path=/var/www/grav
sudo mkdir -p $final_path
sudo cp -a ../sources/* $final_path
sudo cp -a ../src/* $final_path
# Set permissions to roundcube directory
sudo chown -R www-data: $final_path

@ -1 +0,0 @@
Subproject commit d3097e4fd00cc4ea3b0a8a16ff529358cb30cdbd

221
src/.debug Normal file
View file

@ -0,0 +1,221 @@
<?php
/**
* Valid output methods are: auto, FirePHP, html and text
* The auto mode will select FirePHP if it is available, otherwise it will use html
* Using FirePHP requires FireBug with the FirePHP extension enabled in FireFox (instructions at firephp.org)
*/
define('DEBUG_OUTPUT_METHOD', 'auto');
/**
* Powers the debug system
*/
class debug {
/**
* Internal buffer exposing data between methods
* @var float $_timeStart
* @var string $_mode, $_modeStatic
*/
protected $_timeStart, $_mode;
protected static $_modeStatic;
/**
* Time storage for the timer
* @var array
*/
public static $timer = array();
/**
* Internal buffer to store SQL queries and log messages
* @var array
*/
private static $_queries = array(), $_log = array(), $_initTime = null;
/**
* Get the PHP microtime() relative to the start of the instance
* @return float
*/
public static function microtime() {
$now = microtime(true);
if (!self::$_initTime) {
self::$_initTime = $now;
}
return ($now - self::$_initTime);
}
/**
* Timer - sets breakpoints (aka. split times) and returns time since last breakpoint
*
* @param mixed $breakpointId Optional, any vector type is valid
* @param mixed $id Optional, any vector type is valid
* @return string Nicely formatted float
*/
public static function timer($breakpointId = null, $id = 'default') {
if (!isset(self::$timer[$id])) {
$return = 0;
}
if (!$breakpointId) {
self::$timer[$id][] = self::microtime();
} else {
self::$timer[$id][$breakpointId] = self::microtime();
}
if (!isset($return)) {
$return = (end(self::$timer[$id]) - prev(self::$timer[$id]));
}
return number_format($return, 5);
}
/**
* Return the total execution time for a timer - minimum of two breakpoints required for a non-zero return
*
* @param mixed $id Optional, any vector type is valid
* @return string Nicely formatted float
*/
public static function timerTotal($id = 'default') {
$return = 0;
if (isset(self::$timer[$id]) && count(self::$timer[$id]) > 1) {
$return = (end(self::$timer[$id]) - reset(self::$timer[$id]));
}
return number_format($return, 5);
}
/**
* Reports the total time table for a timer via the debug output mode
*
* @param mixed $id Optional, any vector type is valid
* @return string Nicely formatted float
*/
public static function timerReport($id = 'default') {
if (!isset(self::$timer[$id])) {
return null;
}
$last = reset(self::$timer[$id]);
if (self::$_modeStatic == 'FirePHP') {
$FirePHP = FirePHP::getInstance(true);
foreach (self::$timer[$id] as $breakpointId => $val) {
$FirePHP->info((string) $breakpointId, number_format(($val - $last), 5));
$last = $val;
}
} else {
foreach (self::$timer[$id] as $breakpointId => $val) {
$return[] = number_format(($val - $last), 5) . ' - ' . $breakpointId;
$last = $val;
}
if (self::$_modeStatic == 'html' || self::$_modeStatic == 'text') {
$recordDelimiter = PHP_EOL . PHP_EOL;
if (self::$_modeStatic == 'html') {
$recordDelimiter .= '<hr />';
}
echo $recordDelimiter . implode($recordDelimiter, $return) . $recordDelimiter;
}
return $return;
}
}
/**
* Logs a SQL query
*
* @param string $query
* @param float $executionTime Optional
* @param string $error Optional
*/
public static function logQuery($query, $executionTime = null, $error = null) {
self::$_queries[] = array('query' => $query, 'executionTime' => $executionTime, 'error' => $error);
}
/**
* Logs a message
*
* @param str $data
* @param str $type Valid options are log, info, warn and error
*/
public static function log($data, $type = 'log') {
self::$_log[] = array('data' => $data, 'method' => $type, 'time' => self::microtime());
}
/**
* Initializes the debug system
*
* @param string $mode Optional. Mode of output by default is html, but the framework automatically overrides
* this with your DEBUG_OUTPUT_METHOD setting
*/
public function __construct($mode = 'html') {
if ($mode == 'auto') {
$mode = (strpos($_SERVER['HTTP_USER_AGENT'], 'FirePHP') !== false ? 'FirePHP' : 'html');
}
if ($mode == 'FirePHP' && !class_exists('FirePHP')) {
$configInit = new configInit;
$firephpDir = $configInit->packagesPath() . 'FirePHPCore' . $configInit->DS;
if (is_dir($firephpDir)) {
require_once $firephpDir . 'FirePHP.class.php';
}
if (!class_exists('FirePHP')) {
$mode = 'html';
}
}
if ($mode == 'FirePHP' && ini_get('output_buffering') != 'On') {
ob_start();
}
self::$_modeStatic = $this->_mode = $mode;
$this->_timeStart = self::microtime();
}
/**
* Tallies up the debug info and outputs it
*/
public function __destruct() {
$execTime = 'Page execution time: ' . number_format(self::microtime() - $this->_timeStart, 5);
$sqlExecString = 'Execution time of previous SQL: ';
if ($this->_mode == 'FirePHP') {
$FirePHP = FirePHP::getInstance(true);
$FirePHP->info($execTime);
if (!empty(self::$_log)) {
$timeLast = $this->_timeStart;
foreach (self::$_log as $logArray) {
$data = '(' . number_format($logArray['time'] - $timeLast, 5) . ') ' . $logArray['data'];
$FirePHP->{$logArray['method']}($data);
$timeLast = $logArray['time'];
}
}
if (!empty(self::$_queries)) {
foreach (self::$_queries as $queryArray) {
$FirePHP->group('SQL');
$FirePHP->log($queryArray['query']);
if ($queryArray['executionTime'] !== null) {
$FirePHP->info($sqlExecString . $queryArray['executionTime']);
}
if ($queryArray['error'] !== null) {
$FirePHP->error($queryArray['error']);
}
$FirePHP->groupEnd();
}
}
} else {
$recordDelimiter = PHP_EOL . PHP_EOL;
$lineDelimiter = PHP_EOL;
if ($this->_mode == 'html') {
echo '<br style="clear: both;" />';
$recordDelimiter .= '<hr />';
$lineDelimiter .= '<br />';
}
echo $recordDelimiter . $execTime;
if (!empty(self::$_log)) {
foreach (self::$_log as $logArray) {
echo $lineDelimiter . $logArray['method'] . ': ' . get::htmlentities($logArray['data']);
}
}
if (!empty(self::$_queries)) {
foreach (self::$_queries as $queryArray) {
echo $recordDelimiter . ($this->_mode == 'html' ? get::htmlentities($queryArray['query'])
: $queryArray['query']);
if ($queryArray['executionTime'] !== null) {
echo $lineDelimiter . $sqlExecString . $queryArray['executionTime'];
}
if ($queryArray['error'] !== null) {
echo $lineDelimiter . $queryArray['error'];
}
}
}
}
}
}
$debugObject = new debug(DEBUG_OUTPUT_METHOD);

5
src/.hg_archival.txt Normal file
View file

@ -0,0 +1,5 @@
repo: 4bb20edbf41fe2cf5294bfad1428e943f7d3fe65
node: 01251bf0ea8aeaec434f4226bee735eaf1bc14ba
branch: default
latesttag: null
latesttagdistance: 25

1
src/.hgignore Normal file
View file

@ -0,0 +1 @@
.DS_Store

63
src/.htaccess Normal file
View file

@ -0,0 +1,63 @@
<IfModule mod_rewrite.c>
RewriteEngine On
## 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 - 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 - 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 ^(.git|cache|bin|logs|backup)/(.*) 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]
# Block all direct access to .md files:
RewriteRule \.md$ error [L]
# Block all direct access to files and folders beginning with a dot
RewriteRule (^\.|/\.) - [F]
# Block access to specific files in the root folder
RewriteRule ^(LICENSE|composer.lock|composer.json|nginx.conf|web.config)$ error [F]
## End - Security
</IfModule>
# Begin - Prevent Browsing and Set Default Resources
Options -Indexes
DirectoryIndex index.php index.html index.htm
# End - Prevent Browsing and Set Default Resources

170
src/.installer Normal file
View file

@ -0,0 +1,170 @@
<?php
/**
* Upgrades the Vork framework and installs Vork Apps
*/
class vorkInstaller {
/**
* ID of the upload-form field
*/
const fileId = 'file';
/**
* Path to the install log - default is in the root of the Vork installation (one level below webroot)
*/
const logFile = '../.install.log';
/**
* Cache of the vorkInstall object contained in the install/upgrade package
* @var vorkInstall
*/
public $vorkInstall;
/**
* Copy instance-scope temp file into a regular persistant temp-file
* @return string Filename of the new temp-file
*/
protected function _makeUploadPersist() {
$persistantName = $_FILES[self::fileId]['tmp_name'] . '.vorkcache';
copy($_FILES[self::fileId]['tmp_name'], $persistantName);
return $persistantName;
}
/**
* Install/upgrade engine
*/
protected function _install($file = null) {
$iniFilename = 'vork.ini';
$za = new ZipArchive();
$za->open(!$file ? $_FILES[self::fileId]['tmp_name'] : $file);
if (!($fileCount = $za->numFiles) || !($this->vorkInstall = $za->statName($iniFilename))) {
$_POST['errors'][self::fileId] = 'Not a valid Vork app or Vork upgrade file (missing the ' . $iniFilename
. ' file), try re-downloading from www.Vork.us';
return;
}
$fp = $za->getStream($iniFilename);
while (!feof($fp)) {
$contents[] = fread($fp, 8192);
}
fclose($fp);
if (isset($contents)) {
$contents = implode($contents);
if (function_exists('parse_ini_string')) { //PHP 5.3+
$ini = parse_ini_string($contents);
} else {
$tmpName = tempnam(sys_get_temp_dir(), 'ini_');
file_put_contents($tmpName, $contents);
$ini = parse_ini_file($tmpName);
unlink($tmpName);
}
$ini['ts'] = time();
} else {
$_POST['errors'][self::fileId] = 'The ' . $iniFilename . ' file in this Vork app is invalid';
return false;
}
if (!$file && $za->statName('.installer')) { //upgrade installer, then re-run using the newest installer
$za->extractTo('..', '.installer');
$file = $this->_makeUploadPersist();
load::redirect('/vork?install=' . urlencode($file));
return;
}
$this->vorkInstall = new vorkInstall;
for ($i = 0; $i < $fileCount; $i++) {
$filepath = str_replace('\\', '/', $za->getNameIndex($i));
$filepathParts = explode('/', $filepath);
$fileName = array_pop($filepathParts);
$path = '';
foreach ($filepathParts as $part) {
$path .= $part;
if (!file_exists('../' . $path)) {
mkdir('../' . $path); //.. to drop back from webroot to Vork base directory
}
$path .= '/';
}
if ($fileName && $path . $fileName != $iniFilename && $path . $fileName != '.installer') {
$files[] = $path . $fileName; //directories are skipped since they have an empty string for $fileName
}
}
if (isset($files)) {
$za->extractTo('..', $files);
}
$za->close();
if ($file) {
unlink($file);
}
file_put_contents(self::logFile, json_encode($ini) . PHP_EOL, FILE_APPEND);
}
/**
* Verify file upload
*/
protected function _fileUploaded() {
if ($_FILES[self::fileId]['error'] == UPLOAD_ERR_OK && !$_FILES[self::fileId]['size']) {
$_FILES[self::fileId]['error'] = UPLOAD_ERR_NO_FILE;
}
switch ($_FILES[self::fileId]['error']) {
case UPLOAD_ERR_INI_SIZE:
$ini = 'upload_max_filesize';
//break intentionally omitted
case UPLOAD_ERR_FORM_SIZE:
$error = 'Error: file is too large to be uploaded - increase the '
. (isset($ini) ? $ini : 'MAX_FILE_SIZE') . ' setting in your php.ini file';
break;
case UPLOAD_ERR_PARTIAL:
case UPLOAD_ERR_NO_FILE:
$error = 'Error: the upload did not complete, please try again';
break;
case UPLOAD_ERR_NO_TMP_DIR:
$error = 'Error: your PHP installation has no temp-directory, create a folder at'
. realpath(sys_get_temp_dir()) . ' and grant PHP read/write access';
break;
case UPLOAD_ERR_CANT_WRITE:
$error = 'Error: cannot write to temp-directory, adjust your file-write permissions';
break;
case UPLOAD_ERR_OK:
$this->_install();
break;
}
if (isset($error)) {
$_POST['errors'][self::fileId] = $error;
}
}
/**
* View
*/
public function __construct() {
$html = get::helper('html');
$form = get::helper('form');
$title = 'Vork Installer + Upgrader';
echo $html->header(array('title' => $title, 'cssInline' =>
'body {background: #feeebd url(http://ajax.googleapis.com/ajax/libs/jqueryui/1/themes/sunny/images/'
. 'ui-bg_highlight-soft_100_feeebd_1x100.png) 50% top repeat-x; font-family: "Arial";} '
. 'fieldset {border: 0px solid;} h1 {margin: 0px;} .errormessage {color: #ff0000;}'));
echo $html->h1($title);
$formArgs['enctype'] = 'multipart/form-data';
if (isset($_FILES[self::fileId])) {
$this->_fileUploaded();
} else if (isset($_GET['install'])) {
$this->_install($_GET['install']);
}
if (!isset($_POST['errors']) && $this->vorkInstall) {
$formArgs['alert'] = 'Install/upgrade of ' . $this->vorkInstall->meta['package'] . ' '
. $this->vorkInstall->meta['version'] . ' completed';
}
echo $form->getFormOpen($formArgs);
echo $html->div($form->getInput(array('name' => self::fileId, 'type' => 'file',
'label' => 'Upload Vork App or Vork Framework Upgrade'))
. $form->getInput(array('type' => 'submit', 'value' => 'Install app/upgade')));
echo $form->getFormClose();
echo $html->footer();
}
}
new vorkInstaller;
exit(0);

892
src/CHANGELOG.md Normal file
View file

@ -0,0 +1,892 @@
# v1.0.0-rc.4
## 10/29/2015
1. [](#bugfix)
* Fixed a fatal error if you have a collection with missing or invalid `@page: /route`
# v1.0.0-rc.3
## 10/29/2015
1. [](#new)
* New Page collection options! `@self.parent, @self.siblings, @self.descendants` + more
* Whitelist of file types for fallback route functionality (images by default)
1. [](#improved)
* Assets switched from defines to streams
1. [](#bugfix)
* README.md typos fixed
* Fixed issue with routes that have lang string in them (`/en/english`)
* Trim strings before validation so whitespace is not satisfy 'required'
# v1.0.0-rc.2
## 10/27/2015
1. [](#new)
* Added support for CSS Asset groups
* Added a `wrapped_site` system option for themes/plugins to use
* Pass `Page` object as event to `onTwigPageVariables()` event hook
* New `Data.items()` method to get all items
1. [](#improved)
* Missing pipelined remote asset will now fail quietly
* More reliably handle inline JS and CSS to remove only surrounding HTML tags
* `Medium.meta` returns new Data object so null checks are possible
* Improved Medium metadata merging to allow for automatic title/alt/class attributes
* Moved Grav object to global variable rather than template variable (useful for macros)
* German language improvements
* Updated bundled composer
1. [](#bugfix)
* Accept variety of `true` values in `User.authorize()` method
* Fix for `Validation` throwing an error if no label set
# v1.0.0-rc.1
## 10/23/2015
1. [](#new)
* Use native PECL YAML parser if installed for 4X speed boost in parsing YAML files
* Support for inherited theme class
* Added new default language prepend system configuration option
* New `|evaluate` Twig filter to evaluate a string as twig
* New system option to ignore all **hidden** files and folders
* New system option for default redirect code
* Added ability to append specific `[30x]` codes to redirect URLs
* Added `url_taxonomy_filters` for page collections
* Added `@root` page and `recurse` flag for page collections
* Support for **multiple** page collection types as an array
* Added Dutch language file
* Added Russian language file
* Added `remove` method to User object
1. [](#improved)
* Moved hardcoded mimetypes to `media.yaml` to be treated as Page media files
* Set `errors: display: false` by default in `system.yaml`
* Strip out extra slashes in the URI
* Validate hostname to ensure it is valid
* Ignore more SCM folders in Backups
* Removed `home_redirect` settings from `system.yaml`
* Added Page `media` as root twig object for consistency
* Updated to latest vendor libraries
* Optimizations to Asset pipeline logic for minor speed increase
* Block direct access to a variety of files in `.htaccess` for increased security
* Debugbar vendor library update
* Always fallback to english if other translations are not available
1. [](#bugfix)
* Fix for redirecting external URL with multi-language
* Fix for Asset pipeline not respecting asset groups
* Fix language files with child/parent theme relationships
* Fixed a regression issue resulting in incorrect default language
* Ensure error handler is initialized before URI is processed
* Use default language in Twig if active language is not set
* Fixed issue with `safeEmailFilter()` Twig filter not separating with `;` properly
* Fixed empty YAML file causing error with native PECL YAML parser
* Fixed `SVG` mimetype
* Fixed incorrect `Cache-control: max-age` value format
# v0.9.45
## 10/08/2015
1. [](#bugfix)
* Fixed a regression issue resulting in incorrect default language
# v0.9.44
## 10/07/2015
1. [](#new)
* Added Redis back as a supported cache mechanism
* Allow Twig `nicetime` translations
* Added `-y` option for 'Yes to all' in `bin/gpm update`
* Added CSS `media` attribute to the Assets manager
* New German language support
* New Czech language support
* New French language support
* Added `modulus` twig filter
1. [](#improved)
* URL decode in medium actions to allow complex syntax
* Take into account `HTTP_HOST` before `SERVER_NAME` (helpful with Nginx)
* More friendly cache naming to ease manual management of cache systems
* Added default Apache resource for `DirectoryIndex`
1. [](#bugfix)
* Fix GPM failure when offline
* Fix `open_basedir` error in `bin/gpm install`
* Fix an HHVM error in Truncator
* Fix for XSS vulnerability with params
* Fix chaining for responsive size derivatives
* Fix for saving pages when removing the page title and all other header elements
* Fix when saving array fields
* Fix for ports being included in `HTTP_HOST`
* Fix for Truncator to handle PHP tags gracefully
* Fix for locate style lang codes in `getNativeName()`
* Urldecode image basenames in markdown
# v0.9.43
## 09/16/2015
1. [](#new)
* Added new `AudioMedium` for HTML5 audio
* Added ability for Assets to be added and displayed in separate *groups*
* New support for responsive image derivative sizes
1. [](#improved)
* GPM theme install now uses a `copy` method so new files are not lost (e.g. `/css/custom.css`)
* Code analysis improvements and cleanup
* Removed Twig panel from debugger (no longer supported in Twig 1.20)
* Updated composer packages
* Prepend active language to `convertUrl()` when used in markdown links
* Added some pre/post flight options for installer via blueprints
* Hyphenize the site name in the backup filename
1. [](#bugfix)
* Fix broken routable logic
* Check for `phpinfo()` method in case it is restricted by hosting provider
* Fixes for windows when running GPM
* Fix for ampersand (`&`) causing error in `truncateHtml()` via `Page.summary()`
# v0.9.42
## 09/11/2015
1. [](#bugfix)
* Fixed `User.authorise()` to be backwards compabile
# v0.9.41
## 09/11/2015
1. [](#new)
* New and improved multibyte-safe TruncateHTML function and filter
* Added support for custom page date format
* Added a `string` Twig filter to render as json_encoded string
* Added `authorize` Twig filter
* Added support for theme inheritance in the admin
* Support for multiple content collections on a page
* Added configurable files/folders ignores for pages
* Added the ability to set the default PHP locale and override via multi-lang configuration
* Added ability to save as YAML via admin
* Added check for `mbstring` support
* Added new `redirect` header for pages
1. [](#improved)
* Changed dependencies from `develop` to `master`
* Updated logging to log everything from `debug` level on (was `warning`)
* Added missing `accounts/` folder
* Default to performing a 301 redirect for URIs with trailing slashes
* Improved Twig error messages
* Allow validating of forms from anywhere such as plugins
* Added logic so modular pages are by default non-routable
* Hide password input in `bin/grav newuser` command
1. [](#bugfix)
* Fixed `Pages.all()` not returning modular pages
* Fix for modular template types not getting found
* Fix for `markdown_extra:` overriding `markdown:extra:` setting
* Fix for multi-site routing
* Fix for multi-lang page name error
* Fixed a redirect loop in `URI` class
* Fixed a potential error when `unsupported_inline_types` is empty
* Correctly generate 2x retina image
* Typo fixes in page publish/unpublish blueprint
# v0.9.40
## 08/31/2015
1. [](#new)
* Added some new Twig filters: `defined`, `rtrim`, `ltrim`
* Admin support for customizable page file name + template override
1. [](#improved)
* Better message for incompatible/unsupported Twig template
* Improved User blueprints with better help
* Switched to composer **install** rather than **update** by default
* Admin autofocus on page title
* `.htaccess` hardening (`.htaccess` & `htaccess.txt`)
* Cache safety checks for missing folders
1. [](#bugfix)
* Fixed issue with unescaped `o` character in date formats
# v0.9.39
## 08/25/2015
1. [](#bugfix)
* `Page.active()` not triggering on **homepage**
* Fix for invalid session name in Opera browser
# v0.9.38
## 08/24/2015
1. [](#new)
* Added `language` to **user** blueprint
* Added translations to blueprints
* New extending logic for blueprints
* Blueprints are now loaded with Streams to allow for better overrides
* Added new Symfony `dump()` method
1. [](#improved)
* Catch YAML header parse exception so site doesn't die
* Better `Page.parent()` logic
* Improved GPM display layout
* Tweaked default page layout
* Unset route and slug for improved reliability of route changes
* Added requirements to README.md
* Updated various libraries
* Allow use of custom page date field for dateRange collections
1. [](#bugfix)
* Slug fixes with GPM
* Unset plaintext password on save
* Fix for trailing `/` not matching active children
# v0.9.37
## 08/12/2015
3. [](#bugfix)
* Fixed issue when saving `header.process` in page forms via the **admin plugin**
* Fixed error due to use of `set_time_limit` that might be disabled on some hosts
# v0.9.36
## 08/11/2015
1. [](#new)
* Added a new `newuser` CLI command to create user accounts
* Added `default` blueprint for all templates
* Support `user` and `system` language translation merging
1. [](#improved)
* Added isSymlink method in GPM to determine if Grav is symbolically linked or not
* Refactored page recursing
* Updated blueprints to use new toggles
* Updated blueprints to use current date for date format fields
* Updated composer.phar
* Use sessions for admin even when disabled for site
* Use `GRAV_ROOT` in session identifier
# v0.9.35
## 08/06/2015
1. [](#new)
* Added `body_classes` field
* Added `visiblity` toggle and help tooltips on new page form
* Added new `Page.unsetRoute()` method to allow admin to regenerate the route
2. [](#improved)
* User save no longer stores username each time
* Page list form field now shows all pages except root
* Removed required option from page title
* Added configuration settings for running Nginx in sub directory
3. [](#bugfix)
* Fixed deep translation merging
* Fixed broken **metadata** merging with site defaults
* Fixed broken **summary** field
* Fixed broken robots field
* Fixed GPM issue when using cURL, throwing an `Undefined offset: 1` exception
* Removed duplicate hidden page `type` field
# v0.9.34
## 08/04/2015
1. [](#new)
* Added new `cache_all` system setting + media `cache()` method
* Added base languages configuration
* Added property language to page to help plugins identify page language
* New `Utils::arrayFilterRecursive()` method
2. [](#improved)
* Improved Session handling to support site and admin independently
* Allow Twig variables to be modified in other events
* Blueprint updates in preparation for Admin plugin
* Changed `Inflector` from static to object and added multi-language support
* Support for admin override of a page's blueprints
3. [](#bugfix)
* Removed unused `use` in `VideoMedium` that was causing error
* Array fix in `User.authorise()` method
* Fix for typo in `translations_fallback`
* Fixed moving page to the root
# v0.9.33
## 07/21/2015
1. [](#new)
* Added new `onImageMediumSaved()` event (useful for post-image processing)
* Added `Vary: Accept-Encoding` option
2. [](#improved)
* Multilang-safe delimeter position
* Refactored Twig classes and added optional umask setting
* Removed `pageinit()` timing
* `Page->routable()` now takes `published()` state into account
* Improved how page extension is set
* Support `Language->translate()` method taking string and array
3. [](#bugfix)
* Fixed `backup` command to include empty folders
# v0.9.32
## 07/14/2015
1. [](#new)
* Detect users preferred language via `http_accept_language` setting
* Added new `translateArray()` language method
2. [](#improved)
* Support `en` translations by default for plugins & themes
* Improved default generator tag
* Minor language tweaks and fixes
3. [](#bugfix)
* Fix for session active language and homepage redirects
* Ignore root-level page rather than throwing error
# v0.9.31
## 07/09/2015
1. [](#new)
* Added xml, json, css and js to valid media file types
2. [](#improved)
* Better handling of unsupported media type downloads
* Improved `bin/grav backup` command to mimic admin plugin location/name
3. [](#bugfix)
* Critical fix for broken language translations
* Fix for Twig markdown filter error
* Safety check for download extension
# v0.9.30
## 07/08/2015
1. [](#new)
* BIG NEWS! Extensive Multi-Language support is all new in 0.9.30!
* Translation support via Twig filter/function and PHP method
* Page specific default route
* Page specific route aliases
* Canonical URL route support
* Added built-in session support
* New `Page.rawRoute()` to get a consistent folder-based route to a page
* Added option to always redirect to default page on alias URL
* Added language safe redirect function for use in core and plugins
2. [](#improved)
* Improved `Page.active()` and `Page.activeChild()` methods to support route aliases
* Various spelling corrections in `.php` comments, `.md` and `.yaml` files
* `Utils::startsWith()` and `Utils::endsWith()` now support needle arrays
* Added a new timer around `pageInitialized` event
* Updated jQuery library to v2.1.4
3. [](#bugfix)
* In-page CSS and JS files are now handled properly
* Fix for `enable_media_timestamp` not working properly
# v0.9.29
## 06/22/2015
1. [](#new)
* New and improved Regex-powered redirect and route alias logic
* Added new `onBuildPagesInitialized` event for memory critical or time-consuming plugins
* Added a `setSummary()` method for pages
2. [](#improved)
* Improved `MergeConfig()` logic for more control
* Travis skeleton build trigger implemented
* Set composer.json versions to stable versions where possible
* Disabled `last_modified` and `etag` page headers by default (causing too much page caching)
3. [](#bugfix)
* Preload classes during `bin/gpm selfupgrade` to avoid issues with updated classes
* Fix for directory relative _down_ links
# v0.9.28
## 06/16/2015
1. [](#new)
* Added method to set raw markdown on a page
* Added ability to enabled system and page level `etag` and `last_modified` headers
2. [](#improved)
* Improved image path processing
* Improved query string handling
* Optimization to image handling supporting URL encoded filenames
* Use global `composer` when available rather than Grv provided one
* Use `PHP_BINARY` contant rather than `php` executable
* Updated Doctrine Cache library
* Updated Symfony libraries
* Moved `convertUrl()` method to Uri object
3. [](#bugfix)
* Fix incorrect slug causing problems with CLI `uninstall`
* Fix Twig runtime error with assets pipeline in sufolder installations
* Fix for `+` in image filenames
* Fix for dot files causing issues with page processing
* Fix for Uri path detection on Windows platform
* Fix for alternative media resolutions
* Fix for modularTypes key properties
# v0.9.27
## 05/09/2015
1. [](#new)
* Added new composer CLI command
* Added page-level summary header overrides
* Added `size` back for Media objects
* Refactored Backup command in preparation for admin plugin
* Added a new `parseLinks` method to Plugins class
* Added `starts_with` and `ends_with` Twig filters
2. [](#improved)
* Optimized install of vendor libraries for speed improvement
* Improved configuration handling in preparation for admin plugin
* Cache optimization: Don't cache Twig templates when you pass dynamic params
* Moved `Utils::rcopy` to `Folder::rcopy`
* Improved `Folder::doDelete`
* Added check for required Curl in GPM
* Updated included composer.phar to latest version
* Various blueprint fixes for admin plugin
* Various PSR and code cleanup tasks
3. [](#bugfix)
* Fix issue with Gzip not working with `onShutDown()` event
* Fix for URLs with trailing slashes
* Handle condition where certain errors resulted in blank page
* Fix for issue with theme name equal to base_url and asset pipeline
* Fix to properly normalize font rewrite path
* Fix for absolute URLs below the current page
* Fix for `..` page references
# v0.9.26
## 04/24/2015
3. [](#bugfix)
* Fixed issue with homepage routes failing with 'dirname' error
# v0.9.25
## 04/24/2015
1. [](#new)
* Added support for E-Tag, Last-Modified, Cache-Control and Page-based expires headers
2. [](#improved)
* Refactored media image handling to make it more flexible and support absolute paths
* Refactored page modification check process to make it faster
* User account improvements in preparation for admin plugin
* Protect against timing attacks
* Reset default system expires time to 0 seconds (can override if you need to)
3. [](#bugfix)
* Fix issues with spaces in webroot when using `bin/grav install`
* Fix for spaces in relative directory
* Bug fix in collection filtering
# v0.9.24
## 04/15/2015
1. [](#new)
* Added support for chunked downloads of Assets
* Added new `onBeforeDownload()` event
* Added new `download()` and `getMimeType()` methods to Utils class
* Added configuration option for supported page types
* Added assets and media timestamp options (off by default)
* Added page expires configuration option
2. [](#bugfix)
* Fixed issue with Nginx/Gzip and `ob_flush()` throwing error
* Fixed assets actions on 'direct media' URLs
* Fix for 'direct assets` with any parameters
# v0.9.23
## 04/09/2015
1. [](#bugfix)
* Fix for broken GPM `selfupgrade` (Grav 0.9.21 and 0.9.22 will need to manually upgrade to this version)
# v0.9.22
## 04/08/2015
1. [](#bugfix)
* Fix to normalize GRAV_ROOT path for Windows
* Fix to normalize Media image paths for Windows
* Fix for GPM `selfupgrade` when you are on latest version
# v0.9.21
## 04/07/2015
1. [](#new)
* Major Media functionality enhancements: SVG, Animated GIF, Video support!
* Added ability to configure default image quality in system configuration
* Added `sizes` attributes for custom retina image breakpoints
2. [](#improved)
* Don't scale @1x retina images
* Add filter to Iterator class
* Updated various composer packages
* Various PSR fixes
# v0.9.20
## 03/24/2015
1. [](#new)
* Added `addAsyncJs()` and `addDeferJs()` to Assets manager
* Added support for extranal URL redirects
2. [](#improved)
* Fix unpredictable asset ordering when set from plugin/system
* Updated `nginx.conf` to ensure system assets are accessible
* Ensure images are served as static files in Nginx
* Updated vendor libraries to latest versions
* Updated included composer.phar to latest version
3. [](#bugfix)
* Fixed issue with markdown links to `#` breaking HTML
# v0.9.19
## 02/28/2015
1. [](#new)
* Added named assets capability and bundled jQuery into Grav core
* Added `first()` and `last()` to `Iterator` class
2. [](#improved)
* Improved page modification routine to skip _dot files_
* Only use files to calculate page modification dates
* Broke out Folder iterators into their own classes
* Various Sensiolabs Insight fixes
3. [](#bugfix)
* Fixed `Iterator.nth()` method
# v0.9.18
## 02/19/2015
1. [](#new)
* Added ability for GPM `install` to automatically install `_demo` content if found (w/backup)
* Added ability for themes and plugins to have dependencies required to install via GPM
* Added ability to override the system timezone rather than relying on server setting only
* Added new Twig filter `random_string` for generating random id values
* Added new Twig filter `markdown` for on-the-fly markdown processing
* Added new Twig filter `absoluteUrl` to convert relative to absolute URLs
* Added new `processTemplate()` method to Twig object for on-the-fly processing of twig template
* Added `rcopy()` and `contains()` helper methods in Utils
2. [](#improved)
* Provided new `param_sep` variable to better support Apache on Windows
* Moved parsedown configuration into the trait
* Added optional **deep-copy** option to `mergeConfig()` for plugins
* Updated bundled `composer.phar` package
* Various Sensiolabs Insight fixes - Silver level now!
* Various PSR Fixes
3. [](#bugfix)
* Fix for windows platforms not displaying installed themes/plugins via GPM
* Fix page IDs not picking up folder-only pages
# v0.9.17
## 02/05/2015
1. [](#new)
* Added **full HHVM support!** Get your speed on with Facebook's crazy fast PHP JIT compiler
2. [](#improved)
* More flexible page summary control
* Support **CamelCase** plugin and theme class names. Replaces dashes and underscores
* Moved summary delimiter into `site.yaml` so it can be configurable
* Various PSR fixes
3. [](#bugfix)
* Fix for `mergeConfig()` not falling back to defaults
* Fix for `addInlineCss()` and `addInlineJs()` Assets not working between Twig tags
* Fix for Markdown adding HTML tags into inline CSS and JS
# v0.9.16
## 01/30/2015
1. [](#new)
* Added **Retina** and **Responsive** image support via Grav media and `srcset` image attribute
* Added image debug option that overlays responsive resolution
* Added a new image cache stream
2. [](#improved)
* Improved the markdown Lightbox functionality to better mimic Twig version
* Fullsize Lightbox can now have filters applied
* Added a new `mergeConfig()` method to Plugin class to merge system + page header configuration
* Added a new `disable()` method to Plugin class to programmatically disable a plugin
* Updated Parsedown and Parsedown Extra to address bugs
* Various PSR fixes
3. [](#bugfix)
* Fix bug with image dispatch in traditionally _non-routable_ pages
* Fix for markdown link not working on non-current pages
* Fix for markdown images not being found on homepage
# v0.9.15
## 01/23/2015
3. [](#bugfix)
* Typo in video mime types
* Fix for old `markdown_extra` system setting not getting picked up
* Fix in regex for Markdown links with numeric values in path
* Fix for broken image routing mechanism that got broken at some point
* Fix for markdown images/links in pages with page slug override
# v0.9.14
## 01/23/2015
1. [](#new)
* Added **GZip** support
* Added multiple configurations via `setup.php`
* Added base structure for unit tests
* New `onPageContentRaw()` plugin event that processes before any page processing
* Added ability to dynamically set Metadata on page
* Added ability to dynamically configure Markdown processing via Parsedown options
2. [](#improved)
* Refactored `page.content()` method to be more flexible and reliable
* Various updates and fixes for streams resulting in better multi-site support
* Updated Twig, Parsedown, ParsedownExtra, DoctrineCache libraries
* Refactored Parsedown trait
* Force modular pages to be non-visible in menus
* Moved RewriteBase before Exploits in `.htaccess`
* Added standard video formats to Media support
* Added priority for inline assets
* Check for uniqueness when adding multiple inline assets
* Improved support for Twig-based URLs inside Markdown links and images
* Improved Twig `url()` function
3. [](#bugfix)
* Fix for HTML entities quotes in Metadata values
* Fix for `published` setting to have precedent of `publish_date` and `unpublish_date`
* Fix for `onShutdown()` events not closing connections properly in **php-fpm** environments
# v0.9.13
## 01/09/2015
1. [](#new)
* Added new published `true|false` state in page headers
* Added `publish_date` in page headers to automatically publish page
* Added `unpublish_date` in page headers to automatically unpublish page
* Added `dateRange()` capability for collections
* Added ability to dynamically control Cache lifetime programmatically
* Added ability to sort by anything in the page header. E.g. `sort: header.taxonomy.year`
* Added various helper methods to collections: `copy, nonVisible, modular, nonModular, published, nonPublished, nonRoutable`
2. [](#improved)
* Modified all Collection methods so they can be chained together: `$collection->published()->visible()`
* Set default Cache lifetime to default of 1 week (604800 seconds) - was infinite
* House-cleaning of some unused methods in Pages object
3. [](#bugfix)
* Fix `uninstall` GPM command that was broken in last release
* Fix for intermittent `undefined index` error when working with Collections
* Fix for date of some pages being set to incorrect future timestamps
# v0.9.12
## 01/06/2015
1. [](#new)
* Added an all-access robots.txt file for search engines
* Added new GPM `uninstall` command
* Added support for **in-page** Twig processing in **modular** pages
* Added configurable support for `undefined` Twig functions and filters
2. [](#improved)
* Fall back to default `.html` template if error occurs on non-html pages
* Added ability to have PSR-1 friendly plugin names (CamelCase, no-dashes)
* Fix to `composer.json` to deter API rate-limit errors
* Added **non-exception-throwing** handler for undefined methods on `Medium` objects
3. [](#bugfix)
* Fix description for `self-upgrade` method of GPM command
* Fix for incorrect version number when performing GPM `update`
* Fix for argument description of GPM `install` command
* Fix for recalcitrant CodeKit mac application
# v0.9.11
## 12/21/2014
1. [](#new)
* Added support for simple redirects as well as routes
2. [](#improved)
* Handle Twig errors more cleanly
3. [](#bugfix)
* Fix for error caused by invalid or missing user agent string
* Fix for directory relative links and URL fragments (#pagelink)
* Fix for relative links with no subfolder in `base_url`
# v0.9.10
## 12/12/2014
1. [](#new)
* Added Facebook-style `nicetime` date Twig filter
2. [](#improved)
* Moved `clear-cache` functionality into Cache object required for Admin plugin
3. [](#bugfix)
* Fix for undefined index with previous/next buttons
# v0.9.9
## 12/05/2014
1. [](#new)
* Added new `@page` collection type
* Added `ksort` and `contains` Twig filters
* Added `gist` Twig function
2. [](#improved)
* Refactored Page previous/next/adjacent functionality
* Updated to Symfony 2.6 for yaml/console/event-dispatcher libraries
* More PSR code fixes
3. [](#bugfix)
* Fix for over-escaped apostrophes in YAML
# v0.9.8
## 12/01/2014
1. [](#new)
* Added configuration option to set default lifetime on cache saves
* Added ability to set HTTP status code from page header
* Implemented simple wild-card custom routing
2. [](#improved)
* Fixed elusive double load to fully cache issue (crossing fingers...)
* Ensure Twig tags are treated as block items in markdown
* Removed some older deprecated methods
* Ensure onPageContentProcessed() event only fires when not cached
* More PSR code fixes
3. [](#bugfix)
* Fix issue with miscalculation of blog separator location `===`
# v0.9.7
## 11/24/2014
1. [](#improved)
* Nginx configuration updated
* Added gitter.im badge to README
* Removed `set_time_limit()` and put checks around `ignore_user_abort`
* More PSR code fixes
2. [](#bugfix)
* Fix issue with non-valid asset path showing up when they shouldn't
* Fix for JS asset pipeline and scripts that don't end in `;`
* Fix for schema-based markdown URLs broken routes (eg `mailto:`)
# v0.9.6
## 11/17/2014
1. [](#improved)
* Moved base_url variables into Grav container
* Forced media sorting to use natural sort order by default
* Various PSR code tidying
* Added filename, extension, thumb to all medium objects
2. [](#bugfix)
* Fix for infinite loop in page.content()
* Fix hostname for configuration overrides
* Fix for cached configuration
* Fix for relative URLs in markdown on installs with no base_url
* Fix for page media images with uppercase extension
# v0.9.5
## 11/09/2014
1. [](#new)
* Added quality setting to medium for compression configuration of images
* Added new onPageContentProcessed() event that is post-content processing but pre-caching
2. [](#improved)
* Added support for AND and OR taxonomy filtering. AND by default (was OR)
* Added specific clearing options for CLI clear-cache command
* Moved environment method to URI so it can be accessible in plugins and themes
* Set Grav's output variable to public so it can be manipulated in onOutputGenerated event
* Updated vendor libraries to latest versions
* Better handing of 'home' in active menu state detection
* Various PSR code tidying
* Improved some error messages and notices
3. [](#bugfix)
* Force route rebuild when configuration changes
* Fix for 'installed undefined' error in CLI versions command
* Do not remove the JSON/Text error handlers
* Fix for supporting inline JS and CSS when Asset pipeline enabled
* Fix for Data URLs in CSS being badly formed
* Fix Markdown links with fragment and query elements
# v0.9.4
## 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
## 10/09/2014
1. [](#new)
* GPM (Grav Package Manager) Added
* Support for multiple Grav configurations
* Dynamic media support via URL
* Added inlineCss and inlineJs support for Assets
2. [](#improved)
* YAML caching for increased performance
* Use stream wrapper in pages, plugins and themes
* Switched to RocketTheme toolbox for some core functionality
* Renamed `setup` CLI command to `sandbox`
* Broke cache types out into multiple directories in the cache folder
* Removed vendor libs from github repository
* Various PSR cleanup of code
* Various Blueprint updates to support upcoming admin plugin
* Added ability to filter page children for normal/modular/all
* Added `sort_by_key` twig filter
* Added `visible()` and `routable()` filters to page collections
* Use session class in shutdown process
* Improvements to modular page loading
* Various code cleanup and optimizations
3. [](#bugfix)
* Fixed file checking not updating the last modified time. For real this time!
* Switched debugger to PRODUCTION mode by default
* Various fixes in URI class for increased reliability
# v0.9.2
## 09/15/2014
1. [](#new)
* New flexible site and page metadata support including ObjectGraph and Facebook
* New method to get user IP address in URI object
* Added new onShutdown() event that fires after connection is closed for Async features
2. [](#improved)
* Skip assets pipeline minify on Windows platforms by default due to PHP issue 47689
* Fixed multiple level menus not highlighting correctly
* Updated some blueprints in preparation for admin plugin
* Fail gracefully when theme does not exist
* Add stream support into ResourceLocator::addPath()
* Separate themes from plugins, add themes:// stream and onTask events
* Added barDump() to Debugger
* Removed stray test page
* Override modified only if a non-markdown file was modified
* Added assets attributes support
* Auto-run composer install when running the Grav CLI
* Vendor folder removed from repository
* Minor configuration performance optimizations
* Minor debugger performance optimizations
3. [](#bugfix)
* Fix url() twig function when Grav isn't installed at root
* Workaround for PHP bug 52065
* Fixed getList() method on Pages object that was not working
* Fix for open_basedir error
* index.php now warns if not running on PHP 5.4
* Removed memcached option (redundant)
* Removed memcache from auto setup, added memcache server configuration option
* Fix broken password validation
* Back to proper PSR-4 Autoloader
# v0.9.1
## 09/02/2014
1. [](#new)
* Added new `theme://` PHP stream for current theme
2. [](#improved)
* Default to new `file` modification checking rather than `folder`
* Added support for various markdown link formats to convert to Grav-friendly URLs
* Moved configure() from Theme to Themes class
* Fix autoloading without composer update -o
* Added support for Twig url method
* Minor code cleanup
3. [](#bugfix)
* Fixed issue with page changes not being picked up
* Fixed Minify to provide `@supports` tag compatibility
* Fixed ResourceLocator not working with multiple paths
* Fixed issue with Markdown process not stripping LFs
* Restrict file type extensions for added security
* Fixed template inheritance
* Moved Browser class to proper location
# v0.9.0
## 08/25/2014
1. [](#new)
* Addition of Dependency Injection Container
* Refactored plugins to use Symfony Event Dispatcher
* New Asset Manager to provide unified management of JavaScript and CSS
* Asset Pipelining to provide unification, minify, and optimization of JavaScript and CSS
* Grav Media support directly in Markdown syntax
* Additional Grav Generator meta tag in default themes
* Added support for PHP Stream Wrapper for resource location
* Markdown Extra support
* Browser object for fast browser detection
2. [](#improved)
* PSR-4 Autoloader mechanism
* Tracy Debugger new `detect` option to detect running environment
* Added new `random` collection sort option
* Make media images progressive by default
* Additional URI filtering for improved security
* Safety checks to ensure PHP 5.4.0+
* Move to Slidebars side navigation in default Antimatter theme
* Updates to `.htaccess` including section on `RewriteBase` which is needed for some hosting providers
3. [](#bugfix)
* Fixed issue when installing in an apache userdir (~username) folder
* Various mobile CSS issues in default themes
* Various minor bug fixes
# v0.8.0
## 08/13/2014
1. [](#new)
* Initial Release

21
src/LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Grav
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

110
src/README.md Normal file
View file

@ -0,0 +1,110 @@
# ![](https://avatars1.githubusercontent.com/u/8237355?v=2&s=50) Grav
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad/mini.png)](https://insight.sensiolabs.com/projects/cfd20465-d0f8-4a0a-8444-467f5b5f16ad) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/getgrav/grav?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Grav is a **Fast**, **Simple**, and **Flexible**, file-based Web-platform. There is **Zero** installation required. Just extract the ZIP archive, and you are already up and running. It follows similar principles to other flat-file CMS platforms, but has a different design philosophy than most. Grav comes with a powerful **Package Management System** to allow for simple installation and upgrading of plugins and themes, as well as simple updating of Grav itself.
The underlying architecture of Grav is designed to use well-established and _best-in-class_ technologies to ensure that Grav is simple to use and easy to extend. Some of these key technologies include:
* [Twig Templating](http://twig.sensiolabs.org/): for powerful control of the user interface
* [Markdown](http://en.wikipedia.org/wiki/Markdown): for easy content creation
* [YAML](http://yaml.org): for simple configuration
* [Parsedown](http://parsedown.org/): for fast Markdown and Markdown Extra support
* [Doctrine Cache](http://docs.doctrine-project.org/en/2.0.x/reference/caching.html): layer for performance
* [Pimple Dependency Injection Container](http://pimple.sensiolabs.org/): for extensibility and maintainability
* [Symfony Event Dispatcher](http://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
* [Symfony Console](http://symfony.com/doc/current/components/console/introduction.html): for CLI interface
* [Gregwar Image Library](https://github.com/Gregwar/Image): for dynamic image manipulation
# Requirements
- PHP 5.4 or higher. Check the [required modules list](http://learn.getgrav.org/basics/requirements#php-requirements)
- Check the [Apache](http://learn.getgrav.org/basics/requirements#apache-requirements) or [IIS](http://learn.getgrav.org/basics/requirements#iis-requirements) requirements
# QuickStart
You have two options to get Grav:
### Downloading a Grav Package
You can download a **ready-built** package from the [Downloads page on http://getgrav.org](http://getgrav.org/downloads)
### From GitHub
1. Clone the Grav repository from [https://github.com/getgrav/grav]() to a folder in the webroot of your server, e.g. `~/webroot/grav`. Launch a **terminal** or **console** and navigate to the webroot folder:
```
$ cd ~/webroot
$ git clone https://github.com/getgrav/grav.git
```
2. Install the **plugin** and **theme dependencies** by using the [Grav CLI application](http://learn.getgrav.org/advanced/grav-cli) `bin/grav`:
```
$ cd ~/webroot/grav
$ bin/grav install
```
Check out the [install procedures](http://learn.getgrav.org/basics/installation) for more information.
# Adding Functionality
You can download [plugins](http://getgrav.org/downloads/plugins) or [themes](http://getgrav.org/downloads/themes) manually from the appropriate tab on the [Downloads page on http://getgrav.org](http://getgrav.org/downloads), but the preferred solution is to use the [Grav Package Manager](http://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
```
$ bin/gpm index
```
This will display all the available plugins and then you can install one or more with:
```
$ bin/gpm install <plugin/theme>
```
# Updating
To update Grav you should use the [Grav Package Manager](http://learn.getgrav.org/advanced/grav-gpm) or `GPM`:
```
$ bin/gpm selfupgrade
```
To update plugins and themes:
```
$ bin/gpm update
```
# Contributing
We appreciate any contribution to Grav, whether it is related to bugs, grammar, or simply a suggestion or improvement.
However, we ask that any contributions follow our simple guidelines in order to be properly received.
All our projects follow the [GitFlow branching model][gitflow-model], from development to release. If you are not familiar with it, there are several guides and tutorials to make you understand what it is about.
You will probably want to get started by installing [this very good collection of git extensions][gitflow-extensions].
What you mainly want to know is that:
- All the main activity happens in the `develop` branch. Any pull request should be addressed only to that branch. We will not consider pull requests made to the `master`.
- It's very well appreciated, and highly suggested, to start a new feature whenever you want to make changes or add functionalities. It will make it much easier for us to just checkout your feature branch and test it, before merging it into `develop`
# Getting Started
* [What is Grav?](http://learn.getgrav.org/basics/what-is-grav)
* [Install](http://learn.getgrav.org/basics/installation) Grav in few seconds
* Understand the [Configuration](http://learn.getgrav.org/basics/grav-configuration)
* Take a peek at our available free [Skeletons](http://getgrav.org/downloads/skeletons#extras)
* If you have questions, check out `#grav` on irc.freenode.net
* Have fun!
# Exploring more
* Have a look at our [Basic Tutorial](http://learn.getgrav.org/basics/basic-tutorial)
* Dive into more [advanced](http://learn.getgrav.org/advanced) functions
# License
See [LICENSE](LICENSE)
[gitflow-model]: http://nvie.com/posts/a-successful-git-branching-model/
[gitflow-extensions]: https://github.com/nvie/gitflow

0
src/assets/.gitkeep Normal file
View file

0
src/backup/.gitkeep Normal file
View file

BIN
src/bin/composer.phar Executable file

Binary file not shown.

56
src/bin/gpm Executable file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env php
<?php
define('GRAV_CLI', true);
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
}
if (!file_exists(__DIR__ . '/../vendor')){
require_once __DIR__ . '/../system/src/Grav/Common/Composer.php';
}
use Grav\Common\Composer;
if (!file_exists(__DIR__ . '/../vendor')){
// Before we can even start, we need to run composer first
$composer = Composer::getComposerExecutor();
echo "Preparing to install vendor dependencies...\n\n";
echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install');
echo "\n\n";
}
use Symfony\Component\Console\Application;
use Grav\Common\Grav;
$autoload = require_once(__DIR__ . '/../vendor/autoload.php');
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
if (!file_exists(ROOT_DIR . 'index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
if (!function_exists('curl_version')) {
exit('FATAL: GPM requires PHP Curl module to be installed');
}
$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\UninstallCommand(),
new \Grav\Console\Gpm\UpdateCommand(),
new \Grav\Console\Gpm\SelfupgradeCommand(),
));
$app->run();

46
src/bin/grav Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env php
<?php
define('GRAV_CLI', true);
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
}
if (!file_exists(__DIR__ . '/../vendor')){
require_once __DIR__ . '/../system/src/Grav/Common/Composer.php';
}
use Grav\Common\Composer;
if (!file_exists(__DIR__ . '/../vendor')){
// Before we can even start, we need to run composer first
$composer = Composer::getComposerExecutor();
echo "Preparing to install vendor dependencies...\n\n";
echo system($composer.' --working-dir="'.__DIR__.'/../" --no-interaction --no-dev --prefer-dist -o install');
echo "\n\n";
}
use Symfony\Component\Console\Application;
require_once __DIR__ . '/../vendor/autoload.php';
if (!ini_get('date.timezone')) {
date_default_timezone_set('UTC');
}
if (!file_exists(ROOT_DIR . 'index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
$app = new Application('Grav CLI Application', '0.1.0');
$app->addCommands(array(
new Grav\Console\Cli\InstallCommand(),
new Grav\Console\Cli\ComposerCommand(),
new Grav\Console\Cli\SandboxCommand(),
new Grav\Console\Cli\CleanCommand(),
new Grav\Console\Cli\ClearCacheCommand(),
new Grav\Console\Cli\BackupCommand(),
new Grav\Console\Cli\NewProjectCommand(),
new Grav\Console\Cli\NewUserCommand(),
));
$app->run();

0
src/cache/.gitkeep vendored Normal file
View file

36
src/composer.json Normal file
View file

@ -0,0 +1,36 @@
{
"name": "getgrav/grav",
"type": "library",
"description": "Modern, Crazy Fast, Ridiculously Easy and Amazingly Powerful Flat-File CMS",
"keywords": ["cms","flat-file cms","flat cms","flatfile cms","php"],
"homepage": "http://getgrav.org",
"license": "MIT",
"require": {
"php": ">=5.4.0",
"twig/twig": "~1.16",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "~2.7",
"symfony/console": "~2.7",
"symfony/event-dispatcher": "~2.7",
"symfony/var-dumper": "~2.7",
"doctrine/cache": "~1.4",
"filp/whoops": "1.2.*@dev",
"monolog/monolog": "~1.0",
"gregwar/image": "~2.0",
"ircmaxell/password-compat": "1.0.*",
"mrclay/minify": "~2.2",
"donatj/phpuseragentparser": "~0.3",
"pimple/pimple": "~3.0",
"rockettheme/toolbox": "1.1.*",
"maximebf/debugbar": "~1.10"
},
"autoload": {
"psr-4": {
"Grav\\": "system/src/Grav"
},
"files": ["system/defines.php"]
},
"archive": {
"exclude": ["VERSION"]
}
}

1012
src/composer.lock generated Normal file

File diff suppressed because it is too large Load diff

233
src/config Normal file
View file

@ -0,0 +1,233 @@
<?php
date_default_timezone_set('EST'); //set to the timezone of your application
session_start(); //if no use for sessions, comment this line out
/**
* Framework configuration - this object contains your application-specific settings
*/
class config extends configDefaults {
/**
* The SITE_NAME and SITE_DOMAIN constants are used in the default templates; you must set this if you will use the
* default framework functionality such as the "default" layout or the preset email-elements.
* You can always access these from anywhere in your application via:
* get::$config->SITE_NAME
* get::$config->SITE_DOMAIN
*/
const SITE_NAME = 'YourSite';
const SITE_DOMAIN = 'YourSite.com';
/**
* Database name - create new constants for additional connections (commented out example for DB_SLAVE_NAME follows)
* Defining DB-name constants is a good practice but not required if you hard-code them in dbConnect() below
*/
const DB_NAME = 'vork';
//const DB_SLAVE_NAME = 'slave4readonly';
/**
* Most RDBMS users will only need to adjust the connection string in the first $db object. The syntax of this must
* always match the syntax of the constructor or connect method of your relational-database PHP extension.
*
* For the MySQLi extension all arguments are optional and formatted as:
* self::$db = new $db($host, $username, $passwd, $dbname, $port, $socket);
*
* Note: in most setups $host is either "localhost" or set to an IP address
* for Amazon RDS it would be something like: myinstance.crwjauxgijdf.us-east-1.rds.amazonaws.com
*
* For the syntax of other extensions refer to: http://www.php.net/manual/en/refs.database.vendors.php
*
* @param string $modelObject One of the values in self::$modelObjects determining which DB connection to establish
*/
public function dbConnect($modelObject) {
$db = (!DEBUG_MODE ? 'db' : 'dbDebug');
switch ($modelObject) { //a case must exist for each value in self::$modelObjects (additional cases are benign)
case 'db':
self::$db = new $db('HOSTNAME', 'USERNAME', 'PASSWORD', self::DB_NAME);
break;
case 'mongo':
if (!class_exists('vorkMongo')) {
require self::basepath() . 'sql' . self::DS . 'mongodb';
}
$m = new vorkMongo();
self::$mongo = $m->selectDB(self::DB_NAME);
//Upsert arg is array in newer MongoDB (leave as-is), Boolean in older- remove comments around (Boolean)
$this->mongoUpsert = /* (Boolean) */ array('upsert' => true);
break;
case 'dbro':
//if an additional connection is needed, adjust credentials below and follow the instructions in the
//comment for $modelObjects just after this class.
self::$dbro = new $db('HOSTNAME', 'USERNAME', 'PASSWORD', self::DB_SLAVE_NAME);
break;
}
}
/**
* Using multiple database connections and/or NoSQL like MongoDB or CouchDB
*
* If you are only using one DB connection and it is an RDBMS (any databaes except non-relational like Mongo)
* then you should skip this step.
*
* Each database connection (both RDBMS & NoSQL) needs to be defined in the $modelObjects array and have a static
* propery existing by the same name. "db" exists by default so to add a second read-only connection you set
* $modelObjects to array('db', 'dbro') and create a new $dbro static property (commented-out examples follow).
*
* Use the same process for adding a MongoDB connection in conjuction with an RDBMS (substituting in the last
* example "mongo" for "dbro"). To use MongoDB as the sole database just overwrite $modelObjects with
* array('mongo') - this is instead of keeping the default 'db' connection within the array. You will also want to
* comment out the last line in this file which is RDBMS-specific: require '.config-rdbms';
*
* Important: each connection defined here must establish the connection within the dbConnect() method above, an
* example for both mongo and dbro exists, for other connections just provide a unique name and access from within
* your models by name: $this->mydbname->query(...);
*
* Caution: do NOT uncomment more than one of the following examples; it will trigger a PHP fatal error if you do!
*/
//Adds MongoDB support in conjunction with other databases
//public static $modelObjects = array('db', 'mongo'); public static $mongo;
//Uses MongoDB as the exclusive datasource
//public static $modelObjects = array('mongo'); public static $mongo;
//Adds an additional database connection called "dbro"
//public static $modelObjects = array('db', 'dbro'); public static $dbro;
/**
* If true all self::$modelObjects database connections are made upon loading the first model
* If false your connections are lazy-loaded when needed but each of your models must extend the model abstract
* eg.: class wikiModel extends model {...}
*/
const MODEL_AUTOCONNECT = true;
/**
* Enables the caching functions by initializing get::$cache with a cache object (Memcache is default, but
* can be any object-oriented cache mechanism.)
*/
public function cache() {
/* // uncomment this section to enable Memcache caching (Memcache must be installed and enabled in PHP)
if (!class_exists('memcache')) {
return false;
}
get::$cache = new Memcache;
//get::$cache->addServer('HOSTNAME', 'PORT')
//Following is the typical setting for a single-server configuration:
get::$cache->addServer('localhost', 11211);
*/
}
/**
* Enables special AJAX handling - eg.: AJAX requests will skip the layout file and just return the view contents
*
* Can be set globally here and then overridden form within any controller-action or component
*
* Valid values:
* (Boolean) true or false will enable or disable AJAX in all controllers
* array('controlerName1' => true,
* 'controlerName2' => 'actionName',
* 'controlerName3' => array('action1', 'action2'))
*
* @var mixed Boolean or an array with controller-name keys & val either true, an action name or an array of actions
*/
public $ajaxEnabled = true;
/**
* Execute a file that is global to the application before the page loads
* File must be in the root of the mvc directory
*/
const APP_CONSTRUCT = null;
/**
* Execute a file that is global to the application after the page loads
* File must be in the root of the mvc directory
*/
const APP_DESTRUCT = null;
/**
* Enter your ShareThis publisher code to enable the shareThis tools helper
*/
//const SHARE_THIS = 'd123456789-1234-1234-1234-a1e123456f';
/**
* Enter your AddThis publisher code to enable the addThis tools helper
*/
//const ADD_THIS = 'xa-1234567890123e';
/**
* Enter your Google AdSense client ID to enable use of the adSense tools helper
*/
//const GOOGLE_AD_CLIENT = '';
/**
* Enter your Google Checkout credentials to enable use of the helper
*/
/*
public $GOOGLE_CHECKOUT = array('useSandbox' => false,
'live' => array('id' => 'YOURID', 'key' => 'YOURKEY'),
'sandbox' => array('id' => 'YOURID', 'key' => 'YOURKEY'));
*/
/**
* Enter your AuthorizeNet credentials to enable use of the chargeAuthNet method of the cc component
*/
/*
const AUTHNET_LOGIN = '';
const AUTHNET_PASSWORD = '';
*/
/**
* Meetup API key - enables usage of the Meetup tools-helper
*/
//const MEETUP_API_KEY = '47e794a9c4766374c761a57833a77';
/**
* Enter your UPS account credentials and XML Access Key (refer to ups.com for signup) to enable use of
* the UPS functions in the shipping component
*
* @var array
*/
//public $UPS = array('xmlAccessKey' => 'YOURKEY000000000', 'userid' => 'YOURID', 'password' => 'YOURPASS');
/**
* Enter your Amazon Web Services credentials to enable use of the amazon component
*/
/*
public $AMAZON = array('id' => '0000-0000-1234', 'accessKey' => 'AAAABBBBX12345CCDDDD',
'secretKey' => 'aA01234560bB01234560cC01234560dD');
*/
/**
* Enter your PayPal email to enable use of the PayPal component
*/
//const PAYPAL_EMAIL = 'you@yourdomain.com';
/**
* Convenience function to determine if a user is a "superuser" (someone who gets complete access to
* all information) and by default only used in the errorhandler view (to determine verbosity) but typical applications
* find many uses for this
*
* Note, if your application uses this method to grant access to private resources then you are highly-recommended
* to use a stronger authentication method than just checking the IP address (IPs can easily be spoofed.)
*
* @return boolean
*/
public function isSuperuser() {
return (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] == '127.0.0.1'); // <--change this before use
}
/**
* Set any application-specific constants or properties after this comment.
* examples:
* const MY_CONSTANT = 'Hello, Shalom and Bonjour';
* public $myProperty = array(1, 2, 3);
*
* Access them from any MVC element via:
* get::$config->MY_CONSTANT
* get::$config->myProperty
*/
//const EXAMPLE_CONSTANT = 'your data...';
}
/**
* If you are using a relational-database other than MySQL or Amazon RDS (eg. SQLite, Oracle, etc.) then you need to
* set your RDBMS-type in the .config-rdbms file. If you are NOT using a relational-database then comment out the
* next line. Note: MongoDB and other NoSQL databases are NOT relational and do not require the .config-rdbms file
*/
require '.config-rdbms';

63
src/htaccess.txt Normal file
View file

@ -0,0 +1,63 @@
<IfModule mod_rewrite.c>
RewriteEngine On
## 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 - 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 - 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 ^(.git|cache|bin|logs|backup)/(.*) 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]
# Block all direct access to .md files:
RewriteRule \.md$ error [L]
# Block all direct access to files and folders beginning with a dot
RewriteRule (^\.|/\.) - [F]
# Block access to specific files in the root folder
RewriteRule ^(LICENSE|composer.lock|composer.json|nginx.conf|web.config)$ error [F]
## End - Security
</IfModule>
# Begin - Prevent Browsing and Set Default Resources
Options -Indexes
DirectoryIndex index.php index.html index.htm
# End - Prevent Browsing and Set Default Resources

0
src/images/.gitkeep Normal file
View file

42
src/index.php Normal file
View file

@ -0,0 +1,42 @@
<?php
namespace Grav;
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
throw new \RuntimeException(sprintf('You are running PHP %s, but Grav needs at least <strong>PHP %s</strong> to run.', $ver, $req));
}
// Ensure vendor libraries exist
$autoload = __DIR__ . '/vendor/autoload.php';
if (!is_file($autoload)) {
throw new \RuntimeException("Please run: <i>bin/grav install</i>");
}
use Grav\Common\Grav;
// Register the auto-loader.
$loader = require_once $autoload;
// Set timezone to default, falls back to system if php.ini not set
date_default_timezone_set(@date_default_timezone_get());
// Set internal encoding if mbstring loaded
if (!extension_loaded('mbstring')) {
throw new \RuntimeException("'mbstring' extension is not loaded. This is required for Grav to run correctly");
}
mb_internal_encoding('UTF-8');
// Get the Grav instance
$grav = Grav::instance(
array(
'loader' => $loader
)
);
// Process the page
try {
$grav->process();
} catch (\Exception $e) {
$grav->fireEvent('onFatalException');
throw $e;
}

0
src/logs/.gitkeep Normal file
View file

87
src/nginx.conf Normal file
View file

@ -0,0 +1,87 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location / {
root html;
index index.php;
if (!-e $request_filename){ rewrite ^(.*)$ /index.php last; }
}
# if you want grav in a sub-directory of your main site
# (for example, example.com/mygrav) then you need this rewrite:
location /mygrav {
index index.php;
if (!-e $request_filename){ rewrite ^(.*)$ /mygrav/$2 last; }
try_files $uri $uri/ /index.php?$args;
}
# if using grav in a sub-directory of your site,
# prepend the actual path to each location
# for example: /mygrav/images
# and: /mygrav/user
# and: /mygrav/cache
# and so on
location /images/ {
# Serve images as static
}
location /user {
rewrite ^/user/accounts/(.*)$ /error redirect;
rewrite ^/user/config/(.*)$ /error redirect;
rewrite ^/user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
location /cache {
rewrite ^/cache/(.*) /error redirect;
}
location /bin {
rewrite ^/bin/(.*)$ /error redirect;
}
location /backup {
rewrite ^/backup/(.*) /error redirect;
}
location /system {
rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
location /vendor {
rewrite ^/vendor/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect;
}
# Remember to change 127.0.0.1:9000 to the Ip/port
# you configured php-cgi.exe to run from
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
}

2
src/robots.txt Normal file
View file

@ -0,0 +1,2 @@
User-agent: *
Disallow:

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
src/system/assets/grav.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

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

@ -0,0 +1,5 @@
title: PLUGIN_ADMIN.MEDIA
form:
validation: loose
fields:

View file

@ -0,0 +1,116 @@
title: PLUGIN_ADMIN.SITE
form:
validation: loose
fields:
content:
type: section
title: PLUGIN_ADMIN.DEFAULTS
underline: true
fields:
title:
type: text
label: PLUGIN_ADMIN.SITE_TITLE
size: large
placeholder: PLUGIN_ADMIN.SITE_TITLE_PLACEHOLDER
help: PLUGIN_ADMIN.SITE_TITLE_HELP
author.name:
type: text
size: large
label: PLUGIN_ADMIN.DEFAULT_AUTHOR
help: PLUGIN_ADMIN.DEFAULT_AUTHOR_HELP
author.email:
type: text
size: large
label: PLUGIN_ADMIN.DEFAULT_EMAIL
help: PLUGIN_ADMIN.DEFAULT_EMAIL_HELP
validate:
type: email
taxonomies:
type: selectize
size: large
label: PLUGIN_ADMIN.TAXONOMY_TYPES
classes: fancy
help: PLUGIN_ADMIN.TAXONOMY_TYPES_HELP
validate:
type: commalist
summary:
type: section
title: PLUGIN_ADMIN.PAGE_SUMMARY
underline: true
fields:
summary.enabled:
type: toggle
label: PLUGIN_ADMIN.ENABLED
highlight: 1
help: PLUGIN_ADMIN.ENABLED_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
summary.size:
type: text
size: x-small
label: PLUGIN_ADMIN.SUMMARY_SIZE
help: PLUGIN_ADMIN.SUMMARY_SIZE_HELP
validate:
type: int
min: 0
max: 65536
summary.format:
type: toggle
label: PLUGIN_ADMIN.FORMAT
classes: fancy
help: PLUGIN_ADMIN.FORMAT_HELP
highlight: short
options:
'short': PLUGIN_ADMIN.SHORT
'long': PLUGIN_ADMIN.LONG
summary.delimiter:
type: text
size: x-small
label: PLUGIN_ADMIN.DELIMITER
help: PLUGIN_ADMIN.DELIMITER_HELP
metadata:
type: section
title: PLUGIN_ADMIN.METADATA
underline: true
fields:
metadata:
type: array
label: PLUGIN_ADMIN.METADATA
help: PLUGIN_ADMIN.METADATA_HELP
placeholder_key: PLUGIN_ADMIN.METADATA_KEY
placeholder_value: PLUGIN_ADMIN.METADATA_VALUE
routes:
type: section
title: PLUGIN_ADMIN.REDIRECTS_AND_ROUTES
underline: true
fields:
redirects:
type: array
label: PLUGIN_ADMIN.CUSTOM_REDIRECTS
help: PLUGIN_ADMIN.CUSTOM_REDIRECTS_HELP
placeholder_key: PLUGIN_ADMIN.CUSTOM_REDIRECTS_PLACEHOLDER_KEY
placeholder_value: PLUGIN_ADMIN.CUSTOM_REDIRECTS_PLACEHOLDER_VALUE
routes:
type: array
label: PLUGIN_ADMIN.CUSTOM_ROUTES
help: PLUGIN_ADMIN.CUSTOM_ROUTES_HELP
placeholder_key: PLUGIN_ADMIN.CUSTOM_ROUTES_PLACEHOLDER_KEY
placeholder_value: PLUGIN_ADMIN.CUSTOM_ROUTES_PLACEHOLDER_VALUE

View file

@ -0,0 +1,7 @@
title: PLUGIN_ADMIN.FILE_STREAMS
form:
validation: loose
fields:
schemes.xxx:
type: array

View file

@ -0,0 +1,793 @@
title: PLUGIN_ADMIN.SYSTEM
form:
validation: loose
fields:
content:
type: section
title: PLUGIN_ADMIN.CONTENT
underline: true
fields:
home.alias:
type: pages
size: medium
classes: fancy
label: PLUGIN_ADMIN.HOME_PAGE
show_all: false
show_modular: false
show_root: false
help: PLUGIN_ADMIN.HOME_PAGE_HELP
pages.theme:
type: themeselect
classes: fancy
selectize: true
size: medium
label: PLUGIN_ADMIN.DEFAULT_THEME
help: PLUGIN_ADMIN.DEFAULT_THEME_HELP
pages.process:
type: checkboxes
label: PLUGIN_ADMIN.PROCESS
help: PLUGIN_ADMIN.PROCESS_HELP
default: [markdown: true, twig: true]
options:
markdown: Markdown
twig: Twig
use: keys
timezone:
type: select
label: PLUGIN_ADMIN.TIMEZONE
size: medium
classes: fancy
help: PLUGIN_ADMIN.TIMEZONE_HELP
'@data-options': '\Grav\Common\Utils::timezones'
default: ''
options:
'': 'Default (Server Timezone)'
pages.dateformat.default:
type: select
size: medium
selectize:
create: true
label: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT
help: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_HELP
placeholder: PLUGIN_ADMIN.DEFAULT_DATE_FORMAT_PLACEHOLDER
'@data-options': '\Grav\Common\Utils::dateFormats'
options:
"": Auto Guess or Enter Custom
validate:
type: string
pages.dateformat.short:
type: dateformat
size: medium
classes: fancy
label: PLUGIN_ADMIN.SHORT_DATE_FORMAT
help: PLUGIN_ADMIN.SHORT_DATE_FORMAT_HELP
default: "jS M Y"
options:
"F jS \\a\\t g:ia": Date1
"l jS \\of F g:i A": Date2
"D, m M Y G:i:s": Date3
"d-m-y G:i": Date4
"jS M Y": Date5
pages.dateformat.long:
type: dateformat
size: medium
classes: fancy
label: PLUGIN_ADMIN.LONG_DATE_FORMAT
help: PLUGIN_ADMIN.LONG_DATE_FORMAT_HELP
options:
"F jS \\a\\t g:ia": Date1
"l jS \\of F g:i A": Date2
"D, m M Y G:i:s": Date3
"d-m-y G:i": Date4
"jS M Y": Date5
pages.order.by:
type: select
size: long
classes: fancy
label: PLUGIN_ADMIN.DEFAULT_ORDERING
help: PLUGIN_ADMIN.DEFAULT_ORDERING_HELP
options:
default: PLUGIN_ADMIN.DEFAULT_ORDERING_DEFAULT
folder: PLUGIN_ADMIN.DEFAULT_ORDERING_FOLDER
title: PLUGIN_ADMIN.DEFAULT_ORDERING_TITLE
date: PLUGIN_ADMIN.DEFAULT_ORDERING_DATE
pages.order.dir:
type: toggle
label: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION
highlight: asc
default: desc
help: PLUGIN_ADMIN.DEFAULT_ORDER_DIRECTION_HELP
options:
asc: PLUGIN_ADMIN.ASCENDING
desc: PLUGIN_ADMIN.DESCENDING
pages.list.count:
type: text
size: x-small
label: PLUGIN_ADMIN.DEFAULT_PAGE_COUNT
help: PLUGIN_ADMIN.DEFAULT_PAGE_COUNT_HELP
validate:
type: number
min: 1
pages.publish_dates:
type: toggle
label: PLUGIN_ADMIN.DATE_BASED_PUBLISHING
help: PLUGIN_ADMIN.DATE_BASED_PUBLISHING_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.events:
type: checkboxes
label: PLUGIN_ADMIN.EVENTS
help: PLUGIN_ADMIN.EVENTS_HELP
default: [page: true, twig: true]
options:
page: Page Events
twig: Twig Events
use: keys
pages.redirect_default_route:
type: toggle
label: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE
help: PLUGIN_ADMIN.REDIRECT_DEFAULT_ROUTE_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.redirect_default_code:
type: select
size: medium
classes: fancy
label: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE
help: PLUGIN_ADMIN.REDIRECT_DEFAULT_CODE_HELP
options:
301: 301 - Permanent
303: 303 - Other
307: 307 - Temporary
pages.redirect_trailing_slash:
type: toggle
label: PLUGIN_ADMIN.REDIRECT_TRAILING_SLASH
help: PLUGIN_ADMIN.REDIRECT_TRAILING_SLASH_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.ignore_hidden:
type: toggle
label: PLUGIN_ADMIN.IGNORE_HIDDEN
help: PLUGIN_ADMIN.IGNORE_HIDDEN_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.ignore_files:
type: selectize
size: large
label: PLUGIN_ADMIN.IGNORE_FILES
help: PLUGIN_ADMIN.IGNORE_FILES_HELP
classes: fancy
validate:
type: commalist
pages.ignore_folders:
type: selectize
size: large
label: PLUGIN_ADMIN.IGNORE_FOLDERS
help: PLUGIN_ADMIN.IGNORE_FOLDERS_HELP
classes: fancy
validate:
type: commalist
pages.url_taxonomy_filters:
type: toggle
label: PLUGIN_ADMIN.ALLOW_URL_TAXONOMY_FILTERS
help: PLUGIN_ADMIN.ALLOW_URL_TAXONOMY_FILTERS_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.fallback_types:
type: selectize
size: large
label: PLUGIN_ADMIN.FALLBACK_TYPES
help: PLUGIN_ADMIN.FALLBACK_TYPES_HELP
classes: fancy
validate:
type: commalist
languages:
type: section
title: PLUGIN_ADMIN.LANGUAGES
underline: true
fields:
languages.supported:
type: selectize
size: large
label: PLUGIN_ADMIN.SUPPORTED
help: PLUGIN_ADMIN.SUPPORTED_HELP
classes: fancy
validate:
type: commalist
languages.include_default_lang:
type: toggle
label: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG
help: PLUGIN_ADMIN.INCLUDE_DEFAULT_LANG_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
languages.translations:
type: toggle
label: PLUGIN_ADMIN.TRANSLATIONS_ENABLED
help: PLUGIN_ADMIN.TRANSLATIONS_ENABLED_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
languages.translations_fallback:
type: toggle
label: PLUGIN_ADMIN.TRANSLATIONS_FALLBACK
help: PLUGIN_ADMIN.TRANSLATIONS_FALLBACK_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
languages.session_store_active:
type: toggle
label: PLUGIN_ADMIN.ACTIVE_LANGUAGE_IN_SESSION
help: PLUGIN_ADMIN.ACTIVE_LANGUAGE_IN_SESSION_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
languages.http_accept_language:
type: toggle
label: PLUGIN_ADMIN.HTTP_ACCEPT_LANGUAGE
help: PLUGIN_ADMIN.HTTP_ACCEPT_LANGUAGE_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
languages.override_locale:
type: toggle
label: PLUGIN_ADMIN.OVERRIDE_LOCALE
help: PLUGIN_ADMIN.OVERRIDE_LOCALE_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
http_headers:
type: section
title: PLUGIN_ADMIN.HTTP_HEADERS
underline: true
fields:
pages.expires:
type: text
size: small
label: PLUGIN_ADMIN.EXPIRES
help: PLUGIN_ADMIN.EXPIRES_HELP
validate:
type: number
min: 1
pages.last_modified:
type: toggle
label: PLUGIN_ADMIN.LAST_MODIFIED
help: PLUGIN_ADMIN.LAST_MODIFIED_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.etag:
type: toggle
label: PLUGIN_ADMIN.ETAG
help: PLUGIN_ADMIN.ETAG_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.vary_accept_encoding:
type: toggle
label: PLUGIN_ADMIN.VARY_ACCEPT_ENCODING
help: PLUGIN_ADMIN.VARY_ACCEPT_ENCODING_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
markdown:
type: section
title: Markdown
underline: true
fields:
pages.markdown.extra:
type: toggle
label: Markdown extra
help: PLUGIN_ADMIN.MARKDOWN_EXTRA_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.markdown.auto_line_breaks:
type: toggle
label: PLUGIN_ADMIN.AUTO_LINE_BREAKS
help: PLUGIN_ADMIN.AUTO_LINE_BREAKS_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.markdown.auto_url_links:
type: toggle
label: PLUGIN_ADMIN.AUTO_URL_LINKS
help: PLUGIN_ADMIN.AUTO_URL_LINKS_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
pages.markdown.escape_markup:
type: toggle
label: PLUGIN_ADMIN.ESCAPE_MARKUP
help: PLUGIN_ADMIN.ESCAPE_MARKUP_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
caching:
type: section
title: PLUGIN_ADMIN.CACHING
underline: true
fields:
cache.enabled:
type: toggle
label: PLUGIN_ADMIN.CACHING
help: PLUGIN_ADMIN.CACHING_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
cache.check.method:
type: select
size: small
classes: fancy
label: PLUGIN_ADMIN.CACHE_CHECK_METHOD
help: PLUGIN_ADMIN.CACHE_CHECK_METHOD_HELP
options:
file: File
folder: Folder
none: None
cache.driver:
type: select
size: small
classes: fancy
label: PLUGIN_ADMIN.CACHE_DRIVER
help: PLUGIN_ADMIN.CACHE_DRIVER_HELP
options:
auto: Auto detect
file: File
apc: APC
xcache: XCache
memcache: MemCache
wincache: WinCache
cache.prefix:
type: text
size: x-small
label: PLUGIN_ADMIN.CACHE_PREFIX
help: PLUGIN_ADMIN.CACHE_PREFIX_HELP
placeholder: PLUGIN_ADMIN.CACHE_PREFIX_PLACEHOLDER
cache.lifetime:
type: text
size: small
label: PLUGIN_ADMIN.LIFETIME
help: PLUGIN_ADMIN.LIFETIME_HELP
validate:
type: number
cache.gzip:
type: toggle
label: PLUGIN_ADMIN.GZIP_COMPRESSION
help: PLUGIN_ADMIN.GZIP_COMPRESSION_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
twig:
type: section
title: PLUGIN_ADMIN.TWIG_TEMPLATING
underline: true
fields:
twig.cache:
type: toggle
label: PLUGIN_ADMIN.TWIG_CACHING
help: PLUGIN_ADMIN.TWIG_CACHING_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
twig.debug:
type: toggle
label: PLUGIN_ADMIN.TWIG_DEBUG
help: PLUGIN_ADMIN.TWIG_DEBUG_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
twig.auto_reload:
type: toggle
label: PLUGIN_ADMIN.DETECT_CHANGES
help: PLUGIN_ADMIN.DETECT_CHANGES_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
twig.autoescape:
type: toggle
label: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES
help: PLUGIN_ADMIN.AUTOESCAPE_VARIABLES_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets:
type: section
title: PLUGIN_ADMIN.ASSETS
underline: true
fields:
assets.css_pipeline:
type: toggle
label: PLUGIN_ADMIN.CSS_PIPELINE
help: PLUGIN_ADMIN.CSS_PIPELINE_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets.css_minify:
type: toggle
label: PLUGIN_ADMIN.CSS_MINIFY
help: PLUGIN_ADMIN.CSS_MINIFY_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets.css_minify_windows:
type: toggle
label: PLUGIN_ADMIN.CSS_MINIFY_WINDOWS_OVERRIDE
help: PLUGIN_ADMIN.CSS_MINIFY_WINDOWS_OVERRIDE_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets.css_rewrite:
type: toggle
label: PLUGIN_ADMIN.CSS_REWRITE
help: PLUGIN_ADMIN.CSS_REWRITE_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets.js_pipeline:
type: toggle
label: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE
help: PLUGIN_ADMIN.JAVASCRIPT_PIPELINE_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets.js_minify:
type: toggle
label: PLUGIN_ADMIN.JAVASCRIPT_MINIFY
help: PLUGIN_ADMIN.JAVASCRIPT_MINIFY_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets.enable_asset_timestamp:
type: toggle
label: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS
help: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
assets.collections:
type: array
label: PLUGIN_ADMIN.COLLECTIONS
placeholder_key: collection_name
placeholder_value: collection_path
errors:
type: section
title: PLUGIN_ADMIN.ERROR_HANDLER
underline: true
fields:
errors.display:
type: toggle
label: PLUGIN_ADMIN.DISPLAY_ERRORS
help: PLUGIN_ADMIN.DISPLAY_ERRORS_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
errors.log:
type: toggle
label: PLUGIN_ADMIN.LOG_ERRORS
help: PLUGIN_ADMIN.LOG_ERRORS_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
debugger:
type: section
title: PLUGIN_ADMIN.DEBUGGER
underline: true
fields:
debugger.enabled:
type: toggle
label: PLUGIN_ADMIN.DEBUGGER
help: PLUGIN_ADMIN.DEBUGGER_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
debugger.shutdown.close_connection:
type: toggle
label: PLUGIN_ADMIN.SHUTDOWN_CLOSE_CONNECTION
help: PLUGIN_ADMIN.SHUTDOWN_CLOSE_CONNECTION_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
media:
type: section
title: PLUGIN_ADMIN.MEDIA
underline: true
fields:
images.default_image_quality:
type: text
label: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY
help: PLUGIN_ADMIN.DEFAULT_IMAGE_QUALITY_HELP
classes: x-small
validate:
type: number
min: 1
max: 100
images.cache_all:
type: toggle
label: PLUGIN_ADMIN.CACHE_ALL
help: PLUGIN_ADMIN.CACHE_ALL_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
images.debug:
type: toggle
label: PLUGIN_ADMIN.IMAGES_DEBUG
help: PLUGIN_ADMIN.IMAGES_DEBUG_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
media.upload_limit:
type: text
label: PLUGIN_ADMIN.UPLOAD_LIMIT
help: PLUGIN_ADMIN.UPLOAD_LIMIT_HELP
classes: small
validate:
type: number
media.enable_media_timestamp:
type: toggle
label: PLUGIN_ADMIN.ENABLE_MEDIA_TIMESTAMP
help: PLUGIN_ADMIN.ENABLE_MEDIA_TIMESTAMP_HELP
highlight: 0
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
session:
type: section
title: PLUGIN_ADMIN.SESSION
underline: true
fields:
session.enabled:
type: toggle
label: PLUGIN_ADMIN.ENABLED
help: PLUGIN_ADMIN.SESSION_ENABLED_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
session.timeout:
type: text
size: small
label: PLUGIN_ADMIN.TIMEOUT
help: PLUGIN_ADMIN.TIMEOUT_HELP
validate:
type: number
min: 1
session.name:
type: text
size: small
label: PLUGIN_ADMIN.NAME
help: PLUGIN_ADMIN.SESSION_NAME_HELP
advanced:
type: section
title: PLUGIN_ADMIN.ADVANCED
underline: true
fields:
wrapped_site:
type: toggle
label: PLUGIN_ADMIN.WRAPPED_SITE
highlight: 0
help: PLUGIN_ADMIN.WRAPPED_SITE_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
absolute_urls:
type: toggle
label: PLUGIN_ADMIN.ABSOLUTE_URLS
highlight: 0
help: PLUGIN_ADMIN.ABSOLUTE_URLS_HELP
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
param_sep:
type: select
size: medium
label: PLUGIN_ADMIN.PARAMETER_SEPARATOR
classes: fancy
help: PLUGIN_ADMIN.PARAMETER_SEPARATOR_HELP
default: ''
options:
':': ': (default)'
';': '; (for Apache running on Windows)'

View file

@ -0,0 +1,276 @@
title: PLUGIN_ADMIN.DEFAULT
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: PLUGIN_ADMIN.CONTENT
fields:
header.title:
type: text
autofocus: true
style: vertical
label: PLUGIN_ADMIN.TITLE
content:
type: markdown
label: PLUGIN_ADMIN.CONTENT
validate:
type: textarea
uploads:
type: pagemedia
label: PLUGIN_ADMIN.PAGE_MEDIA
options:
type: tab
title: PLUGIN_ADMIN.OPTIONS
fields:
publishing:
type: section
title: Publishing
underline: true
fields:
header.published:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.PUBLISHED
help: PLUGIN_ADMIN.PUBLISHED_HELP
highlight: 1
size: medium
options:
1: PLUGIN_ADMIN.YES
0: PLUGIN_ADMIN.NO
validate:
type: bool
header.date:
type: datetime
label: PLUGIN_ADMIN.DATE
toggleable: true
help: PLUGIN_ADMIN.DATE_HELP
header.publish_date:
type: datetime
label: PLUGIN_ADMIN.PUBLISHED_DATE
toggleable: true
help: PLUGIN_ADMIN.PUBLISHED_DATE_HELP
header.unpublish_date:
type: datetime
label: PLUGIN_ADMIN.UNPUBLISHED_DATE
toggleable: true
help: PLUGIN_ADMIN.UNPUBLISHED_DATE_HELP
header.metadata:
toggleable: true
type: array
label: PLUGIN_ADMIN.METADATA
help: PLUGIN_ADMIN.METADATA_HELP
placeholder_key: PLUGIN_ADMIN.METADATA_KEY
placeholder_value: PLUGIN_ADMIN.METADATA_VALUE
taxonomies:
type: section
title: PLUGIN_ADMIN.TAXONOMIES
underline: true
fields:
header.taxonomy:
type: taxonomy
label: PLUGIN_ADMIN.TAXONOMY
multiple: true
validate:
type: array
advanced:
type: tab
title: PLUGIN_ADMIN.ADVANCED
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
settings:
type: section
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
validate:
type: slug
route:
type: select
label: PLUGIN_ADMIN.PARENT
classes: fancy
'@data-options': '\Grav\Common\Page\Pages::parents'
'@data-default': '\Grav\Plugin\admin::route'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT
name:
type: select
classes: fancy
label: PLUGIN_ADMIN.PAGE_FILE
help: PLUGIN_ADMIN.PAGE_FILE_HELP
default: default
'@data-options': '\Grav\Common\Page\Pages::pageTypes'
header.body_classes:
type: text
label: PLUGIN_ADMIN.BODY_CLASSES
column2:
type: column
fields:
order_title:
type: section
title: PLUGIN_ADMIN.ORDERING
underline: true
order:
type: order
label: PLUGIN_ADMIN.PAGE_ORDER
sitemap:
overrides:
type: section
title: PLUGIN_ADMIN.OVERRIDES
underline: true
fields:
header.menu:
type: text
label: PLUGIN_ADMIN.MENU
toggleable: true
help: PLUGIN_ADMIN.MENU_HELP
header.slug:
type: text
label: PLUGIN_ADMIN.SLUG
toggleable: true
help: PLUGIN_ADMIN.SLUG_HELP
validate:
message: PLUGIN_ADMIN.SLUG_VALIDATE_MESSAGE
rule: slug
header.redirect:
type: text
label: PLUGIN_ADMIN.REDIRECT
toggleable: true
help: PLUGIN_ADMIN.REDIRECT_HELP
header.process:
type: checkboxes
label: PLUGIN_ADMIN.PROCESS
toggleable: true
'@config-default': system.pages.process
default:
markdown: true
twig: false
options:
markdown: Markdown
twig: Twig
use: keys
header.child_type:
type: select
toggleable: true
label: PLUGIN_ADMIN.DEFAULT_CHILD_TYPE
default: default
placeholder: PLUGIN_ADMIN.USE_GLOBAL
'@data-options': '\Grav\Common\Page\Pages::types'
header.routable:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.ROUTABLE
help: PLUGIN_ADMIN.ROUTABLE_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
header.cache_enable:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.CACHING
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
header.visible:
type: toggle
toggleable: true
label: PLUGIN_ADMIN.VISIBLE
help: PLUGIN_ADMIN.VISIBLE_HELP
highlight: 1
options:
1: PLUGIN_ADMIN.ENABLED
0: PLUGIN_ADMIN.DISABLED
validate:
type: bool
header.template:
type: select
toggleable: true
classes: fancy
label: PLUGIN_ADMIN.DISPLAY_TEMPLATE
default: default
'@data-options': '\Grav\Common\Page\Pages::types'
header.order_by:
type: hidden
header.order_manual:
type: hidden
validate:
type: commalist
blueprint:
type: blueprint

View file

@ -0,0 +1,47 @@
title: PLUGIN_ADMIN.MODULAR
@extends:
type: default
context: blueprints://pages
form:
fields:
tabs:
type: tabs
active: 1
fields:
content:
fields:
header.content.items:
type: select
label: PLUGIN_ADMIN.ITEMS
default: '@self.modular'
options:
'@self.modular': Children
header.content.order.by:
type: select
label: PLUGIN_ADMIN.ORDER_BY
default: date
options:
folder: PLUGIN_ADMIN.FOLDER
title: PLUGIN_ADMIN.TITLE
date: PLUGIN_ADMIN.DATE
default: PLUGIN_ADMIN.DEFAULT
header.content.order.dir:
type: select
label: PLUGIN_ADMIN.ORDER
default: desc
options:
asc: PLUGIN_ADMIN.ASCENDING
desc: PLUGIN_ADMIN.DESCENDING
header.process:
type: ignore
content:
type: ignore
uploads:
type: ignore

View file

@ -0,0 +1,56 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
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:
type: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PAGE
classes: fancy
'@data-options': '\Grav\Common\Page\Pages::parents'
'@data-default': '\Grav\Plugin\admin::route'
options:
'': PLUGIN_ADMIN.DEFAULT_OPTION_SELECT
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

@ -0,0 +1,97 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: PLUGIN_ADMIN.CONTENT
fields:
frontmatter:
type: frontmatter
label: PLUGIN_ADMIN.FRONTMATTER
content:
type: markdown
label: PLUGIN_ADMIN.CONTENT
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:
type: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PARENT
classes: fancy
'@data-options': '\Grav\Common\Page\Pages::parents'
'@data-default': '\Grav\Plugin\admin::route'
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

@ -0,0 +1,17 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
route:
type: select
label: PLUGIN_ADMIN.PARENT
classes: fancy
'@data-options': '\Grav\Common\Page\Pages::parents'
'@data-default': '\Grav\Plugin\admin::route'
options:
'/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT

View file

@ -0,0 +1,66 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
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:
type: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PARENT_PAGE
classes: fancy
'@data-options': '\Grav\Common\Page\Pages::parents'
'@data-default': '\Grav\Plugin\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::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

@ -0,0 +1,96 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: PLUGIN_ADMIN.CONTENT
fields:
frontmatter:
type: frontmatter
label: PLUGIN_ADMIN.FRONTMATTER
autofocus: true
content:
type: markdown
label: PLUGIN_ADMIN.CONTENT
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:
type: slug
required: true
route:
type: select
label: PLUGIN_ADMIN.PARENT
classes: fancy
'@data-options': '\Grav\Common\Page\Pages::parents'
'@data-default': '\Grav\Plugin\admin::route'
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

@ -0,0 +1,56 @@
title: Site
form:
validation: loose
fields:
content:
type: section
title: PLUGIN_ADMIN.ACCOUNT
fields:
username:
type: text
size: large
label: PLUGIN_ADMIN.USERNAME
disabled: true
readonly: true
email:
type: email
size: large
label: PLUGIN_ADMIN.EMAIL
validate:
type: email
message: PLUGIN_ADMIN.EMAIL_VALIDATION_MESSAGE
required: true
password:
type: password
size: large
label: PLUGIN_ADMIN.PASSWORD
validate:
required: true
message: PLUGIN_ADMIN.PASSWORD_VALIDATION_MESSAGE
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
fullname:
type: text
size: large
label: PLUGIN_ADMIN.FULL_NAME
validate:
required: true
title:
type: text
size: large
label: PLUGIN_ADMIN.TITLE
language:
type: select
label: PLUGIN_ADMIN.LANGUAGE
size: medium
classes: fancy
'@data-options': '\Grav\Plugin\admin::adminLanguages'
default: 'en'
help: PLUGIN_ADMIN.LANGUAGE_HELP

View file

@ -0,0 +1,16 @@
title: PLUGIN_ADMIN.ADD_ACCOUNT
form:
validation: loose
fields:
content:
type: section
title: PLUGIN_ADMIN.ADD_ACCOUNT
username:
type: text
label: PLUGIN_ADMIN.USERNAME
help: PLUGIN_ADMIN.USERNAME_HELP
validate:
required: true

View file

@ -0,0 +1,190 @@
defaults:
type: file
thumb: media/thumb.png
mime: application/octet-stream
image:
filters:
default:
- enableProgressive
jpg:
type: image
thumb: media/thumb-jpg.png
mime: image/jpeg
jpe:
type: image
thumb: media/thumb-jpg.png
mime: image/jpeg
jpeg:
type: image
thumb: media/thumb-jpeg.png
mime: image/jpeg
png:
type: image
thumb: media/thumb-png.png
mime: image/png
gif:
type: animated
thumb: media/thumb-gif.png
mime: image/gif
svg:
type: vector
thumb: media/thumb-gif.png
mime: image/svg+xml
mp4:
type: video
thumb: media/thumb-mp4.png
mime: video/mp4
mov:
type: video
thumb: media/thumb-mov.png
mime: video/quicktime
m4v:
type: video
thumb: media/thumb-m4v.png
mime: video/x-m4v
swf:
type: video
thumb: media/thumb-swf.png
mime: video/x-flv
flv:
type: video
thumb: media/thumb-flv.png
mime: video/x-flv
mp3:
type: audio
thumb: media/thumb-mp3.png
mime: audio/mp3
ogg:
type: audio
thumb: media/thumb-ogg.png
mime: audio/ogg
wma:
type: audio
thumb: media/thumb-wma.png
mime: audio/wma
m4a:
type: audio
thumb: media/thumb-m4a.png
mime: audio/m4a
wav:
type: audio
thumb: media/thumb-wav.png
mime: audio/wav
aiff:
type: audio
mime: audio/aiff
aif:
type: audio
mime: audio/aif
txt:
type: file
thumb: media/thumb-txt.png
mime: text/plain
xml:
type: file
thumb: media/thumb-xml.png
mime: application/xml
doc:
type: file
thumb: media/thumb-doc.png
mime: application/msword
docx:
type: file
mime: application/msword
xls:
type: file
mime: application/vnd.ms-excel
xlt:
type: file
mime: application/vnd.ms-excel
xlm:
type: file
mime: application/vnd.ms-excel
xld:
type: file
mime: application/vnd.ms-excel
xla:
type: file
mime: application/vnd.ms-excel
xlc:
type: file
mime: application/vnd.ms-excel
xlw:
type: file
mime: application/vnd.ms-excel
xll:
type: file
mime: application/vnd.ms-excel
ppt:
type: file
mime: application/vnd.ms-powerpoint
pps:
type: file
mime: application/vnd.ms-powerpoint
rtf:
type: file
mime: application/rtf
bmp:
type: file
mime: image/bmp
tiff:
type: file
mime: image/tiff
mpeg:
type: file
mime: video/mpeg
mpg:
type: file
mime: video/mpeg
mpe:
type: file
mime: video/mpeg
avi:
type: file
mime: video/msvideo
wmv:
type: file
mime: video/x-ms-wmv
html:
type: file
thumb: media/thumb-html.png
mime: text/html
htm:
type: file
thumb: media/thumb-html.png
mime: text/html
pdf:
type: file
thumb: media/thumb-pdf.png
mime: application/pdf
zip:
type: file
thumb: media/thumb-zip.png
mime: application/zip
gz:
type: file
thumb: media/thumb-gz.png
mime: application/gzip
tar:
type: file
mime: application/x-tar
css:
type: file
thumb: media/thumb-css.png
mime: text/css
js:
type: file
thumb: media/thumb-js.png
mime: application/javascript
json:
type: file
thumb: media/thumb-json.png
mime: application/json

View file

@ -0,0 +1,34 @@
title: Grav # Name of the site
author:
name: John Appleseed # Default author name
email: 'john@email.com' # Default author email
taxonomies: [category,tag] # Arbitrary list of taxonomy types
metadata:
description: 'My Grav Site' # Site description
summary:
enabled: true # enable or disable summary of page
format: short # long = summary delimiter will be ignored; short = use the first occurrence of delimiter or size
size: 300 # Maximum length of summary (characters)
delimiter: === # The summary delimiter
redirects:
/redirect-test: / # Redirect test goes to home page
/old/(.*): /new/$1 # Would redirect /old/my-page to /new/my-page
routes:
/something/else: '/blog/sample-3' # Alias for /blog/sample-3
/new/(.*): '/blog/$1' # Regex any /new/my-page URL to /blog/my-page Route
blog:
route: '/blog' # Custom value added (accessible via system.blog.route)
#menu: # Sample Menu Example
# - text: Source
# icon: github
# url: https://github.com/getgrav/grav
# - icon: twitter
# url: http://twitter.com/getgrav

View file

@ -0,0 +1,21 @@
schemes:
asset:
type: ReadOnlyStream
paths:
- assets
image:
type: ReadOnlyStream
paths:
- user://images
- system://images
page:
type: ReadOnlyStream
paths:
- user://pages
account:
type: ReadOnlyStream
paths:
- user://accounts

View file

@ -0,0 +1,112 @@
absolute_urls: false # Absolute or relative URLs for `base_url`
timezone: '' # Valid values: http://php.net/manual/en/timezones.php
default_locale: # Default locale (defaults to system)
param_sep: ':' # Parameter separator, use ';' for Apache on windows
wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform
languages:
supported: [] # List of languages supported. eg: [en, fr, de]
include_default_lang: true # Include the default lang prefix in all URLs
translations: true # Enable translations by default
translations_fallback: true # Fallback through supported translations if active lang doesn't exist
session_store_active: false # Store active language in session
http_accept_language: false # Attempt to set the language based on http_accept_language header in the browser
override_locale: false # Override the default or system locale with language specific one
home:
alias: '/home' # Default path for home, ie /
pages:
theme: antimatter # Default theme (defaults to "antimatter" theme)
order:
by: default # Order pages by "default", "alpha" or "date"
dir: asc # Default ordering direction, "asc" or "desc"
list:
count: 20 # Default item count per page
dateformat:
default: # The default date format Grav expects in the `date: ` field
short: 'jS M Y' # Short date format
long: 'F jS \a\t g:ia' # Long date format
publish_dates: true # automatically publish/unpublish based on dates
process:
markdown: true # Process Markdown
twig: false # Process Twig
events:
page: true # Enable page level events
twig: true # Enable twig level events
markdown:
extra: false # Enable support for Markdown Extra support (GFM by default)
auto_line_breaks: false # Enable automatic line breaks
auto_url_links: false # Enable automatic HTML links
escape_markup: false # Escape markup tags into entities
special_chars: # List of special characters to automatically convert to entities
'>': 'gt'
'<': 'lt'
types: [txt,xml,html,json,rss,atom] # list of valid page types
expires: 604800 # Page expires time in seconds (604800 seconds = 7 days)
last_modified: false # Set the last modified date header based on file modifcation timestamp
etag: false # Set the etag header tag
vary_accept_encoding: false # Add `Vary: Accept-Encoding` header
redirect_default_route: false # Automatically redirect to a page's default route
redirect_default_code: 301 # Default code to use for redirects
redirect_trailing_slash: true # Handle automatically or 301 redirect a trailing / URL
ignore_files: [.DS_Store] # Files to ignore in Pages
ignore_folders: [.git, .idea] # Folders to ignore in Pages
ignore_hidden: true # Ignore all Hidden files and folders
url_taxonomy_filters: true # Enable auto-magic URL-based taxonomy filters for page collections
fallback_types: [png,jpg,jpeg,gif] # Allowed types of files found if accessed via Page route
cache:
enabled: true # Set to true to enable caching
check:
method: file # Method to check for updates in pages: file|folder|none
driver: auto # One of: auto|file|apc|xcache|memcache|wincache
prefix: 'g' # Cache prefix string (prevents cache conflicts)
lifetime: 604800 # Lifetime of cached data in seconds (0 = infinite)
gzip: false # GZip compress the page output
twig:
cache: true # Set to true to enable twig caching
debug: false # Enable Twig debug
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars
undefined_functions: true # Allow undefined functions
undefined_filters: true # Allow undefined filters
assets: # Configuration for Assets Manager (JS, CSS)
css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
css_minify: true # Minify the CSS during pipelining
css_minify_windows: false # Minify Override for Windows platforms. False by default due to ThreadStackSize
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
enable_asset_timestamp: false # Enable asset timestamps
collections:
jquery: system://assets/jquery/jquery-2.1.4.min.js
errors:
display: false # Display full backtrace-style error page
log: true # Log errors to /logs folder
debugger:
enabled: false # Enable Grav debugger and following settings
shutdown:
close_connection: true # Close the connection before calling onShutdown(). false for debugging
images:
default_image_quality: 85 # Default image quality to use when resampling images (85%)
cache_all: false # Cache all image by default
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
media:
enable_media_timestamp: false # Enable media timetsamps
upload_limit: 0 # Set maximum upload size in bytes (0 is unlimited)
unsupported_inline_types: [] # Array of unsupported media file types to try to display inline
session:
enabled: true # Enable Session support
timeout: 1800 # Timeout in seconds
name: grav-site # Name prefix of the session cookie
security:
default_hash: $2y$10$kwsyMVwM8/7j0K/6LHT.g.Fs49xOCTp2b8hh/S5.dPJuJcJB6T.UK

42
src/system/defines.php Normal file
View file

@ -0,0 +1,42 @@
<?php
// Some standard defines
define('GRAV', true);
define('GRAV_VERSION', '1.0.0-rc.4');
define('DS', '/');
// Directories and Paths
if (!defined('GRAV_ROOT')) {
define('GRAV_ROOT', str_replace(DIRECTORY_SEPARATOR, DS, getcwd()));
}
define('ROOT_DIR', GRAV_ROOT . '/');
define('USER_PATH', 'user/');
define('USER_DIR', ROOT_DIR . USER_PATH);
define('SYSTEM_DIR', ROOT_DIR .'system/');
define('CACHE_DIR', ROOT_DIR . 'cache/');
define('LOG_DIR', ROOT_DIR .'logs/');
// DEPRECATED: Do not use!
define('ASSETS_DIR', ROOT_DIR . 'assets/');
define('IMAGES_DIR', ROOT_DIR . 'images/');
define('ACCOUNTS_DIR', USER_DIR .'accounts/');
define('PAGES_DIR', USER_DIR .'pages/');
define('DATA_DIR', USER_DIR .'data/');
define('LIB_DIR', SYSTEM_DIR .'src/');
define('PLUGINS_DIR', USER_DIR .'plugins/');
define('THEMES_DIR', USER_DIR .'themes/');
define('VENDOR_DIR', ROOT_DIR .'vendor/');
// END DEPRECATED
// Some extensions
define('CONTENT_EXT', '.md');
define('TEMPLATE_EXT', '.html.twig');
define('TWIG_EXT', '.twig');
define('PLUGIN_EXT', '.php');
define('YAML_EXT', '.yaml');
// Content types
define('RAW_CONTENT', 1);
define('TWIG_CONTENT', 2);
define('TWIG_CONTENT_LIST', 3);
define('TWIG_TEMPLATES', 4);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,37 @@
NICETIME:
NO_DATE_PROVIDED: Datum nebylo vloženo
BAD_DATE: Chybné datum
AGO: zpět
FROM_NOW: od teď
SECOND: sekunda
MINUTE: minuta
HOUR: hodina
DAY: den
WEEK: týden
MONTH: měsíc
YEAR: rok
DECADE: dekáda
SEC: sek
MIN: min
HR: hod
DAY: den
WK: t
MO: m
YR: r
DEC: dek
SECOND_PLURAL: sekundy
MINUTE_PLURAL: minuty
HOUR_PLURAL: hodiny
DAY_PLURAL: dny
WEEK_PLURAL: týdny
MONTH_PLURAL: měsíce
YEAR_PLURAL: roky
DECADE_PLURAL: dekády
SEC_PLURAL: sek
MIN_PLURAL: min
HR_PLURAL: hod
DAY_PLURAL: dny
WK_PLURAL: t
MO_PLURAL: m
YR_PLURAL: r
DEC_PLURAL: dek

View file

@ -0,0 +1,43 @@
INFLECTOR_IRREGULAR:
'person': 'Personen'
'man': 'Menschen'
'child': 'Kinder'
'sex': 'Geschlecht'
'move': 'Züge'
NICETIME:
NO_DATE_PROVIDED: Keine Daten vorhanden
BAD_DATE: Falsches Datum
AGO: her
FROM_NOW: ab jetzt
SECOND: Sekunde
MINUTE: Minute
HOUR: Stunde
DAY: Tag
WEEK: Woche
MONTH: Monat
YEAR: Jahr
DECADE: Dekade
SEC: sek
MIN: min
HR: std
DAY: Tag
WK: wo
MO: mo
YR: yh
DEC: dec
SECOND_PLURAL: Sekunden
MINUTE_PLURAL: Minuten
HOUR_PLURAL: Stunden
DAY_PLURAL: Tage
WEEK_PLURAL: Wochen
MONTH_PLURAL: Monate
YEAR_PLURAL: Jahre
DECADE_PLURAL: Dekaden
SEC_PLURAL: Sekunden
MIN_PLURAL: Minuten
HR_PLURAL: Stunden
DAY_PLURAL: Tage
WK_PLURAL: Wochen
MO_PLURAL: Monate
YR_PLURAL: Jahre
DEC_PLURAL: Dekaden

View file

@ -0,0 +1,94 @@
FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Invalid Frontmatter\n\nPath: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
INFLECTOR_PLURALS:
'/(quiz)$/i': '\1zes'
'/^(ox)$/i': '\1en'
'/([m|l])ouse$/i': '\1ice'
'/(matr|vert|ind)ix|ex$/i': '\1ices'
'/(x|ch|ss|sh)$/i': '\1es'
'/([^aeiouy]|qu)ies$/i': '\1y'
'/([^aeiouy]|qu)y$/i': '\1ies'
'/(hive)$/i': '\1s'
'/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
'/sis$/i': 'ses'
'/([ti])um$/i': '\1a'
'/(buffal|tomat)o$/i': '\1oes'
'/(bu)s$/i': '\1ses'
'/(alias|status)/i': '\1es'
'/(octop|vir)us$/i': '\1i'
'/(ax|test)is$/i': '\1es'
'/s$/i': 's'
'/$/': 's'
INFLECTOR_SINGULAR:
'/(quiz)zes$/i': '\1'
'/(matr)ices$/i': '\1ix'
'/(vert|ind)ices$/i': '\1ex'
'/^(ox)en/i': '\1'
'/(alias|status)es$/i': '\1'
'/([octop|vir])i$/i': '\1us'
'/(cris|ax|test)es$/i': '\1is'
'/(shoe)s$/i': '\1'
'/(o)es$/i': '\1'
'/(bus)es$/i': '\1'
'/([m|l])ice$/i': '\1ouse'
'/(x|ch|ss|sh)es$/i': '\1'
'/(m)ovies$/i': '\1ovie'
'/(s)eries$/i': '\1eries'
'/([^aeiouy]|qu)ies$/i': '\1y'
'/([lr])ves$/i': '\1f'
'/(tive)s$/i': '\1'
'/(hive)s$/i': '\1'
'/([^f])ves$/i': '\1fe'
'/(^analy)ses$/i': '\1sis'
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
'/([ti])a$/i': '\1um'
'/(n)ews$/i': '\1ews'
'/s$/i': ''
INFLECTOR_UNCOUNTABLE: ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep']
INFLECTOR_IRREGULAR:
'person': 'people'
'man': 'men'
'child': 'children'
'sex': 'sexes'
'move': 'moves'
INFLECTOR_ORDINALS:
'default': 'th'
'first': 'st'
'second': 'nd'
'third': 'rd'
NICETIME:
NO_DATE_PROVIDED: No date provided
BAD_DATE: Bad date
AGO: ago
FROM_NOW: from now
SECOND: second
MINUTE: minute
HOUR: hour
DAY: day
WEEK: week
MONTH: month
YEAR: year
DECADE: decade
SEC: sec
MIN: min
HR: hr
DAY: day
WK: wk
MO: mo
YR: yr
DEC: dec
SECOND_PLURAL: seconds
MINUTE_PLURAL: minutes
HOUR_PLURAL: hours
DAY_PLURAL: days
WEEK_PLURAL: weeks
MONTH_PLURAL: months
YEAR_PLURAL: years
DECADE_PLURAL: decades
SEC_PLURAL: secs
MIN_PLURAL: mins
HR_PLURAL: hrs
DAY_PLURAL: days
WK_PLURAL: wks
MO_PLURAL: mos
YR_PLURAL: yrs
DEC_PLURAL: decs

View file

@ -0,0 +1,60 @@
INFLECTOR_PLURALS:
'/$/': 's'
'/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)$/': '\1x'
'/(bleu|émeu|landau|lieu|pneu|sarrau)$/': '\1s'
'/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/': '\1aux'
'/(s|x|z)$/': '\1'
'/ail$/': 'ails'
'/al$/': 'aux'
'/s$/i': 's'
INFLECTOR_SINGULAR:
'/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/': '\1'
'/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/': '\1ail'
'/(journ|chev)aux$/': '\1al'
'/ails$/': 'ail'
'/s$/i': ''
INFLECTOR_IRREGULAR:
'madame': 'mesdames'
'mademoiselle': 'mesdemoiselles'
'monsieur': 'messieurs'
INFLECTOR_ORDINALS:
'default': 'ème'
'first': 'er'
'second': 'nd'
NICETIME:
NO_DATE_PROVIDED: Aucune date
BAD_DATE: Date erronée
AGO: plus tôt
FROM_NOW: à partir de maintenant
SECOND: seconde
MINUTE: minute
HOUR: heure
DAY: jour
WEEK: semaine
MONTH: mois
YEAR: an
DECADE: décennie
SEC: s
MIN: m
HR: h
DAY: j
WK: s
MO: m
YR: a
DEC: d
SECOND_PLURAL: secondes
MINUTE_PLURAL: minutes
HOUR_PLURAL: heures
DAY_PLURAL: jours
WEEK_PLURAL: semaines
MONTH_PLURAL: mois
YEAR_PLURAL: ans
DECADE_PLURAL: décennies
SEC_PLURAL: s
MIN_PLURAL: m
HR_PLURAL: h
DAY_PLURAL: j
WK_PLURAL: s
MO_PLURAL: m
YR_PLURAL: a
DEC_PLURAL: d

View file

@ -0,0 +1,21 @@
NICETIME:
NO_DATE_PROVIDED: Nessuna data fornita
BAD_DATE: Data errata
AGO: fa
FROM_NOW: da adesso
SECOND: secondo
MINUTE: minuto
HOUR: ora
DAY: giorno
WEEK: settimana
MONTH: mese
YEAR: anno
DECADE: decade
SECOND_PLURAL: secondi
MINUTE_PLURAL: minuti
HOUR_PLURAL: ore
DAY_PLURAL: giorni
WEEK_PLURAL: settimane
MONTH_PLURAL: mesi
YEAR_PLURAL: anni
DECADE_PLURAL: decadi

View file

@ -0,0 +1,43 @@
INFLECTOR_IRREGULAR:
'person': 'personen'
'man': 'mensen'
'child': 'kinderen'
'sex': 'geslacht'
'move': 'verplaatsen'
NICETIME:
NO_DATE_PROVIDED: geen datum opgegeven
BAD_DATE: Datumformaat onjuist
AGO: geleden
FROM_NOW: vanaf nu
SECOND: seconde
MINUTE: minuut
HOUR: uur
DAY: dag
WEEK: week
MONTH: maand
YEAR: jaar
DECADE: decenium
SEC: sec
MIN: min
HR: hr
DAY: dag
WK: wk
MO: ma
YR: yr
DEC: dec
SECOND_PLURAL: seconden
MINUTE_PLURAL: minuten
HOUR_PLURAL: uren
DAY_PLURAL: dagen
WEEK_PLURAL: weken
MONTH_PLURAL: maanden
YEAR_PLURAL: jaren
DECADE_PLURAL: decennia
SEC_PLURAL: seconden
MIN_PLURAL: minuten
HR_PLURAL: uren
DAY_PLURAL: dagen
WK_PLURAL: weken
MO_PLURAL: maanden
YR_PLURAL: jaren
DEC_PLURAL: decs

View file

@ -0,0 +1,43 @@
INFLECTOR_IRREGULAR:
'person': 'люди'
'man': 'человек'
'child': 'ребенок'
'sex': 'пол'
'move': 'движется'
NICETIME:
NO_DATE_PROVIDED: Дата не указана
BAD_DATE: Неверная дата
AGO: назад
FROM_NOW: теперь
SECOND: секунда
MINUTE: минута
HOUR: час
DAY: день
WEEK: неделя
MONTH: месяц
YEAR: год
DECADE: десятилетие
SEC: с
MIN: мин
HR: ч
DAY: д
WK: нед
MO: мес
YR: г.
DEC: гг.
SECOND_PLURAL: секунды
MINUTE_PLURAL: минуты
HOUR_PLURAL: часы
DAY_PLURAL: дни
WEEK_PLURAL: недели
MONTH_PLURAL: месяцы
YEAR_PLURAL: годы
DECADE_PLURAL: десятилетия
SEC_PLURAL: с
MIN_PLURAL: мин
HR_PLURAL: ч
DAY_PLURAL: д
WK_PLURAL: нед
MO_PLURAL: мес
YR_PLURAL: г.
DEC_PLURAL: гг.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,130 @@
<?php
namespace Grav\Common\Backup;
use Grav\Common\GravTrait;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Inflector;
/**
* The ZipBackup class lets you create simple zip-backups of a grav site
*
* @author RocketTheme
* @license MIT
*/
class ZipBackup
{
use GravTrait;
protected static $ignorePaths = [
'backup',
'cache',
'images',
'logs'
];
protected static $ignoreFolders = [
'.git',
'.svn',
'.hg',
'.idea'
];
public static function backup($destination = null, callable $messager = null)
{
if (!$destination) {
$destination = self::getGrav()['locator']->findResource('backup://', true);
if (!$destination)
throw new \RuntimeException('The backup folder is missing.');
Folder::mkdir($destination);
}
$name = self::getGrav()['config']->get('site.title', basename(GRAV_ROOT));
$inflector = new Inflector();
if (is_dir($destination)) {
$date = date('YmdHis', time());
$filename = trim($inflector->hyphenize($name), '-') . '-' . $date . '.zip';
$destination = rtrim($destination, DS) . DS . $filename;
}
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => 'Creating new Backup "' . $destination . '"'
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => ''
]);
$zip = new \ZipArchive();
$zip->open($destination, \ZipArchive::CREATE);
static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager);
$messager && $messager([
'type' => 'progress',
'percentage' => false,
'complete' => true
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => ''
]);
$messager && $messager([
'type' => 'message',
'level' => 'info',
'message' => 'Saving and compressing archive...'
]);
$zip->close();
return $destination;
}
/**
* @param $folder
* @param $zipFile
* @param $exclusiveLength
* @param $messager
*/
private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, callable $messager = null)
{
$handle = opendir($folder);
while (false !== $f = readdir($handle)) {
if ($f != '.' && $f != '..') {
$filePath = "$folder/$f";
// Remove prefix from file path before add to zip.
$localPath = substr($filePath, $exclusiveLength);
if (in_array($f, static::$ignoreFolders)) {
continue;
} elseif (in_array($localPath, static::$ignorePaths)) {
$zipFile->addEmptyDir($f);
continue;
}
if (is_file($filePath)) {
$zipFile->addFile($filePath, $localPath);
$messager && $messager([
'type' => 'progress',
'percentage' => false,
'complete' => false
]);
} elseif (is_dir($filePath)) {
// Add sub-directory.
$zipFile->addEmptyDir($localPath);
static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager);
}
}
}
closedir($handle);
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace Grav\Common;
/**
* Simple wrapper for the very simple parse_user_agent() function
*/
class Browser
{
protected $useragent = [];
public function __construct()
{
try {
$this->useragent = parse_user_agent();
} catch (\InvalidArgumentException $e) {
$this->useragent = parse_user_agent("Mozilla/5.0 (compatible; Unknown;)");
}
}
public function getBrowser()
{
return strtolower($this->useragent['browser']);
}
public function getPlatform()
{
return strtolower($this->useragent['platform']);
}
public function getLongVersion()
{
return $this->useragent['version'];
}
public function getVersion()
{
$version = explode('.', $this->getLongVersion());
return intval($version[0]);
}
/**
* Determine if the request comes from a human, or from a bot/crawler
*/
public function isHuman()
{
$browser = $this->getBrowser();
if (empty($browser)) {
return false;
}
if (preg_match('~(bot|crawl)~i', $browser)) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,314 @@
<?php
namespace Grav\Common;
use \Doctrine\Common\Cache\Cache as DoctrineCache;
use Grav\Common\Config\Config;
use Grav\Common\Filesystem\Folder;
/**
* The GravCache object is used throughout Grav to store and retrieve cached data.
* It uses DoctrineCache library and supports a variety of caching mechanisms. Those include:
*
* APC
* XCache
* RedisCache
* MemCache
* MemCacheD
* FileSystem
*
* @author RocketTheme
* @license MIT
*/
class Cache extends Getters
{
/**
* @var string Cache key.
*/
protected $key;
protected $lifetime;
protected $now;
protected $config;
/**
* @var DoctrineCache
*/
protected $driver;
/**
* @var bool
*/
protected $enabled;
protected $cache_dir;
protected static $standard_remove = [
'cache/twig/',
'cache/doctrine/',
'cache/compiled/',
'cache/validated-',
'images/',
'assets/',
];
protected static $all_remove = [
'cache/',
'images/',
'assets/'
];
protected static $assets_remove = [
'assets/'
];
protected static $images_remove = [
'images/'
];
protected static $cache_remove = [
'cache/'
];
/**
* Constructor
*
* @params Grav $grav
*/
public function __construct(Grav $grav)
{
$this->init($grav);
}
/**
* Initialization that sets a base key and the driver based on configuration settings
*
* @param Grav $grav
* @return void
*/
public function init(Grav $grav)
{
/** @var Config $config */
$this->config = $grav['config'];
$this->now = time();
$this->cache_dir = $grav['locator']->findResource('cache://doctrine', true, true);
/** @var Uri $uri */
$uri = $grav['uri'];
$prefix = $this->config->get('system.cache.prefix');
$this->enabled = (bool) $this->config->get('system.cache.enabled');
// Cache key allows us to invalidate all cache on configuration changes.
$this->key = ($prefix ? $prefix : 'g') . '-' . substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
$this->driver = $this->getCacheDriver();
// Set the cache namespace to our unique key
$this->driver->setNamespace($this->key);
}
/**
* Automatically picks the cache mechanism to use. If you pick one manually it will use that
* If there is no config option for $driver in the config, or it's set to 'auto', it will
* pick the best option based on which cache extensions are installed.
*
* @return DoctrineCacheDriver The cache driver to use
*/
public function getCacheDriver()
{
$setting = $this->config->get('system.cache.driver');
$driver_name = 'file';
if (!$setting || $setting == 'auto') {
if (extension_loaded('apc')) {
$driver_name = 'apc';
} elseif (extension_loaded('wincache')) {
$driver_name = 'wincache';
} elseif (extension_loaded('xcache')) {
$driver_name = 'xcache';
}
} else {
$driver_name = $setting;
}
switch ($driver_name) {
case 'apc':
$driver = new \Doctrine\Common\Cache\ApcCache();
break;
case 'wincache':
$driver = new \Doctrine\Common\Cache\WinCacheCache();
break;
case 'xcache':
$driver = new \Doctrine\Common\Cache\XcacheCache();
break;
case 'memcache':
$memcache = new \Memcache();
$memcache->connect($this->config->get('system.cache.memcache.server','localhost'),
$this->config->get('system.cache.memcache.port', 11211));
$driver = new \Doctrine\Common\Cache\MemcacheCache();
$driver->setMemcache($memcache);
break;
case 'redis':
$redis = new \Redis();
$redis->connect($this->config->get('system.cache.redis.server','localhost'),
$this->config->get('system.cache.redis.port', 6379));
$driver = new \Doctrine\Common\Cache\RedisCache();
$driver->setRedis($redis);
break;
default:
$driver = new \Doctrine\Common\Cache\FilesystemCache($this->cache_dir);
break;
}
return $driver;
}
/**
* Gets a cached entry if it exists based on an id. If it does not exist, it returns false
*
* @param string $id the id of the cached entry
* @return object returns the cached entry, can be any type, or false if doesn't exist
*/
public function fetch($id)
{
if ($this->enabled) {
return $this->driver->fetch($id);
} else {
return false;
}
}
/**
* Stores a new cached entry.
*
* @param string $id the id of the cached entry
* @param array|object $data the data for the cached entry to store
* @param int $lifetime the lifetime to store the entry in seconds
*/
public function save($id, $data, $lifetime = null)
{
if ($this->enabled) {
if ($lifetime === null) {
$lifetime = $this->getLifetime();
}
$this->driver->save($id, $data, $lifetime);
}
}
/**
* Getter method to get the cache key
*/
public function getKey()
{
return $this->key;
}
/**
* Helper method to clear all Grav caches
*
* @param string $remove standard|all|assets-only|images-only|cache-only
*
* @return array
*/
public static function clearCache($remove = 'standard')
{
$output = [];
$user_config = USER_DIR . 'config/system.yaml';
switch($remove) {
case 'all':
$remove_paths = self::$all_remove;
break;
case 'assets-only':
$remove_paths = self::$assets_remove;
break;
case 'images-only':
$remove_paths = self::$images_remove;
break;
case 'cache-only':
$remove_paths = self::$cache_remove;
break;
default:
$remove_paths = self::$standard_remove;
}
foreach ($remove_paths as $path) {
$anything = false;
$files = glob(ROOT_DIR . $path . '*');
if (is_array($files)) {
foreach ($files as $file) {
if (is_file($file)) {
if (@unlink($file)) {
$anything = true;
}
} elseif (is_dir($file)) {
if (@Folder::delete($file)) {
$anything = true;
}
}
}
}
if ($anything) {
$output[] = '<red>Cleared: </red>' . $path . '*';
}
}
$output[] = '';
if (($remove == 'all' || $remove == 'standard') && file_exists($user_config)) {
touch($user_config);
$output[] = '<red>Touched: </red>' . $user_config;
$output[] = '';
}
return $output;
}
/**
* Set the cache lifetime programmatically
*
* @param int $future timestamp
*/
public function setLifetime($future)
{
if (!$future) {
return;
}
$interval = $future - $this->now;
if ($interval > 0 && $interval < $this->getLifetime()) {
$this->lifetime = $interval;
}
}
/**
* Retrieve the cache lifetime (in seconds)
*
* @return mixed
*/
public function getLifetime()
{
if ($this->lifetime === null) {
$this->lifetime = $this->config->get('system.cache.lifetime') ?: 604800; // 1 week default
}
return $this->lifetime;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace Grav\Common;
/**
* Offers composer helper methods.
*
* @author eschmar
* @license MIT
*/
class Composer
{
/** @const Default composer location */
const DEFAULT_PATH = "bin/composer.phar";
/**
* Returns the location of composer.
*
* @return string
*/
public static function getComposerLocation()
{
if (!function_exists('shell_exec') || strtolower(substr(PHP_OS, 0, 3)) === 'win') {
return self::DEFAULT_PATH;
}
// check for global composer install
$path = trim(shell_exec("command -v composer"));
// fall back to grav bundled composer
if (!$path || !preg_match('/(composer|composer\.phar)$/', $path)) {
$path = self::DEFAULT_PATH;
}
return $path;
}
public static function getComposerExecutor()
{
$executor = PHP_BINARY . ' ';
$composer = static::getComposerLocation();
if ($composer !== static::DEFAULT_PATH && is_executable($composer)) {
$file = fopen($composer, 'r');
$firstLine = fgets($file);
fclose($file);
if (!preg_match('/^#!.+php/i', $firstLine)) {
$executor = '';
}
}
return $executor . $composer;
}
}

View file

@ -0,0 +1,207 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\Filesystem\Folder;
use RocketTheme\Toolbox\Blueprints\Blueprints as BaseBlueprints;
use RocketTheme\Toolbox\File\PhpFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* The Blueprints class contains configuration rules.
*
* @author RocketTheme
* @license MIT
*/
class Blueprints extends BaseBlueprints
{
protected $grav;
protected $files = [];
protected $blueprints;
public function __construct(array $serialized = null, Grav $grav = null)
{
parent::__construct($serialized);
$this->grav = $grav ?: Grav::instance();
}
public function init()
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$blueprints = $locator->findResources('blueprints://config');
$plugins = $locator->findResources('plugins://');
$blueprintFiles = $this->getBlueprintFiles($blueprints, $plugins);
$this->loadCompiledBlueprints($plugins + $blueprints, $blueprintFiles);
}
protected function loadCompiledBlueprints($blueprints, $blueprintFiles)
{
$checksum = md5(serialize($blueprints));
$filename = CACHE_DIR . 'compiled/blueprints/' . $checksum .'.php';
$checksum .= ':'.md5(serialize($blueprintFiles));
$class = get_class($this);
$file = PhpFile::instance($filename);
if ($file->exists()) {
$cache = $file->exists() ? $file->content() : null;
} else {
$cache = null;
}
// Load real file if cache isn't up to date (or is invalid).
if (
!is_array($cache)
|| empty($cache['checksum'])
|| empty($cache['$class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load blueprints.
$this->blueprints = new Blueprints();
foreach ($blueprintFiles as $key => $files) {
$this->loadBlueprints($key);
}
$cache = [
'@class' => $class,
'checksum' => $checksum,
'files' => $blueprintFiles,
'data' => $this->blueprints->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$file->save($cache);
$file->unlock();
}
} else {
$this->blueprints = new Blueprints($cache['data']);
}
}
/**
* Load global blueprints.
*
* @param string $key
* @param array $files
*/
public function loadBlueprints($key, array $files = null)
{
if (is_null($files)) {
$files = $this->files[$key];
}
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->blueprints->embed($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;
}
/**
* 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 (is_file($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];
}
}

View file

@ -0,0 +1,479 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\Grav;
use Grav\Common\Data\Data;
use RocketTheme\Toolbox\Blueprints\Blueprints;
use RocketTheme\Toolbox\File\PhpFile;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* The Config class contains configuration information.
*
* @author RocketTheme
* @license MIT
*/
class Config extends Data
{
protected $grav;
protected $streams = [
'system' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['system'],
]
],
'user' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user'],
]
],
'blueprints' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://blueprints', 'system/blueprints'],
]
],
'config' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://config', 'system/config'],
]
],
'plugins' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://plugins'],
]
],
'plugin' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://plugins'],
]
],
'themes' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://themes'],
]
],
'languages' => [
'type' => 'ReadOnlyStream',
'prefixes' => [
'' => ['user://languages', 'system/languages'],
]
],
'cache' => [
'type' => 'Stream',
'prefixes' => [
'' => ['cache'],
'images' => ['images']
]
],
'log' => [
'type' => 'Stream',
'prefixes' => [
'' => ['logs']
]
],
'backup' => [
'type' => 'Stream',
'prefixes' => [
'' => ['backup']
]
]
];
protected $setup = [];
protected $blueprintFiles = [];
protected $configFiles = [];
protected $languageFiles = [];
protected $checksum;
protected $timestamp;
protected $configLookup;
protected $blueprintLookup;
protected $pluginLookup;
protected $languagesLookup;
protected $finder;
protected $environment;
protected $messages = [];
protected $languages;
public function __construct(array $setup = 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;
// Make sure that
if (!isset($setup['streams']['schemes'])) {
$setup['streams']['schemes'] = [];
}
$setup['streams']['schemes'] += $this->streams;
$setup = $this->autoDetectEnvironmentConfig($setup);
$this->setup = $setup;
parent::__construct($setup);
$this->check();
}
public function key()
{
return $this->checksum();
}
public function reload()
{
$this->items = $this->setup;
$this->check();
$this->init();
$this->debug();
return $this;
}
protected function check()
{
$streams = isset($this->items['streams']['schemes']) ? $this->items['streams']['schemes'] : null;
if (!is_array($streams)) {
throw new \InvalidArgumentException('Configuration is missing streams.schemes!');
}
$diff = array_keys(array_diff_key($this->streams, $streams));
if ($diff) {
throw new \InvalidArgumentException(
sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
);
}
}
public function debug()
{
foreach ($this->messages as $message) {
$this->grav['debugger']->addMessage($message);
}
$this->messages = [];
}
public function init()
{
/** @var UniformResourceLocator $locator */
$locator = $this->grav['locator'];
$this->configLookup = $locator->findResources('config://');
$this->blueprintLookup = $locator->findResources('blueprints://config');
$this->pluginLookup = $locator->findResources('plugins://');
$this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master');
$this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master');
// process languages if supported
if ($this->get('system.languages.translations', true)) {
$this->languagesLookup = $locator->findResources('languages://');
$this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master');
}
$this->initializeLocator($locator);
}
public function checksum()
{
if (empty($this->checksum)) {
$checkBlueprints = $this->get('system.cache.check.blueprints', false);
$checkLanguages = $this->get('system.cache.check.languages', false);
$checkConfig = $this->get('system.cache.check.config', true);
$checkSystem = $this->get('system.cache.check.system', true);
if (!$checkBlueprints && !$checkLanguages && !$checkConfig && !$checkSystem) {
$this->messages[] = 'Skip configuration timestamp check.';
return false;
}
// Generate checksum according to the configuration settings.
if (!$checkConfig) {
// Just check changes in system.yaml files and ignore all the other files.
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
} else {
// Check changes in all configuration files.
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
}
if ($checkBlueprints) {
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
} else {
$cb = [];
}
if ($checkLanguages) {
$cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup);
} else {
$cl = [];
}
$this->checksum = md5(json_encode([$cc, $cb, $cl]));
}
return $this->checksum;
}
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 . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/blueprints/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$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)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['checksum'] != $checksum
|| $cache['@class'] != $class
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load blueprints.
$this->blueprints = new Blueprints;
foreach ($blueprintFiles as $files) {
$this->loadBlueprintFiles($files);
}
$cache = [
'@class' => $class,
'checksum' => $checksum,
'files' => $blueprintFiles,
'data' => $this->blueprints->toArray()
];
// 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();
}
} else {
$this->blueprints = new Blueprints($cache['data']);
}
}
protected function loadCompiledConfig($configs, $plugins, $filename = null)
{
$filename = $filename
? CACHE_DIR . 'compiled/config/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/config/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$class = get_class($this);
$checksum = $this->checksum();
if (
!is_array($cache)
|| !isset($cache['checksum'])
|| !isset($cache['@class'])
|| $cache['@class'] != $class
) {
$this->messages[] = 'No cached configuration, compiling new configuration..';
} else if ($cache['checksum'] !== $checksum) {
$this->messages[] = 'Configuration checksum mismatch, reloading configuration..';
} else {
$this->messages[] = 'Configuration checksum matches, using cached version.';
$this->items = $cache['data'];
return;
}
$configFiles = $this->finder->locateConfigFiles($configs, $plugins);
// Attempt to lock the file for writing.
$file->lock(false);
// Load configuration.
foreach ($configFiles as $files) {
$this->loadConfigFiles($files);
}
$cache = [
'@class' => $class,
'timestamp' => time(),
'checksum' => $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();
}
$this->items = $cache['data'];
}
/**
* @param $languages
* @param $plugins
* @param null $filename
*/
protected function loadCompiledLanguages($languages, $plugins, $filename = null)
{
$checksum = md5(json_encode($languages));
$filename = $filename
? CACHE_DIR . 'compiled/languages/' . $filename . '-' . $this->environment . '.php'
: CACHE_DIR . 'compiled/languages/' . $checksum . '-' . $this->environment . '.php';
$file = PhpFile::instance($filename);
$cache = $file->exists() ? $file->content() : null;
$languageFiles = $this->finder->locateLanguageFiles($languages, $plugins);
$checksum .= ':' . md5(json_encode($languageFiles));
$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
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Load languages.
$this->languages = new Languages;
$pluginPaths = str_ireplace(GRAV_ROOT . '/', '', array_reverse($plugins));
foreach ($pluginPaths as $path) {
if (isset($languageFiles[$path])) {
foreach ((array) $languageFiles[$path] as $plugin => $item) {
$lang_file = CompiledYamlFile::instance($item['file']);
$content = $lang_file->content();
$this->languages->mergeRecursive($content);
}
unset($languageFiles[$path]);
}
}
foreach ($languageFiles as $location) {
foreach ($location as $lang => $item) {
$lang_file = CompiledYamlFile::instance($item['file']);
$content = $lang_file->content();
$this->languages->join($lang, $content, '/');
}
}
$cache = [
'@class' => $class,
'checksum' => $checksum,
'files' => $languageFiles,
'data' => $this->languages->toArray()
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$this->messages[] = 'Saving compiled languages.';
$file->save($cache);
$file->unlock();
}
} else {
$this->languages = new Languages($cache['data']);
}
}
/**
* Load blueprints.
*
* @param array $files
*/
public function loadBlueprintFiles(array $files)
{
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->blueprints->embed($name, $file->content(), '/');
}
}
/**
* Load configuration.
*
* @param array $files
*/
public function loadConfigFiles(array $files)
{
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->join($name, $file->content(), '/');
}
}
/**
* Initialize resource locator by using the configuration.
*
* @param UniformResourceLocator $locator
*/
public function initializeLocator(UniformResourceLocator $locator)
{
$locator->reset();
$schemes = (array) $this->get('streams.schemes', []);
foreach ($schemes as $scheme => $config) {
if (isset($config['paths'])) {
$locator->addPath($scheme, '', $config['paths']);
}
if (isset($config['prefixes'])) {
foreach ($config['prefixes'] as $prefix => $paths) {
$locator->addPath($scheme, $prefix, $paths);
}
}
}
}
/**
* Get available streams and their types from the configuration.
*
* @return array
*/
public function getStreams()
{
$schemes = [];
foreach ((array) $this->get('streams.schemes') as $scheme => $config) {
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
if ($type[0] != '\\') {
$type = '\\RocketTheme\\Toolbox\\StreamWrapper\\' . $type;
}
$schemes[$scheme] = $type;
}
return $schemes;
}
public function getLanguages()
{
return $this->languages;
}
}

View file

@ -0,0 +1,186 @@
<?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;
}
public function locateLanguageFiles(array $languages, array $plugins)
{
$list = [];
foreach (array_reverse($plugins) as $folder) {
$list += $this->detectLanguagesInFolder($folder, 'languages');
}
foreach (array_reverse($languages) 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 \FilesystemIterator($folder);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir()) {
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];
}
protected function detectLanguagesInFolder($folder, $lookup = null)
{
$path = trim(Folder::getRelativePath($folder), '/');
$list = [];
if (is_dir($folder)) {
$iterator = new \FilesystemIterator($folder);
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {
if (!$directory->isDir()) {
continue;
}
$name = $directory->getBasename();
$find = ($lookup ?: $name) . '.yaml';
$filename = "{$path}/{$name}/$find";
if (file_exists($filename)) {
$list[$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

@ -0,0 +1,27 @@
<?php
namespace Grav\Common\Config;
use Grav\Common\Data\Data;
/**
* The Languages class contains configuration rules.
*
* @author RocketTheme
* @license MIT
*/
class Languages extends Data
{
public function reformat()
{
if (isset($this->items['plugins'])) {
$this->items = array_merge_recursive($this->items, $this->items['plugins']);
unset($this->items['plugins']);
}
}
public function mergeRecursive(array $data)
{
$this->items = array_merge_recursive($this->items, $data);
}
}

View file

@ -0,0 +1,456 @@
<?php
namespace Grav\Common\Data;
use Grav\Common\GravTrait;
use RocketTheme\Toolbox\ArrayTraits\Export;
/**
* Blueprint handles the inside logic of blueprints.
*
* @author RocketTheme
* @license MIT
*/
class Blueprint
{
use Export, DataMutatorTrait, GravTrait;
public $name;
public $initialized = false;
protected $items;
protected $context;
protected $fields;
protected $rules = array();
protected $nested = array();
protected $filter = ['validation' => 1];
/**
* @param string $name
* @param array $data
* @param Blueprints $context
*/
public function __construct($name, array $data = array(), Blueprints $context = null)
{
$this->name = $name;
$this->items = $data;
$this->context = $context;
}
/**
* Set filter for inherited properties.
*
* @param array $filter List of field names to be inherited.
*/
public function setFilter(array $filter)
{
$this->filter = array_flip($filter);
}
/**
* Return all form fields.
*
* @return array
*/
public function fields()
{
if (!isset($this->fields)) {
$this->fields = [];
$this->embed('', $this->items);
}
return $this->fields;
}
/**
* Validate data against blueprints.
*
* @param array $data
* @throws \RuntimeException
*/
public function validate(array $data)
{
// Initialize data
$this->fields();
try {
$this->validateArray($data, $this->nested);
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('<b>Validation failed:</b> %s', $e->getMessage()));
}
}
/**
* Merge two arrays by using blueprints.
*
* @param array $data1
* @param array $data2
* @return array
*/
public function mergeData(array $data1, array $data2)
{
// Initialize data
$this->fields();
return $this->mergeArrays($data1, $data2, $this->nested);
}
/**
* Filter data by using blueprints.
*
* @param array $data
* @return array
*/
public function filter(array $data)
{
// Initialize data
$this->fields();
return $this->filterArray($data, $this->nested);
}
/**
* Return data fields that do not exist in blueprints.
*
* @param array $data
* @param string $prefix
* @return array
*/
public function extra(array $data, $prefix = '')
{
// Initialize data
$this->fields();
$rules = $this->nested;
// Drill down to prefix level
if (!empty($prefix)) {
$parts = explode('.', trim($prefix, '.'));
foreach ($parts as $part) {
$rules = isset($rules[$part]) ? $rules[$part] : [];
}
}
return $this->extraArray($data, $rules, $prefix);
}
/**
* Extend blueprint with another blueprint.
*
* @param Blueprint $extends
* @param bool $append
*/
public function extend(Blueprint $extends, $append = false)
{
$blueprints = $append ? $this->items : $extends->toArray();
$appended = $append ? $extends->toArray() : $this->items;
$bref_stack = array(&$blueprints);
$head_stack = array($appended);
do {
end($bref_stack);
$bref = &$bref_stack[key($bref_stack)];
$head = array_pop($head_stack);
unset($bref_stack[key($bref_stack)]);
foreach (array_keys($head) as $key) {
if (isset($key, $bref[$key]) && is_array($bref[$key]) && is_array($head[$key])) {
$bref_stack[] = &$bref[$key];
$head_stack[] = $head[$key];
} else {
$bref = array_merge($bref, array($key => $head[$key]));
}
}
} while (count($head_stack));
$this->items = $blueprints;
}
/**
* Convert object into an array.
*
* @return array
*/
public function getState()
{
return ['name' => $this->name, 'items' => $this->items, 'rules' => $this->rules, 'nested' => $this->nested];
}
/**
* Embed an array to the blueprint.
*
* @param $name
* @param array $value
* @param string $separator
*/
public function embed($name, array $value, $separator = '.')
{
if (!isset($value['form']['fields']) || !is_array($value['form']['fields'])) {
return;
}
// Initialize data
$this->fields();
$prefix = $name ? strtr($name, $separator, '.') . '.' : '';
$params = array_intersect_key($this->filter, $value);
$this->parseFormFields($value['form']['fields'], $params, $prefix, $this->fields);
}
/**
* @param array $data
* @param array $rules
* @throws \RuntimeException
* @internal
*/
protected function validateArray(array $data, array $rules)
{
$this->checkRequired($data, $rules);
foreach ($data as $key => $field) {
$val = isset($rules[$key]) ? $rules[$key] : null;
$rule = is_string($val) ? $this->rules[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
Validation::validate($field, $rule);
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$this->validateArray($field, $val);
} elseif (isset($this->items['form']['validation']) && $this->items['form']['validation'] == 'strict') {
// Undefined/extra item.
throw new \RuntimeException(sprintf('%s is not defined in blueprints', $key));
}
}
}
/**
* @param array $data
* @param array $rules
* @return array
* @internal
*/
protected function filterArray(array $data, array $rules)
{
$results = array();
foreach ($data as $key => $field) {
$val = isset($rules[$key]) ? $rules[$key] : null;
$rule = is_string($val) ? $this->rules[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
if (is_array($field) && count($field) == 1 && reset($field) == '') {
continue;
}
$field = Validation::filter($field, $rule);
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$field = $this->filterArray($field, $val);
} elseif (isset($this->items['form']['validation']) && $this->items['form']['validation'] == 'strict') {
$field = null;
}
if (isset($field) && (!is_array($field) || !empty($field))) {
$results[$key] = $field;
}
}
return $results;
}
/**
* @param array $data1
* @param array $data2
* @param array $rules
* @return array
* @internal
*/
protected function mergeArrays(array $data1, array $data2, array $rules)
{
foreach ($data2 as $key => $field) {
$val = isset($rules[$key]) ? $rules[$key] : null;
$rule = is_string($val) ? $this->rules[$val] : null;
if (!$rule && array_key_exists($key, $data1) && is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$data1[$key] = $this->mergeArrays($data1[$key], $field, $val);
} else {
// Otherwise just take value from the data2.
$data1[$key] = $field;
}
}
return $data1;
}
/**
* @param array $data
* @param array $rules
* @param string $prefix
* @return array
* @internal
*/
protected function extraArray(array $data, array $rules, $prefix)
{
$array = array();
foreach ($data as $key => $field) {
$val = isset($rules[$key]) ? $rules[$key] : null;
$rule = is_string($val) ? $this->rules[$val] : null;
if ($rule) {
// Item has been defined in blueprints.
} elseif (is_array($field) && is_array($val)) {
// Array has been defined in blueprints.
$array += $this->ExtraArray($field, $val, $prefix . $key . '.');
} else {
// Undefined/extra item.
$array[$prefix.$key] = $field;
}
}
return $array;
}
/**
* Gets all field definitions from the blueprints.
*
* @param array $fields
* @param array $params
* @param string $prefix
* @param array $current
* @internal
*/
protected function parseFormFields(array &$fields, $params, $prefix, array &$current)
{
// Go though all the fields in current level.
foreach ($fields as $key => &$field) {
$current[$key] = &$field;
// Set name from the array key.
$field['name'] = $prefix . $key;
$field += $params;
if (isset($field['fields']) && (!isset($field['type']) || $field['type'] !== 'list')) {
// Recursively get all the nested fields.
$newParams = array_intersect_key($this->filter, $field);
$this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']);
} else if ($field['type'] !== 'ignore') {
// Add rule.
$this->rules[$prefix . $key] = &$field;
$this->addProperty($prefix . $key);
foreach ($field as $name => $value) {
// Support nested blueprints.
if ($this->context && $name == '@import') {
$values = (array) $value;
if (!isset($field['fields'])) {
$field['fields'] = array();
}
foreach ($values as $bname) {
$b = $this->context->get($bname);
$field['fields'] = array_merge($field['fields'], $b->fields());
}
}
// Support for callable data values.
elseif (substr($name, 0, 6) == '@data-') {
$property = substr($name, 6);
if (is_array($value)) {
$func = array_shift($value);
} else {
$func = $value;
$value = array();
}
list($o, $f) = preg_split('/::/', $func);
if (!$f && function_exists($o)) {
$data = call_user_func_array($o, $value);
} elseif ($f && method_exists($o, $f)) {
$data = call_user_func_array(array($o, $f), $value);
}
// If function returns a value,
if (isset($data)) {
if (isset($field[$property]) && is_array($field[$property]) && is_array($data)) {
// Combine field and @data-field together.
$field[$property] += $data;
} else {
// Or create/replace field with @data-field.
$field[$property] = $data;
}
}
}
elseif (substr($name, 0, 8) == '@config-') {
$property = substr($name, 8);
$default = isset($field[$property]) ? $field[$property] : null;
$config = self::getGrav()['config']->get($value, $default);
if (!is_null($config)) {
$field[$property] = $config;
}
}
}
// Initialize predefined validation rule.
if (isset($field['validate']['rule']) && $field['type'] !== 'ignore') {
$field['validate'] += $this->getRule($field['validate']['rule']);
}
}
}
}
/**
* Add property to the definition.
*
* @param string $path Comma separated path to the property.
* @internal
*/
protected function addProperty($path)
{
$parts = explode('.', $path);
$item = array_pop($parts);
$nested = &$this->nested;
foreach ($parts as $part) {
if (!isset($nested[$part])) {
$nested[$part] = array();
}
$nested = &$nested[$part];
}
if (!isset($nested[$item])) {
$nested[$item] = $path;
}
}
/**
* @param $rule
* @return array
* @internal
*/
protected function getRule($rule)
{
if (isset($this->items['rules'][$rule]) && is_array($this->items['rules'][$rule])) {
return $this->items['rules'][$rule];
}
return array();
}
/**
* @param array $data
* @param array $fields
* @throws \RuntimeException
* @internal
*/
protected function checkRequired(array $data, array $fields)
{
foreach ($fields as $name => $field) {
if (!is_string($field)) {
continue;
}
$field = $this->rules[$field];
if (isset($field['validate']['required'])
&& $field['validate']['required'] === true
&& empty($data[$name])) {
throw new \RuntimeException("Missing required field: {$field['name']}");
}
}
}
}

View file

@ -0,0 +1,145 @@
<?php
namespace Grav\Common\Data;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\GravTrait;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
/**
* Blueprints class keeps track on blueprint instances.
*
* @author RocketTheme
* @license MIT
*/
class Blueprints
{
use GravTrait;
protected $search;
protected $types;
protected $instances = array();
/**
* @param string|array $search Search path.
*/
public function __construct($search)
{
$this->search = $search;
}
/**
* Get blueprint.
*
* @param string $type Blueprint type.
* @return Blueprint
* @throws \RuntimeException
*/
public function get($type)
{
if (!isset($this->instances[$type])) {
$parents = [];
if (is_string($this->search)) {
$filename = $this->search . $type . YAML_EXT;
// Check if search is a stream and resolve the path.
if (strpos($filename, '://')) {
$grav = static::getGrav();
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
$parents = $locator->findResources($filename);
$filename = array_shift($parents);
}
} else {
$filename = isset($this->search[$type]) ? $this->search[$type] : '';
}
if ($filename && is_file($filename)) {
$file = CompiledYamlFile::instance($filename);
$blueprints = $file->content();
} else {
$blueprints = [];
}
$blueprint = new Blueprint($type, $blueprints, $this);
if (isset($blueprints['@extends'])) {
// Extend blueprint by other blueprints.
$extends = (array) $blueprints['@extends'];
if (is_string(key($extends))) {
$extends = [ $extends ];
}
foreach ($extends as $extendConfig) {
$extendType = !is_string($extendConfig) ? empty($extendConfig['type']) ? false : $extendConfig['type'] : $extendConfig;
if (!$extendType) {
continue;
} elseif ($extendType === '@parent') {
$parentFile = array_shift($parents);
if (!$parentFile || !is_file($parentFile)) {
continue;
}
$blueprints = CompiledYamlFile::instance($parentFile)->content();
$parent = new Blueprint($type.'-parent', $blueprints, $this);
$blueprint->extend($parent);
continue;
}
if (is_string($extendConfig) || empty($extendConfig['context'])) {
$context = $this;
} else {
// Load blueprints from external context.
$array = explode('://', $extendConfig['context'], 2);
$scheme = array_shift($array);
$path = array_shift($array);
if ($path) {
$scheme .= '://';
$extendType = $path ? "{$path}/{$extendType}" : $extendType;
}
$context = new self($scheme);
}
$blueprint->extend($context->get($extendType));
}
}
$this->instances[$type] = $blueprint;
}
return $this->instances[$type];
}
/**
* Get all available blueprint types.
*
* @return array List of type=>name
*/
public function types()
{
if ($this->types === null) {
$this->types = array();
// Check if search is a stream.
if (strpos($this->search, '://')) {
// Stream: use UniformResourceIterator.
$grav = static::getGrav();
/** @var UniformResourceLocator $locator */
$locator = $grav['locator'];
$iterator = $locator->getIterator($this->search, null);
} else {
// Not a stream: use DirectoryIterator.
$iterator = new \DirectoryIterator($this->search);
}
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
if (!$file->isFile() || '.' . $file->getExtension() != YAML_EXT) {
continue;
}
$name = $file->getBasename(YAML_EXT);
$this->types[$name] = ucfirst(strtr($name, '_', ' '));
}
}
return $this->types;
}
}

View file

@ -0,0 +1,240 @@
<?php
namespace Grav\Common\Data;
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
use RocketTheme\Toolbox\ArrayTraits\Countable;
use RocketTheme\Toolbox\ArrayTraits\Export;
use RocketTheme\Toolbox\File\File;
use RocketTheme\Toolbox\File\FileInterface;
/**
* Recursive data object
*
* @author RocketTheme
* @license MIT
*/
class Data implements DataInterface
{
use ArrayAccessWithGetters, Countable, Export, DataMutatorTrait;
protected $gettersVariable = 'items';
protected $items;
/**
* @var Blueprints
*/
protected $blueprints;
/**
* @var File
*/
protected $storage;
/**
* @param array $items
* @param Blueprint $blueprints
*/
public function __construct(array $items = array(), Blueprint $blueprints = null)
{
$this->items = $items;
$this->blueprints = $blueprints;
}
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->value('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function value($name, $default = null, $separator = '.')
{
return $this->get($name, $default, $separator);
}
/**
* Set default value by using dot notation for nested arrays/objects.
*
* @example $data->def('this.is.my.nested.variable', 'default');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
*/
public function def($name, $default = null, $separator = '.')
{
$this->set($name, $this->get($name, $default, $separator), $separator);
}
/**
* Join two values together by using blueprints if available.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
*/
public function join($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
if ($old === null) {
// Variable does not exist yet: just use the incoming value.
} elseif ($this->blueprints) {
// Blueprints: join values by using blueprints.
$value = $this->blueprints->mergeData($old, $value, $name, $separator);
} else {
// No blueprints: replace existing top level variables with the new ones.
$value = array_merge($old, $value);
}
$this->set($name, $value, $separator);
}
/**
* Join two values together by using blueprints if available.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
*/
public function joinDefaults($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
if ($old === null) {
// Variable does not exist yet: just use the incoming value.
} elseif ($this->blueprints) {
// Blueprints: join values by using blueprints.
$value = $this->blueprints->mergeData($value, $old, $name, $separator);
} else {
// No blueprints: replace existing top level variables with the new ones.
$value = array_merge($value, $old);
}
$this->set($name, $value, $separator);
}
/**
* Merge two sets of data together.
*
* @param array $data
* @return void
*/
public function merge(array $data)
{
if ($this->blueprints) {
$this->items = $this->blueprints->mergeData($this->items, $data);
} else {
$this->items = array_merge($this->items, $data);
}
}
/**
* Add default data to the set.
*
* @param array $data
* @return void
*/
public function setDefaults(array $data)
{
if ($this->blueprints) {
$this->items = $this->blueprints->mergeData($data, $this->items);
} else {
$this->items = array_merge($data, $this->items);
}
}
/**
* Return blueprints.
*
* @return Blueprint
*/
public function blueprints()
{
return $this->blueprints;
}
/**
* Validate by blueprints.
*
* @throws \Exception
*/
public function validate()
{
if ($this->blueprints) {
$this->blueprints->validate($this->items);
}
}
/**
* Filter all items by using blueprints.
*/
public function filter()
{
if ($this->blueprints) {
$this->items = $this->blueprints->filter($this->items);
}
}
/**
* Get extra items which haven't been defined in blueprints.
*
* @return array
*/
public function extra()
{
return $this->blueprints ? $this->blueprints->extra($this->items) : array();
}
/**
* Save data if storage has been defined.
*/
public function save()
{
$file = $this->file();
if ($file) {
$file->save($this->items);
}
}
/**
* Returns whether the data already exists in the storage.
*
* NOTE: This method does not check if the data is current.
*
* @return bool
*/
public function exists()
{
return $this->file()->exists();
}
/**
* Return unmodified data as raw string.
*
* NOTE: This function only returns data which has been saved to the storage.
*
* @return string
*/
public function raw()
{
return $this->file()->raw();
}
/**
* Set or get the data storage.
*
* @param FileInterface $storage Optionally enter a new storage.
* @return FileInterface
*/
public function file(FileInterface $storage = null)
{
if ($storage) {
$this->storage = $storage;
}
return $this->storage;
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Grav\Common\Data;
use RocketTheme\Toolbox\File\FileInterface;
/**
* Data interface
*
* @author RocketTheme
* @license MIT
*/
interface DataInterface
{
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->value('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function value($name, $default = null, $separator = '.');
/**
* Merge external data.
*
* @param array $data
* @return mixed
*/
public function merge(array $data);
/**
* Return blueprints.
*/
public function blueprints();
/**
* Validate by blueprints.
*
* @throws \Exception
*/
public function validate();
/**
* Filter all items by using blueprints.
*/
public function filter();
/**
* Get extra items which haven't been defined in blueprints.
*/
public function extra();
/**
* Save data into the file.
*/
public function save();
/**
* Set or get the data storage.
*
* @param FileInterface $storage Optionally enter a new storage.
* @return FileInterface
*/
public function file(FileInterface $storage = null);
}

View file

@ -0,0 +1,68 @@
<?php
namespace Grav\Common\Data;
trait DataMutatorTrait
{
/**
* Get value by using dot notation for nested arrays/objects.
*
* @example $value = $data->get('this.is.my.nested.variable');
*
* @param string $name Dot separated path to the requested value.
* @param mixed $default Default value (or null).
* @param string $separator Separator, defaults to '.'
* @return mixed Value.
*/
public function get($name, $default = null, $separator = '.')
{
$path = explode($separator, $name);
$current = $this->items;
foreach ($path as $field) {
if (is_object($current) && isset($current->{$field})) {
$current = $current->{$field};
} elseif (is_array($current) && isset($current[$field])) {
$current = $current[$field];
} else {
return $default;
}
}
return $current;
}
/**
* Set value by using dot notation for nested arrays/objects.
*
* @example $value = $data->set('this.is.my.nested.variable', true);
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value New value.
* @param string $separator Separator, defaults to '.'
*/
public function set($name, $value, $separator = '.')
{
$path = explode($separator, $name);
$current = &$this->items;
foreach ($path as $field) {
if (is_object($current)) {
// Handle objects.
if (!isset($current->{$field})) {
$current->{$field} = array();
}
$current = &$current->{$field};
} else {
// Handle arrays and scalars.
if (!is_array($current)) {
$current = array($field => array());
} elseif (!isset($current[$field])) {
$current[$field] = array();
}
$current = &$current[$field];
}
}
$current = $value;
}
}

View file

@ -0,0 +1,672 @@
<?php
namespace Grav\Common\Data;
use Grav\Common\GravTrait;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
/**
* Data validation.
*
* @author RocketTheme
* @license MIT
*/
class Validation
{
use GravTrait;
/**
* Validate value against a blueprint field definition.
*
* @param mixed $value
* @param array $field
* @throws \RuntimeException
*/
public static function validate($value, array $field)
{
$validate = isset($field['validate']) ? (array) $field['validate'] : array();
// If value isn't required, we will stop validation if empty value is given.
if (empty($validate['required']) && ($value === null || $value === '')) {
return;
}
// Get language class
$language = self::getGrav()['language'];
// Validate type with fallback type text.
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'type'.strtr($type, '-', '_');
$name = ucfirst(isset($field['label']) ? $field['label'] : $field['name']);
$message = (string) isset($field['validate']['message']) ? $field['validate']['message'] : 'Invalid input in "' . $language->translate($name) . '""';
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $validate, $field);
} else {
$success = self::typeText($value, $validate, $field);
}
if (!$success) {
throw new \RuntimeException($message);
}
// Check individual rules
foreach ($validate as $rule => $params) {
$method = 'validate'.strtr($rule, '-', '_');
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $params);
if (!$success) {
throw new \RuntimeException($message);
}
}
}
}
/**
* Filter value against a blueprint field definition.
*
* @param mixed $value
* @param array $field
* @return mixed Filtered value.
*/
public static function filter($value, array $field)
{
$validate = isset($field['validate']) ? (array) $field['validate'] : array();
// If value isn't required, we will return null if empty value is given.
if (empty($validate['required']) && ($value === null || $value === '')) {
return null;
}
// if this is a YAML field, simply parse it and return the value
if (isset($field['yaml']) && $field['yaml'] === true) {
try {
$yaml = new Parser();
return $yaml->parse($value);
} catch (ParseException $e) {
throw new \RuntimeException($e->getMessage());
}
}
// Validate type with fallback type text.
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'filter'.strtr($type, '-', '_');
if (method_exists(__CLASS__, $method)) {
$value = self::$method($value, $validate, $field);
} else {
$value = self::filterText($value, $validate, $field);
}
return $value;
}
/**
* HTML5 input: text
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeText($value, array $params, array $field)
{
if (!is_string($value)) {
return false;
}
if (isset($params['min']) && strlen($value) < $params['min']) {
return false;
}
if (isset($params['max']) && strlen($value) > $params['max']) {
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && (strlen($value) - $min) % $params['step'] == 0) {
return false;
}
if ((!isset($params['multiline']) || !$params['multiline']) && preg_match('/\R/um', $value)) {
return false;
}
return true;
}
protected static function filterText($value, array $params, array $field)
{
return (string) $value;
}
protected static function filterCommaList($value, array $params, array $field)
{
return is_array($value) ? $value : preg_split('/\s*,\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
}
protected static function typeCommaList($value, array $params, array $field)
{
return is_array($value) ? true : self::typeText($value, $params, $field);
}
/**
* HTML5 input: textarea
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeTextarea($value, array $params, array $field)
{
if (!isset($params['multiline'])) {
$params['multiline'] = true;
}
return self::typeText($value, $params, $field);
}
/**
* HTML5 input: password
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typePassword($value, array $params, array $field)
{
return self::typeText($value, $params, $field);
}
/**
* HTML5 input: hidden
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeHidden($value, array $params, array $field)
{
return self::typeText($value, $params, $field);
}
/**
* Custom input: checkbox list
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeCheckboxes($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
}
protected static function filterCheckboxes($value, array $params, array $field)
{
return self::filterArray($value, $params, $field);
}
/**
* HTML5 input: checkbox
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeCheckbox($value, array $params, array $field)
{
$value = (string) $value;
if (!isset($field['value'])) {
$field['value'] = 1;
}
if ($value && $value != $field['value']) {
return false;
}
return true;
}
/**
* HTML5 input: radio
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeRadio($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
}
/**
* Custom input: toggle
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeToggle($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
}
/**
* HTML5 input: select
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeSelect($value, array $params, array $field)
{
return self::typeArray((array) $value, $params, $field);
}
/**
* HTML5 input: number
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeNumber($value, array $params, array $field)
{
if (!is_numeric($value)) {
return false;
}
if (isset($params['min']) && $value < $params['min']) {
return false;
}
if (isset($params['max']) && $value > $params['max']) {
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && fmod($value - $min, $params['step']) == 0) {
return false;
}
return true;
}
protected static function filterNumber($value, array $params, array $field)
{
return (int) $value;
}
protected static function filterDateTime($value, array $params, array $field)
{
$format = self::getGrav()['config']->get('system.pages.dateformat.default');
if ($format) {
$converted = new \DateTime($value);
return $converted->format($format);
}
return $value;
}
/**
* HTML5 input: range
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeRange($value, array $params, array $field)
{
return self::typeNumber($value, $params, $field);
}
protected static function filterRange($value, array $params, array $field)
{
return self::filterNumber($value, $params, $field);
}
/**
* HTML5 input: color
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeColor($value, array $params, array $field)
{
return preg_match('/^\#[0-9a-fA-F]{3}[0-9a-fA-F]{3}?$/u', $value);
}
/**
* HTML5 input: email
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeEmail($value, array $params, array $field)
{
return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_EMAIL);
}
/**
* HTML5 input: url
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeUrl($value, array $params, array $field)
{
return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_URL);
}
/**
* HTML5 input: datetime
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeDatetime($value, array $params, array $field)
{
if ($value instanceof \DateTime) {
return true;
} elseif (!is_string($value)) {
return false;
} elseif (!isset($params['format'])) {
return false !== strtotime($value);
}
$dateFromFormat = \DateTime::createFromFormat($params['format'], $value);
return $dateFromFormat && $value === date($params['format'], $dateFromFormat->getTimestamp());
}
/**
* HTML5 input: datetime-local
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeDatetimeLocal($value, array $params, array $field)
{
return self::typeDatetime($value, $params, $field);
}
/**
* HTML5 input: date
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeDate($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'Y-m-d';
}
return self::typeDatetime($value, $params, $field);
}
/**
* HTML5 input: time
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeTime($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'H:i';
}
return self::typeDatetime($value, $params, $field);
}
/**
* HTML5 input: month
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeMonth($value, array $params, array $field)
{
$params = array($params);
if (!isset($params['format'])) {
$params['format'] = 'Y-m';
}
return self::typeDatetime($value, $params, $field);
}
/**
* HTML5 input: week
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeWeek($value, array $params, array $field)
{
if (!isset($params['format']) && !preg_match('/^\d{4}-W\d{2}$/u', $value)) {
return false;
}
return self::typeDatetime($value, $params, $field);
}
/**
* Custom input: array
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeArray($value, array $params, array $field)
{
if (!is_array($value)) {
return false;
}
if (isset($field['multiple'])) {
if (isset($params['min']) && count($value) < $params['min']) {
return false;
}
if (isset($params['max']) && count($value) > $params['max']) {
return false;
}
$min = isset($params['min']) ? $params['min'] : 0;
if (isset($params['step']) && (count($value) - $min) % $params['step'] == 0) {
return false;
}
}
$options = isset($field['options']) ? array_keys($field['options']) : array();
$values = isset($field['use']) && $field['use'] == 'keys' ? array_keys($value) : $value;
if ($options && array_diff($values, $options)) {
return false;
}
return true;
}
protected static function filterArray($value, $params, $field)
{
$values = (array) $value;
$options = isset($field['options']) ? array_keys($field['options']) : array();
$multi = isset($field['multiple']) ? $field['multiple'] : false;
if ($options) {
$useKey = isset($field['use']) && $field['use'] == 'keys';
foreach ($values as $key => $value) {
$values[$key] = $useKey ? (bool) $value : $value;
}
}
if ($multi) {
foreach ($values as $key => $value) {
if (is_array($value)) {
$value = implode(',', $value);
}
$values[$key] = array_map('trim', explode(',', $value));
}
}
return $values;
}
public static function typeList($value, array $params, array $field)
{
if (!is_array($value)) {
return false;
}
if (isset($field['fields'])) {
foreach ($value as $key => $item) {
foreach ($field['fields'] as $subKey => $subField) {
$subKey = trim($subKey, '.');
$subValue = isset($item[$subKey]) ? $item[$subKey] : null;
self::validate($subValue, $subField);
}
}
}
return true;
}
protected static function filterList($value, array $params, array $field)
{
return (array) $value;
}
/**
* Custom input: ignore (will not validate)
*
* @param mixed $value Value to be validated.
* @param array $params Validation parameters.
* @param array $field Blueprint for the field.
* @return bool True if validation succeeded.
*/
public static function typeIgnore($value, array $params, array $field)
{
return true;
}
public static function filterIgnore($value, array $params, array $field)
{
return $value;
}
// HTML5 attributes (min, max and range are handled inside the types)
public static function validateRequired($value, $params)
{
if (is_string($value)) {
$value = trim($value);
}
return (bool) $params !== true || !empty($value);
}
public static function validatePattern($value, $params)
{
return (bool) preg_match("`^{$params}$`u", $value);
}
// Internal types
public static function validateAlpha($value, $params)
{
return ctype_alpha($value);
}
public static function validateAlnum($value, $params)
{
return ctype_alnum($value);
}
public static function typeBool($value, $params)
{
return is_bool($value) || $value == 1 || $value == 0;
}
public static function validateBool($value, $params)
{
return is_bool($value) || $value == 1 || $value == 0;
}
protected static function filterBool($value, $params)
{
return (bool) $value;
}
public static function validateDigit($value, $params)
{
return ctype_digit($value);
}
public static function validateFloat($value, $params)
{
return is_float(filter_var($value, FILTER_VALIDATE_FLOAT));
}
protected static function filterFloat($value, $params)
{
return (float) $value;
}
public static function validateHex($value, $params)
{
return ctype_xdigit($value);
}
public static function validateInt($value, $params)
{
return is_numeric($value) && (int) $value == $value;
}
protected static function filterInt($value, $params)
{
return (int) $value;
}
public static function validateArray($value, $params)
{
return is_array($value) || ($value instanceof \ArrayAccess
&& $value instanceof \Traversable
&& $value instanceof \Countable);
}
public static function validateJson($value, $params)
{
return (bool) (json_decode($value));
}
}

View file

@ -0,0 +1,121 @@
<?php
namespace Grav\Common;
use DebugBar\JavascriptRenderer;
use DebugBar\StandardDebugBar;
/**
* Class Debugger
* @package Grav\Common
*/
class Debugger
{
protected $grav;
protected $debugbar;
protected $renderer;
protected $enabled;
public function __construct()
{
$this->debugbar = new StandardDebugBar();
$this->debugbar['time']->addMeasure('Loading', $this->debugbar['time']->getRequestStartTime(), microtime(true));
}
public function init()
{
$this->grav = Grav::instance();
if ($this->enabled()) {
$this->debugbar->addCollector(new \DebugBar\DataCollector\ConfigCollector((array)$this->grav['config']->get('system')));
}
return $this;
}
public function enabled($state = null)
{
if (isset($state)) {
$this->enabled = $state;
} else {
if (!isset($this->enabled)) {
$this->enabled = $this->grav['config']->get('system.debugger.enabled');
}
}
return $this->enabled;
}
public function addAssets()
{
if ($this->enabled()) {
$assets = $this->grav['assets'];
// Add jquery library
$assets->add('jquery', 101);
$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 function addCollector($collector)
{
$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, $description = null)
{
if ($name[0] == '_' || $this->grav['config']->get('system.debugger.enabled')) {
$this->debugbar['time']->startMeasure($name, $description);
}
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,52 @@
<?php
namespace Grav\Common\Errors;
use Grav\Common\Grav;
use Whoops\Handler\CallbackHandler;
use Whoops\Handler\HandlerInterface;
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']);
$this->handlerStack = array('simple' => new SimplePageHandler()) + $this->handlerStack;
}
if (isset($config['log']) && !$config['log']) {
unset($this->handlerStack['log']);
}
}
}

View file

@ -0,0 +1,52 @@
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;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}

View file

@ -0,0 +1,30 @@
<?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>Sorry, something went terribly wrong!</p>
<h3><?php echo $code ?> - <?php echo $message ?></h3>
<h5>For further details please review your <code>logs/</code> folder, or enable displaying of errors in your system configuration.</h5>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,96 @@
<?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()
{
$inspector = $this->getInspector();
$helper = new TemplateHelper();
$templateFile = $this->getResource("layout.html.php");
$cssFile = $this->getResource("error.css");
$code = $inspector->getException()->getCode();
$message = $inspector->getException()->getMessage();
if ($inspector->getException() instanceof \ErrorException) {
$code = Misc::translateErrorCode($code);
}
$vars = array(
"stylesheet" => file_get_contents($cssFile),
"code" => $code,
"message" => $message,
);
$helper->setVariables($vars);
$helper->render($templateFile);
return Handler::QUIT;
}
/**
* @param $resource
*
* @return string
*/
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

@ -0,0 +1,73 @@
<?php
namespace Grav\Common\File;
use RocketTheme\Toolbox\File\PhpFile;
/**
* Class CompiledFile
* @package Grav\Common\File
*
* @property string $filename
* @property string $extension
* @property string $raw
* @property array|string $content
*/
trait CompiledFile
{
/**
* Get/set parsed file contents.
*
* @param mixed $var
* @return string
*/
public function content($var = null)
{
// Set some options
$this->settings(['native' => true, 'compat' => true]);
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
if ($var === null && $this->raw === null && $this->content === null) {
$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;
// Load real file if cache isn't up to date (or is invalid).
if (
!isset($cache['@class'])
|| $cache['@class'] != $class
|| $cache['modified'] != $modified
|| $cache['filename'] != $this->filename
) {
// Attempt to lock the file for writing.
$file->lock(false);
// Decode RAW file into compiled array.
$data = (array) $this->decode($this->raw());
$cache = [
'@class' => $class,
'filename' => $this->filename,
'modified' => $modified,
'data' => $data
];
// If compiled file wasn't already locked by another process, save it.
if ($file->locked() !== false) {
$file->save($cache);
$file->unlock();
}
}
$this->content = $cache['data'];
}
return parent::content($var);
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Grav\Common\File;
use RocketTheme\Toolbox\File\MarkdownFile;
class CompiledMarkdownFile extends MarkdownFile
{
use CompiledFile;
}

View file

@ -0,0 +1,9 @@
<?php
namespace Grav\Common\File;
use RocketTheme\Toolbox\File\YamlFile;
class CompiledYamlFile extends YamlFile
{
use CompiledFile;
}

View file

@ -0,0 +1,353 @@
<?php
namespace Grav\Common\Filesystem;
/**
* Folder helper class.
*
* @author RocketTheme
* @license MIT
*/
abstract class Folder
{
/**
* Recursively find the last modified time under given path.
*
* @param string $path
* @return int
*/
public static function lastModifiedFolder($path)
{
$last_modified = 0;
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$filterItr = new RecursiveFolderFilterIterator($dirItr);
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
/** @var \RecursiveDirectoryIterator $file */
foreach ($itr as $dir) {
$dir_modified = $dir->getMTime();
if ($dir_modified > $last_modified) {
$last_modified = $dir_modified;
}
}
return $last_modified;
}
/**
* Recursively find the last modified time under given path by file.
*
* @param string $path
* @param string $extensions which files to search for specifically
*
* @return int
*/
public static function lastModifiedFile($path, $extensions = 'md|yaml')
{
$last_modified = 0;
$dirItr = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
$itrItr = new \RecursiveIteratorIterator($dirItr, \RecursiveIteratorIterator::SELF_FIRST);
$itr = new \RegexIterator($itrItr, '/^.+\.'.$extensions.'$/i');
/** @var \RecursiveDirectoryIterator $file */
foreach ($itr as $filepath => $file) {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
}
}
return $last_modified;
}
/**
* Get relative path between target and base path. If path isn't relative, return full path.
*
* @param string $path
* @param string $base
* @return string
*/
public static function getRelativePath($path, $base = GRAV_ROOT)
{
if ($base) {
$base = preg_replace('![\\\/]+!', '/', $base);
$path = preg_replace('![\\\/]+!', '/', $path);
if (strpos($path, $base) === 0) {
$path = ltrim(substr($path, strlen($base)), '/');
}
}
return $path;
}
/**
* Shift first directory out of the path.
*
* @param string $path
* @return string
*/
public static function shift(&$path)
{
$parts = explode('/', trim($path, '/'), 2);
$result = array_shift($parts);
$path = array_shift($parts);
return $result ?: null;
}
/**
* Return recursive list of all files and directories under given path.
*
* @param string $path
* @param array $params
* @return array
* @throws \RuntimeException
*/
public static function all($path, array $params = array())
{
if ($path === false) {
throw new \RuntimeException("Path to {$path} doesn't exist.");
}
$compare = isset($params['compare']) ? 'get' . $params['compare'] : null;
$pattern = isset($params['pattern']) ? $params['pattern'] : null;
$filters = isset($params['filters']) ? $params['filters'] : null;
$recursive = isset($params['recursive']) ? $params['recursive'] : true;
$key = isset($params['key']) ? 'get' . $params['key'] : null;
$value = isset($params['value']) ? 'get' . $params['value'] : ($recursive ? 'getSubPathname' : 'getFilename');
if ($recursive) {
$directory = new \RecursiveDirectoryIterator($path,
\RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
} else {
$iterator = new \FilesystemIterator($path);
}
$results = array();
/** @var \RecursiveDirectoryIterator $file */
foreach ($iterator as $file) {
if ($compare && $pattern && !preg_match($pattern, $file->{$compare}())) {
continue;
}
$fileKey = $key ? $file->{$key}() : null;
$filePath = $file->{$value}();
if ($filters) {
if (isset($filters['key'])) {
$fileKey = preg_replace($filters['key'], '', $fileKey);
}
if (isset($filters['value'])) {
$filter = $filters['value'];
if (is_callable($filter)) {
$filePath = call_user_func($filter, $file);
} else {
$filePath = preg_replace($filter, '', $filePath);
}
}
}
if ($fileKey !== null) {
$results[$fileKey] = $filePath;
} else {
$results[] = $filePath;
}
}
return $results;
}
/**
* Recursively copy directory in filesystem.
*
* @param string $source
* @param string $target
* @throws \RuntimeException
*/
public static function copy($source, $target)
{
$source = rtrim($source, '\\/');
$target = rtrim($target, '\\/');
if (!is_dir($source)) {
throw new \RuntimeException('Cannot copy non-existing folder.');
}
// Make sure that path to the target exists before copying.
self::mkdir($target);
$success = true;
// Go through all sub-directories and copy everything.
$files = self::all($source);
foreach ($files as $file) {
$src = $source .'/'. $file;
$dst = $target .'/'. $file;
if (is_dir($src)) {
// Create current directory.
$success &= @mkdir($dst);
} else {
// Or copy current file.
$success &= @copy($src, $dst);
}
}
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
}
// Make sure that the change will be detected when caching.
@touch(dirname($target));
}
/**
* Move directory in filesystem.
*
* @param string $source
* @param string $target
* @throws \RuntimeException
*/
public static function move($source, $target)
{
if (!is_dir($source)) {
throw new \RuntimeException('Cannot move non-existing folder.');
}
// Make sure that path to the target exists before moving.
self::mkdir(dirname($target));
// Just rename the directory.
$success = @rename($source, $target);
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
}
// Make sure that the change will be detected when caching.
@touch(dirname($source));
@touch(dirname($target));
}
/**
* Recursively delete directory from filesystem.
*
* @param string $target
* @throws \RuntimeException
* @return bool
*/
public static function delete($target)
{
if (!is_dir($target)) {
return;
}
$success = self::doDelete($target);
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
}
// Make sure that the change will be detected when caching.
@touch(dirname($target));
return $success;
}
/**
* @param string $folder
* @throws \RuntimeException
* @internal
*/
public static function mkdir($folder)
{
if (is_dir($folder)) {
return;
}
$success = @mkdir($folder, 0777, true);
if (!$success) {
$error = error_get_last();
throw new \RuntimeException($error['message']);
}
}
/**
* Recursive copy of one directory to another
*
* @param $src
* @param $dest
*
* @return bool
*/
public static function rcopy($src, $dest)
{
// If the src is not a directory do a simple file copy
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
return false;
}
}
// Open the source directory to read in files
$i = new \DirectoryIterator($src);
/** @var \DirectoryIterator $f */
foreach ($i as $f) {
if ($f->isFile()) {
copy($f->getRealPath(), "$dest/" . $f->getFilename());
} else {
if (!$f->isDot() && $f->isDir()) {
static::rcopy($f->getRealPath(), "$dest/$f");
}
}
}
return true;
}
/**
* @param string $folder
* @return bool
* @internal
*/
protected static function doDelete($folder)
{
// Special case for symbolic links.
if (is_link($folder)) {
return @unlink($folder);
}
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
/** @var \DirectoryIterator $fileinfo */
foreach ($files as $fileinfo) {
if ($fileinfo->isDir()) {
if (false === rmdir($fileinfo->getRealPath())) {
return false;
}
} else {
if (false === unlink($fileinfo->getRealPath())) {
return false;
}
}
}
return rmdir($folder);
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Grav\Common\Filesystem;
use Grav\Common\GravTrait;
class RecursiveFolderFilterIterator extends \RecursiveFilterIterator
{
use GravTrait;
protected static $folder_ignores;
public function __construct(\RecursiveIterator $iterator)
{
parent::__construct($iterator);
if (empty($this::$folder_ignores)) {
$this::$folder_ignores = self::getGrav()['config']->get('system.pages.ignore_folders');
}
}
public function accept()
{
/** @var $current \SplFileInfo */
$current = $this->current();
if ($current->isDir() && !in_array($current->getFilename(), $this::$folder_ignores)) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Grav\Common\GPM;
use Grav\Common\GravTrait;
use Grav\Common\Iterator;
abstract class AbstractCollection extends Iterator {
use GravTrait;
public function toJson()
{
$items = [];
foreach ($this->items as $name => $package) {
$items[$name] = $package->toArray();
}
return json_encode($items);
}
public function toArray()
{
$items = [];
foreach ($this->items as $name => $package) {
$items[$name] = $package->toArray();
}
return $items;
}
}

Some files were not shown because too many files have changed in this diff Show more