Source
21
bin/grav
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
require_once(__DIR__ . '/../system/defines.php');
|
||||
require_once(__DIR__ . '/../vendor/autoload.php');
|
||||
require_once(__DIR__ . '/../system/autoload.php');
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
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\InstallCommand(),
|
||||
new Grav\Console\PackageCommand(),
|
||||
));
|
||||
$app->run();
|
||||
25
composer.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "rhuk/grav",
|
||||
"type": "library",
|
||||
"description": "Grav is a powerful flat CMS influenced by Pico, Stacey, Kirby and others...",
|
||||
"keywords": ["cms"],
|
||||
"homepage": "http://getgrav.org",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Andy Miller",
|
||||
"email": "rhuk@getgrav.org"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.10",
|
||||
"twig/twig": "1.16.*@dev",
|
||||
"erusev/parsedown": "dev-master",
|
||||
"symfony/yaml": "2.5.*@dev",
|
||||
"symfony/console": "2.5.*@dev",
|
||||
"doctrine/cache": "1.4.*@dev",
|
||||
"tracy/tracy": "dev-master",
|
||||
"gregwar/image": "dev-master",
|
||||
"ircmaxell/password-compat": "1.0.*"
|
||||
}
|
||||
}
|
||||
42
index.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Tracy\Debugger;
|
||||
|
||||
require_once(__DIR__ . '/system/defines.php');
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
date_default_timezone_set('GMT');
|
||||
}
|
||||
|
||||
// Use output buffering to prevent headers from being sent too early.
|
||||
ob_start();
|
||||
|
||||
// Register all the classes to the auto-loader.
|
||||
require_once(VENDOR_DIR .'autoload.php');
|
||||
require_once(SYSTEM_DIR .'autoload.php');
|
||||
|
||||
// Create Required Folders if they don't exist
|
||||
if (!file_exists(LOG_DIR)) mkdir(LOG_DIR);
|
||||
if (!file_exists(CACHE_DIR)) mkdir(CACHE_DIR);
|
||||
|
||||
// Start the timer and enable debugger in production mode as we do not have system configuration yet.
|
||||
// Debugger catches all errors and logs them, for example if the script doesn't have write permissions.
|
||||
Debugger::timer();
|
||||
Debugger::enable(Debugger::PRODUCTION, LOG_DIR);
|
||||
|
||||
// Register all the Grav bits into registry.
|
||||
$registry = Registry::instance();
|
||||
$registry->store('Grav', new Grav);
|
||||
$registry->store('Uri', new Uri);
|
||||
$registry->store('Config', Config::instance(CACHE_DIR . 'config.php'));
|
||||
$registry->store('Cache', new Cache);
|
||||
$registry->store('Twig', new Twig);
|
||||
$registry->store('Pages', new Page\Pages);
|
||||
$registry->store('Taxonomy', new Taxonomy);
|
||||
|
||||
/** @var Grav $grav */
|
||||
$grav = $registry->retrieve('Grav');
|
||||
$grav->process();
|
||||
|
||||
ob_end_flush();
|
||||
10
system/autoload.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
// Initiate Autoload of Grav classes
|
||||
spl_autoload_register(function ($class) {
|
||||
|
||||
if (strpos($class, 'Grav\\Common') === 0 || strpos($class, 'Grav\\Console') === 0) {
|
||||
$filename = str_replace('\\', '/', LIB_DIR.$class.'.php');
|
||||
include($filename);
|
||||
}
|
||||
});
|
||||
5
system/blueprints/assets.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
title: Assets
|
||||
validation: loose
|
||||
|
||||
form:
|
||||
fields:
|
||||
28
system/blueprints/page.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
fields:
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'': '- Root -'
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Folder
|
||||
validate:
|
||||
type: slug
|
||||
required: true
|
||||
|
||||
type:
|
||||
type: select
|
||||
label: Page Type
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
42
system/blueprints/site.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
title: Site settings
|
||||
validation: strict
|
||||
|
||||
form:
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Site title
|
||||
|
||||
description:
|
||||
type: textarea
|
||||
label: Description
|
||||
|
||||
summary.size:
|
||||
type: text
|
||||
label: Summary size
|
||||
validate:
|
||||
type: int
|
||||
min: 0
|
||||
max: 65536
|
||||
|
||||
author.name:
|
||||
type: text
|
||||
label: Default author
|
||||
|
||||
author.email:
|
||||
type: text
|
||||
label: Default email
|
||||
|
||||
taxonomies:
|
||||
type: text
|
||||
label: Taxonomy types
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
blog.route:
|
||||
type: text
|
||||
label: Blog URL
|
||||
|
||||
routes:
|
||||
type: array
|
||||
label: Custom routes
|
||||
275
system/blueprints/system.yaml
Normal file
@@ -0,0 +1,275 @@
|
||||
title: Configuration
|
||||
validation: strict
|
||||
|
||||
form:
|
||||
fields:
|
||||
basics:
|
||||
type: section
|
||||
title: Basics
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Site Title
|
||||
placeholder: "Site wide title"
|
||||
help: Default title for your site
|
||||
|
||||
base_url_absolute:
|
||||
type: text
|
||||
label: Absolute Base URL
|
||||
placeholder: "Override Absolute base URL (e.g. http://example.com)"
|
||||
help: You can provide a base URL to use rather than letting Grav guess what it is
|
||||
|
||||
base_url_relative:
|
||||
type: text
|
||||
label: Relative Base URL
|
||||
placeholder: "Override Relative base URL (e.g. /subdirectory/site)"
|
||||
help: You can provide a base URL to use rather than letting Grav guess what it is
|
||||
|
||||
pages.dateformat.short:
|
||||
type: select
|
||||
label: PHP date format
|
||||
help: "Set the PHP date format"
|
||||
default: 'jS M Y'
|
||||
options:
|
||||
'F jS \\a\\t g:ia': "January 1st at 11:59pm"
|
||||
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
|
||||
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
|
||||
'd-m-y G:i': "01-01-14 23:59"
|
||||
'jS M Y': "10th Feb 2014"
|
||||
|
||||
pages.dateformat.long:
|
||||
type: select
|
||||
label: Default date format
|
||||
help: "Set default date format rather than default for |date() filter"
|
||||
options:
|
||||
'F jS \a\t g:ia': "January 1st at 11:59pm"
|
||||
'l jS of F g:i A': "Monday 1st of January at 11:59 PM"
|
||||
'D, m M Y G:i:s': "Mon, 01 Jan 2014 23:59:00"
|
||||
'd-m-y G:i': "01-01-14 23:59"
|
||||
'jS M Y': "10th Feb 2014"
|
||||
|
||||
pages.theme:
|
||||
type: themeselect
|
||||
label: Default Theme
|
||||
help: "Set the theme (defaults to 'default')"
|
||||
|
||||
content:
|
||||
type: section
|
||||
title: Content
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
home.alias:
|
||||
type: pages
|
||||
label: Home page
|
||||
show_all: false
|
||||
show_modular: false
|
||||
show_root: false
|
||||
help: "The page that Grav will use as the default landing page"
|
||||
|
||||
pages.order.by:
|
||||
type: select
|
||||
label: Default ordering
|
||||
options:
|
||||
default: Default - based on folder name
|
||||
folder: Folder - based on prefix-less folder name
|
||||
title: Title - based on title field in header
|
||||
date: Date - based on date field in header
|
||||
|
||||
pages.order.dir:
|
||||
type: toggle
|
||||
label: Ordering direction
|
||||
default: asc
|
||||
options:
|
||||
asc: Ascending
|
||||
desc: Descending
|
||||
|
||||
pages.list.count:
|
||||
type: text
|
||||
label: Item count
|
||||
help: "Default max pages count"
|
||||
validate:
|
||||
type: number
|
||||
min: 1
|
||||
|
||||
pages.process:
|
||||
type: checkboxes
|
||||
label: Process
|
||||
default: [markdown: true, twig: true]
|
||||
options:
|
||||
markdown: Markdown
|
||||
twig: Twig
|
||||
use: keys
|
||||
|
||||
events:
|
||||
type: section
|
||||
title: Events
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
pages.events.page:
|
||||
type: toggle
|
||||
label: Page events
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
pages.events.twig:
|
||||
type: toggle
|
||||
label: Twig events
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
caching:
|
||||
type: section
|
||||
title: Caching
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
cache.enabled:
|
||||
type: toggle
|
||||
label: Caching
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
cache.check.pages:
|
||||
type: toggle
|
||||
label: Detect changes in pages
|
||||
help: Be careful changing this setting. If you disable this setting, Grav no longer automatically updates the pages when the underlaying files are changed.
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
cache.prefix:
|
||||
type: text
|
||||
label: Cache prefix
|
||||
placeholder: "Derived from base URL (override by entering random string)"
|
||||
|
||||
cache.driver:
|
||||
type: select
|
||||
label: Cache driver
|
||||
options:
|
||||
auto: Auto detect
|
||||
file: File
|
||||
apc: APC
|
||||
xcache: XCache
|
||||
memcache: MemCache
|
||||
memcached: MemCached
|
||||
wincache: WinCache
|
||||
|
||||
twig:
|
||||
type: section
|
||||
title: Twig Templating
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
twig.cache:
|
||||
type: toggle
|
||||
label: Twig caching
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.debug:
|
||||
type: toggle
|
||||
label: Twig debug
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.auto_reload:
|
||||
type: toggle
|
||||
label: Detect changes
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
twig.autoescape:
|
||||
type: toggle
|
||||
label: Autoescape variables
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger:
|
||||
type: section
|
||||
title: Debugger
|
||||
underline: true
|
||||
|
||||
fields:
|
||||
debugger.enabled:
|
||||
type: toggle
|
||||
label: Debugger
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.max_depth:
|
||||
type: select
|
||||
label: Detail levels
|
||||
placeholder: "How many nested levels to display for objects or arrays"
|
||||
options:
|
||||
1: 1 level
|
||||
2: 2 levels
|
||||
3: 3 levels
|
||||
4: 4 levels
|
||||
5: 5 levels
|
||||
6: 6 levels
|
||||
7: 7 levels
|
||||
8: 8 levels
|
||||
9: 9 levels
|
||||
10: 10 levels
|
||||
validate:
|
||||
type: number
|
||||
|
||||
debugger.log.enabled:
|
||||
type: toggle
|
||||
label: Logging
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
debugger.log.timing:
|
||||
type: toggle
|
||||
label: Log timings
|
||||
highlight: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
58
system/config/assets.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
jpg:
|
||||
type: image
|
||||
thumb: assets/thumb-jpg.png
|
||||
mime: image/jpeg
|
||||
jpeg:
|
||||
type: image
|
||||
thumb: assets/thumb-jpeg.png
|
||||
mime: image/jpeg
|
||||
png:
|
||||
type: image
|
||||
thumb: assets/thumb-png.png
|
||||
mime: image/png
|
||||
gif:
|
||||
type: image
|
||||
thumb: assets/thumb-gif.png
|
||||
mime: image/gif
|
||||
|
||||
mp4:
|
||||
type: video
|
||||
thumb: assets/thumb-mp4.png
|
||||
mime: video/mp4
|
||||
mov:
|
||||
type: video
|
||||
thumb: assets/thumb-mov.png
|
||||
mime: video/quicktime
|
||||
m4v:
|
||||
type: video
|
||||
thumb: assets/thumb-m4v.png
|
||||
mime: video/x-m4v
|
||||
swf:
|
||||
type: video
|
||||
thumb: assets/thumb-swf.png
|
||||
mime: video/x-flv
|
||||
|
||||
txt:
|
||||
type: file
|
||||
thumb: assets/thumb-txt.png
|
||||
mime: text/plain
|
||||
doc:
|
||||
type: file
|
||||
thumb: assets/thumb-doc.png
|
||||
mime: application/msword
|
||||
html:
|
||||
type: file
|
||||
thumb: assets/thumb-html.png
|
||||
mime: text/html
|
||||
pdf:
|
||||
type: file
|
||||
thumb: assets/thumb-pdf.png
|
||||
mime: application/pdf
|
||||
zip:
|
||||
type: file
|
||||
thumb: assets/thumb-zip.png
|
||||
mime: application/zip
|
||||
gz:
|
||||
type: file
|
||||
thumb: assets/thumb-gz.png
|
||||
mime: application/gzip
|
||||
13
system/config/site.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
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
|
||||
blog:
|
||||
route: '/blog' # Route to blog
|
||||
description: 'Grav Site Description' # Site description
|
||||
summary:
|
||||
size: 300 # Maximum length of summary (characters)
|
||||
routes:
|
||||
/something/else: '/blog/sample-3' # Alias for /blog/sample-3
|
||||
/another/one/here: '/blog/sample-3' # Another alias for /blog/sample-3
|
||||
39
system/config/system.yaml
Normal file
@@ -0,0 +1,39 @@
|
||||
home:
|
||||
alias: '/home' # Default path for home, ie /
|
||||
|
||||
pages:
|
||||
theme: antimatter # Default theme (defaults to "antimatter" theme)
|
||||
order:
|
||||
by: defaults # Order pages by "default", "alpha" or "date"
|
||||
dir: asc # Default ordering direction, "asc" or "desc"
|
||||
list:
|
||||
count: 20 # Default item count per page
|
||||
dateformat:
|
||||
short: 'jS M Y' # Short date format
|
||||
long: 'F jS \a\t g:ia' # Long date format
|
||||
process:
|
||||
markdown: true # Process Markdown
|
||||
twig: false # Process Twig
|
||||
events:
|
||||
page: false # Enable page level events
|
||||
twig: true # Enable twig level events
|
||||
|
||||
cache:
|
||||
enabled: true # Set to true to enable caching
|
||||
check:
|
||||
pages: true # Check to see if page has been modifying to flush the cache
|
||||
driver: auto # One of: auto|file|apc|xcache|memcache|memcached|wincache
|
||||
prefix: 'g' # Cache prefix string (prevents cache conflicts)
|
||||
|
||||
twig:
|
||||
cache: false # Set to true to enable twig caching
|
||||
debug: true # Enable Twig debug
|
||||
auto_reload: true # Refresh cache on changes
|
||||
autoescape: false # Autoescape Twig vars
|
||||
|
||||
debugger:
|
||||
enabled: true # Enable Grav debugger
|
||||
max_depth: 10 # How many nested levels to display for objects or arrays
|
||||
log:
|
||||
enabled: true # Enable logging
|
||||
timing: false # Enable timing logging
|
||||
42
system/defines.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
// Some standard defines
|
||||
define('GRAV', true);
|
||||
define('GRAV_VERSION', '0.1.4');
|
||||
define('DS', '/');
|
||||
|
||||
// Directories and Paths
|
||||
if (!defined('ROOT_DIR')) {
|
||||
define('ROOT_DIR', getcwd() .'/');
|
||||
}
|
||||
define('USER_PATH', 'user/');
|
||||
define('USER_DIR', ROOT_DIR . USER_PATH);
|
||||
define('SYSTEM_DIR', ROOT_DIR .'system/');
|
||||
define('CACHE_DIR', ROOT_DIR .'cache/');
|
||||
define('IMAGES_DIR', ROOT_DIR . 'images/');
|
||||
define('LOG_DIR', ROOT_DIR .'logs/');
|
||||
define('VENDOR_DIR', ROOT_DIR .'vendor/');
|
||||
define('LIB_DIR', SYSTEM_DIR .'src/');
|
||||
define('ACCOUNTS_DIR', USER_DIR .'accounts/');
|
||||
define('DATA_DIR', USER_DIR .'data/');
|
||||
define('PAGES_DIR', USER_DIR .'pages/');
|
||||
define('BLOCKS_DIR', USER_DIR .'blocks/');
|
||||
|
||||
define('PLUGINS_DIR', USER_DIR .'plugins/');
|
||||
define('THEMES_DIR', USER_DIR .'themes/');
|
||||
|
||||
// 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);
|
||||
|
||||
// Misc Defines
|
||||
define('SUMMARY_DELIMITER', '===');
|
||||
BIN
system/images/assets/thumb-doc.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
system/images/assets/thumb-gif.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
system/images/assets/thumb-gz.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
system/images/assets/thumb-html.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
system/images/assets/thumb-jpeg.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
system/images/assets/thumb-jpg.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
system/images/assets/thumb-m4v.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
system/images/assets/thumb-mov.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
system/images/assets/thumb-mp4.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
system/images/assets/thumb-pdf.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
system/images/assets/thumb-png.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
system/images/assets/thumb-swf.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
system/images/assets/thumb-txt.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
system/images/assets/thumb-zip.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
145
system/src/Grav/Common/Cache.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @var \Doctrine\Common\Cache\Cache
|
||||
*/
|
||||
protected $driver;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $enabled;
|
||||
|
||||
/**
|
||||
* Initialization that sets a base key and the driver based on configuration settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = Registry::get('Config');
|
||||
$prefix = $config->get('system.cache.prefix');
|
||||
/** @var Uri $uri */
|
||||
$uri = Registry::get('Uri');
|
||||
|
||||
$this->enabled = (bool) $config->get('system.cache.enabled');
|
||||
|
||||
// Cache key allows us to invalidate all cache on configuration changes.
|
||||
$this->key = substr(md5(($prefix ? $prefix : 'g') . $uri->rootUrl(true) . $config->key . GRAV_VERSION), 2, 8);
|
||||
|
||||
switch ($this->getCacheDriverName($config->get('system.cache.driver'))) {
|
||||
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':
|
||||
$driver = new \Doctrine\Common\Cache\MemcacheCache();
|
||||
break;
|
||||
|
||||
case 'memcached':
|
||||
$driver = new \Doctrine\Common\Cache\MemcachedCache();
|
||||
break;
|
||||
|
||||
default:
|
||||
$driver = new \Doctrine\Common\Cache\FilesystemCache(CACHE_DIR);
|
||||
break;
|
||||
}
|
||||
$this->driver = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param string $setting
|
||||
* @return string The name of the best cache driver to use
|
||||
*/
|
||||
protected function getCacheDriverName($setting = null)
|
||||
{
|
||||
|
||||
if (!$setting || $setting == 'auto') {
|
||||
if (extension_loaded('apc') && ini_get('apc.enabled')) {
|
||||
return 'apc';
|
||||
} elseif (extension_loaded('wincache')) {
|
||||
return 'wincache';
|
||||
} elseif (extension_loaded('xcache') && ini_get('xcache.size') && ini_get('xcache.cacher')) {
|
||||
return 'xcache';
|
||||
} else {
|
||||
return 'file';
|
||||
}
|
||||
} else {
|
||||
return $setting;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$id = $this->key . $id;
|
||||
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) {
|
||||
$id = $this->key . $id;
|
||||
$this->driver->save($id, $data, $lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter method to get the cache key
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
}
|
||||
256
system/src/Grav/Common/Config.php
Normal file
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Data\Blueprints;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Filesystem\File;
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
|
||||
/**
|
||||
* The Config class contains configuration information.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Config extends Data
|
||||
{
|
||||
/**
|
||||
* @var string Configuration location in the disk.
|
||||
*/
|
||||
public $filename;
|
||||
|
||||
/**
|
||||
* @var string MD5 from the files.
|
||||
*/
|
||||
public $key;
|
||||
|
||||
/**
|
||||
* @var array Configuration file list.
|
||||
*/
|
||||
public $files = array();
|
||||
|
||||
/**
|
||||
* @var bool Flag to tell if configuration needs to be saved.
|
||||
*/
|
||||
public $updated = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($filename)
|
||||
{
|
||||
$this->filename = realpath(dirname($filename)) . '/' . basename($filename);
|
||||
|
||||
$this->reload(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force reload of the configuration from the disk.
|
||||
*
|
||||
* @param bool $force
|
||||
* @return $this
|
||||
*/
|
||||
public function reload($force = true)
|
||||
{
|
||||
// Build file map.
|
||||
$files = $this->build();
|
||||
$key = md5(serialize($files) . GRAV_VERSION);
|
||||
|
||||
if ($force || $key != $this->key) {
|
||||
// First take non-blocking lock to the file.
|
||||
File\Config::instance($this->filename)->lock(false);
|
||||
|
||||
// Reset configuration.
|
||||
$this->items = array();
|
||||
$this->files = array();
|
||||
$this->init($files);
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save configuration into file.
|
||||
*
|
||||
* Note: Only saves the file if updated flag is set!
|
||||
*
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
// If configuration was updated, store it as cached version.
|
||||
try {
|
||||
$file = File\Config::instance($this->filename);
|
||||
|
||||
// Only save configuration file if it wasn't locked. Also invalidate opcache after saving.
|
||||
// This prevents us from saving the file multiple times in a row and gives faster recovery.
|
||||
if ($file->locked() !== false) {
|
||||
$file->save($this);
|
||||
$file->unlock();
|
||||
}
|
||||
$this->updated = false;
|
||||
} catch (\Exception $e) {
|
||||
throw new \RuntimeException('Writing to cache folder failed (configuration).', 500, $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets configuration instance.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return \Grav\Common\Config
|
||||
*/
|
||||
public static function instance($filename)
|
||||
{
|
||||
// Load cached version if available..
|
||||
if (file_exists($filename)) {
|
||||
clearstatcache(true, $filename);
|
||||
require_once $filename;
|
||||
|
||||
if (class_exists('\Grav\Config')) {
|
||||
$instance = new \Grav\Config($filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Or initialize new configuration object..
|
||||
if (!isset($instance)) {
|
||||
$instance = new static($filename);
|
||||
}
|
||||
|
||||
// If configuration was updated, store it as cached version.
|
||||
if ($instance->updated) {
|
||||
$instance->save();
|
||||
}
|
||||
|
||||
|
||||
// If not set, add manually current base url.
|
||||
if (empty($instance->items['system']['base_url_absolute'])) {
|
||||
$instance->items['system']['base_url_absolute'] = Registry::get('Uri')->rootUrl(true);
|
||||
}
|
||||
|
||||
if (empty($instance->items['system']['base_url_relative'])) {
|
||||
$instance->items['system']['base_url_relative'] = Registry::get('Uri')->rootUrl(false);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert configuration into an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return array('key' => $this->key, 'files' => $this->files, 'items' => $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize object by loading all the configuration files.
|
||||
*
|
||||
* @param array $files
|
||||
*/
|
||||
protected function init(array $files)
|
||||
{
|
||||
$this->updated = true;
|
||||
|
||||
// Combine all configuration files into one larger lookup table (only keys matter).
|
||||
$allFiles = $files['user'] + $files['plugins'] + $files['system'];
|
||||
|
||||
// Then sort the files to have all parent nodes first.
|
||||
// This is to make sure that child nodes override parents content.
|
||||
uksort(
|
||||
$allFiles,
|
||||
function($a, $b) {
|
||||
$diff = substr_count($a, '/') - substr_count($b, '/');
|
||||
return $diff ? $diff : strcmp($a, $b);
|
||||
}
|
||||
);
|
||||
|
||||
$systemBlueprints = new Blueprints(SYSTEM_DIR . 'blueprints');
|
||||
$pluginBlueprints = new Blueprints(USER_DIR);
|
||||
|
||||
$items = array();
|
||||
foreach ($allFiles as $name => $dummy) {
|
||||
$lookup = array(
|
||||
'system' => SYSTEM_DIR . 'config/' . $name . YAML_EXT,
|
||||
'plugins' => USER_DIR . $name . '/' . basename($name) . YAML_EXT,
|
||||
'user' => USER_DIR . 'config/' . $name . YAML_EXT,
|
||||
);
|
||||
if (strpos($name, 'plugins/') === 0) {
|
||||
$blueprint = $pluginBlueprints->get("{$name}/blueprints");
|
||||
} else {
|
||||
$blueprint = $systemBlueprints->get($name);
|
||||
}
|
||||
|
||||
$data = new Data(array(), $blueprint);
|
||||
foreach ($lookup as $key => $path) {
|
||||
if (is_file($path)) {
|
||||
$data->merge(File\Yaml::instance($path)->content());
|
||||
}
|
||||
}
|
||||
// $data->validate();
|
||||
// $data->filter();
|
||||
|
||||
// Find the current sub-tree location.
|
||||
$current = &$items;
|
||||
$parts = explode('/', $name);
|
||||
foreach ($parts as $part) {
|
||||
if (!isset($current[$part])) {
|
||||
$current[$part] = array();
|
||||
}
|
||||
$current = &$current[$part];
|
||||
}
|
||||
|
||||
// Handle both updated and deleted configuration files.
|
||||
$current = $data->toArray();
|
||||
}
|
||||
|
||||
$this->items = $items;
|
||||
$this->files = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of configuration files with their timestamps. Used for loading settings and caching them.
|
||||
*
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
protected function build()
|
||||
{
|
||||
// Find all plugins with default configuration options.
|
||||
$plugins = array();
|
||||
$iterator = new \DirectoryIterator(PLUGINS_DIR);
|
||||
|
||||
/** @var \DirectoryIterator $plugin */
|
||||
foreach ($iterator as $plugin) {
|
||||
$name = $plugin->getBasename();
|
||||
$file = $plugin->getPathname() . DS . $name . YAML_EXT;
|
||||
|
||||
if (!is_file($file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$modified = filemtime($file);
|
||||
$plugins["plugins/{$name}"] = $modified;
|
||||
}
|
||||
|
||||
// Find all system and user configuration files.
|
||||
$options = array(
|
||||
'compare' => 'Filename',
|
||||
'pattern' => '|\.yaml$|',
|
||||
'filters' => array('key' => '|\.yaml$|'),
|
||||
'key' => 'SubPathname',
|
||||
'value' => 'MTime'
|
||||
);
|
||||
|
||||
$system = Folder::all(SYSTEM_DIR . 'config', $options);
|
||||
$user = Folder::all(USER_DIR . 'config', $options);
|
||||
|
||||
return array('system' => $system, 'plugins' => $plugins, 'user' => $user);
|
||||
}
|
||||
}
|
||||
473
system/src/Grav/Common/Data/Blueprint.php
Normal file
@@ -0,0 +1,473 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use \Grav\Common\Registry;
|
||||
use \Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Blueprint handles the inside logic of blueprints.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Blueprint
|
||||
{
|
||||
public $name;
|
||||
public $initialized = false;
|
||||
protected $blueprints;
|
||||
protected $context;
|
||||
protected $fields;
|
||||
protected $rules = array();
|
||||
protected $nested = array();
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param array $data
|
||||
* @param Blueprints $context
|
||||
*/
|
||||
public function __construct($name, array $data, Blueprints $context)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->blueprints = $data;
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->blueprints;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sey 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->blueprints;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all form fields.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function fields()
|
||||
{
|
||||
if (!isset($this->fields)) {
|
||||
$this->fields = isset($this->blueprints['form']['fields']) ? $this->blueprints['form']['fields'] : array();
|
||||
$this->getFields($this->fields);
|
||||
}
|
||||
|
||||
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('Page validation failed: %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();
|
||||
return $this->extraArray($data, $this->nested, $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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->blueprints['validation']) && $this->blueprints['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.
|
||||
$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->blueprints['validation']) && $this->blueprints['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);
|
||||
} else {
|
||||
// Undefined/extra item.
|
||||
$array[$prefix.$key] = $field;
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all field definitions from the blueprints.
|
||||
*
|
||||
* @param array $fields
|
||||
* @internal
|
||||
*/
|
||||
protected function getFields(array &$fields)
|
||||
{
|
||||
// Go though all the fields in current level.
|
||||
foreach ($fields as $key => &$field) {
|
||||
// Set name from the array key.
|
||||
$field['name'] = $key;
|
||||
|
||||
if (isset($field['fields'])) {
|
||||
// Recursively get all the nested fields.
|
||||
$this->getFields($field['fields']);
|
||||
} else {
|
||||
// Add rule.
|
||||
$this->rules[$key] = &$field;
|
||||
$this->addProperty($key);
|
||||
|
||||
foreach ($field as $name => $value) {
|
||||
// Support nested blueprints.
|
||||
if ($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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize predefined validation rule.
|
||||
if (isset($field['validate']['rule'])) {
|
||||
$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->blueprints['rules'][$rule]) && is_array($this->blueprints['rules'][$rule])) {
|
||||
return $this->blueprints['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']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert blueprints into an array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->blueprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert blueprints into YAML string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toYaml()
|
||||
{
|
||||
return Yaml::dump($this->blueprints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert blueprints into JSON string.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return json_encode($this->blueprints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend blueprint with another blueprint.
|
||||
*
|
||||
* @param Blueprint $extends
|
||||
* @param bool $append
|
||||
*/
|
||||
public function extend(Blueprint $extends, $append = false)
|
||||
{
|
||||
$blueprints = $append ? $this->blueprints : $extends->toArray();
|
||||
$appended = $append ? $extends->toArray() : $this->blueprints;
|
||||
|
||||
$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->blueprints = $blueprints;
|
||||
}
|
||||
}
|
||||
81
system/src/Grav/Common/Data/Blueprints.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Blueprints class keeps track on blueprint instances.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Blueprints
|
||||
{
|
||||
protected $search;
|
||||
protected $types;
|
||||
protected $instances = array();
|
||||
|
||||
/**
|
||||
* @param string $search Search path.
|
||||
*/
|
||||
public function __construct($search)
|
||||
{
|
||||
$this->search = rtrim($search, '\\/') . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blueprint.
|
||||
*
|
||||
* @param string $type Blueprint type.
|
||||
* @return Blueprint
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function get($type)
|
||||
{
|
||||
if (!isset($this->instances[$type])) {
|
||||
if (is_file($this->search . $type . YAML_EXT)) {
|
||||
$blueprints = (array) Yaml::parse($this->search . $type . YAML_EXT);
|
||||
} else {
|
||||
// throw new \RuntimeException("Blueprints for '{$type}' cannot be found! {$this->search}{$type}");
|
||||
$blueprints = array();
|
||||
}
|
||||
|
||||
$blueprint = new Blueprint($type, $blueprints, $this);
|
||||
|
||||
if (isset($blueprints['@extends'])) {
|
||||
// Extend blueprint by other blueprints.
|
||||
$extends = (array) $blueprints['@extends'];
|
||||
foreach ($extends as $extendType) {
|
||||
$blueprint->extend($this->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();
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
235
system/src/Grav/Common/Data/Data.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Filesystem\FileInterface;
|
||||
use \Grav\Common\Getters;
|
||||
use \Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* Recursive data object
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Data extends Getters implements DataInterface
|
||||
{
|
||||
protected $gettersVariable = 'items';
|
||||
protected $items;
|
||||
|
||||
/**
|
||||
* @var Blueprints
|
||||
*/
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @var File\General
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sey 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 '.'
|
||||
* @return mixed Value.
|
||||
*/
|
||||
public function def($name, $default = null, $separator = '.')
|
||||
{
|
||||
$this->set($name, $this->get($name, $default, $separator), $separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two sets of data together.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function merge(array $data)
|
||||
{
|
||||
if ($this->blueprints) {
|
||||
$this->items = $this->blueprints->mergeData($this->items, $data);
|
||||
} else {
|
||||
$this->items = array_merge($this->items, $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
69
system/src/Grav/Common/Data/DataInterface.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
use Grav\Common\Filesystem\FileInterface;
|
||||
use \Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
603
system/src/Grav/Common/Data/Validation.php
Normal file
@@ -0,0 +1,603 @@
|
||||
<?php
|
||||
namespace Grav\Common\Data;
|
||||
|
||||
/**
|
||||
* Data validation.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Validation
|
||||
{
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// Validate type with fallback type text.
|
||||
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
|
||||
$method = 'type'.strtr($type, '-', '_');
|
||||
if (method_exists(__CLASS__, $method)) {
|
||||
$success = self::$method($value, $validate, $field);
|
||||
} else {
|
||||
$success = self::typeText($value, $validate, $field);
|
||||
}
|
||||
if (!$success) {
|
||||
$name = $field['label'] ? $field['label'] : $field['name'];
|
||||
throw new \RuntimeException("invalid input in {$name}");
|
||||
}
|
||||
|
||||
// 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('Failed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
var_dump($value);
|
||||
var_dump($params);
|
||||
var_dump($field);
|
||||
die();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
// TODO: add min, max and range.
|
||||
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 typeDatetime_local($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();
|
||||
|
||||
if ($options) {
|
||||
$useKey = isset($field['use']) && $field['use'] == 'keys';
|
||||
foreach ($values as $key => $value) {
|
||||
$values[$key] = $useKey ? (bool) $value : $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// HTML5 attributes (min, max and range are handled inside the types)
|
||||
|
||||
public static function validateRequired($value, $params)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
129
system/src/Grav/Common/Filesystem/File/Config.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* File handling class.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Config extends General
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension = '.php';
|
||||
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Saves configuration file and invalidates opcache.
|
||||
*
|
||||
* @param mixed $data Optional data to be saved, usually array.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data = null)
|
||||
{
|
||||
parent::save($data);
|
||||
|
||||
// Invalidate configuration file from the opcache.
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
// PHP 5.5.5+
|
||||
@opcache_invalidate($this->filename);
|
||||
} elseif (function_exists('apc_invalidate')) {
|
||||
// APC
|
||||
@apc_invalidate($this->filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param \Grav\Common\Config $var
|
||||
* @return \Grav\Common\Config
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
if (!($var instanceof \Grav\Common\Config)) {
|
||||
throw new \RuntimeException('Provided data is not configuration');
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode configuration object into RAW string (PHP class).
|
||||
*
|
||||
* @param \Grav\Common\Config $var
|
||||
* @return string
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
if (!($var instanceof \Grav\Common\Config)) {
|
||||
throw new \RuntimeException('Provided data is not configuration');
|
||||
}
|
||||
|
||||
// Build the object variables string
|
||||
$vars = array();
|
||||
$options = $var->toArray();
|
||||
|
||||
foreach ($options as $k => $v) {
|
||||
if (is_int($v)) {
|
||||
$vars[] = "\tpublic $" . $k . " = " . $v . ";";
|
||||
} elseif (is_bool($v)) {
|
||||
$vars[] = "\tpublic $" . $k . " = " . ($v ? 'true' : 'false') . ";";
|
||||
} elseif (is_scalar($v)) {
|
||||
$vars[] = "\tpublic $" . $k . " = '" . addcslashes($v, '\\\'') . "';";
|
||||
} elseif (is_array($v) || is_object($v)) {
|
||||
$vars[] = "\tpublic $" . $k . " = " . $this->encodeArray((array) $v) . ";";
|
||||
}
|
||||
}
|
||||
$vars = implode("\n", $vars);
|
||||
|
||||
return "<?php\nnamespace Grav;\n\nclass Config extends \\Grav\\Common\\Config {\n {$vars}\n}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get an array as an exported string.
|
||||
*
|
||||
* @param array $a The array to get as a string.
|
||||
* @param int $level Used internally to indent rows.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function encodeArray($a, $level = 1)
|
||||
{
|
||||
$r = array();
|
||||
foreach ($a as $k => $v) {
|
||||
if (is_array($v) || is_object($v)) {
|
||||
$r[] = '"' . $k . '" => ' . $this->encodeArray((array) $v, $level+1);
|
||||
} elseif (is_int($v)) {
|
||||
$r[] = "'" . $k . "' => " . $v;
|
||||
} elseif (is_bool($v)) {
|
||||
$r[] = "'" . $k . "' => " . ($v ? 'true' : 'false');
|
||||
} else {
|
||||
$r[] .= "'" . $k . "' => " . "'" . addslashes($v) . "'";
|
||||
}
|
||||
}
|
||||
|
||||
$tabs = str_repeat("\t", $level);
|
||||
return "array(\n\t{$tabs}" . implode(",\n\t{$tabs}", $r) . "\n{$tabs})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return \Grav\Common\Config
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
// TODO: improve this one later, works only for single file...
|
||||
return class_exists('\Grav\Config') ? new \Grav\Config($this->filename) : new Config($this->filename);
|
||||
}
|
||||
}
|
||||
352
system/src/Grav/Common/Filesystem/File/General.php
Normal file
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
use Grav\Common\Filesystem\FileInterface;
|
||||
|
||||
/**
|
||||
* General file handling class.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class General implements FileInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $filename;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
protected $handle;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $locked;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
/**
|
||||
* @var string Raw file contents.
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* @var array Parsed file contents.
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Get file instance.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return FileInterface
|
||||
*/
|
||||
public static function instance($filename)
|
||||
{
|
||||
if (!isset(static::$instances[$filename])) {
|
||||
static::$instances[$filename] = new static;
|
||||
static::$instances[$filename]->init($filename);
|
||||
}
|
||||
return static::$instances[$filename];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent constructor from being used.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent cloning.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function __clone()
|
||||
{
|
||||
//Me not like clones! Me smash clones!
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filename.
|
||||
*
|
||||
* @param $filename
|
||||
*/
|
||||
protected function init($filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set the file location.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
public function filename($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->filename = $var;
|
||||
}
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return basename of the file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function basename()
|
||||
{
|
||||
return basename($this->filename, $this->extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file exits.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists()
|
||||
{
|
||||
return is_file($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return file modification time.
|
||||
*
|
||||
* @return int|bool Timestamp or false if file doesn't exist.
|
||||
*/
|
||||
public function modified()
|
||||
{
|
||||
return is_file($this->filename) ? filemtime($this->filename) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock file for writing. You need to manually unlock().
|
||||
*
|
||||
* @param bool $block For non-blocking lock, set the parameter to false.
|
||||
* @return bool
|
||||
*/
|
||||
public function lock($block = true)
|
||||
{
|
||||
if (!$this->handle) {
|
||||
$this->handle = fopen($this->filename, 'wb+');
|
||||
}
|
||||
$lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB;
|
||||
return $this->locked = flock($this->handle, $lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if file has been locked for writing.
|
||||
*
|
||||
* @return bool|null True = locked, false = failed, null = not locked.
|
||||
*/
|
||||
public function locked()
|
||||
{
|
||||
return $this->locked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock()
|
||||
{
|
||||
if (!$this->handle) {
|
||||
return;
|
||||
}
|
||||
if ($this->locked) {
|
||||
flock($this->handle, LOCK_UN);
|
||||
$this->locked = null;
|
||||
}
|
||||
fclose($this->handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file can be written.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function writable()
|
||||
{
|
||||
return is_writable($this->filename) || $this->writableDir(dirname($this->filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* (Re)Load a file and return RAW file contents.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
$this->raw = $this->exists() ? (string) file_get_contents($this->filename) : '';
|
||||
$this->content = null;
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set raw file contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
public function raw($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->raw = (string) $var;
|
||||
$this->content = null;
|
||||
}
|
||||
|
||||
if (!is_string($this->raw)) {
|
||||
$this->raw = $this->load();
|
||||
}
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set parsed file contents.
|
||||
*
|
||||
* @param mixed $var
|
||||
* @return string
|
||||
*/
|
||||
public function content($var = null)
|
||||
{
|
||||
if ($var !== null) {
|
||||
$this->content = $this->check($var);
|
||||
|
||||
// Update RAW, too.
|
||||
$this->raw = $this->encode($this->content);
|
||||
|
||||
} elseif ($this->content === null) {
|
||||
// Decode RAW file.
|
||||
$this->content = $this->decode($this->raw());
|
||||
}
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save file.
|
||||
*
|
||||
* @param mixed $data Optional data to be saved, usually array.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data = null)
|
||||
{
|
||||
if ($data !== null) {
|
||||
$this->content($data);
|
||||
}
|
||||
|
||||
if (!$this->mkdir(dirname($this->filename))) {
|
||||
throw new \RuntimeException('Creating directory failed for ' . $this->filename);
|
||||
}
|
||||
if (!$this->locked) {
|
||||
// Obtain blocking lock or fail.
|
||||
if (!$this->lock()) {
|
||||
throw new \RuntimeException('Obtaining write lock failed on file: ' . $this->filename);
|
||||
}
|
||||
$lock = true;
|
||||
}
|
||||
|
||||
if (@fwrite($this->handle, $this->raw()) === false) {
|
||||
$this->unlock();
|
||||
throw new \RuntimeException('Saving file failed: ' . $this->filename);
|
||||
}
|
||||
|
||||
if (isset($lock)) {
|
||||
$this->unlock();
|
||||
}
|
||||
|
||||
// Touch the directory as well, thus marking it modified.
|
||||
@touch(dirname($this->filename));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file from filesystem.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
return unlink($this->filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* Override in derived class.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
return (string) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* Override in derived class.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
return (string) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* Override in derived class.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
return (string) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
protected function mkdir($dir)
|
||||
{
|
||||
return is_dir($dir) || mkdir($dir, 0777, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
protected function writableDir($dir)
|
||||
{
|
||||
if ($dir && !file_exists($dir)) {
|
||||
return $this->writableDir(dirname($dir));
|
||||
}
|
||||
|
||||
return $dir && is_dir($dir) && is_writable($dir);
|
||||
}
|
||||
}
|
||||
54
system/src/Grav/Common/Filesystem/File/Json.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* File handling class for JSON.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Json extends General
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension = '.json';
|
||||
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
return (array) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
return (string) json_encode($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
return (array) json_decode($var);
|
||||
}
|
||||
}
|
||||
69
system/src/Grav/Common/Filesystem/File/Log.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* File handling class for Log files.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Log extends General
|
||||
{
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->extension = '.log';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
return (array) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string (unsupported).
|
||||
*
|
||||
* @param string $var
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
throw new \Exception('Saving log file is forbidden.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
$lines = (array) preg_split('#(\r\n|\n|\r)#', $var);
|
||||
|
||||
$results = array();
|
||||
foreach ($lines as $line) {
|
||||
preg_match('#^\[(.*)\] (.*) @ (.*) @@ (.*)$#', $line, $matches);
|
||||
if ($matches) {
|
||||
$results[] = ['date' => $matches[1], 'message' => $matches[2], 'url' => $matches[3], 'file' => $matches[4]];
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
120
system/src/Grav/Common/Filesystem/File/Markdown.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
|
||||
/**
|
||||
* File handling class.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Markdown extends General
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $extension = '.md';
|
||||
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Get/set file header.
|
||||
*
|
||||
* @param array $var
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function header(array $var = null)
|
||||
{
|
||||
$content = $this->content();
|
||||
|
||||
if ($var !== null) {
|
||||
$content['header'] = $var;
|
||||
$this->content($content);
|
||||
}
|
||||
|
||||
return $content['header'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set markdown content.
|
||||
*
|
||||
* @param string $var
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function markdown($var = null)
|
||||
{
|
||||
$content = $this->content();
|
||||
|
||||
if ($var !== null) {
|
||||
$content['markdown'] = (string) $var;
|
||||
$this->content($content);
|
||||
}
|
||||
|
||||
return $content['markdown'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
$var = (array) $var;
|
||||
if (!isset($var['header']) || !is_array($var['header'])) {
|
||||
$var['header'] = array();
|
||||
}
|
||||
if (!isset($var['markdown']) || !is_string($var['markdown'])) {
|
||||
$var['markdown'] = '';
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
// Create Markdown file with YAML header.
|
||||
$o = (!empty($var['header']) ? "---\n" . trim(YamlParser::dump($var['header'])) . "\n---\n\n" : '') . $var['markdown'];
|
||||
|
||||
// Normalize line endings to Unix style.
|
||||
$o = preg_replace("/(\r\n|\r)/", "\n", $o);
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
$content = array();
|
||||
|
||||
// Normalize line endings to Unix style.
|
||||
$var = preg_replace("/(\r\n|\r)/", "\n", $var);
|
||||
|
||||
// Parse header.
|
||||
preg_match("/---\n(.+?)\n---(\n\n|$)/uism", $this->raw(), $m);
|
||||
$content['header'] = isset($m[1]) ? YamlParser::parse(preg_replace("/\n\t/", "\n ", $m[1])) : array();
|
||||
|
||||
// Strip header to get content.
|
||||
$content['markdown'] = trim(preg_replace("/---\n(.+?)\n---(\n\n|$)/uism", '', $var));
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
61
system/src/Grav/Common/Filesystem/File/Yaml.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem\File;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml as YamlParser;
|
||||
|
||||
/**
|
||||
* File handling class for YAML.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Yaml extends General
|
||||
{
|
||||
/**
|
||||
* @var array|General[]
|
||||
*/
|
||||
static protected $instances = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->extension = YAML_EXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check contents and make sure it is in correct format.
|
||||
*
|
||||
* @param array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function check($var)
|
||||
{
|
||||
return (array) $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode contents into RAW string.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
protected function encode($var)
|
||||
{
|
||||
return (string) YamlParser::dump($var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode RAW string into contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return array mixed
|
||||
*/
|
||||
protected function decode($var)
|
||||
{
|
||||
return (array) YamlParser::parse($var);
|
||||
}
|
||||
}
|
||||
100
system/src/Grav/Common/Filesystem/FileInterface.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace Grav\Common\Filesystem;
|
||||
|
||||
/**
|
||||
* File interface.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
interface FileInterface
|
||||
{
|
||||
/**
|
||||
* Get file instance.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return mixed
|
||||
*/
|
||||
public static function instance($filename);
|
||||
|
||||
/**
|
||||
* Check if file exits.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists();
|
||||
|
||||
/**
|
||||
* Return file modification time.
|
||||
*
|
||||
* @return int Timestamp
|
||||
*/
|
||||
public function modified();
|
||||
|
||||
/**
|
||||
* Lock file for writing. Lock gets automatically released during the save().
|
||||
*
|
||||
* @param bool $block For non-blocking lock, set the parameter to false.
|
||||
* @return bool
|
||||
*/
|
||||
public function lock($block = true);
|
||||
|
||||
/**
|
||||
* Returns true if file has been locked for writing.
|
||||
*
|
||||
* @return bool|null True = locked, false = failed, null = not locked.
|
||||
*/
|
||||
public function locked();
|
||||
|
||||
/**
|
||||
* Unlock file.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function unlock();
|
||||
|
||||
/**
|
||||
* Check if file can be written.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function writable();
|
||||
|
||||
/**
|
||||
* (Re)Load a file and return its contents.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function load();
|
||||
|
||||
/**
|
||||
* Get/set raw file contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
public function raw($var = null);
|
||||
|
||||
/**
|
||||
* Get/set parsed file contents.
|
||||
*
|
||||
* @param string $var
|
||||
* @return string
|
||||
*/
|
||||
public function content($var = null);
|
||||
|
||||
/**
|
||||
* Save file.
|
||||
*
|
||||
* @param string $data Optional data to be saved.
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save($data = null);
|
||||
|
||||
/**
|
||||
* Delete file from filesystem.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete();
|
||||
}
|
||||
221
system/src/Grav/Common/Filesystem/Folder.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?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 lastModified($path)
|
||||
{
|
||||
$directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
$last_modified = 0;
|
||||
|
||||
/** @var \RecursiveDirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$dir_modified = $file->getMTime();
|
||||
if ($dir_modified > $last_modified) {
|
||||
$last_modified = $dir_modified;
|
||||
}
|
||||
}
|
||||
return $last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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())
|
||||
{
|
||||
$path = realpath($path);
|
||||
|
||||
if ($path === false) {
|
||||
throw new \RuntimeException("Path to {$path} doesn't exist.");
|
||||
}
|
||||
|
||||
$compare = $params['compare'] ? 'get' . $params['compare'] : null;
|
||||
$pattern = $params['pattern'] ? $params['pattern'] : null;
|
||||
$filters = $params['filters'] ? $params['filters'] : null;
|
||||
$key = $params['key'] ? 'get' . $params['key'] : null;
|
||||
$value = $params['value'] ? 'get' . $params['value'] : 'SubPathname';
|
||||
|
||||
$directory = new \RecursiveDirectoryIterator($path,
|
||||
\RecursiveDirectoryIterator::SKIP_DOTS + \FilesystemIterator::UNIX_PATHS + \FilesystemIterator::CURRENT_AS_SELF);
|
||||
$iterator = new \RecursiveIteratorIterator($directory, \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
$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'])) {
|
||||
$filePath = preg_replace($filters['value'], '', $filePath);
|
||||
}
|
||||
}
|
||||
|
||||
$results[$fileKey] = $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
|
||||
*/
|
||||
public static function delete($target)
|
||||
{
|
||||
if (!is_dir($target)) {
|
||||
throw new \RuntimeException('Cannot delete non-existing folder.');
|
||||
}
|
||||
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
protected static function doDelete($folder)
|
||||
{
|
||||
// Special case for symbolic links.
|
||||
if (is_link($folder)) {
|
||||
return @unlink($folder);
|
||||
}
|
||||
|
||||
// Go through all items in filesystem and recursively remove everything.
|
||||
$files = array_diff(scandir($folder), array('.', '..'));
|
||||
foreach ($files as $file) {
|
||||
$path = "{$folder}/{$file}";
|
||||
(is_dir($path)) ? self::doDelete($path) : @unlink($path);
|
||||
}
|
||||
|
||||
return @rmdir($folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folder
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected static function mkdir($folder)
|
||||
{
|
||||
if (is_dir($folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$success = @mkdir($folder, 0777, true);
|
||||
|
||||
if (!$success) {
|
||||
$error = error_get_last();
|
||||
throw new \RuntimeException($error['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
150
system/src/Grav/Common/Getters.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* Abstract class to implement magic __get(), __set(), __isset() and __unset().
|
||||
* Also implements ArrayAccess.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class Getters implements \ArrayAccess, \Countable
|
||||
{
|
||||
/**
|
||||
* Define variable used in getters.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $gettersVariable = null;
|
||||
|
||||
/**
|
||||
* Magic setter method
|
||||
*
|
||||
* @param mixed $offset Asset name value
|
||||
* @param mixed $value Asset value
|
||||
*/
|
||||
public function __set($offset, $value)
|
||||
{
|
||||
$this->offsetSet($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic getter method
|
||||
*
|
||||
* @param mixed $offset Asset name value
|
||||
* @return mixed Asset value
|
||||
*/
|
||||
public function __get($offset)
|
||||
{
|
||||
return $this->offsetGet($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to determine if the attribute is set
|
||||
*
|
||||
* @param mixed $offset Asset name value
|
||||
* @return boolean True if the value is set
|
||||
*/
|
||||
public function __isset($offset)
|
||||
{
|
||||
return $this->offsetExists($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method to unset the attribute
|
||||
*
|
||||
* @param mixed $offset The name value to unset
|
||||
*/
|
||||
public function __unset($offset)
|
||||
{
|
||||
$this->offsetUnset($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
if ($this->gettersVariable) {
|
||||
$var = $this->gettersVariable;
|
||||
return isset($this->{$var}[$offset]);
|
||||
} else {
|
||||
return isset($this->{$offset});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if ($this->gettersVariable) {
|
||||
$var = $this->gettersVariable;
|
||||
return isset($this->{$var}[$offset]) ? $this->{$var}[$offset] : null;
|
||||
} else {
|
||||
return isset($this->{$offset}) ? $this->{$offset} : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($this->gettersVariable) {
|
||||
$var = $this->gettersVariable;
|
||||
$this->{$var}[$offset] = $value;
|
||||
} else {
|
||||
$this->{$offset} = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
if ($this->gettersVariable) {
|
||||
$var = $this->gettersVariable;
|
||||
unset($this->{$var}[$offset]);
|
||||
} else {
|
||||
unset($this->{$offset});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
if ($this->gettersVariable) {
|
||||
$var = $this->gettersVariable;
|
||||
count($this->{$var});
|
||||
} else {
|
||||
count($this->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associative array of object properties.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
if ($this->gettersVariable) {
|
||||
$var = $this->gettersVariable;
|
||||
return $this->{$var};
|
||||
} else {
|
||||
$properties = (array) $this;
|
||||
$list = array();
|
||||
foreach ($properties as $property => $value) {
|
||||
if ($property[0] != "\0") $list[$property] = $value;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
}
|
||||
214
system/src/Grav/Common/Grav.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use \Tracy\Debugger;
|
||||
use \Grav\Common\Page\Page;
|
||||
use \Grav\Common\Page\Pages;
|
||||
|
||||
|
||||
/**
|
||||
* Grav
|
||||
*
|
||||
* @author Andy Miller
|
||||
* @link http://www.rockettheme.com
|
||||
* @license http://opensource.org/licenses/MIT
|
||||
* @version 0.1
|
||||
*
|
||||
* Originally based on Pico by Gilbert Pellegrom - http://pico.dev7studios.com
|
||||
* Influeced by Pico, Stacey, Kirby, PieCrust and other great platforms...
|
||||
*
|
||||
* @property Plugins $plugins
|
||||
* @property Config $config
|
||||
* @property Cache $cache
|
||||
* @property Uri $uri
|
||||
* @property Pages $pages
|
||||
* @property Page $page
|
||||
*/
|
||||
class Grav extends Getters
|
||||
{
|
||||
/**
|
||||
* @var string Grav output.
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $plugins;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var Cache
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @var Uri
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* @var Pages
|
||||
*/
|
||||
protected $pages;
|
||||
|
||||
/**
|
||||
* @var Page
|
||||
*/
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* @var Twig
|
||||
*/
|
||||
protected $twig;
|
||||
|
||||
/**
|
||||
* @var Taxonomy
|
||||
*/
|
||||
protected $taxonomy;
|
||||
|
||||
public function process()
|
||||
{
|
||||
// Get the URI and URL (needed for configuration)
|
||||
$this->uri = Registry::get('Uri');
|
||||
|
||||
// Get the Configuration settings and caching
|
||||
$this->config = Registry::get('Config');
|
||||
|
||||
Debugger::$logDirectory = $this->config->get('system.debugger.log.enabled') ? LOG_DIR : null;
|
||||
Debugger::$maxDepth = $this->config->get('system.debugger.max_depth');
|
||||
|
||||
// Switch debugger into development mode if configured
|
||||
if ($this->config->get('system.debugger.enabled')) {
|
||||
if (function_exists('ini_set')) {
|
||||
ini_set('display_errors', true);
|
||||
}
|
||||
Debugger::$productionMode = Debugger::DEVELOPMENT;
|
||||
$this->fireEvent('onAfterInitDebug');
|
||||
}
|
||||
|
||||
// Get the Caching setup
|
||||
$this->cache = Registry::get('Cache');
|
||||
$this->cache->init();
|
||||
|
||||
// Get Plugins
|
||||
$plugins = new Plugins();
|
||||
$this->plugins = $plugins->load();
|
||||
$this->fireEvent('onAfterInitPlugins');
|
||||
|
||||
// Get current theme and hook it into plugins.
|
||||
$themes = new Themes();
|
||||
$this->plugins['Theme'] = $themes->load();
|
||||
|
||||
// Get twig object
|
||||
$this->twig = Registry::get('Twig');
|
||||
$this->twig->init();
|
||||
|
||||
// Get all the Pages that Grav knows about
|
||||
$this->pages = Registry::get('Pages');
|
||||
$this->pages->init();
|
||||
$this->fireEvent('onAfterGetPages');
|
||||
|
||||
// Get the taxonomy and set it on the grav object
|
||||
$this->taxonomy = Registry::get('Taxonomy');
|
||||
|
||||
// Get current page
|
||||
$this->page = $this->pages->dispatch($this->uri->route());
|
||||
$this->fireEvent('onAfterGetPage');
|
||||
|
||||
// If there's no page, throw exception
|
||||
if (!$this->page) {
|
||||
throw new \RuntimeException('Page Not Found', 404);
|
||||
}
|
||||
|
||||
// Process whole page as required
|
||||
$this->output = $this->twig->processSite($this->uri->extension());
|
||||
$this->fireEvent('onAfterGetOutput');
|
||||
|
||||
// Set the header type
|
||||
$this->header();
|
||||
|
||||
echo $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect browser to another location.
|
||||
*
|
||||
* @param string $route Internal route.
|
||||
* @param int $code Redirection code (30x)
|
||||
*/
|
||||
public function redirect($route, $code = 303)
|
||||
{
|
||||
header("Location: " . rtrim($this->uri->rootUrl(), '/') .'/'. trim($route, '/'), true, $code);
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns mime type for the file format.
|
||||
*
|
||||
* @param string $format
|
||||
* @return string
|
||||
*/
|
||||
public function mime($format)
|
||||
{
|
||||
switch ($format) {
|
||||
case 'json':
|
||||
return 'application/json';
|
||||
case 'html':
|
||||
return 'text/html';
|
||||
case 'atom':
|
||||
return 'application/atom+xml';
|
||||
case 'rss':
|
||||
return 'application/rss+xml';
|
||||
case 'xml':
|
||||
return 'application/xml';
|
||||
}
|
||||
return 'text/html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set response header.
|
||||
*/
|
||||
public function header()
|
||||
{
|
||||
header('Content-type: ' . $this->mime($this->uri->extension()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message.
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
protected static function log($message)
|
||||
{
|
||||
if (Debugger::$logDirectory) {
|
||||
Debugger::log(sprintf($message, Debugger::timer() * 1000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes any hooks and runs them.
|
||||
*/
|
||||
public function fireEvent()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$hook_id = array_shift($args);
|
||||
$no_timing_hooks = array('onAfterPageProcessed','onAfterFolderProcessed', 'onAfterCollectionProcessed');
|
||||
|
||||
if (!empty($this->plugins)) {
|
||||
foreach ($this->plugins as $plugin) {
|
||||
if (is_callable(array($plugin, $hook_id))) {
|
||||
call_user_func_array(array($plugin, $hook_id), $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->config->get('system.debugger.log.timing') && !in_array($hook_id, $no_timing_hooks)) {
|
||||
static::log($hook_id.': %f ms');
|
||||
}
|
||||
}
|
||||
}
|
||||
370
system/src/Grav/Common/Inflector.php
Normal file
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* This file was originally part of the Akelos Framework
|
||||
*/
|
||||
|
||||
/**
|
||||
* Inflector for pluralize and singularize English nouns.
|
||||
*
|
||||
* This Inflector is a port of Ruby on Rails Inflector.
|
||||
*
|
||||
* It can be really helpful for developers that want to
|
||||
* create frameworks based on naming conventions rather than
|
||||
* configurations.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
class Inflector
|
||||
{
|
||||
|
||||
/**
|
||||
* Pluralizes English nouns.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word English noun to pluralize
|
||||
* @return string Plural noun
|
||||
*/
|
||||
public static function pluralize($word, $count = 2)
|
||||
{
|
||||
if ($count == 1) {
|
||||
return $word;
|
||||
}
|
||||
|
||||
$plural = array(
|
||||
'/(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');
|
||||
|
||||
$uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep');
|
||||
|
||||
$irregular = array(
|
||||
'person' => 'people',
|
||||
'man' => 'men',
|
||||
'child' => 'children',
|
||||
'sex' => 'sexes',
|
||||
'move' => 'moves');
|
||||
|
||||
$lowercased_word = strtolower($word);
|
||||
|
||||
foreach ($uncountable as $_uncountable) {
|
||||
if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) {
|
||||
return $word;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($irregular as $_plural => $_singular) {
|
||||
if (preg_match('/('.$_plural.')$/i', $word, $arr)) {
|
||||
return preg_replace('/('.$_plural.')$/i', substr($arr[0], 0, 1).substr($_singular, 1), $word);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($plural as $rule => $replacement) {
|
||||
if (preg_match($rule, $word)) {
|
||||
return preg_replace($rule, $replacement, $word);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Singularizes English nouns.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word English noun to singularize
|
||||
* @return string Singular noun.
|
||||
*/
|
||||
public static function singularize($word, $count = 1)
|
||||
{
|
||||
if ($count != 1) {
|
||||
return $word;
|
||||
}
|
||||
|
||||
$singular = array (
|
||||
'/(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' => '',
|
||||
);
|
||||
|
||||
$uncountable = array('equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep');
|
||||
|
||||
$irregular = array(
|
||||
'person' => 'people',
|
||||
'man' => 'men',
|
||||
'child' => 'children',
|
||||
'sex' => 'sexes',
|
||||
'move' => 'moves');
|
||||
|
||||
$lowercased_word = strtolower($word);
|
||||
foreach ($uncountable as $_uncountable) {
|
||||
if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) {
|
||||
return $word;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($irregular as $_plural => $_singular) {
|
||||
if (preg_match('/('.$_singular.')$/i', $word, $arr)) {
|
||||
return preg_replace('/('.$_singular.')$/i', substr($arr[0], 0, 1).substr($_plural, 1), $word);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($singular as $rule => $replacement) {
|
||||
if (preg_match($rule, $word)) {
|
||||
return preg_replace($rule, $replacement, $word);
|
||||
}
|
||||
}
|
||||
|
||||
return $word;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an underscored or CamelCase word into a English
|
||||
* sentence.
|
||||
*
|
||||
* The titleize static public function converts text like "WelcomePage",
|
||||
* "welcome_page" or "welcome page" to this "Welcome
|
||||
* Page".
|
||||
* If second parameter is set to 'first' it will only
|
||||
* capitalize the first character of the title.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word Word to format as tile
|
||||
* @param string $uppercase If set to 'first' it will only uppercase the
|
||||
* first character. Otherwise it will uppercase all
|
||||
* the words in the title.
|
||||
* @return string Text formatted as title
|
||||
*/
|
||||
public static function titleize($word, $uppercase = '')
|
||||
{
|
||||
$uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords';
|
||||
return $uppercase(static::humanize(static::underscorize($word)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given word as CamelCased
|
||||
*
|
||||
* Converts a word like "send_email" to "SendEmail". It
|
||||
* will remove non alphanumeric character from the word, so
|
||||
* "who's online" will be converted to "WhoSOnline"
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @see variablize
|
||||
* @param string $word Word to convert to camel case
|
||||
* @return string UpperCamelCasedWord
|
||||
*/
|
||||
public static function camelize($word)
|
||||
{
|
||||
return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a word "into_it_s_underscored_version"
|
||||
*
|
||||
* Convert any "CamelCased" or "ordinary Word" into an
|
||||
* "underscored_word".
|
||||
*
|
||||
* This can be really useful for creating friendly URLs.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word Word to underscore
|
||||
* @return string Underscored word
|
||||
*/
|
||||
public static function underscorize($word)
|
||||
{
|
||||
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1_\2', $word);
|
||||
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1_\2', $regex1);
|
||||
$regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '_', $regex2);
|
||||
return strtolower($regex3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a word "into-it-s-hyphenated-version"
|
||||
*
|
||||
* Convert any "CamelCased" or "ordinary Word" into an
|
||||
* "hyphenated-word".
|
||||
*
|
||||
* This can be really useful for creating friendly URLs.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word Word to hyphenate
|
||||
* @return string hyphenized word
|
||||
*/
|
||||
public static function hyphenize($word)
|
||||
{
|
||||
$regex1 = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\1-\2', $word);
|
||||
$regex2 = preg_replace('/([a-zd])([A-Z])/', '\1-\2', $regex1);
|
||||
$regex3 = preg_replace('/[^A-Z^a-z^0-9]+/', '-', $regex2);
|
||||
return strtolower($regex3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable string from $word
|
||||
*
|
||||
* Returns a human-readable string from $word, by replacing
|
||||
* underscores with a space, and by upper-casing the initial
|
||||
* character by default.
|
||||
*
|
||||
* If you need to uppercase all the words you just have to
|
||||
* pass 'all' as a second parameter.
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param string $word String to "humanize"
|
||||
* @param string $uppercase If set to 'all' it will uppercase all the words
|
||||
* instead of just the first one.
|
||||
* @return string Human-readable word
|
||||
*/
|
||||
public static function humanize($word, $uppercase = '')
|
||||
{
|
||||
$uppercase = $uppercase == 'all' ? 'ucwords' : 'ucfirst';
|
||||
return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as camelize but first char is underscored
|
||||
*
|
||||
* Converts a word like "send_email" to "sendEmail". It
|
||||
* will remove non alphanumeric character from the word, so
|
||||
* "who's online" will be converted to "whoSOnline"
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @see camelize
|
||||
* @param string $word Word to lowerCamelCase
|
||||
* @return string Returns a lowerCamelCasedWord
|
||||
*/
|
||||
public static function variablize($word)
|
||||
{
|
||||
$word = static::camelize($word);
|
||||
return strtolower($word[0]).substr($word, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a class name to its table name according to rails
|
||||
* naming conventions.
|
||||
*
|
||||
* Converts "Person" to "people"
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @see classify
|
||||
* @param string $class_name Class name for getting related table_name.
|
||||
* @return string plural_table_name
|
||||
*/
|
||||
public static function tableize($class_name)
|
||||
{
|
||||
return static::pluralize(static::underscore($class_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a table name to its class name according to rails
|
||||
* naming conventions.
|
||||
*
|
||||
* Converts "people" to "Person"
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @see tableize
|
||||
* @param string $table_name Table name for getting related ClassName.
|
||||
* @return string SingularClassName
|
||||
*/
|
||||
public static function classify($table_name)
|
||||
{
|
||||
return static::camelize(static::singularize($table_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts number to its ordinal English form.
|
||||
*
|
||||
* This method converts 13 to 13th, 2 to 2nd ...
|
||||
*
|
||||
* @access static public
|
||||
* @static
|
||||
* @param integer $number Number to get its ordinal value
|
||||
* @return string Ordinal representation of given string.
|
||||
*/
|
||||
public static function ordinalize($number)
|
||||
{
|
||||
if (in_array(($number % 100), range(11, 13))) {
|
||||
return $number.'th';
|
||||
} else {
|
||||
switch (($number % 10)) {
|
||||
case 1:
|
||||
return $number.'st';
|
||||
break;
|
||||
case 2:
|
||||
return $number.'nd';
|
||||
break;
|
||||
case 3:
|
||||
return $number.'rd';
|
||||
break;
|
||||
default:
|
||||
return $number.'th';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function monthize($days)
|
||||
{
|
||||
$now = new JDate();
|
||||
$end = new JDate();
|
||||
|
||||
$duration = new DateInterval("P{$days}D");
|
||||
|
||||
$diff = $end->add($duration)->diff($now);
|
||||
|
||||
// handle years
|
||||
if ($diff->y > 0) {
|
||||
$diff->m = $diff->m + 12*$diff->y;
|
||||
}
|
||||
|
||||
return $diff->m;
|
||||
}
|
||||
}
|
||||
394
system/src/Grav/Common/Iterator.php
Normal file
@@ -0,0 +1,394 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Class Iterator
|
||||
* @package Grav\Common
|
||||
*/
|
||||
class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $items = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $unset = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $items Initial items inside the iterator.
|
||||
*/
|
||||
public function __construct(array $items = array())
|
||||
{
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert function calls for the existing keys into their values.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $args
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($key, $args)
|
||||
{
|
||||
return (isset($this->items[$key])) ? $this->items[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array getter shorthand to get items.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return (isset($this->items[$key])) ? $this->items[$key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array setter shorthand to set the value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$this->items[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array isset shorthand to set the value.
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset($key)
|
||||
{
|
||||
return isset($this->items[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Array unset shorthand to remove the key.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function __unset($key)
|
||||
{
|
||||
$this->offsetUnset($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone the iterator.
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
$this->$key = clone $this->$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convents iterator to a comma separated list.
|
||||
*
|
||||
* @return string
|
||||
* @todo Add support to nested sets.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return implode(',', $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from the list.
|
||||
*
|
||||
* @param $key
|
||||
*/
|
||||
public function remove($key)
|
||||
{
|
||||
$this->offsetUnset($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return previous item.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function prev()
|
||||
{
|
||||
return prev($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return nth item.
|
||||
*
|
||||
* @param int $key
|
||||
* @return mixed|bool
|
||||
*/
|
||||
public function nth($key)
|
||||
{
|
||||
$items = array_values($this->items);
|
||||
return (isset($items[$key])) ? $this->offsetGet($items[$key]) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $needle Searched value.
|
||||
* @return string|bool Key if found, otherwise false.
|
||||
*/
|
||||
public function indexOf($needle)
|
||||
{
|
||||
foreach (array_values($this->items) as $key => $value) {
|
||||
if ($value === $needle) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffle items.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function shuffle()
|
||||
{
|
||||
$keys = array_keys($this->items);
|
||||
shuffle($keys);
|
||||
|
||||
$new = array();
|
||||
foreach($keys as $key) {
|
||||
$new[$key] = $this->items[$key];
|
||||
}
|
||||
|
||||
$this->items = $new;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice the list.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @return $this
|
||||
*/
|
||||
public function slice($offset, $length = null)
|
||||
{
|
||||
$this->items = array_slice($this->items, $offset, $length);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick one or more random entries.
|
||||
*
|
||||
* @param int $num Specifies how many entries should be picked.
|
||||
* @return $this
|
||||
*/
|
||||
public function random($num = 1)
|
||||
{
|
||||
$this->items = array_intersect_key($this->items, array_flip((array) array_rand($this->items, $num)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append new elements to the list.
|
||||
*
|
||||
* @param array|Iterator $items Items to be appended. Existing keys will be overridden with the new values.
|
||||
* @return $this
|
||||
*/
|
||||
public function append($items)
|
||||
{
|
||||
if ($items instanceof static) {
|
||||
$items = $items->toArray();
|
||||
}
|
||||
$this->items = array_merge($this->items, (array) $items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Implements export functions to array, YAML and JSON.
|
||||
|
||||
/**
|
||||
* Return items as an array.
|
||||
*
|
||||
* @return array Array presentation of the iterator.
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return YAML encoded string of items.
|
||||
*
|
||||
* @return string YAML presentation of the iterator.
|
||||
*/
|
||||
public function toYaml()
|
||||
{
|
||||
return Yaml::dump($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON encoded string of items.
|
||||
*
|
||||
* @return string JSON presentation of the iterator.
|
||||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return json_encode($this->items);
|
||||
}
|
||||
|
||||
// Implements Iterator.
|
||||
|
||||
/**
|
||||
* Returns the current element.
|
||||
*
|
||||
* @return mixed Can return any type.
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return current($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key of the current element.
|
||||
*
|
||||
* @return mixed Returns scalar on success, or NULL on failure.
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return key($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the current position to the next element.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
if ($this->unset) {
|
||||
// If current item was unset, position is already in the next element (do nothing).
|
||||
$this->unset = false;
|
||||
} else {
|
||||
next($this->items);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds back to the first element of the Iterator.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->unset = false;
|
||||
reset($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called after Iterator::rewind() and Iterator::next() to check if the current position is valid.
|
||||
*
|
||||
* @return bool Returns TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return key($this->items) !== null;
|
||||
}
|
||||
|
||||
// Implements ArrayAccess
|
||||
|
||||
/**
|
||||
* Whether or not an offset exists.
|
||||
*
|
||||
* @param mixed $offset An offset to check for.
|
||||
* @return bool Returns TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->items[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value at specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to retrieve.
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return isset($this->items[$offset]) ? $this->items[$offset] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a value to the specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to assign the value to.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (is_null($offset)) {
|
||||
$this->items[] = $value;
|
||||
} else {
|
||||
$this->items[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets an offset.
|
||||
*
|
||||
* @param mixed $offset The offset to unset.
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
if ($offset == key($this->items)) {
|
||||
$this->unset = true;
|
||||
}
|
||||
unset($this->items[$offset]);
|
||||
}
|
||||
|
||||
// Implements Countable
|
||||
|
||||
/**
|
||||
* This method is executed when using the count() function.
|
||||
*
|
||||
* @return int The count of items.
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
// Implements Serializable
|
||||
|
||||
/**
|
||||
* Returns string representation of the object.
|
||||
*
|
||||
* @return string Returns the string representation of the object.
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return serialize($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during unserialization of the object.
|
||||
*
|
||||
* @param string $serialized The string representation of the object.
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
$this->items = unserialize($serialized);
|
||||
}
|
||||
}
|
||||
321
system/src/Grav/Common/Page/Asset.php
Normal file
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Data\Blueprint;
|
||||
use Grav\Common\Uri;
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\Filesystem\File\Yaml;
|
||||
use Grav\Common\Registry;
|
||||
use Gregwar\Image\Image as ImageFile;
|
||||
|
||||
/**
|
||||
* The Image asset holds information related to an individual image. These are then stored in the Assets object.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*
|
||||
* @property string $file_name
|
||||
* @property string $type
|
||||
* @property string $name Alias of file_name
|
||||
* @property string $description
|
||||
* @property string $url
|
||||
* @property string $path
|
||||
* @property string $thumb
|
||||
* @property int $width
|
||||
* @property int $height
|
||||
* @property string $mime
|
||||
* @property int $modified
|
||||
*
|
||||
* Asset can have up to 3 files:
|
||||
* - video.mov Asset file itself.
|
||||
* - video.mov.meta.yaml Metadata for the asset.
|
||||
* - video.mov.thumb.jpg Thumbnail image for the asset.
|
||||
*
|
||||
*/
|
||||
class Asset extends Data
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var ImageFile
|
||||
*/
|
||||
protected $image;
|
||||
|
||||
protected $type = 'guess';
|
||||
protected $quality = 80;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $meta = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $linkTarget;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $linkAttributes;
|
||||
|
||||
public function __construct($items = array(), Blueprint $blueprint = null)
|
||||
{
|
||||
parent::__construct($items, $blueprint);
|
||||
|
||||
if ($this->get('type') == 'image') {
|
||||
$filePath = $this->get('path') . '/' . $this->get('filename');
|
||||
$image_info = getimagesize($filePath);
|
||||
$this->set('thumb', $filePath);
|
||||
$this->def('width', $image_info[0]);
|
||||
$this->def('height', $image_info[1]);
|
||||
$this->def('mime', $image_info['mime']);
|
||||
$this->reset();
|
||||
} else {
|
||||
$this->def('mime', 'application/octet-stream');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string representation of the object (html or url).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->linkImage ? $this->html() : $this->url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL to file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function url()
|
||||
{
|
||||
$config = Registry::get('Config');
|
||||
|
||||
if ($this->image) {
|
||||
$output = $this->image->cacheFile($this->type, $this->quality);
|
||||
$this->reset();
|
||||
} else {
|
||||
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
|
||||
$output = $relPath . '/' . $this->get('filename');
|
||||
}
|
||||
|
||||
return $config->get('system.base_url_relative') . '/'. $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets image output format.
|
||||
*
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
*/
|
||||
public function format($type = null, $quality = 80)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
$this->quality = $quality;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <img> tag from the asset.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $class
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @return string
|
||||
*/
|
||||
public function img($title = null, $class = null, $type = null, $quality = 80)
|
||||
{
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
|
||||
$output = $this->html($title, $class, $type, $quality);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTML markup from the asset.
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $class
|
||||
* @param string $type
|
||||
* @param int $quality
|
||||
* @return string
|
||||
*/
|
||||
public function html($title = null, $class = null, $type = null, $quality = 80)
|
||||
{
|
||||
$title = $title ? $title : $this->get('title');
|
||||
$class = $class ? $class : '';
|
||||
|
||||
if ($this->image) {
|
||||
$type = $type ? $type : $this->type;
|
||||
$quality = $quality ? $quality : $this->quality;
|
||||
|
||||
$url = $this->url($type, $quality);
|
||||
$this->reset();
|
||||
|
||||
$output = '<img src="' . $url . '" class="'. $class . '" alt="' . $title . '" />';
|
||||
} else {
|
||||
$output = $title;
|
||||
}
|
||||
|
||||
if ($this->linkTarget) {
|
||||
$config = Registry::get('Config');
|
||||
|
||||
$output = '<a href="' . $config->get('system.base_url_relative') . '/'. $this->linkTarget
|
||||
. '"' . $this->linkAttributes. ' class="'. $class . '">' . $output . '</a>';
|
||||
|
||||
$this->linkTarget = $this->linkAttributes = null;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return lightbox HTML for the asset.
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return $this
|
||||
*/
|
||||
public function lightbox($width = null, $height = null)
|
||||
{
|
||||
$this->linkAttributes = ' rel="lightbox"';
|
||||
|
||||
return $this->link($width, $height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return link HTML for the asset.
|
||||
*
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return $this
|
||||
*/
|
||||
public function link($width = null, $height = null)
|
||||
{
|
||||
if ($this->image) {
|
||||
$image = clone $this->image;
|
||||
if ($width && $height) {
|
||||
$image->cropResize($width, $height);
|
||||
}
|
||||
$this->linkTarget = $image->cacheFile($this->type, $this->quality);
|
||||
} else {
|
||||
// TODO: we need to find out URI in a bit better way.
|
||||
$relPath = preg_replace('|^' . ROOT_DIR . '|', '', $this->get('path'));
|
||||
$this->linkTarget = $relPath. '/' . $this->get('filename');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset image.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reset()
|
||||
{
|
||||
$this->image = null;
|
||||
|
||||
if ($this->get('type') == 'image') {
|
||||
$this->image();
|
||||
$this->filter();
|
||||
}
|
||||
$this->type = 'guess';
|
||||
$this->quality = 80;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the call to the image processing method.
|
||||
*
|
||||
* @param string $method
|
||||
* @param mixed $args
|
||||
* @return $this|mixed
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
if ($method == 'cropZoom') {
|
||||
$method = 'zoomCrop';
|
||||
}
|
||||
|
||||
// Always initialize image.
|
||||
if (!$this->image) {
|
||||
$this->image();
|
||||
}
|
||||
$result = call_user_func_array(array($this->image, $method), $args);
|
||||
|
||||
// Returns either current object or result of the action.
|
||||
return $result instanceof ImageFile ? $this : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets asset image, resets image manipulation operations.
|
||||
*
|
||||
* @param string $variable
|
||||
* @return $this
|
||||
*/
|
||||
public function image($variable = 'thumb')
|
||||
{
|
||||
// TODO: add default file
|
||||
$file = $this->get($variable);
|
||||
$this->image = ImageFile::open($file)
|
||||
->setCacheDir(basename(IMAGES_DIR))
|
||||
->setActualCacheDir(IMAGES_DIR)
|
||||
->setPrettyName(basename($this->get('basename')));
|
||||
|
||||
$this->filter();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add meta file for the asset.
|
||||
*
|
||||
* @param $type
|
||||
* @return $this
|
||||
*/
|
||||
public function addMetaFile($type)
|
||||
{
|
||||
$this->meta[$type] = $type;
|
||||
|
||||
$path = $this->get('path') . '/' . $this->get('filename') . '.meta.' . $type;
|
||||
if ($type == 'yaml') {
|
||||
$this->merge(Yaml::instance($path)->content());
|
||||
} elseif (in_array($type, array('jpg', 'jpeg', 'png', 'gif'))) {
|
||||
$this->set('thumb', $path);
|
||||
}
|
||||
$this->reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter image by using user defined filter parameters.
|
||||
*
|
||||
* @param string $filter Filter to be used.
|
||||
*/
|
||||
public function filter($filter = 'image.filters.default')
|
||||
{
|
||||
$filters = (array) $this->get($filter, array());
|
||||
foreach ($filters as $params) {
|
||||
$params = (array) $params;
|
||||
$method = array_shift($params);
|
||||
$this->__call($method, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
201
system/src/Grav/Common/Page/Assets.php
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use Grav\Common\Getters;
|
||||
use Grav\Common\Registry;
|
||||
use Grav\Config;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
* Assets is a holder object that contains references to the assets of page. This object is created and
|
||||
* populated during the getAssets() method in the Pages object
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Assets extends Getters
|
||||
{
|
||||
protected $gettersVariable = 'instances';
|
||||
protected $path;
|
||||
|
||||
protected $instances = array();
|
||||
protected $images = array();
|
||||
protected $videos = array();
|
||||
protected $files = array();
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
// Handle special cases where page doesn't exist in filesystem.
|
||||
if (!is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->path = $path;
|
||||
|
||||
$iterator = new \DirectoryIterator($path);
|
||||
|
||||
/** @var \DirectoryIterator $info */
|
||||
foreach ($iterator as $info) {
|
||||
// Ignore folders and Markdown files.
|
||||
if ($info->isDot() || !$info->isFile() || $info->getExtension() == 'md') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find out the real filename, in case of we are at the metadata.
|
||||
$filename = $info->getFilename();
|
||||
list($basename, $ext, $meta) = $this->getFileParts($filename);
|
||||
|
||||
// Get asset instance creating it if it didn't exist.
|
||||
$asset = $this->get("{$basename}.{$ext}", true);
|
||||
if (!$asset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Assign meta files to the asset.
|
||||
if ($meta) {
|
||||
$asset->addMetaFile($meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get asset by basename and extension.
|
||||
*
|
||||
* @param string $filename
|
||||
* @param bool $create
|
||||
* @return Asset|null
|
||||
*/
|
||||
public function get($filename, $create = false)
|
||||
{
|
||||
if ($create && !isset($this->instances[$filename])) {
|
||||
$parts = explode('.', $filename);
|
||||
$ext = array_pop($parts);
|
||||
$basename = implode('.', $parts);
|
||||
|
||||
/** @var Config $config */
|
||||
$config = Registry::get('Config');
|
||||
|
||||
// Check if asset type has been configured.
|
||||
$params = $config->get("assets.{$ext}");
|
||||
if (!$params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$filePath = $this->path . '/' . $filename;
|
||||
$params += array(
|
||||
'type' => 'file',
|
||||
'thumb' => 'assets/thumb.png',
|
||||
'mime' => 'application/octet-stream',
|
||||
'name' => $filename,
|
||||
'filename' => $filename,
|
||||
'basename' => $basename,
|
||||
'extension' => $ext,
|
||||
'path' => $this->path,
|
||||
'modified' => filemtime($filePath),
|
||||
);
|
||||
|
||||
$lookup = array(
|
||||
USER_DIR . 'images/',
|
||||
SYSTEM_DIR . 'images/',
|
||||
);
|
||||
foreach ($lookup as $path) {
|
||||
if (is_file($path . $params['thumb'])) {
|
||||
$params['thumb'] = $path . $params['thumb'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->add(new Asset($params));
|
||||
}
|
||||
|
||||
return isset($this->instances[$filename]) ? $this->instances[$filename] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all assets.
|
||||
*
|
||||
* @return array|Asset[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all image assets.
|
||||
*
|
||||
* @return array|Asset[]
|
||||
*/
|
||||
public function images()
|
||||
{
|
||||
return $this->images;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all video assets.
|
||||
*
|
||||
* @return array|Asset[]
|
||||
*/
|
||||
public function videos()
|
||||
{
|
||||
return $this->videos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all file assets.
|
||||
*
|
||||
* @return array|Asset[]
|
||||
*/
|
||||
public function files()
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function add($file)
|
||||
{
|
||||
$this->instances[$file->filename] = $file;
|
||||
switch ($file->type) {
|
||||
case 'image':
|
||||
$this->images[$file->filename] = $file;
|
||||
break;
|
||||
case 'video':
|
||||
$this->videos[$file->filename] = $file;
|
||||
break;
|
||||
default:
|
||||
$this->files[$file->filename] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filename, extension and meta part.
|
||||
*
|
||||
* @param string $filename
|
||||
* @return array
|
||||
*/
|
||||
protected function getFileParts($filename)
|
||||
{
|
||||
$fileParts = explode('.', $filename);
|
||||
|
||||
$name = array_shift($fileParts);
|
||||
$extension = null;
|
||||
while (($part = array_shift($fileParts)) !== null) {
|
||||
if ($part != 'meta') {
|
||||
if (isset($extension)) {
|
||||
$name .= '.' . $extension;
|
||||
}
|
||||
$extension = $part;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$meta = implode('.', $fileParts);
|
||||
|
||||
return array($name, $extension, $meta);
|
||||
}
|
||||
}
|
||||
187
system/src/Grav/Common/Page/Collection.php
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
use Grav\Common\Iterator;
|
||||
use Grav\Common\Registry;
|
||||
|
||||
/**
|
||||
* Collection of Pages.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Collection extends Iterator
|
||||
{
|
||||
/**
|
||||
* @var Pages
|
||||
*/
|
||||
protected $pages;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params;
|
||||
|
||||
public function __construct($items = array(), array $params = array(), Pages $pages = null) {
|
||||
parent::__construct($items);
|
||||
|
||||
$this->params = $params;
|
||||
$this->pages = $pages ? $pages : Registry::get('Pages');
|
||||
}
|
||||
|
||||
public function params()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set parameters to the Collection
|
||||
*
|
||||
* @param array $params
|
||||
* @return $this
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->params = array_merge($this->params, $params);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current page.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
$current = parent::key();
|
||||
return $this->pages->get($current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current slug.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
$current = parent::current();
|
||||
return $current['slug'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value at specified offset.
|
||||
*
|
||||
* @param mixed $offset The offset to retrieve.
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return !empty($this->items[$offset]) ? $this->pages->get($offset) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item from the list.
|
||||
*
|
||||
* @param Page|string|null $key
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function remove($key = null)
|
||||
{
|
||||
if ($key instanceof Page) {
|
||||
$key = $key->path();
|
||||
} elseif (is_null($key)) {
|
||||
$key = key($this->items);
|
||||
}
|
||||
if (!is_string($key)) {
|
||||
throw new \InvalidArgumentException('Invalid argument $key.');
|
||||
}
|
||||
|
||||
parent::remove($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder collection.
|
||||
*
|
||||
* @param string $by
|
||||
* @param string $dir
|
||||
* @param array $manual
|
||||
* @return $this
|
||||
*/
|
||||
public function order($by, $dir = 'asc', $manual = null)
|
||||
{
|
||||
$this->items = $this->pages->sortCollection($this, $by, $dir, $manual);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the first in the collection
|
||||
* @param string $path
|
||||
* @return boolean True if item is first
|
||||
*/
|
||||
public function isFirst($path)
|
||||
{
|
||||
if ($this->items && $path == array_keys($this->items)[0]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this item is the last in the collection
|
||||
* @param string $path
|
||||
* @return boolean True if item is last
|
||||
*/
|
||||
public function isLast($path)
|
||||
{
|
||||
if ($this->items && $path == array_keys($this->items)[count($this->items)-1]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the previous sibling based on current position
|
||||
*
|
||||
* @return Object the previous item
|
||||
*/
|
||||
public function prevSibling($path)
|
||||
{
|
||||
return $this->adjacentSibling($path, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next sibling based on current position
|
||||
*
|
||||
* @return Object the next item
|
||||
*/
|
||||
public function nextSibling($path)
|
||||
{
|
||||
return $this->adjacentSibling($path, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adjacent sibling based on a direction
|
||||
* @param integer $direction either -1 or +1
|
||||
* @return Object the sibling item
|
||||
*/
|
||||
public function adjacentSibling($path, $direction = 1)
|
||||
{
|
||||
|
||||
$values = array_keys($this->items);
|
||||
$keys = array_flip($values);
|
||||
$index = $keys[$path] - $direction;
|
||||
|
||||
return isset($values[$index]) ? $this->offsetGet($values[$index]) : $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item in the current position
|
||||
* @param String $path the path the item
|
||||
* @return Object item in the array the the current position
|
||||
*/
|
||||
public function currentPosition($path) {
|
||||
return array_search($path,array_keys($this->items));
|
||||
}
|
||||
}
|
||||
1588
system/src/Grav/Common/Page/Page.php
Normal file
547
system/src/Grav/Common/Page/Pages.php
Normal file
@@ -0,0 +1,547 @@
|
||||
<?php
|
||||
namespace Grav\Common\Page;
|
||||
|
||||
use \Grav\Common\Filesystem\Folder;
|
||||
use \Grav\Common\Grav;
|
||||
use \Grav\Common\Config;
|
||||
use \Grav\Common\Data;
|
||||
use \Grav\Common\Registry;
|
||||
use \Grav\Common\Utils;
|
||||
use \Grav\Common\Cache;
|
||||
use \Grav\Common\Taxonomy;
|
||||
|
||||
/**
|
||||
* GravPages is the class that is the entry point into the hierarchy of pages
|
||||
*/
|
||||
class Pages
|
||||
{
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
protected $grav;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var array|Page[]
|
||||
*/
|
||||
protected $instances;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected $children;
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected $routes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $sort;
|
||||
|
||||
/**
|
||||
* @var Data\Blueprints
|
||||
*/
|
||||
protected $blueprints;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $last_modified;
|
||||
|
||||
/**
|
||||
* Class initialization. Must be called before using this class.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->grav = Registry::get('Grav');
|
||||
$this->config = Registry::get('Config');
|
||||
|
||||
$this->buildPages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set last modification time.
|
||||
*
|
||||
* @param int $modified
|
||||
* @return int|null
|
||||
*/
|
||||
public function lastModified($modified = null)
|
||||
{
|
||||
if ($modified && $modified > $this->last_modified) {
|
||||
$this->last_modified = $modified;
|
||||
}
|
||||
return $this->last_modified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all pages.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function instances()
|
||||
{
|
||||
return $this->instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all routes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function routes()
|
||||
{
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a page and assigns a route to it.
|
||||
*
|
||||
* @param Page $page Page to be added.
|
||||
* @param string $route Optional route (uses route from the object if not set).
|
||||
*/
|
||||
public function addPage(Page $page, $route = null)
|
||||
{
|
||||
if (!isset($this->instances[$page->path()])) {
|
||||
$this->instances[$page->path()] = $page;
|
||||
}
|
||||
$route = $page->route($route);
|
||||
if ($page->parent()) {
|
||||
$this->children[$page->parent()->path()][$page->path()] = array('slug' => $page->slug());
|
||||
}
|
||||
$this->routes[$route] = $page->path();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort sub-pages in a page.
|
||||
*
|
||||
* @param Page $page
|
||||
* @param string $order_by
|
||||
* @param string $order_dir
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sort(Page $page, $order_by = null, $order_dir = null)
|
||||
{
|
||||
if ($order_by === null) {
|
||||
$order_by = $page->orderBy();
|
||||
}
|
||||
if ($order_dir === null) {
|
||||
$order_dir = $page->orderDir();
|
||||
}
|
||||
|
||||
$path = $page->path();
|
||||
$children = isset($this->children[$path]) ? $this->children[$path] : array();
|
||||
|
||||
if (!$children) {
|
||||
return $children;
|
||||
}
|
||||
|
||||
if (!isset($this->sort[$path][$order_by])) {
|
||||
$this->buildSort($path, $children, $order_by, $page->orderManual());
|
||||
}
|
||||
|
||||
$sort = $this->sort[$path][$order_by];
|
||||
|
||||
if ($order_dir != 'asc') {
|
||||
$sort = array_reverse($sort);
|
||||
}
|
||||
|
||||
return $sort;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $collection
|
||||
* @param $orderBy
|
||||
* @param string $orderDir
|
||||
* @param null $orderManual
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function sortCollection(Collection $collection, $orderBy, $orderDir = 'asc', $orderManual = null)
|
||||
{
|
||||
$items = $collection->toArray();
|
||||
|
||||
$lookup = md5(serialize($items));
|
||||
if (!isset($this->sort[$lookup][$orderBy])) {
|
||||
$this->buildSort($lookup, $items, $orderBy, $orderManual);
|
||||
}
|
||||
|
||||
$sort = $this->sort[$lookup][$orderBy];
|
||||
|
||||
if ($orderDir != 'asc') {
|
||||
$sort = array_reverse($sort);
|
||||
}
|
||||
|
||||
return $sort;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a page instance.
|
||||
*
|
||||
* @param string $path
|
||||
* @return Page
|
||||
*/
|
||||
public function get($path)
|
||||
{
|
||||
if (!is_null($path) && !is_string($path)) throw new \Exception();
|
||||
return isset($this->instances[(string) $path]) ? $this->instances[(string) $path] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get children of the path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return Collection
|
||||
*/
|
||||
public function children($path)
|
||||
{
|
||||
$children = isset($this->children[(string) $path]) ? $this->children[(string) $path] : array();
|
||||
return new Collection($children, array(), $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch URI to a page.
|
||||
*
|
||||
* @param $url
|
||||
* @param bool $all
|
||||
* @return Page|null
|
||||
*/
|
||||
public function dispatch($url, $all = false)
|
||||
{
|
||||
// Fetch page if there's a defined route to it.
|
||||
$page = isset($this->routes[$url]) ? $this->get($this->routes[$url]) : null;
|
||||
|
||||
// If the page cannot be reached, look into site wide routes.
|
||||
if (!$all && (!$page || !$page->routable())) {
|
||||
$route = $this->config->get("site.routes.{$url}");
|
||||
if ($route) {
|
||||
$page = $this->dispatch($route, $all);
|
||||
}
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get root page.
|
||||
*
|
||||
* @return Page
|
||||
*/
|
||||
public function root()
|
||||
{
|
||||
return $this->instances[rtrim(PAGES_DIR, DS)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a blueprint for a page type.
|
||||
*
|
||||
* @param string $type
|
||||
* @return Data\Blueprint
|
||||
*/
|
||||
public function blueprints($type)
|
||||
{
|
||||
if (!isset($this->blueprints)) {
|
||||
$this->blueprints = new Data\Blueprints(THEMES_DIR . $this->config->get('system.pages.theme') . '/blueprints/');
|
||||
}
|
||||
|
||||
try {
|
||||
$blueprint = $this->blueprints->get($type);
|
||||
} catch (\RuntimeException $e) {
|
||||
$blueprint = $this->blueprints->get('default');
|
||||
}
|
||||
|
||||
if (!$blueprint->initialized) {
|
||||
/** @var Grav $grav */
|
||||
$grav = Registry::get('Grav');
|
||||
$grav->fireEvent('onCreateBlueprint', $blueprint);
|
||||
$blueprint->initialized = true;
|
||||
}
|
||||
|
||||
return $blueprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of route/title of all pages.
|
||||
*
|
||||
* @param Page $current
|
||||
* @param int $level
|
||||
* @return array
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function getList(Page $current = null, $level = 0)
|
||||
{
|
||||
if (!$current) {
|
||||
if ($level) {
|
||||
throw new \RuntimeException('Internal error');
|
||||
}
|
||||
|
||||
$current = $this->root();
|
||||
}
|
||||
|
||||
$list = array();
|
||||
if ($current->routable()) {
|
||||
$list[$current->route()] = str_repeat(' ', ($level-1)*2) . $current->title();
|
||||
}
|
||||
|
||||
foreach ($current as $next) {
|
||||
$list = array_merge($list, $this->getList($next, $level + 1));
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available page types.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function types()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = Registry::get('Config');
|
||||
$blueprints = new Data\Blueprints(THEMES_DIR . $config->get('system.pages.theme') . '/blueprints/');
|
||||
|
||||
return $blueprints->types();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available parents.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function parents()
|
||||
{
|
||||
/** @var Pages $pages */
|
||||
$pages = Registry::get('Pages');
|
||||
return $pages->getList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds pages.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function buildPages()
|
||||
{
|
||||
$this->sort = array();
|
||||
if ($this->config->get('system.cache.enabled')) {
|
||||
/** @var Cache $cache */
|
||||
$cache = Registry::get('Cache');
|
||||
/** @var Taxonomy $taxonomy */
|
||||
$taxonomy = Registry::get('Taxonomy');
|
||||
$last_modified = $this->config->get('system.cache.check.pages', true)
|
||||
? Folder::lastModified(PAGES_DIR) : 0;
|
||||
$page_cache_id = md5(USER_DIR.$last_modified);
|
||||
|
||||
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
|
||||
if (!$this->instances) {
|
||||
$this->recurse();
|
||||
$this->buildRoutes();
|
||||
|
||||
// save pages, routes, taxonomy, and sort to cache
|
||||
$cache->save(
|
||||
$page_cache_id,
|
||||
array($this->instances, $this->routes, $this->children, $taxonomy->taxonomy(), $this->sort)
|
||||
);
|
||||
} else {
|
||||
// If pages was found in cache, set the taxonomy
|
||||
$taxonomy->taxonomy($taxonomy_map);
|
||||
}
|
||||
} else {
|
||||
$this->recurse();
|
||||
$this->buildRoutes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function to load & build page relationships.
|
||||
*
|
||||
* @param string $directory
|
||||
* @param null $parent
|
||||
* @return Page
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function recurse($directory = PAGES_DIR, &$parent = null)
|
||||
{
|
||||
$directory = rtrim($directory, DS);
|
||||
$iterator = new \DirectoryIterator($directory);
|
||||
$page = new Page;
|
||||
|
||||
$page->path($directory);
|
||||
$page->parent($parent);
|
||||
$page->orderDir($this->config->get('system.pages.order.dir'));
|
||||
$page->orderBy($this->config->get('system.pages.order.by'));
|
||||
|
||||
// Add into instances
|
||||
if (!isset($this->instances[$page->path()])) {
|
||||
$this->instances[$page->path()] = $page;
|
||||
if ($parent && $page->path()) {
|
||||
$this->children[$parent->path()][$page->path()] = array('slug' => $page->slug());
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException('Fatal error when creating page instances.');
|
||||
}
|
||||
|
||||
/** @var \DirectoryIterator $file */
|
||||
foreach ($iterator as $file) {
|
||||
$name = $file->getFilename();
|
||||
|
||||
if ($file->isFile() && Utils::endsWith($name, CONTENT_EXT)) {
|
||||
|
||||
$page->init($file);
|
||||
|
||||
if ($this->config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onAfterPageProcessed', $page);
|
||||
}
|
||||
|
||||
} elseif ($file->isDir() && !$file->isDot()) {
|
||||
|
||||
if (!$page->path()) {
|
||||
$page->path($file->getPath());
|
||||
}
|
||||
|
||||
$path = $directory.DS.$name;
|
||||
$child = $this->recurse($path, $page);
|
||||
|
||||
if (Utils::startsWith($name, '_')) {
|
||||
$child->routable(false);
|
||||
}
|
||||
|
||||
$this->children[$page->path()][$child->path()] = array('slug' => $child->slug());
|
||||
|
||||
// set the modified time if not already set
|
||||
if (!$page->date()) {
|
||||
$page->date($file->getMTime());
|
||||
}
|
||||
|
||||
// set the last modified time on pages
|
||||
$this->lastModified($file->getMTime());
|
||||
|
||||
if ($this->config->get('system.pages.events.page')) {
|
||||
$this->grav->fireEvent('onAfterFolderProcessed', $page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort based on Defaults or Page Overridden sort order
|
||||
$this->children[$page->path()] = $this->sort($page);
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected function buildRoutes()
|
||||
{
|
||||
/** @var $taxonomy Taxonomy */
|
||||
$taxonomy = Registry::get('Taxonomy');
|
||||
|
||||
// Build routes and taxonomy map.
|
||||
/** @var $page Page */
|
||||
foreach ($this->instances as $page) {
|
||||
|
||||
$parent = $page->parent();
|
||||
|
||||
if ($parent) {
|
||||
$route = rtrim($parent->route(), '/') . '/' . $page->slug();
|
||||
$this->routes[$route] = $page->path();
|
||||
$page->route($route);
|
||||
}
|
||||
|
||||
if (!empty($route)) {
|
||||
$taxonomy->addTaxonomy($page);
|
||||
} else {
|
||||
$page->routable(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Alias and set default route to home page.
|
||||
$home = trim($this->config->get('system.home.alias'), '/');
|
||||
if ($home && isset($this->routes['/' . $home])) {
|
||||
$this->routes['/'] = $this->routes['/' . $home];
|
||||
$this->get($this->routes['/' . $home])->route('/');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param array $pages
|
||||
* @param string $order_by
|
||||
* @param array $manual
|
||||
* @throws \RuntimeException
|
||||
* @internal
|
||||
*/
|
||||
protected function buildSort($path, array $pages, $order_by = 'default', $manual = null)
|
||||
{
|
||||
$list = array();
|
||||
|
||||
foreach ($pages as $key => $info) {
|
||||
|
||||
$child = isset($this->instances[$key]) ? $this->instances[$key] : null;
|
||||
if (!$child) {
|
||||
throw new \RuntimeException("Page does not exist: {$key}");
|
||||
}
|
||||
|
||||
switch ($order_by) {
|
||||
case 'title':
|
||||
$list[$key] = $child->title();
|
||||
break;
|
||||
case 'date':
|
||||
$list[$key] = $child->date();
|
||||
break;
|
||||
case 'modified':
|
||||
$list[$key] = $child->modified();
|
||||
break;
|
||||
case 'slug':
|
||||
$list[$key] = $info['slug'];
|
||||
break;
|
||||
case 'basename':
|
||||
$list[$key] = basename($key);
|
||||
break;
|
||||
case 'manual':
|
||||
case 'default':
|
||||
default:
|
||||
$list[$key] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by the new list.
|
||||
asort($list);
|
||||
|
||||
// Move manually ordered items into the beginning of the list. Order of the unlisted items does not change.
|
||||
if (is_array($manual) && !empty($manual)) {
|
||||
$new_list = array();
|
||||
$i = count($manual);
|
||||
|
||||
foreach ($list as $key => $dummy) {
|
||||
$info = $pages[$key];
|
||||
$order = array_search($info['slug'], $manual);
|
||||
if ($order === false) {
|
||||
$order = $i++;
|
||||
}
|
||||
$new_list[$key] = (int) $order;
|
||||
}
|
||||
|
||||
$list = $new_list;
|
||||
|
||||
// Apply manual ordering to the list.
|
||||
asort($list);
|
||||
}
|
||||
|
||||
foreach ($list as $key => $sort) {
|
||||
$info = $pages[$key];
|
||||
// TODO: order by manual needs a hash from the passed variables if we make this more general.
|
||||
$this->sort[$path][$order_by][$key] = $info;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
system/src/Grav/Common/Plugin.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* The Plugin object just holds the id and path to a plugin.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Plugin
|
||||
{
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
public $config;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Config $config
|
||||
*/
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
}
|
||||
110
system/src/Grav/Common/Plugins.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* The Plugins object holds an array of all the plugin objects that
|
||||
* Grav knows about
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Plugins
|
||||
{
|
||||
/**
|
||||
* @var array|Plugin[]
|
||||
*/
|
||||
protected $plugins;
|
||||
|
||||
|
||||
/**
|
||||
* Recurses through the plugins directory creating Plugin objects for each plugin it finds.
|
||||
*
|
||||
* @return array|Plugin[] array of Plugin objects
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
/** @var Config $config */
|
||||
$config = Registry::get('Config');
|
||||
$plugins = (array) $config->get('plugins');
|
||||
|
||||
foreach ($plugins as $plugin => $data) {
|
||||
if (empty($data['enabled'])) {
|
||||
// Only load enabled plugins.
|
||||
continue;
|
||||
}
|
||||
|
||||
$folder = PLUGINS_DIR . $plugin;
|
||||
$filePath = $folder . DS . $plugin . PLUGIN_EXT;
|
||||
if (!is_file($filePath)) {
|
||||
throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found!", $filePath, $plugin));
|
||||
}
|
||||
|
||||
require_once $filePath;
|
||||
|
||||
$pluginClass = 'Grav\\Plugin\\'.ucfirst($plugin).'Plugin';
|
||||
|
||||
if (!class_exists($pluginClass)) {
|
||||
throw new \RuntimeException(sprintf("Plugin '%s' class not found!", $plugin));
|
||||
}
|
||||
|
||||
$this->plugins[$pluginClass] = new $pluginClass($config);
|
||||
}
|
||||
|
||||
return $this->plugins;
|
||||
}
|
||||
|
||||
public function add($plugin)
|
||||
{
|
||||
if (is_object($plugin)) {
|
||||
$this->plugins[get_class($plugin)] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of all plugin data with their blueprints.
|
||||
*
|
||||
* @return array|Data\Data[]
|
||||
*/
|
||||
static public function all()
|
||||
{
|
||||
$list = array();
|
||||
$iterator = new \DirectoryIterator(PLUGINS_DIR);
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach ($iterator as $directory) {
|
||||
if (!$directory->isDir() || $directory->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $directory->getBasename();
|
||||
$list[$type] = self::get($type);
|
||||
}
|
||||
|
||||
ksort($list);
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
static public function get($type)
|
||||
{
|
||||
$blueprints = new Data\Blueprints(PLUGINS_DIR . $type);
|
||||
$blueprint = $blueprints->get('blueprints');
|
||||
$blueprint->name = $type;
|
||||
|
||||
// Load default configuration.
|
||||
$file = File\Yaml::instance(PLUGINS_DIR . "{$type}/{$type}" . YAML_EXT);
|
||||
$obj = new Data\Data($file->content(), $blueprint);
|
||||
|
||||
// Override with user configuration.
|
||||
$file = File\Yaml::instance(USER_DIR . "config/plugins/{$type}" . YAML_EXT);
|
||||
$obj->merge($file->content());
|
||||
|
||||
// Save configuration always to user/config.
|
||||
$obj->file($file);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
98
system/src/Grav/Common/Registry.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* The Registry class is an implementation of the Registry Pattern to store and retrieve
|
||||
* instances of objects used by Grav
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Registry
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $registry = array();
|
||||
|
||||
/**
|
||||
* @var Registry
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Return global instance.
|
||||
*
|
||||
* @return Registry
|
||||
*/
|
||||
public static function instance()
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new Registry();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry from the registry.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function get($key)
|
||||
{
|
||||
if (!isset(self::$instance->registry[$key])) {
|
||||
throw new \Exception("There is no entry for key " . $key);
|
||||
}
|
||||
|
||||
return self::$instance->registry[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
private function __clone()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Store entry to the registry.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function store($key, $value)
|
||||
{
|
||||
if (isset($this->registry[$key])) {
|
||||
throw new \Exception("There is already an entry for key " . $key);
|
||||
}
|
||||
|
||||
$this->registry[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entry from the registry.
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function retrieve($key)
|
||||
{
|
||||
if (!isset($this->registry[$key])) {
|
||||
throw new \Exception("There is no entry for key " . $key);
|
||||
}
|
||||
|
||||
return $this->registry[$key];
|
||||
}
|
||||
}
|
||||
91
system/src/Grav/Common/Session/Message.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace Grav\Common\Session;
|
||||
|
||||
use Grav\Common\Getters;
|
||||
|
||||
/**
|
||||
* Session wide messages.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Message
|
||||
{
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected $messages = array();
|
||||
|
||||
/**
|
||||
* Add message to the queue.
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $scope
|
||||
* @return $this
|
||||
*/
|
||||
public function add($message, $scope = 'default')
|
||||
{
|
||||
$message = array('message' => $message, 'scope' => $scope);
|
||||
|
||||
$this->messages[] = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear message queue.
|
||||
*
|
||||
* @param string $scope
|
||||
* @return $this
|
||||
*/
|
||||
public function clear($scope = null)
|
||||
{
|
||||
if ($scope === null) {
|
||||
$this->messages = array();
|
||||
} else {
|
||||
foreach ($this->messages as $key => $message) {
|
||||
if ($message['scope'] == $scope) {
|
||||
unset($this->messages[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all messages.
|
||||
*
|
||||
* @param string $scope
|
||||
* @return array
|
||||
*/
|
||||
public function all($scope = null)
|
||||
{
|
||||
if ($scope === null) {
|
||||
return array_values($this->messages);
|
||||
}
|
||||
|
||||
$messages = array();
|
||||
foreach ($this->messages as $message) {
|
||||
if ($message['scope'] == $scope) {
|
||||
$messages[] = $message;
|
||||
}
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and clear message queue.
|
||||
*
|
||||
* @param string $scope
|
||||
* @return array
|
||||
*/
|
||||
public function fetch($scope = null)
|
||||
{
|
||||
$messages = $this->all($scope);
|
||||
$this->clear($scope);
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
}
|
||||
245
system/src/Grav/Common/Session/Session.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
namespace Grav\Common\Session;
|
||||
|
||||
/**
|
||||
* Session handling.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Session implements \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $started = false;
|
||||
|
||||
/**
|
||||
* @var Session
|
||||
*/
|
||||
static $instance;
|
||||
|
||||
/**
|
||||
* @param int $lifetime Defaults to 1800 seconds.
|
||||
* @param string $path Cookie path.
|
||||
*/
|
||||
public function __construct($lifetime, $path)
|
||||
{
|
||||
if (isset(self::$instance)) {
|
||||
throw new \RuntimeException("Session has already been initialized.", 500);
|
||||
}
|
||||
|
||||
// Destroy any existing sessions started with session.auto_start
|
||||
if (session_id())
|
||||
{
|
||||
session_unset();
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
// Disable transparent sid support
|
||||
ini_set('session.use_trans_sid', '0');
|
||||
|
||||
// Only allow cookies
|
||||
ini_set('session.use_cookies', 1);
|
||||
|
||||
session_set_cookie_params($lifetime, $path);
|
||||
register_shutdown_function('session_write_close');
|
||||
session_cache_limiter('none');
|
||||
|
||||
self::$instance = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current session instance.
|
||||
*
|
||||
* @return Session
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function instance()
|
||||
{
|
||||
if (!isset(self::$instance)) {
|
||||
throw new \RuntimeException("Session hasn't been initialized.", 500);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the session storage
|
||||
*
|
||||
* @return $this
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
if (!session_start()) {
|
||||
throw new \RuntimeException('Failed to start session');
|
||||
}
|
||||
|
||||
$this->started = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session ID
|
||||
*
|
||||
* @return string Session ID
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return session_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set session Id
|
||||
*
|
||||
* @param string $id Session ID
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setId($id)
|
||||
{
|
||||
session_id($id);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get session name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return session_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set session name
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
session_name($name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the current session.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function invalidate()
|
||||
{
|
||||
$params = session_get_cookie_params();
|
||||
setcookie(session_name(), '', time() - 42000,
|
||||
$params['path'], $params['domain'],
|
||||
$params['secure'], $params['httponly']
|
||||
);
|
||||
|
||||
session_unset();
|
||||
session_destroy();
|
||||
|
||||
$this->started = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the session to be saved and closed
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
session_write_close();
|
||||
|
||||
$this->started = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an attribute is defined.
|
||||
*
|
||||
* @param string $name The attribute name
|
||||
*
|
||||
* @return bool True if the attribute is defined, false otherwise
|
||||
*/
|
||||
public function __isset($name)
|
||||
{
|
||||
return isset($_SESSION[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an attribute.
|
||||
*
|
||||
* @param string $name The attribute name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
return isset($_SESSION[$name]) ? $_SESSION[$name] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an attribute.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __set($name, $value)
|
||||
{
|
||||
$_SESSION[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an attribute.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed The removed value or null when it does not exist
|
||||
*/
|
||||
public function __unset($name)
|
||||
{
|
||||
unset($_SESSION[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns attributes.
|
||||
*
|
||||
* @return array Attributes
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $_SESSION;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve an external iterator
|
||||
*
|
||||
* @return \ArrayIterator Return an ArrayIterator of $_SESSION
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($_SESSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the session was started.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function started()
|
||||
{
|
||||
return $this->started;
|
||||
}
|
||||
}
|
||||
99
system/src/Grav/Common/Taxonomy.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use \Grav\Common\Page;
|
||||
|
||||
/**
|
||||
* The Taxonomy object is a singleton that holds a reference to a 'taxonomy map'. This map is
|
||||
* constructed as a multidimensional array.
|
||||
*
|
||||
* uses the taxonomy defined in the site.yaml file and is built when the page objects are recursed.
|
||||
* Basically every time a page is found that has taxonomy references, an entry to the page is stored in
|
||||
* the taxonomy map. The map has the following format:
|
||||
*
|
||||
* [taxonomy_type][taxonomy_value][page_path]
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* [category][blog][path/to/item1]
|
||||
* [tag][grav][path/to/item1]
|
||||
* [tag][grav][path/to/item2]
|
||||
* [tag][dog][path/to/item3]
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Taxonomy
|
||||
{
|
||||
protected $taxonomy_map;
|
||||
|
||||
/**
|
||||
* Constructor that resets the map
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->taxonomy_map = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an individual page and processes the taxonomies configured in its header. It
|
||||
* then adds those taxonomies to the map
|
||||
*
|
||||
* @param Page\Page $page the page to process
|
||||
* @param array $page_taxonomy
|
||||
*/
|
||||
public function addTaxonomy(Page\Page $page, $page_taxonomy = null)
|
||||
{
|
||||
if (!$page_taxonomy) {
|
||||
$page_taxonomy = $page->taxonomy();
|
||||
}
|
||||
|
||||
$config = Registry::get('Config');
|
||||
if ($config->get('site.taxonomies') && count($page_taxonomy) > 0) {
|
||||
foreach ((array) $config->get('site.taxonomies') as $taxonomy) {
|
||||
if (isset($page_taxonomy[$taxonomy])) {
|
||||
foreach ((array) $page_taxonomy[$taxonomy] as $item) {
|
||||
// TODO: move to pages class?
|
||||
$this->taxonomy_map[$taxonomy][(string) $item][$page->path()] = array('slug' => $page->slug());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Page object with the sub-pages containing all the values set for a
|
||||
* particular taxonomy.
|
||||
*
|
||||
* @param array $taxonomies taxonomies to search, eg ['tag'=>['animal','cat']]
|
||||
* @return Page\Page page object with sub-pages set to contain matches found in the taxonomy map
|
||||
*/
|
||||
public function findTaxonomy($taxonomies)
|
||||
{
|
||||
$results = array();
|
||||
|
||||
foreach ((array)$taxonomies as $taxonomy => $items) {
|
||||
foreach ((array) $items as $item) {
|
||||
if (isset($this->taxonomy_map[$taxonomy][$item])) {
|
||||
$results = array_merge($results, $this->taxonomy_map[$taxonomy][$item]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Page\Collection($results, ['taxonomies' => $taxonomies]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and Sets the taxonomy map
|
||||
*
|
||||
* @param array $var the taxonomy map
|
||||
* @return array the taxonomy map
|
||||
*/
|
||||
public function taxonomy($var = null)
|
||||
{
|
||||
if ($var) {
|
||||
$this->taxonomy_map = $var;
|
||||
}
|
||||
return $this->taxonomy_map;
|
||||
}
|
||||
}
|
||||
6
system/src/Grav/Common/Theme.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
class Theme
|
||||
{
|
||||
}
|
||||
101
system/src/Grav/Common/Themes.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use Grav\Common\Filesystem\File;
|
||||
|
||||
/**
|
||||
* The Themes object holds an array of all the theme objects that Grav knows about.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Themes
|
||||
{
|
||||
/**
|
||||
* Return list of all theme data with their blueprints.
|
||||
*
|
||||
* @return array|Data\Data[]
|
||||
*/
|
||||
static public function all()
|
||||
{
|
||||
$list = array();
|
||||
$iterator = new \DirectoryIterator(THEMES_DIR);
|
||||
|
||||
/** @var \DirectoryIterator $directory */
|
||||
foreach ($iterator as $directory) {
|
||||
if (!$directory->isDir() || $directory->isDot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $directory->getBasename();
|
||||
$list[$type] = self::get($type);
|
||||
}
|
||||
|
||||
ksort($list);
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get theme or throw exception if it cannot be found.
|
||||
*
|
||||
* @param string $type
|
||||
* @return Data\Data
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
static public function get($type)
|
||||
{
|
||||
if (!$type) {
|
||||
throw new \RuntimeException('Theme name not provided.');
|
||||
}
|
||||
|
||||
$blueprints = new Data\Blueprints(THEMES_DIR . $type);
|
||||
$blueprint = $blueprints->get('blueprints');
|
||||
$blueprint->name = $type;
|
||||
|
||||
// Find thumbnail.
|
||||
$thumb = THEMES_DIR . "{$type}/thumbnail.jpg";
|
||||
if (file_exists($thumb)) {
|
||||
// TODO: use real URL with base path.
|
||||
$blueprint->set('thumbnail', "/user/themes/{$type}/thumbnail.jpg");
|
||||
}
|
||||
|
||||
// Load default configuration.
|
||||
$file = File\Yaml::instance(THEMES_DIR . "{$type}/{$type}" . YAML_EXT);
|
||||
$obj = new Data\Data($file->content(), $blueprint);
|
||||
|
||||
// Override with user configuration.
|
||||
$file = File\Yaml::instance(USER_DIR . "config/themes/{$type}" . YAML_EXT);
|
||||
$obj->merge($file->content());
|
||||
|
||||
// Save configuration always to user/config.
|
||||
$obj->file($file);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function load($name = null)
|
||||
{
|
||||
if (!$name) {
|
||||
$config = Registry::get('Config');
|
||||
$name = $config->get('system.pages.theme');
|
||||
}
|
||||
|
||||
$file = THEMES_DIR . "{$name}/{$name}.php";
|
||||
if (file_exists($file)) {
|
||||
require_once $file;
|
||||
|
||||
$className = '\\Grav\\Theme\\' . ucfirst($name);
|
||||
|
||||
if (class_exists($className)) {
|
||||
$class = new $className;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($class)) {
|
||||
$class = new Theme;
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
241
system/src/Grav/Common/Twig.php
Normal file
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
use \Grav\Common\Page\Page;
|
||||
|
||||
/**
|
||||
* The Twig object handles all the Twig template rendering for Grav. It's a singleton object
|
||||
* that is optimized so that it only needs to be initialized once and can be reused for individual
|
||||
* page template rendering as well as the main site template rendering.
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class Twig
|
||||
{
|
||||
/**
|
||||
* @var \Twig_Environment
|
||||
*/
|
||||
protected $twig;
|
||||
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
protected $grav;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var Uri
|
||||
*/
|
||||
protected $uri;
|
||||
|
||||
/**
|
||||
* @var Taxonomy
|
||||
*/
|
||||
protected $taxonomy;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $twig_vars;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* @var \Twig_Loader_Filesystem
|
||||
*/
|
||||
protected $loader;
|
||||
|
||||
/**
|
||||
* Twig initialization that sets the twig loader chain, then the environment, then extensions
|
||||
* and also the base set of twig vars
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if (!isset($this->twig)) {
|
||||
|
||||
// get Grav and Config
|
||||
$this->grav = Registry::get('Grav');
|
||||
$this->config = $this->grav->config;
|
||||
$this->uri = Registry::get('Uri');
|
||||
$this->taxonomy = Registry::get('Taxonomy');
|
||||
|
||||
|
||||
$this->twig_paths = array(THEMES_DIR . $this->config->get('system.pages.theme') . '/templates');
|
||||
$this->grav->fireEvent('onAfterTwigTemplatesPaths');
|
||||
|
||||
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
|
||||
$loader_chain = new \Twig_Loader_Chain(array($this->loader, new \Twig_Loader_String()));
|
||||
|
||||
$params = $this->config->get('system.twig');
|
||||
if (!empty($params['cache'])) {
|
||||
$params['cache'] = CACHE_DIR;
|
||||
}
|
||||
|
||||
$this->twig = new \Twig_Environment($loader_chain, $params);
|
||||
$this->grav->fireEvent('onAfterTwigInit');
|
||||
|
||||
// set default date format if set in config
|
||||
if ($this->config->get('system.pages.dateformat.long')) {
|
||||
$this->twig->getExtension('core')->setDateFormat($this->config->get('system.pages.dateformat.long'));
|
||||
}
|
||||
// enable the debug extension if required
|
||||
if ($this->config->get('system.twig.debug')) {
|
||||
$this->twig->addExtension(new \Twig_Extension_Debug());
|
||||
}
|
||||
$this->twig->addExtension(new TwigExtension());
|
||||
$this->grav->fireEvent('onAfterTwigExtensions');
|
||||
|
||||
$baseUrlAbsolute = $this->config->get('system.base_url_absolute');
|
||||
$baseUrlRelative = $this->config->get('system.base_url_relative');
|
||||
$theme = $this->config->get('system.pages.theme');
|
||||
$themeUrl = $baseUrlRelative .'/'. USER_PATH . basename(THEMES_DIR) .'/'. $theme;
|
||||
|
||||
// Set some standard variables for twig
|
||||
$this->twig_vars = array(
|
||||
'config' => $this->config,
|
||||
'uri' => $this->uri,
|
||||
'base_dir' => rtrim(ROOT_DIR, '/'),
|
||||
'base_url_absolute' => $baseUrlAbsolute,
|
||||
'base_url_relative' => $baseUrlRelative,
|
||||
'theme_dir' => THEMES_DIR . $theme,
|
||||
'theme_url' => $themeUrl,
|
||||
'site' => $this->config->get('site'),
|
||||
'stylesheets' => array(),
|
||||
'scripts' => array(),
|
||||
'taxonomy' => $this->taxonomy,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Twig_Environment
|
||||
*/
|
||||
public function twig()
|
||||
{
|
||||
return $this->twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Twig_Loader_Filesystem
|
||||
*/
|
||||
public function loader()
|
||||
{
|
||||
return $this->loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig process that renders a page item. It supports two variations:
|
||||
* 1) Handles modular pages by rendering a specific page based on its modular twig template
|
||||
* 2) Renders individual page items for twig processing before the site rendering
|
||||
*
|
||||
* @param Page $item The page item to render
|
||||
* @param string $content Optional content override
|
||||
* @return string The rendered output
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function processPage(Page $item, $content = null)
|
||||
{
|
||||
$this->init();
|
||||
$content = $content !== null ? $content : $item->content();
|
||||
|
||||
// override the twig header vars for local resolution
|
||||
$this->grav->fireEvent('onAfterPageTwigVars');
|
||||
$twig_vars = $this->twig_vars;
|
||||
|
||||
$twig_vars['page'] = $item;
|
||||
$twig_vars['assets'] = $item->assets();
|
||||
$twig_vars['header'] = $item->header();
|
||||
|
||||
// Get Twig template layout
|
||||
if ($item->modularTwig()) {
|
||||
$twig_vars['content'] = $content;
|
||||
// FIXME: this is inconsistent with main page.
|
||||
$template = $this->template('modular/' . $item->template()) . TEMPLATE_EXT;
|
||||
$output = $this->twig->render($template, $twig_vars);
|
||||
|
||||
if ($template == $output) {
|
||||
throw new \RuntimeException("Template file '{$template}' cannot be found.", 404);
|
||||
}
|
||||
} else {
|
||||
$output = $this->twig->render($content, $twig_vars);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string string to render.
|
||||
* @param array $vars Optional variables
|
||||
* @return string
|
||||
*/
|
||||
public function processString($string, array $vars = array())
|
||||
{
|
||||
$this->init();
|
||||
|
||||
// override the twig header vars for local resolution
|
||||
$this->grav->fireEvent('onAfterStringTwigVars');
|
||||
$vars += $this->twig_vars;
|
||||
|
||||
return $this->twig->render($string, $vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig process that renders the site layout. This is the main twig process that renders the overall
|
||||
* page and handles all the layout for the site display.
|
||||
*
|
||||
* @param string $format Output format (defaults to HTML).
|
||||
* @return string the rendered output
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function processSite($format = null)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
// set the page now its been processed
|
||||
$this->grav->fireEvent('onAfterSiteTwigVars');
|
||||
$twig_vars = $this->twig_vars;
|
||||
$pages = $this->grav->pages;
|
||||
$page = $this->grav->page;
|
||||
|
||||
$twig_vars['pages'] = $pages->root();
|
||||
$twig_vars['page'] = $page;
|
||||
$twig_vars['header'] = $page->header();
|
||||
$twig_vars['content'] = $page->content();
|
||||
$ext = '.' . ($format ? $format : 'html') . TWIG_EXT;
|
||||
|
||||
// Get Twig template layout
|
||||
$template = $this->template($page->template() . $ext);
|
||||
$output = $this->twig->render($template, $twig_vars);
|
||||
|
||||
if ($template == $output) {
|
||||
throw new \RuntimeException("Template file '{$template}' cannot be found.", 404);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper method to get the twig template if it has already been set, else return
|
||||
* the one being passed in
|
||||
*
|
||||
* @param string $template the template name
|
||||
* @return string the template name
|
||||
*/
|
||||
public function template($template)
|
||||
{
|
||||
if (isset($this->template)) {
|
||||
return $this->template;
|
||||
} else {
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
}
|
||||
232
system/src/Grav/Common/TwigExtension.php
Normal file
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
class TwigExtension extends \Twig_Extension
|
||||
{
|
||||
/**
|
||||
* Returns extension name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'GravTwigExtension';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all filters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return array(
|
||||
new \Twig_SimpleFilter('fieldName', array($this,'fieldNameFilter')),
|
||||
new \Twig_SimpleFilter('safe_email', array($this,'safeEmailFilter')),
|
||||
new \Twig_SimpleFilter('randomize', array($this,'randomizeFilter')),
|
||||
new \Twig_SimpleFilter('truncate', array($this,'truncateFilter')),
|
||||
new \Twig_SimpleFilter('removeDisabled', array($this,'removeDisabledFilter')),
|
||||
new \Twig_SimpleFilter('growText', array($this, 'growTextFilter')),
|
||||
new \Twig_SimpleFilter('*ize', array($this,'inflectorFilter')),
|
||||
new \Twig_SimpleFilter('md5', array($this,'md5Filter')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all functions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
return array(
|
||||
new \Twig_SimpleFunction('repeat', array($this, 'repeatFunc'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters field name by changing dot notation into array notation.
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public function fieldNameFilter($str)
|
||||
{
|
||||
$path = explode('.', $str);
|
||||
|
||||
return array_shift($path) . ($path ? '[' . implode('][', $path) . ']' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Protects email address.
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public function safeEmailFilter($str)
|
||||
{
|
||||
$email = '';
|
||||
for ($i = 0; $i < strlen($str); $i++) {
|
||||
$email .= "&#" . ord($str[$i]);
|
||||
}
|
||||
return $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate content by a limit.
|
||||
*
|
||||
* @param string $string
|
||||
* @param int $limit Nax number of characters.
|
||||
* @param string $break Break point.
|
||||
* @param string $pad Appended padding to the end of the string.
|
||||
* @return string
|
||||
*/
|
||||
public function truncateFilter($string, $limit = 150, $break = ".", $pad = "…")
|
||||
{
|
||||
// return with no change if string is shorter than $limit
|
||||
if (strlen($string) <= $limit) {
|
||||
return $string;
|
||||
}
|
||||
|
||||
// is $break present between $limit and the end of the string?
|
||||
if (false !== ($breakpoint = strpos($string, $break, $limit))) {
|
||||
if ($breakpoint < strlen($string) - 1) {
|
||||
$string = substr($string, 0, $breakpoint) . $pad;
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add HTML markup to grow the text size. Effect depends on the text length.
|
||||
*
|
||||
* @param $text
|
||||
* @return string
|
||||
*/
|
||||
public function growTextFilter($text)
|
||||
{
|
||||
$count = str_word_count($text);
|
||||
if ($count < 20) {
|
||||
return '<span class="text-grow-more">'.$text.'</span>';
|
||||
} elseif ($count < 40) {
|
||||
return '<span class="text-grow">'.$text.'</span>';
|
||||
} else {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove disabled objects from the array. If input isn't array, do nothing.
|
||||
*
|
||||
* @param array $original
|
||||
* @return array
|
||||
*/
|
||||
public function removeDisabledFilter($original)
|
||||
{
|
||||
if (!is_array($original)) {
|
||||
return $original;
|
||||
}
|
||||
$new = array();
|
||||
|
||||
foreach ($original as $entry) {
|
||||
if (is_object($entry) && !isset($entry->disabled)) {
|
||||
$new[] = $entry;
|
||||
}
|
||||
}
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array in a random order.
|
||||
*
|
||||
* @param array $original
|
||||
* @param int $offset Can be used to return only slice of the array.
|
||||
* @return array
|
||||
*/
|
||||
public function randomizeFilter($original, $offset = 0)
|
||||
{
|
||||
if (!is_array($original)) {
|
||||
return $original;
|
||||
}
|
||||
|
||||
if ($original instanceof \Traversable) {
|
||||
$original = iterator_to_array($original, false);
|
||||
}
|
||||
|
||||
$sorted = array();
|
||||
$random = array_slice($original, $offset);
|
||||
shuffle($random);
|
||||
|
||||
for ($x=0; $x < sizeof($original); $x++) {
|
||||
if ($x < $offset) {
|
||||
$sorted[] = $original[$x];
|
||||
} else {
|
||||
$sorted[] = array_shift($random);
|
||||
}
|
||||
}
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflector supports following notations:
|
||||
*
|
||||
* {{ 'person'|pluralize }} => people
|
||||
* {{ 'shoes'|singularize }} => shoe
|
||||
* {{ 'welcome page'|titleize }} => "Welcome Page"
|
||||
* {{ 'send_email'|camelize }} => SendEmail
|
||||
* {{ 'CamelCased'|underscorize }} => camel_cased
|
||||
* {{ 'Something Text'|hyphenize }} => something-text
|
||||
* {{ 'something text to read'|humanize }} => "Something text to read"
|
||||
* {{ '181'|monthize}} => 6
|
||||
* {{ '10'|ordinalize }} => 10th
|
||||
*
|
||||
* @param string $action
|
||||
* @param string $data
|
||||
* @param int $count
|
||||
* @return mixed
|
||||
*/
|
||||
public function inflectorFilter($action, $data, $count = null)
|
||||
{
|
||||
// TODO: check this and fix the docblock if needed.
|
||||
$action = $action.'ize';
|
||||
|
||||
if (in_array(
|
||||
$action,
|
||||
array('titleize','camelize','underscorize','hyphenize', 'humanize','ordinalize','monthize')
|
||||
)) {
|
||||
return Inflector::$action($data);
|
||||
} elseif (in_array($action, array('pluralize','singularize'))) {
|
||||
if ($count) {
|
||||
return Inflector::$action($data, $count);
|
||||
} else {
|
||||
return Inflector::$action($data);
|
||||
}
|
||||
} else {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return MD5 hash from the input.
|
||||
*
|
||||
* @param string $str
|
||||
* @return string
|
||||
*/
|
||||
public function md5Filter($str)
|
||||
{
|
||||
return md5($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeat given string x times.
|
||||
*
|
||||
* @param string $input
|
||||
* @param int $multiplier
|
||||
* @return string
|
||||
*/
|
||||
public function repeatFunc($input, $multiplier)
|
||||
{
|
||||
return str_repeat($input, $multiplier);
|
||||
}
|
||||
}
|
||||
287
system/src/Grav/Common/Uri.php
Normal file
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
class Uri
|
||||
{
|
||||
protected $base;
|
||||
protected $root;
|
||||
protected $bits;
|
||||
protected $extension;
|
||||
protected $host;
|
||||
protected $content_path;
|
||||
protected $path;
|
||||
protected $paths;
|
||||
protected $url;
|
||||
protected $query;
|
||||
protected $params;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$base = 'http://';
|
||||
$uri = $_SERVER["REQUEST_URI"];
|
||||
|
||||
if (isset($_SERVER["HTTPS"])) {
|
||||
$base = (@$_SERVER["HTTPS"] == "on") ? "https://" : "http://";
|
||||
}
|
||||
|
||||
$base .= $_SERVER["SERVER_NAME"];
|
||||
|
||||
if ($_SERVER["SERVER_PORT"] != "80" && $_SERVER["SERVER_PORT"] != "443") {
|
||||
$base .= ":".$_SERVER["SERVER_PORT"];
|
||||
}
|
||||
|
||||
$this->base = $base;
|
||||
$this->root = $base . rtrim(substr($_SERVER['PHP_SELF'], 0, strpos($_SERVER['PHP_SELF'], 'index.php')), '/');
|
||||
$this->url = $base . $uri;
|
||||
|
||||
$this->init();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the URI object based on the url set on the object
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// get any params and remove them
|
||||
$uri = str_replace($this->root, '', $this->url);
|
||||
|
||||
$this->params = array();
|
||||
if (strpos($uri, ':')) {
|
||||
$bits = explode('/', $uri);
|
||||
$path = array();
|
||||
foreach ($bits as $bit) {
|
||||
if (strpos($bit, ':') !== false) {
|
||||
$param = explode(':', $bit);
|
||||
if (count($param) == 2) {
|
||||
$this->params[$param[0]] = str_replace('%7C', '/', $param[1]);
|
||||
}
|
||||
} else {
|
||||
$path[] = $bit;
|
||||
}
|
||||
}
|
||||
$uri = implode('/', $path);
|
||||
}
|
||||
|
||||
// remove the extension if there is one set
|
||||
$parts = pathinfo($uri);
|
||||
if (strpos($parts['basename'], '.')) {
|
||||
$uri = rtrim($parts['dirname'], '/').'/'.$parts['filename'];
|
||||
$this->extension = $parts['extension'];
|
||||
}
|
||||
|
||||
// set the new url
|
||||
$this->url = $this->root . $uri;
|
||||
|
||||
// split into bits
|
||||
$this->bits = parse_url($uri);
|
||||
|
||||
$this->query = array();
|
||||
if (isset($this->bits['query'])) {
|
||||
parse_str($this->bits['query'], $this->query);
|
||||
}
|
||||
|
||||
$this->paths = array();
|
||||
$this->path = $this->bits['path'];
|
||||
$this->content_path = trim(str_replace($this->base, '', $this->path), '/');
|
||||
if ($this->content_path != '') {
|
||||
$this->paths = explode('/', $this->content_path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URI path.
|
||||
*
|
||||
* @param string $id
|
||||
* @return string
|
||||
*/
|
||||
public function paths($id = null)
|
||||
{
|
||||
if (isset($id)) {
|
||||
return $this->paths[$id];
|
||||
} else {
|
||||
return implode('/', $this->paths);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return route to the current URI. By default route doesn't include base path.
|
||||
*
|
||||
* @param bool $absolute True to include full path.
|
||||
* @param bool $domain True to include domain. Works only if first parameter is also true.
|
||||
* @return string
|
||||
*/
|
||||
public function route($absolute = false, $domain = false)
|
||||
{
|
||||
return ($absolute ? $this->rootUrl($domain) : '') . '/' . implode('/', $this->paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return full query string or a single query attribute.
|
||||
*
|
||||
* @param string $id Optional attribute.
|
||||
* @return string
|
||||
*/
|
||||
public function query($id = null)
|
||||
{
|
||||
if (isset($id)) {
|
||||
return $this->query[$id];
|
||||
} else {
|
||||
return http_build_query($this->query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all or a single query parameter as a URI compatible string.
|
||||
*
|
||||
* @param string $id Optional parameter name.
|
||||
* @return null|string
|
||||
*/
|
||||
public function params($id = null)
|
||||
{
|
||||
$params = null;
|
||||
if ($id === null) {
|
||||
$output = array();
|
||||
foreach ($this->params as $key => $value) {
|
||||
$output[] = $key . ':' . $value;
|
||||
$params = '/'.implode('/', $output);
|
||||
}
|
||||
} elseif (isset($this->params[$id])) {
|
||||
$params = "/{$id}:".$this->params[$id];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URI parameter.
|
||||
*
|
||||
* @param string $id
|
||||
* @return bool|string
|
||||
*/
|
||||
public function param($id)
|
||||
{
|
||||
if (isset($this->params[$id])) {
|
||||
return urldecode($this->params[$id]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL.
|
||||
*
|
||||
* @param bool $include_host Include hostname.
|
||||
* @return string
|
||||
*/
|
||||
public function url($include_host = false)
|
||||
{
|
||||
if ($include_host) {
|
||||
return $this->url;
|
||||
} else {
|
||||
$url = (str_replace($this->base, '', rtrim($this->url, '/')));
|
||||
return $url ? $url : '/';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Path
|
||||
*
|
||||
* @return String The path of the URI
|
||||
*/
|
||||
public function path() {
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Extension of the URI
|
||||
*
|
||||
* @return String The extension of the URI
|
||||
*/
|
||||
public function extension() {
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the host of the URI
|
||||
*
|
||||
* @return String The host of the URI
|
||||
*/
|
||||
public function host() {
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base of the URI
|
||||
*
|
||||
* @return String The base of the URI
|
||||
*/
|
||||
public function base() {
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return root URL to the site.
|
||||
*
|
||||
* @param bool $include_host Include hostname.
|
||||
* @return mixed
|
||||
*/
|
||||
public function rootUrl($include_host = false)
|
||||
{
|
||||
if ($include_host) {
|
||||
return $this->root;
|
||||
} else {
|
||||
$root = str_replace($this->base, '', $this->root);
|
||||
return $root;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current page number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function currentPage()
|
||||
{
|
||||
if (isset($this->params['page'])) {
|
||||
return $this->params['page'];
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return relative path to the referrer defaulting to current or given page.
|
||||
*
|
||||
* @param string $default
|
||||
* @param string $attributes
|
||||
* @return string
|
||||
*/
|
||||
public function referrer($default = null, $attributes = null)
|
||||
{
|
||||
$referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
|
||||
|
||||
// Check that referrer came from our site.
|
||||
$root = $this->rootUrl(true);
|
||||
if ($referrer) {
|
||||
// Referrer should always have host set and it should come from the same base address.
|
||||
if (stripos($referrer, $root) !== 0) {
|
||||
$referrer = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$referrer) {
|
||||
$referrer = $default ? $default : $this->route(true, true);
|
||||
}
|
||||
|
||||
if ($attributes) {
|
||||
$referrer .= $attributes;
|
||||
}
|
||||
|
||||
// Return relative path.
|
||||
return substr($referrer, strlen($root));
|
||||
}
|
||||
}
|
||||
46
system/src/Grav/Common/User/Authentication.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Grav\Common\User;
|
||||
|
||||
/**
|
||||
* User authentication
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
abstract class Authentication
|
||||
{
|
||||
/**
|
||||
* Create password hash from plaintext password.
|
||||
*
|
||||
* @param string $password Plaintext password.
|
||||
* @return string|bool
|
||||
*/
|
||||
static public function create($password)
|
||||
{
|
||||
return password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a password matches a hash.
|
||||
*
|
||||
* @param string $password Plaintext password.
|
||||
* @param string $hash Hash to verify against.
|
||||
* @return int Returns 0 if the check fails, 1 if password matches, 2 if hash needs to be updated.
|
||||
*/
|
||||
static public function verify($password, $hash)
|
||||
{
|
||||
// Always accept plaintext passwords (needs an update).
|
||||
// FIXME: not safe to do this...
|
||||
if ($password && $password == $hash) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Fail if hash doesn't match.
|
||||
if (!$password || !password_verify($password, $hash)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Otherwise check if hash needs an update.
|
||||
return password_needs_rehash($hash, PASSWORD_DEFAULT) ? 2 : 1;
|
||||
}
|
||||
}
|
||||
45
system/src/Grav/Common/User/User.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
namespace Grav\Common\User;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
/**
|
||||
* User object
|
||||
*
|
||||
* @author RocketTheme
|
||||
* @license MIT
|
||||
*/
|
||||
class User extends Data
|
||||
{
|
||||
/**
|
||||
* Authenticate user.
|
||||
*
|
||||
* If user password needs to be updated, new information will be saved.
|
||||
*
|
||||
* @param string $password Plaintext password.
|
||||
* @return bool
|
||||
*/
|
||||
public function authenticate($password)
|
||||
{
|
||||
$result = Authentication::verify($password, $this->password);
|
||||
|
||||
// Password needs to be updated, save the file.
|
||||
if ($result == 2) {
|
||||
$this->password = Authentication::create($password);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks user authorisation to the action.
|
||||
*
|
||||
* @param string $action
|
||||
* @return bool
|
||||
*/
|
||||
public function authorise($action)
|
||||
{
|
||||
return $this->get("access.{$action}") === true;
|
||||
}
|
||||
}
|
||||
142
system/src/Grav/Common/Utils.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
namespace Grav\Common;
|
||||
|
||||
/**
|
||||
* Misc utilities.
|
||||
*
|
||||
* @package Grav\Common
|
||||
*/
|
||||
abstract class Utils
|
||||
{
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @return bool
|
||||
*/
|
||||
public static function startsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === '' || strpos($haystack, $needle) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @return bool
|
||||
*/
|
||||
public static function endsWith($haystack, $needle)
|
||||
{
|
||||
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two objects into one.
|
||||
*
|
||||
* @param object $obj1
|
||||
* @param object $obj2
|
||||
* @return object
|
||||
*/
|
||||
public static function mergeObjects($obj1, $obj2)
|
||||
{
|
||||
return (object) array_merge((array) $obj1, (array) $obj2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate HTML by text length.
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $length
|
||||
* @param string $ending
|
||||
* @param bool $exact
|
||||
* @param bool $considerHtml
|
||||
* @return string
|
||||
*/
|
||||
public static function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true) {
|
||||
if ($considerHtml) {
|
||||
// if the plain text is shorter than the maximum length, return the whole text
|
||||
if (strlen(preg_replace('/<.*?>/', '', $text)) <= $length) {
|
||||
return $text;
|
||||
}
|
||||
// splits all html-tags to scanable lines
|
||||
preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER);
|
||||
$total_length = strlen($ending);
|
||||
$open_tags = array();
|
||||
$truncate = '';
|
||||
foreach ($lines as $line_matchings) {
|
||||
// if there is any html-tag in this line, handle it and add it (uncounted) to the output
|
||||
if (!empty($line_matchings[1])) {
|
||||
// if it's an "empty element" with or without xhtml-conform closing slash
|
||||
if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) {
|
||||
// do nothing
|
||||
// if tag is a closing tag
|
||||
} else if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) {
|
||||
// delete tag from $open_tags list
|
||||
$pos = array_search($tag_matchings[1], $open_tags);
|
||||
if ($pos !== false) {
|
||||
unset($open_tags[$pos]);
|
||||
}
|
||||
// if tag is an opening tag
|
||||
} else if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) {
|
||||
// add tag to the beginning of $open_tags list
|
||||
array_unshift($open_tags, strtolower($tag_matchings[1]));
|
||||
}
|
||||
// add html-tag to $truncate'd text
|
||||
$truncate .= $line_matchings[1];
|
||||
}
|
||||
// calculate the length of the plain text part of the line; handle entities as one character
|
||||
$content_length = strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2]));
|
||||
if ($total_length+$content_length> $length) {
|
||||
// the number of characters which are left
|
||||
$left = $length - $total_length;
|
||||
$entities_length = 0;
|
||||
// search for html entities
|
||||
if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) {
|
||||
// calculate the real length of all entities in the legal range
|
||||
foreach ($entities[0] as $entity) {
|
||||
if ($entity[1]+1-$entities_length <= $left) {
|
||||
$left--;
|
||||
$entities_length += strlen($entity[0]);
|
||||
} else {
|
||||
// no more characters left
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$truncate .= substr($line_matchings[2], 0, $left+$entities_length);
|
||||
// maximum lenght is reached, so get off the loop
|
||||
break;
|
||||
} else {
|
||||
$truncate .= $line_matchings[2];
|
||||
$total_length += $content_length;
|
||||
}
|
||||
// if the maximum length is reached, get off the loop
|
||||
if($total_length>= $length) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (strlen($text) <= $length) {
|
||||
return $text;
|
||||
} else {
|
||||
$truncate = substr($text, 0, $length - strlen($ending));
|
||||
}
|
||||
}
|
||||
// if the words shouldn't be cut in the middle...
|
||||
if (!$exact) {
|
||||
// ...search the last occurance of a space...
|
||||
$spacepos = strrpos($truncate, ' ');
|
||||
if (isset($spacepos)) {
|
||||
// ...and cut the text in this position
|
||||
$truncate = substr($truncate, 0, $spacepos);
|
||||
}
|
||||
}
|
||||
// add the defined ending to the text
|
||||
$truncate .= $ending;
|
||||
if($considerHtml) {
|
||||
// close all unclosed html-tags
|
||||
foreach ($open_tags as $tag) {
|
||||
$truncate .= '</' . $tag . '>';
|
||||
}
|
||||
}
|
||||
return $truncate;
|
||||
}
|
||||
}
|
||||
283
system/src/Grav/Console/InstallCommand.php
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
|
||||
//Use the Composer classes
|
||||
// use Composer\Console\Application;
|
||||
// use Composer\Command\UpdateCommand;
|
||||
// use Symfony\Component\Console\Input\ArrayInput;
|
||||
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
protected $directories = array('/cache',
|
||||
'/logs',
|
||||
'/images',
|
||||
'/user/accounts',
|
||||
'/user/config',
|
||||
'/user/pages',
|
||||
'/user/data',
|
||||
'/user/plugins',
|
||||
'/user/themes',
|
||||
);
|
||||
|
||||
protected $files = array(
|
||||
'/.htaccess',
|
||||
'/user/config/site.yaml',
|
||||
'/user/config/system.yaml',
|
||||
);
|
||||
|
||||
protected $mappings = array('/index.php' => '/index.php',
|
||||
'/composer.json' => '/composer.json',
|
||||
'/bin' => '/bin',
|
||||
'/system' => '/system',
|
||||
'/vendor' => '/vendor',
|
||||
'/user/plugins/error' => '/user/plugins/error',
|
||||
'/user/plugins/problems' => '/user/plugins/problems',
|
||||
'/user/themes/antimatter' => '/user/themes/antimatter',
|
||||
);
|
||||
|
||||
protected $default_file = "---\ntitle: HomePage\n---\n# HomePage\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque porttitor eu felis sed ornare. Sed a mauris venenatis, pulvinar velit vel, dictum enim. Phasellus ac rutrum velit. Nunc lorem purus, hendrerit sit amet augue aliquet, iaculis ultricies nisl. Suspendisse tincidunt euismod risus, quis feugiat arcu tincidunt eget. Nulla eros mi, commodo vel ipsum vel, aliquet congue odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque velit orci, laoreet at adipiscing eu, interdum quis nibh. Nunc a accumsan purus.";
|
||||
|
||||
protected $source;
|
||||
protected $destination;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('install')
|
||||
->setDescription('Installs the base Grav system')
|
||||
->addArgument(
|
||||
'destination',
|
||||
InputArgument::REQUIRED,
|
||||
'The destination directory to symlink into'
|
||||
)
|
||||
->addOption(
|
||||
'symlink',
|
||||
's',
|
||||
InputOption::VALUE_NONE,
|
||||
'Symlink the base grav system'
|
||||
)
|
||||
->setHelp(<<<EOT
|
||||
The <info>install</info> command help create a development environment that uses symbolic links to link the core of grav to the git cloned repository
|
||||
EOT
|
||||
);
|
||||
$this->source = getcwd();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->destination = $input->getArgument('destination');
|
||||
|
||||
// Create a red output option
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
|
||||
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
|
||||
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
|
||||
|
||||
// Symlink the Core Stuff
|
||||
if ($input->getOption('symlink')) {
|
||||
// Create Some core stuff if it doesn't exist
|
||||
$this->createDirectories($output);
|
||||
|
||||
// Loop through the symlink mappings and create the symlinks
|
||||
$this->symlink($output);
|
||||
|
||||
// Copy the Core STuff
|
||||
} else {
|
||||
$options = true;
|
||||
// Create Some core stuff if it doesn't exist
|
||||
$this->createDirectories($output);
|
||||
|
||||
// Loop through the symlink mappings and copy what otherwise would be symlinks
|
||||
$this->copy($output);
|
||||
}
|
||||
|
||||
$this->pages($output);
|
||||
$this->initFiles($output);
|
||||
$this->perms($output);
|
||||
}
|
||||
|
||||
private function createDirectories($output)
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>Creating Directories</comment>');
|
||||
$dirs_created = false;
|
||||
|
||||
if (!file_exists($this->destination)) {
|
||||
mkdir($this->destination, 0777, true);
|
||||
}
|
||||
|
||||
foreach ($this->directories as $dir) {
|
||||
if (!file_exists($this->destination . $dir)) {
|
||||
$dirs_created = true;
|
||||
$output->writeln(' <cyan>' . $dir . '</cyan>');
|
||||
mkdir($this->destination . $dir, 0777, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$dirs_created) {
|
||||
$output->writeln(' <red>Directories already exist</red>');
|
||||
}
|
||||
}
|
||||
|
||||
private function copy($output)
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>Copying Files</comment>');
|
||||
|
||||
|
||||
foreach ($this->mappings as $source => $target) {
|
||||
if ((int) $source == $source) {
|
||||
$source = $target;
|
||||
}
|
||||
|
||||
$from = $this->source . $source;
|
||||
$to = $this->destination . $target;
|
||||
|
||||
$output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
|
||||
$this->rcopy($from, $to);
|
||||
}
|
||||
}
|
||||
|
||||
private function symlink($output)
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>Resetting Symbolic Links</comment>');
|
||||
|
||||
|
||||
foreach ($this->mappings as $source => $target) {
|
||||
if ((int) $source == $source) {
|
||||
$source = $target;
|
||||
}
|
||||
|
||||
$from = $this->source . $source;
|
||||
$to = $this->destination . $target;
|
||||
|
||||
$output->writeln(' <cyan>' . $source . '</cyan> <comment>-></comment> ' . $to);
|
||||
@unlink ($to);
|
||||
symlink ($from, $to);
|
||||
}
|
||||
}
|
||||
|
||||
private function initFiles($output)
|
||||
{
|
||||
$this->check($output);
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>File Initializing</comment>');
|
||||
$files_init = false;
|
||||
|
||||
// Copy files if they do not exist
|
||||
foreach ($this->files as $source => $target) {
|
||||
if ((int) $source == $source) {
|
||||
$source = $target;
|
||||
}
|
||||
|
||||
$from = $this->source . $source;
|
||||
$to = $this->destination . $target;
|
||||
|
||||
if (!file_exists($to)) {
|
||||
$files_init = true;
|
||||
copy($from, $to);
|
||||
$output->writeln(' <cyan>'.$target.'</cyan> <comment>-></comment> Created');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$files_init) {
|
||||
$output->writeln(' <red>Files already exist</red>');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private function pages($output)
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>Pages Initializing</comment>');
|
||||
|
||||
// get pages files and initialize if no pages exist
|
||||
$pages_dir = $this->destination . '/user/pages';
|
||||
$pages_files = array_diff(scandir($pages_dir), array('..', '.'));
|
||||
|
||||
if (count($pages_files) == 0) {
|
||||
$destination = $this->source . '/user/pages';
|
||||
$this->rcopy($destination, $pages_dir);
|
||||
$output->writeln(' <cyan>'.$destination.'</cyan> <comment>-></comment> Created');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private function perms($output)
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>Permisions Initializing</comment>');
|
||||
|
||||
$dir_perms = 0755;
|
||||
|
||||
// get pages files and initialize if no pages exist
|
||||
chmod($this->destination.'/bin/grav', $dir_perms);
|
||||
$output->writeln(' <cyan>bin/grav</cyan> permissions reset to '. decoct($dir_perms));
|
||||
}
|
||||
|
||||
|
||||
private function check($output)
|
||||
{
|
||||
$success = true;
|
||||
|
||||
if (!file_exists($this->destination)) {
|
||||
$output->writeln(' file: <red>$this->destination</red> does not exist!');
|
||||
$success = false;
|
||||
}
|
||||
|
||||
foreach ($this->directories as $dir) {
|
||||
if (!file_exists($this->destination . $dir)) {
|
||||
$output->writeln(' directory: <red>' . $dir . '</red> does not exist!');
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->mappings as $target => $link) {
|
||||
if (!file_exists($this->destination . $target)) {
|
||||
$output->writeln(' mappings: <red>' . $target . '</red> does not exist!');
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
if (!$success) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<comment>install should be run with --symlink|--s to symlink first</comment>');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
private 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);
|
||||
foreach($i as $f) {
|
||||
if($f->isFile()) {
|
||||
copy($f->getRealPath(), "$dest/" . $f->getFilename());
|
||||
} else if(!$f->isDot() && $f->isDir()) {
|
||||
$this->rcopy($f->getRealPath(), "$dest/$f");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
167
system/src/Grav/Console/PackageCommand.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
namespace Grav\Console;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
|
||||
class PackageCommand extends Command {
|
||||
|
||||
protected $destination_dir = 'distribution';
|
||||
|
||||
protected $paths_to_create = [
|
||||
'cache',
|
||||
'logs',
|
||||
'images',
|
||||
];
|
||||
|
||||
protected $paths_to_remove = [
|
||||
'cache',
|
||||
'logs',
|
||||
'images',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/.travis.yml',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/build.xml',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/composer.json',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/create_pear_package.php',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/package.xml.tpl',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/.gitattributes',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/.gitignore',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/README.git',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/tests',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/test-suite',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/notes',
|
||||
'user/plugins/email/vendor/swiftmailer/swiftmailer/doc',
|
||||
'user/themes/antimatter/.sass-cache',
|
||||
'vendor/doctrine/cache/.travis.yml',
|
||||
'vendor/doctrine/cache/build.properties',
|
||||
'vendor/doctrine/cache/build.xml',
|
||||
'vendor/doctrine/cache/composer.json',
|
||||
'vendor/doctrine/cache/phpunit.xml.dist',
|
||||
'vendor/doctrine/cache/.coveralls.yml',
|
||||
'vendor/doctrine/cache/.gitignore',
|
||||
'vendor/doctrine/cache/.git',
|
||||
'vendor/doctrine/cache/tests',
|
||||
'vendor/erusev/parsedown/composer.json',
|
||||
'vendor/erusev/parsedown/phpunit.xml.dist',
|
||||
'vendor/erusev/parsedown/.travis.yml',
|
||||
'vendor/erusev/parsedown/.git',
|
||||
'vendor/erusev/parsedown/test',
|
||||
'vendor/gregwar/image/Gregwar/Image/composer.json',
|
||||
'vendor/gregwar/image/Gregwar/Image/phpunit.xml',
|
||||
'vendor/gregwar/image/Gregwar/Image/.gitignore',
|
||||
'vendor/gregwar/image/Gregwar/Image/.git',
|
||||
'vendor/gregwar/image/Gregwar/Image/demo',
|
||||
'vendor/gregwar/image/Gregwar/Image/tests',
|
||||
'vendor/gregwar/cache/Gregwar/Cache/composer.json',
|
||||
'vendor/gregwar/cache/Gregwar/Cache/phpunit.xml',
|
||||
'vendor/gregwar/cache/Gregwar/Cache/.gitignore',
|
||||
'vendor/gregwar/cache/Gregwar/Cache/.git',
|
||||
'vendor/gregwar/cache/Gregwar/Cache/demo',
|
||||
'vendor/gregwar/cache/Gregwar/Cache/tests',
|
||||
'vendor/ircmaxell/password-compat/composer.json',
|
||||
'vendor/ircmaxell/password-compat/phpunit.xml.dist',
|
||||
'vendor/ircmaxell/password-compat/version-test.php',
|
||||
'vendor/ircmaxell/password-compat/.travis.yml',
|
||||
'vendor/ircmaxell/password-compat/test',
|
||||
'vendor/symfony/console/Symfony/Component/Console/composer.json',
|
||||
'vendor/symfony/console/Symfony/Component/Console/phpunit.xml.dist',
|
||||
'vendor/symfony/console/Symfony/Component/Console/.gitignore',
|
||||
'vendor/symfony/console/Symfony/Component/Console/.git',
|
||||
'vendor/symfony/console/Symfony/Component/Console/Tests',
|
||||
'vendor/symfony/yaml/Symfony/Component/Yaml/composer.json',
|
||||
'vendor/symfony/yaml/Symfony/Component/Yaml/phpunit.xml.dist',
|
||||
'vendor/symfony/yaml/Symfony/Component/Yaml/.gitignore',
|
||||
'vendor/symfony/yaml/Symfony/Component/Yaml/.git',
|
||||
'vendor/symfony/yaml/Symfony/Component/Yaml/Tests',
|
||||
'vendor/tracy/tracy/.gitattributes',
|
||||
'vendor/tracy/tracy/.travis.yml',
|
||||
'vendor/tracy/tracy/composer.json',
|
||||
'vendor/tracy/tracy/.gitignore',
|
||||
'vendor/tracy/tracy/.git',
|
||||
'vendor/tracy/tracy/examples',
|
||||
'vendor/tracy/tracy/tests',
|
||||
'vendor/twig/twig/.editorconfig',
|
||||
'vendor/twig/twig/.travis.yml',
|
||||
'vendor/twig/twig/.gitignore',
|
||||
'vendor/twig/twig/.git',
|
||||
'vendor/twig/twig/composer.json',
|
||||
'vendor/twig/twig/phpunit.xml.dist',
|
||||
'vendor/twig/twig/doc',
|
||||
'vendor/twig/twig/ext',
|
||||
'vendor/twig/twig/test',
|
||||
];
|
||||
|
||||
protected function configure() {
|
||||
$this
|
||||
->setName("package")
|
||||
->setDescription("Handles packaging chores for Grav")
|
||||
->addOption(
|
||||
'clean',
|
||||
'c',
|
||||
InputOption::VALUE_NONE,
|
||||
'Clean out extra files in vendor folder'
|
||||
)
|
||||
->setHelp('The <info>package</info> command does things and stuff');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
|
||||
|
||||
// Create a red output option
|
||||
$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red'));
|
||||
$output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan'));
|
||||
$output->getFormatter()->setStyle('green', new OutputFormatterStyle('green'));
|
||||
$output->getFormatter()->setStyle('magenta', new OutputFormatterStyle('magenta'));
|
||||
|
||||
if ($input->getOption('clean')) {
|
||||
$this->cleanPaths($output);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// loops over the array of paths and deletes the files/folders
|
||||
private function cleanPaths($output)
|
||||
{
|
||||
$output->writeln('');
|
||||
$output->writeln('<red>DELETING</red>');
|
||||
|
||||
foreach($this->paths_to_remove as $path) {
|
||||
$path = ROOT_DIR . $path;
|
||||
|
||||
if (is_dir($path) && @$this->rrmdir($path)) {
|
||||
$output->writeln('<red>dir: </red>' . $path);
|
||||
} elseif (is_file($path) && @unlink($path)) {
|
||||
$output->writeln('<red>file: </red>' . $path);
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('<green>CREATING</green>');
|
||||
|
||||
foreach($this->paths_to_create as $path) {
|
||||
$path = ROOT_DIR . $path;
|
||||
if (@mkdir($path)) {
|
||||
$output->writeln('<green>dir: </green>' . $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively Delete folder - DANGEROUS! USE WITH CARE!!!!
|
||||
private function rrmdir($dir) {
|
||||
if (is_dir($dir)) {
|
||||
$objects = scandir($dir);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object != "..") {
|
||||
if (filetype($dir."/".$object) == "dir") $this->rrmdir($dir."/".$object); else unlink($dir."/".$object);
|
||||
}
|
||||
}
|
||||
reset($objects);
|
||||
rmdir($dir);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
user/config/site.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
title: Grav
|
||||
author:
|
||||
name: Joe Bloggs
|
||||
email: 'joe@test.com'
|
||||
description: 'Grav is an easy to use, yet powerful, open source flat-file CMS'
|
||||
|
||||
31
user/config/system.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
home:
|
||||
alias: '/home'
|
||||
|
||||
pages:
|
||||
theme: antimatter
|
||||
process:
|
||||
markdown: true
|
||||
twig: false
|
||||
events:
|
||||
page: false
|
||||
twig: true
|
||||
|
||||
cache:
|
||||
enabled: true
|
||||
check:
|
||||
pages: true
|
||||
driver: auto
|
||||
prefix: 'g'
|
||||
|
||||
twig:
|
||||
cache: true
|
||||
debug: true
|
||||
auto_reload: true
|
||||
autoescape: false
|
||||
|
||||
debugger:
|
||||
enabled: true
|
||||
max_depth: 10
|
||||
log:
|
||||
enabled: false
|
||||
timing: false
|
||||
39
user/pages/01.home/default.md
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Home
|
||||
---
|
||||
|
||||
# Grav is Running!
|
||||
## You have installed **Grav** successfully
|
||||
|
||||
Congratulations! You have installed the **Base Grav Package** that provides a **simple page** and the default **antimatter** theme to get you started.
|
||||
|
||||
>>>>> If you want a more **full-featured** base install, you should check out [**Skeleton** packages available in the downloads](http://getgrav.org/downloads).
|
||||
|
||||
### Find out all about Grav
|
||||
|
||||
* Learn about **Grav** by checking out our dedicated [Learn Grav](http://learn.getgrav.org) site.
|
||||
* Download **plugins**, **themes**, as well as other Grav **skeleton** packages from the [Grav Downloads](http://getgrav.org/downloads) page.
|
||||
* Check out our [Grav Development Blog](http://getgrav.org/blog) to find out the latest goings on in the Grav-verse.
|
||||
|
||||
### Edit this Page
|
||||
|
||||
To edit this page, simply navigate to the folder you installed **Grav** into, and then browse to the `user/pages/01.home` folder and open the `default.md` file in your [editor of choice](http://learn.getgrav.org/basics/requirements). You will see the content of this page in [Markdown format](http://learn.getgrav.org/content/markdown).
|
||||
|
||||
### Create a New Page
|
||||
|
||||
Creating a new page is a simple affair in **Grav**. Simply follow these simple steps:
|
||||
|
||||
1. Navigate to your pages folder: `user/pages/` and create a new folder. In this example, we will use [explicit default ordering](http://learn.getgrav.org/content/content-pages) and call the folder `02.mypage`.
|
||||
2. Launch your text editor and paste in the following sample code:
|
||||
|
||||
---
|
||||
title: My New Page
|
||||
---
|
||||
# My New Page!
|
||||
|
||||
This is the body of **my new page** and I can easily use _Markdown_ syntax here.
|
||||
|
||||
3. Save this file in the `user/pages/02.mypage/` folder as `default.md`. This will tell **Grav** to render the page using the **default** template.
|
||||
4. That is it! Reload your browser to see your new page in the menu.
|
||||
|
||||
>>> NOTE: The page will automatically show up in the Menu after the "Home" menu item. If you wish to change the name that shows up in the Menu, simple add: `menu: My Page` between the dashes in the page content. This is called the YAML front matter, and it is where you configure page-specific options.
|
||||
16
user/plugins/error/blueprints.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
name: Error Page
|
||||
version: 1.0.0
|
||||
description: Displays error page.
|
||||
|
||||
form:
|
||||
fields:
|
||||
enabled:
|
||||
type: toggle
|
||||
label: Plugin status
|
||||
highlight: 1
|
||||
default: 0
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
46
user/plugins/error/error.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
|
||||
use \Grav\Common\Plugin;
|
||||
use \Grav\Common\Registry;
|
||||
use \Grav\Common\Grav;
|
||||
use \Grav\Common\Page\Page;
|
||||
use \Grav\Common\Page\Pages;
|
||||
|
||||
class ErrorPlugin extends Plugin
|
||||
{
|
||||
/**
|
||||
* Display error page if no page was found for the current route.
|
||||
*/
|
||||
public function onAfterGetPage()
|
||||
{
|
||||
/** @var Grav $grav */
|
||||
$grav = Registry::get('Grav');
|
||||
/** @var Pages $pages */
|
||||
$pages = Registry::get('Pages');
|
||||
|
||||
// Not found: return error page instead.
|
||||
if ((!$grav->page || !$grav->page->routable())) {
|
||||
|
||||
// try to load user error page
|
||||
$page = $pages->dispatch($this->config->get('error.404', '/error'), true);
|
||||
|
||||
// if none provided use built in
|
||||
if (!$page) {
|
||||
$page = new Page;
|
||||
$page->init(new \SplFileInfo(__DIR__ . '/pages/error.md'));
|
||||
}
|
||||
|
||||
// Set the page
|
||||
$grav->page = $page;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add current directory to twig lookup paths.
|
||||
*/
|
||||
public function onAfterTwigTemplatesPaths()
|
||||
{
|
||||
Registry::get('Twig')->twig_paths[] = __DIR__ . '/templates';
|
||||
}
|
||||
}
|
||||
6
user/plugins/error/error.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
name: 404 Not Found
|
||||
description: 404 Not found
|
||||
|
||||
enabled: true
|
||||
routes:
|
||||
404: '/error'
|
||||
8
user/plugins/error/pages/error.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Error Page
|
||||
robots: noindex,nofollow
|
||||
template: error
|
||||
routable: false
|
||||
code: 404
|
||||
---
|
||||
Woops. Looks like this page doesn't exist.
|
||||
3
user/plugins/error/templates/error.html.twig
Normal file
@@ -0,0 +1,3 @@
|
||||
<h1>Error {{ page.header.code }}</h1>
|
||||
|
||||
<p>{{ page.content }}</p>
|
||||
61
user/plugins/problems/problems.css
Normal file
@@ -0,0 +1,61 @@
|
||||
ul.problems {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
ul.problems li {
|
||||
margin-bottom: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
ul.problems li.success {
|
||||
background: #F1F9F1;
|
||||
border-left: 5px solid #5CB85C;
|
||||
color: #3d8b3d;
|
||||
}
|
||||
|
||||
ul.problems li.error {
|
||||
background: #FDF7F7;
|
||||
border-left: 5px solid #D9534F;
|
||||
color: #b52b27;
|
||||
}
|
||||
|
||||
ul.problems .fa {
|
||||
font-size: 3rem;
|
||||
vertical-align: middle;
|
||||
margin-left: 1rem;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul.problems p {
|
||||
display: block;
|
||||
margin: 0.5rem 0.5rem 0.5rem 5rem;
|
||||
}
|
||||
|
||||
.button.big {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
.clearfix { display: inline-block; }
|
||||
/* start commented backslash hack \*/
|
||||
* html .clearfix { height: 1%; }
|
||||
.clearfix { display: block; }
|
||||
/* close commented backslash hack */
|
||||
173
user/plugins/problems/problems.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
namespace Grav\Plugin;
|
||||
|
||||
use \Grav\Common\Cache;
|
||||
use \Grav\Common\Plugin;
|
||||
use \Grav\Common\Registry;
|
||||
use Tracy\Debugger;
|
||||
|
||||
class ProblemsPlugin extends Plugin
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $active = false;
|
||||
|
||||
protected $results = array();
|
||||
|
||||
|
||||
/**
|
||||
* Enable sitemap only if url matches to the configuration.
|
||||
*/
|
||||
public function onAfterInitPlugins()
|
||||
{
|
||||
$cache = Registry::get('Cache');
|
||||
$validated_prefix = 'validated-';
|
||||
|
||||
$this->check = CACHE_DIR . $validated_prefix .$cache->getKey();
|
||||
|
||||
if(!file_exists($this->check)) {
|
||||
|
||||
// Run through potential issues
|
||||
$this->active = $this->problemChecker();
|
||||
|
||||
// If no issues remain, save a state file in the cache
|
||||
if (!$this->active) {
|
||||
// delete any exising validated files
|
||||
foreach (glob(CACHE_DIR . $validated_prefix. '*') as $filename) {
|
||||
unlink($filename);
|
||||
}
|
||||
|
||||
// create a file in the cache dir so it only runs on cache changes
|
||||
touch($this->check);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function onAfterGetPage()
|
||||
{
|
||||
if (!$this->active) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Grav $grav */
|
||||
$grav = Registry::get('Grav');
|
||||
$grav->page->content("# Issues Found\n##Please **Review** and **Resolve** before continuing...");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add current directory to twig lookup paths.
|
||||
*/
|
||||
public function onAfterTwigTemplatesPaths()
|
||||
{
|
||||
if (!$this->active) {
|
||||
return;
|
||||
}
|
||||
|
||||
Registry::get('Twig')->twig_paths[] = __DIR__ . '/templates';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set needed variables to display the problems.
|
||||
*/
|
||||
public function onAfterSiteTwigVars()
|
||||
{
|
||||
if (!$this->active) {
|
||||
return;
|
||||
}
|
||||
|
||||
$twig = Registry::get('Twig');
|
||||
$twig->template = 'problems.html.twig';
|
||||
$twig->twig_vars['results'] = $this->results;
|
||||
|
||||
if ($this->config->get('plugins.problems.built_in_css')) {
|
||||
$twig->twig_vars['stylesheets'][] = 'user/plugins/problems/problems.css';
|
||||
}
|
||||
}
|
||||
|
||||
protected function problemChecker()
|
||||
{
|
||||
$min_php_version = '5.4.0';
|
||||
$problems_found = false;
|
||||
|
||||
|
||||
$essential_files = [
|
||||
'index.php' => false,
|
||||
'.htaccess' => false,
|
||||
'cache' => true,
|
||||
'logs' => true,
|
||||
'images' => true,
|
||||
'system' => false,
|
||||
'user/data' => true,
|
||||
'user/pages' => false,
|
||||
'user/config' => false,
|
||||
'user/plugins/error' => false,
|
||||
'user/plugins' => false,
|
||||
'user/themes' => false,
|
||||
'vendor' => false
|
||||
];
|
||||
|
||||
// Check PHP version
|
||||
if (version_compare(phpversion(), '5.4.0', '<')) {
|
||||
$problems_found = true;
|
||||
$php_version_adjective = 'lower';
|
||||
$php_version_status = false;
|
||||
|
||||
} else {
|
||||
$php_version_adjective = 'greater';
|
||||
$php_version_status = true;
|
||||
}
|
||||
$this->results['php'] = [$php_version_status => 'Your PHP version (' . phpversion() . ') is '. $php_version_adjective . ' than the minimum required: <b>' . $min_php_version . '</b>'];
|
||||
|
||||
// Check for GD library
|
||||
if (defined('GD_VERSION') && function_exists('gd_info')) {
|
||||
$gd_adjective = '';
|
||||
$gd_status = true;
|
||||
} else {
|
||||
$problems_found = true;
|
||||
$gd_adjective = 'not ';
|
||||
$gd_status = false;
|
||||
}
|
||||
$this->results['gd'] = [$gd_status => 'PHP GD (Image Manipulation Library) is '. $gd_adjective . 'installed'];
|
||||
|
||||
// Check for essential files & perms
|
||||
$file_problems = [];
|
||||
foreach($essential_files as $file => $check_writable) {
|
||||
$file_path = ROOT_DIR . $file;
|
||||
if (!file_exists($file_path)) {
|
||||
$problems_found = true;
|
||||
$file_status = false;
|
||||
$file_adjective = 'does not exist';
|
||||
|
||||
} else {
|
||||
$file_status = true;
|
||||
$file_adjective = 'exists';
|
||||
$is_writeable = is_writable($file_path);
|
||||
$is_dir = is_dir($file_path);
|
||||
|
||||
if ($check_writable) {
|
||||
if (!$is_writeable) {
|
||||
$file_status = false;
|
||||
$problems_found = true;
|
||||
$file_adjective .= ' but is <b class="underline">not writeable</b>';
|
||||
} else {
|
||||
$file_adjective .= ' and <b class="underline">is writeable</b>';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$file_status || $is_dir || $check_writable) {
|
||||
$file_problems[$file_path] = [$file_status => $file_adjective];
|
||||
}
|
||||
}
|
||||
if (sizeof($file_problems) > 0) {
|
||||
|
||||
$this->results['files'] = $file_problems;
|
||||
}
|
||||
|
||||
return $problems_found;
|
||||
}
|
||||
}
|
||||
2
user/plugins/problems/problems.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
enabled: true
|
||||
built_in_css: true
|
||||
55
user/plugins/problems/templates/problems.html.twig
Normal file
@@ -0,0 +1,55 @@
|
||||
{% extends 'partials/base.html.twig' %}
|
||||
|
||||
{% block content %}
|
||||
{{ page.content }}
|
||||
|
||||
<p class="center">
|
||||
<a href="{{ base_url_relative }}" class="button big"><i class="fa fa-refresh"></i> Reload Page</a>
|
||||
</p>
|
||||
|
||||
<ul class="problems">
|
||||
{% for key, result in results %}
|
||||
|
||||
{% if key == 'files' %}
|
||||
|
||||
{% for file, file_result in result %}
|
||||
|
||||
{% for status, description in file_result %}
|
||||
{% if status == 1 %}
|
||||
<li class="success clearfix">
|
||||
<i class="fa fa-check"></i>
|
||||
{% else %}
|
||||
<li class="error clearfix">
|
||||
<i class="fa fa-times"></i>
|
||||
{% endif %}
|
||||
<p>
|
||||
<b>{{ file }}</b> {{ description }}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% for status, description in result %}
|
||||
{% if status == 1 %}
|
||||
<li class="success clearfix">
|
||||
<i class="fa fa-check"></i>
|
||||
{% else %}
|
||||
<li class="error clearfix">
|
||||
<i class="fa fa-times"></i>
|
||||
{% endif %}
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
9
user/themes/antimatter/antimatter.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
namespace Grav\Theme;
|
||||
|
||||
use Grav\Common\Theme;
|
||||
|
||||
class Antimatter extends Theme
|
||||
{
|
||||
|
||||
}
|
||||
2
user/themes/antimatter/antimatter.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
enabled: true
|
||||
color: blue
|
||||
26
user/themes/antimatter/blueprints.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Antimatter
|
||||
version: 1.0
|
||||
author: Team Grav
|
||||
url: http://getgrav.org.com
|
||||
description: This is the default theme for Grav
|
||||
|
||||
form:
|
||||
fields:
|
||||
enabled:
|
||||
type: toggle
|
||||
label: Theme status
|
||||
highlight: 1
|
||||
default: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
color:
|
||||
type: select
|
||||
label: Color
|
||||
default: blue
|
||||
options:
|
||||
blue: Blue
|
||||
red: Red
|
||||
green: Green
|
||||
70
user/themes/antimatter/blueprints/asset/file.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
assets:
|
||||
video:
|
||||
mp4: video/mp4
|
||||
mov: video/quicktime
|
||||
m4v: video/x-m4v
|
||||
swf: video/x-flv
|
||||
|
||||
image:
|
||||
jpg: image/jpeg
|
||||
jpeg: image/jpeg
|
||||
png: image/png
|
||||
gif: image/gif
|
||||
|
||||
file:
|
||||
txt: text/plain
|
||||
doc: application/msword
|
||||
html: text/html
|
||||
pdf: application/pdf
|
||||
zip: application/zip
|
||||
gz: application/gzip
|
||||
|
||||
form:
|
||||
key: filename
|
||||
fields:
|
||||
upload:
|
||||
type: upload
|
||||
label: Upload
|
||||
allow:
|
||||
@assets.*.keys
|
||||
accept:
|
||||
@assets.*.values
|
||||
|
||||
filename:
|
||||
type: text
|
||||
label: Filename
|
||||
|
||||
type:
|
||||
type: hidden
|
||||
default: video
|
||||
|
||||
name:
|
||||
type: unset
|
||||
|
||||
description:
|
||||
type: textarea
|
||||
label: Description
|
||||
|
||||
url:
|
||||
type: unset
|
||||
|
||||
path:
|
||||
type: unset
|
||||
|
||||
thumb:
|
||||
type: unset
|
||||
|
||||
width:
|
||||
type: text
|
||||
label: Width
|
||||
|
||||
height:
|
||||
type: text
|
||||
label: Height
|
||||
|
||||
mime:
|
||||
type: hidden
|
||||
default: 'application/octet-stream'
|
||||
|
||||
modified:
|
||||
type: unset
|
||||
67
user/themes/antimatter/blueprints/blog.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
title: Blog
|
||||
@extends: default
|
||||
|
||||
child_type: item
|
||||
|
||||
form:
|
||||
fields:
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
blog:
|
||||
type: tab
|
||||
title: Blog List
|
||||
|
||||
fields:
|
||||
header.content.items:
|
||||
type: select
|
||||
label: Items
|
||||
default: @self.children
|
||||
options:
|
||||
@self.children: Children
|
||||
|
||||
header.content.limit:
|
||||
type: text
|
||||
label: Max Item Count
|
||||
default: 5
|
||||
validate:
|
||||
required: true
|
||||
pattern: "[1-9][0-9]*"
|
||||
validate:
|
||||
type: int
|
||||
|
||||
header.content.order.by:
|
||||
type: select
|
||||
label: Order By
|
||||
default: date
|
||||
options:
|
||||
folder: Folder
|
||||
title: Title
|
||||
date: Date
|
||||
default: Default
|
||||
|
||||
header.content.order.dir:
|
||||
type: select
|
||||
label: Order
|
||||
default: desc
|
||||
options:
|
||||
asc: Ascending
|
||||
desc: Descending
|
||||
|
||||
header.content.pagination:
|
||||
type: toggle
|
||||
label: Order
|
||||
default: 1
|
||||
options:
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
|
||||
header.pagination:
|
||||
type: hidden
|
||||
default: true
|
||||
|
||||
header.blog_url:
|
||||
type: hidden
|
||||
default: ''
|
||||
163
user/themes/antimatter/blueprints/default.yaml
Normal file
@@ -0,0 +1,163 @@
|
||||
title: Default
|
||||
|
||||
rules:
|
||||
slug:
|
||||
pattern: "[a-z][a-z0-9_\-]+"
|
||||
min: 2
|
||||
max: 80
|
||||
|
||||
form:
|
||||
fields:
|
||||
type:
|
||||
type: hidden
|
||||
label: Page Type
|
||||
default: default
|
||||
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
content:
|
||||
type: tab
|
||||
title: Content
|
||||
|
||||
fields:
|
||||
route:
|
||||
type: select
|
||||
label: Parent
|
||||
@data-options: '\Grav\Common\Page\Pages::parents'
|
||||
@data-default: '\Grav\Plugin\admin::route'
|
||||
options:
|
||||
'': '- Root -'
|
||||
|
||||
order:
|
||||
type: text
|
||||
label: Ordering
|
||||
validate:
|
||||
type: int
|
||||
min: 0
|
||||
|
||||
folder:
|
||||
type: text
|
||||
label: Folder
|
||||
validate:
|
||||
type: slug
|
||||
# required: true
|
||||
|
||||
header.title:
|
||||
type: text
|
||||
label: Title
|
||||
validate:
|
||||
required: true
|
||||
|
||||
content:
|
||||
type: textarea
|
||||
label: Content
|
||||
|
||||
header.processing:
|
||||
type: checkboxes
|
||||
label: Process
|
||||
default: [markdown: true, twig: true]
|
||||
options:
|
||||
markdown: Markdown
|
||||
twig: Twig
|
||||
use: keys
|
||||
|
||||
|
||||
meta:
|
||||
type: tab
|
||||
title: Meta
|
||||
|
||||
fields:
|
||||
header.description:
|
||||
type: textarea
|
||||
label: Description
|
||||
validate:
|
||||
max: 120
|
||||
|
||||
header.keywords:
|
||||
type: text
|
||||
label: Keywords
|
||||
validate:
|
||||
max: 120
|
||||
|
||||
header.robots:
|
||||
type: checkboxes
|
||||
label: Robots
|
||||
options:
|
||||
noindex: No index
|
||||
nofollow: No follow
|
||||
use: keys
|
||||
|
||||
overrides:
|
||||
type: tab
|
||||
title: Overrides
|
||||
|
||||
fields:
|
||||
header.menu:
|
||||
type: text
|
||||
label: Menu
|
||||
|
||||
header.slug:
|
||||
type: text
|
||||
label: Alias
|
||||
validate:
|
||||
rule: slug
|
||||
|
||||
header.cache_enable:
|
||||
type: toggle
|
||||
label: Caching
|
||||
highlight: 1
|
||||
options:
|
||||
'': Global
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.template:
|
||||
type: themeselect
|
||||
label: Theme override
|
||||
options:
|
||||
'': - Select theme -
|
||||
|
||||
header.routable:
|
||||
type: toggle
|
||||
label: Access by URL
|
||||
highlight: 1
|
||||
default: ''
|
||||
options:
|
||||
'': Global
|
||||
1: Enabled
|
||||
0: Disabled
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.child_type:
|
||||
type: select
|
||||
label: Default Child Type
|
||||
default: default
|
||||
@data-options: '\Grav\Common\Page\Pages::types'
|
||||
|
||||
header.order_by:
|
||||
type: hidden
|
||||
|
||||
header.order_manual:
|
||||
type: hidden
|
||||
validate:
|
||||
type: commalist
|
||||
|
||||
assets:
|
||||
type: tab
|
||||
title: Assets
|
||||
|
||||
fields:
|
||||
files:
|
||||
type: spacer
|
||||
title: Files
|
||||
|
||||
assets:
|
||||
type: list
|
||||
add: Add file
|
||||
@import: 'asset/file'
|
||||
2
user/themes/antimatter/blueprints/form.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
title: Nopad
|
||||
@extends: default
|
||||
40
user/themes/antimatter/blueprints/item.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
title: Item
|
||||
@extends: default
|
||||
|
||||
form:
|
||||
fields:
|
||||
tabs:
|
||||
|
||||
fields:
|
||||
blog:
|
||||
type: tab
|
||||
title: Blog Item
|
||||
|
||||
fields:
|
||||
header.date:
|
||||
type: datetime
|
||||
label: Date
|
||||
|
||||
header.taxonomy.category:
|
||||
type: text
|
||||
label: Category
|
||||
default: blog
|
||||
|
||||
header.taxonomy.tag:
|
||||
type: checkboxes
|
||||
label: Tags
|
||||
options:
|
||||
demo: demo
|
||||
grav: grav
|
||||
matias: matias
|
||||
apple: apple
|
||||
sample: sample
|
||||
|
||||
header.username:
|
||||
type: text
|
||||
label: Author
|
||||
|
||||
header.blog_url:
|
||||
type: text
|
||||
label: Blog URL
|
||||
default:
|
||||
27
user/themes/antimatter/blueprints/modular.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
title: Modular
|
||||
@extends: default
|
||||
|
||||
form:
|
||||
fields:
|
||||
tabs:
|
||||
type: tabs
|
||||
active: 1
|
||||
|
||||
fields:
|
||||
content:
|
||||
fields:
|
||||
header.modular:
|
||||
type: select
|
||||
label: 'Modular page'
|
||||
default: 0
|
||||
options:
|
||||
0: 'False'
|
||||
1: 'True'
|
||||
validate:
|
||||
type: bool
|
||||
|
||||
header.order_manual:
|
||||
type: text
|
||||
label: Manual ordering
|
||||
validate:
|
||||
type: commalist
|
||||
621
user/themes/antimatter/css-compiled/nucleus.css
Normal file
@@ -0,0 +1,621 @@
|
||||
*, *::before, *::after {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box; }
|
||||
|
||||
@-webkit-viewport {
|
||||
width: device-width; }
|
||||
@-moz-viewport {
|
||||
width: device-width; }
|
||||
@-ms-viewport {
|
||||
width: device-width; }
|
||||
@-o-viewport {
|
||||
width: device-width; }
|
||||
@viewport {
|
||||
width: device-width; }
|
||||
html {
|
||||
font-size: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%; }
|
||||
|
||||
body {
|
||||
margin: 0; }
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block; }
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
vertical-align: baseline; }
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0; }
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none; }
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
text-decoration: none; }
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0; }
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted; }
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold; }
|
||||
|
||||
dfn {
|
||||
font-style: italic; }
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000; }
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 0.75rem;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline; }
|
||||
|
||||
sup {
|
||||
top: -0.5em; }
|
||||
|
||||
sub {
|
||||
bottom: -0.25em; }
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 100%; }
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden; }
|
||||
|
||||
figure {
|
||||
margin: 1em 40px; }
|
||||
|
||||
hr {
|
||||
height: 0; }
|
||||
|
||||
pre {
|
||||
overflow: auto; }
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre,
|
||||
samp {
|
||||
font-size: 1rem; }
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
margin: 0; }
|
||||
|
||||
button {
|
||||
overflow: visible; }
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none; }
|
||||
|
||||
button,
|
||||
html input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
cursor: pointer; }
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default; }
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0; }
|
||||
|
||||
input {
|
||||
line-height: normal; }
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
padding: 0; }
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto; }
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; }
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none; }
|
||||
|
||||
legend {
|
||||
border: 0;
|
||||
padding: 0; }
|
||||
|
||||
textarea {
|
||||
overflow: auto; }
|
||||
|
||||
optgroup {
|
||||
font-weight: bold; }
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
width: 100%; }
|
||||
|
||||
tr, td, th {
|
||||
vertical-align: middle; }
|
||||
|
||||
th, td {
|
||||
padding: 0.425rem 0; }
|
||||
|
||||
th {
|
||||
text-align: left; }
|
||||
|
||||
.container {
|
||||
width: 75rem;
|
||||
margin: 0 auto;
|
||||
padding: 0; }
|
||||
@media only all and (min-width: 60rem) and (max-width: 74.938rem) {
|
||||
.container {
|
||||
width: 60rem; } }
|
||||
@media only all and (min-width: 48rem) and (max-width: 59.938rem) {
|
||||
.container {
|
||||
width: 48rem; } }
|
||||
@media only all and (min-width: 30.063rem) and (max-width: 47.938rem) {
|
||||
.container {
|
||||
width: 30rem; } }
|
||||
@media only all and (max-width: 30rem) {
|
||||
.container {
|
||||
width: 100%; } }
|
||||
|
||||
.grid {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: box;
|
||||
display: -webkit-flex;
|
||||
display: -moz-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-flow: row;
|
||||
-moz-flex-flow: row;
|
||||
flex-flow: row;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
@media only all and (max-width: 47.938rem) {
|
||||
.grid {
|
||||
-webkit-flex-flow: row wrap;
|
||||
-moz-flex-flow: row wrap;
|
||||
flex-flow: row wrap; } }
|
||||
|
||||
.block {
|
||||
-webkit-box-flex: 1;
|
||||
-moz-box-flex: 1;
|
||||
box-flex: 1;
|
||||
-webkit-flex: 1;
|
||||
-moz-flex: 1;
|
||||
-ms-flex: 1;
|
||||
flex: 1; }
|
||||
@media only all and (max-width: 47.938rem) {
|
||||
.block {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 100%;
|
||||
-moz-flex: 0 100%;
|
||||
-ms-flex: 0 100%;
|
||||
flex: 0 100%; } }
|
||||
|
||||
.content {
|
||||
margin: 0.625rem;
|
||||
padding: 0.938rem; }
|
||||
|
||||
@media only all and (max-width: 47.938rem) {
|
||||
body [class*="size-"] {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 100%;
|
||||
-moz-flex: 0 100%;
|
||||
-ms-flex: 0 100%;
|
||||
flex: 0 100%; } }
|
||||
|
||||
.size-1-2 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 50%;
|
||||
-moz-flex: 0 50%;
|
||||
-ms-flex: 0 50%;
|
||||
flex: 0 50%; }
|
||||
|
||||
.size-1-3 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 33.33333%;
|
||||
-moz-flex: 0 33.33333%;
|
||||
-ms-flex: 0 33.33333%;
|
||||
flex: 0 33.33333%; }
|
||||
|
||||
.size-1-4 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 25%;
|
||||
-moz-flex: 0 25%;
|
||||
-ms-flex: 0 25%;
|
||||
flex: 0 25%; }
|
||||
|
||||
.size-1-5 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 20%;
|
||||
-moz-flex: 0 20%;
|
||||
-ms-flex: 0 20%;
|
||||
flex: 0 20%; }
|
||||
|
||||
.size-1-6 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 16.66667%;
|
||||
-moz-flex: 0 16.66667%;
|
||||
-ms-flex: 0 16.66667%;
|
||||
flex: 0 16.66667%; }
|
||||
|
||||
.size-1-7 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 14.28571%;
|
||||
-moz-flex: 0 14.28571%;
|
||||
-ms-flex: 0 14.28571%;
|
||||
flex: 0 14.28571%; }
|
||||
|
||||
.size-1-8 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 12.5%;
|
||||
-moz-flex: 0 12.5%;
|
||||
-ms-flex: 0 12.5%;
|
||||
flex: 0 12.5%; }
|
||||
|
||||
.size-1-9 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 11.11111%;
|
||||
-moz-flex: 0 11.11111%;
|
||||
-ms-flex: 0 11.11111%;
|
||||
flex: 0 11.11111%; }
|
||||
|
||||
.size-1-10 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 10%;
|
||||
-moz-flex: 0 10%;
|
||||
-ms-flex: 0 10%;
|
||||
flex: 0 10%; }
|
||||
|
||||
.size-1-11 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 9.09091%;
|
||||
-moz-flex: 0 9.09091%;
|
||||
-ms-flex: 0 9.09091%;
|
||||
flex: 0 9.09091%; }
|
||||
|
||||
.size-1-12 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 8.33333%;
|
||||
-moz-flex: 0 8.33333%;
|
||||
-ms-flex: 0 8.33333%;
|
||||
flex: 0 8.33333%; }
|
||||
|
||||
@media only all and (min-width: 48rem) and (max-width: 59.938rem) {
|
||||
.size-tablet-1-2 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 50%;
|
||||
-moz-flex: 0 50%;
|
||||
-ms-flex: 0 50%;
|
||||
flex: 0 50%; }
|
||||
|
||||
.size-tablet-1-3 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 33.33333%;
|
||||
-moz-flex: 0 33.33333%;
|
||||
-ms-flex: 0 33.33333%;
|
||||
flex: 0 33.33333%; }
|
||||
|
||||
.size-tablet-1-4 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 25%;
|
||||
-moz-flex: 0 25%;
|
||||
-ms-flex: 0 25%;
|
||||
flex: 0 25%; }
|
||||
|
||||
.size-tablet-1-5 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 20%;
|
||||
-moz-flex: 0 20%;
|
||||
-ms-flex: 0 20%;
|
||||
flex: 0 20%; }
|
||||
|
||||
.size-tablet-1-6 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 16.66667%;
|
||||
-moz-flex: 0 16.66667%;
|
||||
-ms-flex: 0 16.66667%;
|
||||
flex: 0 16.66667%; }
|
||||
|
||||
.size-tablet-1-7 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 14.28571%;
|
||||
-moz-flex: 0 14.28571%;
|
||||
-ms-flex: 0 14.28571%;
|
||||
flex: 0 14.28571%; }
|
||||
|
||||
.size-tablet-1-8 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 12.5%;
|
||||
-moz-flex: 0 12.5%;
|
||||
-ms-flex: 0 12.5%;
|
||||
flex: 0 12.5%; }
|
||||
|
||||
.size-tablet-1-9 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 11.11111%;
|
||||
-moz-flex: 0 11.11111%;
|
||||
-ms-flex: 0 11.11111%;
|
||||
flex: 0 11.11111%; }
|
||||
|
||||
.size-tablet-1-10 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 10%;
|
||||
-moz-flex: 0 10%;
|
||||
-ms-flex: 0 10%;
|
||||
flex: 0 10%; }
|
||||
|
||||
.size-tablet-1-11 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 9.09091%;
|
||||
-moz-flex: 0 9.09091%;
|
||||
-ms-flex: 0 9.09091%;
|
||||
flex: 0 9.09091%; }
|
||||
|
||||
.size-tablet-1-12 {
|
||||
-webkit-box-flex: 0;
|
||||
-moz-box-flex: 0;
|
||||
box-flex: 0;
|
||||
-webkit-flex: 0 8.33333%;
|
||||
-moz-flex: 0 8.33333%;
|
||||
-ms-flex: 0 8.33333%;
|
||||
flex: 0 8.33333%; } }
|
||||
@media only all and (max-width: 47.938rem) {
|
||||
@supports not (flex-wrap: wrap) {
|
||||
.grid {
|
||||
display: block;
|
||||
-webkit-box-lines: inherit;
|
||||
-moz-box-lines: inherit;
|
||||
box-lines: inherit;
|
||||
-webkit-flex-wrap: inherit;
|
||||
-moz-flex-wrap: inherit;
|
||||
-ms-flex-wrap: inherit;
|
||||
flex-wrap: inherit; }
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
-webkit-box-flex: inherit;
|
||||
-moz-box-flex: inherit;
|
||||
box-flex: inherit;
|
||||
-webkit-flex: inherit;
|
||||
-moz-flex: inherit;
|
||||
-ms-flex: inherit;
|
||||
flex: inherit; } } }
|
||||
.first-block {
|
||||
-webkit-box-ordinal-group: 0;
|
||||
-webkit-order: -1;
|
||||
-ms-flex-order: -1;
|
||||
order: -1; }
|
||||
|
||||
.last-block {
|
||||
-webkit-box-ordinal-group: 2;
|
||||
-webkit-order: 1;
|
||||
-ms-flex-order: 1;
|
||||
order: 1; }
|
||||
|
||||
.fixed-blocks {
|
||||
-webkit-flex-flow: row wrap;
|
||||
-moz-flex-flow: row wrap;
|
||||
flex-flow: row wrap; }
|
||||
.fixed-blocks .block {
|
||||
-webkit-box-flex: inherit;
|
||||
-moz-box-flex: inherit;
|
||||
box-flex: inherit;
|
||||
-webkit-flex: inherit;
|
||||
-moz-flex: inherit;
|
||||
-ms-flex: inherit;
|
||||
flex: inherit;
|
||||
width: 25%; }
|
||||
@media only all and (min-width: 60rem) and (max-width: 74.938rem) {
|
||||
.fixed-blocks .block {
|
||||
width: 33.33333%; } }
|
||||
@media only all and (min-width: 48rem) and (max-width: 59.938rem) {
|
||||
.fixed-blocks .block {
|
||||
width: 50%; } }
|
||||
@media only all and (max-width: 47.938rem) {
|
||||
.fixed-blocks .block {
|
||||
width: 100%; } }
|
||||
|
||||
body {
|
||||
font-size: 1rem;
|
||||
line-height: 1.7; }
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 0.85rem 0 1.7rem 0;
|
||||
text-rendering: optimizeLegibility; }
|
||||
|
||||
h1 {
|
||||
font-size: 3.2rem; }
|
||||
|
||||
h2 {
|
||||
font-size: 2.5rem; }
|
||||
|
||||
h3 {
|
||||
font-size: 2.1rem; }
|
||||
|
||||
h4 {
|
||||
font-size: 1.75rem; }
|
||||
|
||||
h5 {
|
||||
font-size: 1.35rem; }
|
||||
|
||||
h6 {
|
||||
font-size: 0.85rem; }
|
||||
|
||||
p {
|
||||
margin: 1.7rem 0; }
|
||||
|
||||
ul, ol {
|
||||
margin-top: 1.7rem;
|
||||
margin-bottom: 1.7rem; }
|
||||
ul ul, ul ol, ol ul, ol ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0; }
|
||||
|
||||
blockquote {
|
||||
margin: 1.7rem 0;
|
||||
padding-left: 0.85rem; }
|
||||
|
||||
cite {
|
||||
display: block;
|
||||
font-size: 0.875rem; }
|
||||
cite:before {
|
||||
content: "\2014 \0020"; }
|
||||
|
||||
pre {
|
||||
margin: 1.7rem 0;
|
||||
padding: 0.938rem; }
|
||||
|
||||
code {
|
||||
vertical-align: bottom; }
|
||||
|
||||
small {
|
||||
font-size: 0.875rem; }
|
||||
|
||||
hr {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
margin: 1.7rem 0; }
|
||||
|
||||
fieldset {
|
||||
border: 0;
|
||||
padding: 0.938rem;
|
||||
margin: 0 0 1.7rem 0; }
|
||||
|
||||
input,
|
||||
label,
|
||||
select {
|
||||
display: block; }
|
||||
|
||||
label {
|
||||
margin-bottom: 0.425rem; }
|
||||
label.required:after {
|
||||
content: "*"; }
|
||||
label abbr {
|
||||
display: none; }
|
||||
|
||||
textarea, input[type="email"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="url"], input[type="color"], input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="week"], select[multiple=multiple] {
|
||||
-webkit-transition: border-color;
|
||||
-moz-transition: border-color;
|
||||
transition: border-color;
|
||||
border-radius: 0.1875rem;
|
||||
margin-bottom: 0.85rem;
|
||||
padding: 0.425rem 0.425rem;
|
||||
width: 100%; }
|
||||
textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus {
|
||||
outline: none; }
|
||||
|
||||
textarea {
|
||||
resize: vertical; }
|
||||
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
display: inline;
|
||||
margin-right: 0.425rem; }
|
||||
|
||||
input[type="file"] {
|
||||
width: 100%; }
|
||||
|
||||
select {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
margin-bottom: 1.7rem; }
|
||||
|
||||
button,
|
||||
input[type="submit"] {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
border: inherit; }
|
||||
|
||||
/*# sourceMappingURL=nucleus.css.map */
|
||||
7
user/themes/antimatter/css-compiled/nucleus.css.map
Normal file
98
user/themes/antimatter/css-compiled/particles.css
Normal file
@@ -0,0 +1,98 @@
|
||||
.text-left {
|
||||
text-align: left !important; }
|
||||
|
||||
.text-right {
|
||||
text-align: right !important; }
|
||||
|
||||
.text-center {
|
||||
text-align: center !important; }
|
||||
|
||||
.text-justify {
|
||||
text-align: justify !important; }
|
||||
|
||||
@media all and (min-width: 75rem) {
|
||||
.large-desktop-text-left {
|
||||
text-align: left !important; }
|
||||
|
||||
.large-desktop-text-right {
|
||||
text-align: right !important; }
|
||||
|
||||
.large-desktop-text-center {
|
||||
text-align: center !important; }
|
||||
|
||||
.large-desktop-text-justify {
|
||||
text-align: justify !important; } }
|
||||
@media all and (min-width: 60rem) and (max-width: 74.938rem) {
|
||||
.desktop-text-left {
|
||||
text-align: left !important; }
|
||||
|
||||
.desktop-text-right {
|
||||
text-align: right !important; }
|
||||
|
||||
.desktop-text-center {
|
||||
text-align: center !important; }
|
||||
|
||||
.desktop-text-justify {
|
||||
text-align: justify !important; } }
|
||||
@media all and (min-width: 48rem) and (max-width: 59.938rem) {
|
||||
.tablet-text-left {
|
||||
text-align: left !important; }
|
||||
|
||||
.tablet-text-right {
|
||||
text-align: right !important; }
|
||||
|
||||
.tablet-text-center {
|
||||
text-align: center !important; }
|
||||
|
||||
.tablet-text-justify {
|
||||
text-align: justify !important; } }
|
||||
@media all and (min-width: 30.063rem) and (max-width: 47.938rem) {
|
||||
.large-mobile-text-left {
|
||||
text-align: left !important; }
|
||||
|
||||
.large-mobile-text-right {
|
||||
text-align: right !important; }
|
||||
|
||||
.large-mobile-text-center {
|
||||
text-align: center !important; }
|
||||
|
||||
.large-mobile-text-justify {
|
||||
text-align: justify !important; } }
|
||||
@media all and (max-width: 30rem) {
|
||||
.small-mobile-text-left {
|
||||
text-align: left !important; }
|
||||
|
||||
.small-mobile-text-right {
|
||||
text-align: right !important; }
|
||||
|
||||
.small-mobile-text-center {
|
||||
text-align: center !important; }
|
||||
|
||||
.small-mobile-text-justify {
|
||||
text-align: justify !important; } }
|
||||
@media all and (min-width: 48rem) {
|
||||
.no-mobile-text-left {
|
||||
text-align: left !important; }
|
||||
|
||||
.no-mobile-text-right {
|
||||
text-align: right !important; }
|
||||
|
||||
.no-mobile-text-center {
|
||||
text-align: center !important; }
|
||||
|
||||
.no-mobile-text-justify {
|
||||
text-align: justify !important; } }
|
||||
@media all and (max-width: 47.938rem) {
|
||||
.mobile-only-text-left {
|
||||
text-align: left !important; }
|
||||
|
||||
.mobile-only-text-right {
|
||||
text-align: right !important; }
|
||||
|
||||
.mobile-only-text-center {
|
||||
text-align: center !important; }
|
||||
|
||||
.mobile-only-text-justify {
|
||||
text-align: justify !important; } }
|
||||
|
||||
/*# sourceMappingURL=particles.css.map */
|
||||
7
user/themes/antimatter/css-compiled/particles.css.map
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"mappings": "AAsBC,UAAW;EACV,UAAU,EAAE,eAAe;;AAE5B,WAAY;EACX,UAAU,EAAE,gBAAgB;;AAE7B,YAAa;EACZ,UAAU,EAAE,iBAAiB;;AAE9B,aAAc;EACb,UAAU,EAAE,kBAAkB;;AAI9B,iCAA8C;EAC7C,wBAA4C;IAAE,UAAU,EAAE,eAAe;;EACzE,yBAA+C;IAAE,UAAU,EAAE,gBAAgB;;EAC7E,0BAA+C;IAAE,UAAU,EAAE,iBAAiB;;EAC9E,2BAA+C;IAAE,UAAU,EAAE,kBAAkB;AAJhF,4DAA8C;EAC7C,kBAA4C;IAAE,UAAU,EAAE,eAAe;;EACzE,mBAA+C;IAAE,UAAU,EAAE,gBAAgB;;EAC7E,oBAA+C;IAAE,UAAU,EAAE,iBAAiB;;EAC9E,qBAA+C;IAAE,UAAU,EAAE,kBAAkB;AAJhF,4DAA8C;EAC7C,iBAA4C;IAAE,UAAU,EAAE,eAAe;;EACzE,kBAA+C;IAAE,UAAU,EAAE,gBAAgB;;EAC7E,mBAA+C;IAAE,UAAU,EAAE,iBAAiB;;EAC9E,oBAA+C;IAAE,UAAU,EAAE,kBAAkB;AAJhF,gEAA8C;EAC7C,uBAA4C;IAAE,UAAU,EAAE,eAAe;;EACzE,wBAA+C;IAAE,UAAU,EAAE,gBAAgB;;EAC7E,yBAA+C;IAAE,UAAU,EAAE,iBAAiB;;EAC9E,0BAA+C;IAAE,UAAU,EAAE,kBAAkB;AAJhF,iCAA8C;EAC7C,uBAA4C;IAAE,UAAU,EAAE,eAAe;;EACzE,wBAA+C;IAAE,UAAU,EAAE,gBAAgB;;EAC7E,yBAA+C;IAAE,UAAU,EAAE,iBAAiB;;EAC9E,0BAA+C;IAAE,UAAU,EAAE,kBAAkB;AAJhF,iCAA8C;EAC7C,oBAA4C;IAAE,UAAU,EAAE,eAAe;;EACzE,qBAA+C;IAAE,UAAU,EAAE,gBAAgB;;EAC7E,sBAA+C;IAAE,UAAU,EAAE,iBAAiB;;EAC9E,uBAA+C;IAAE,UAAU,EAAE,kBAAkB;AAJhF,qCAA8C;EAC7C,sBAA4C;IAAE,UAAU,EAAE,eAAe;;EACzE,uBAA+C;IAAE,UAAU,EAAE,gBAAgB;;EAC7E,wBAA+C;IAAE,UAAU,EAAE,iBAAiB;;EAC9E,yBAA+C;IAAE,UAAU,EAAE,kBAAkB",
|
||||
"sources": ["../scss/nucleus/particles/_align-text.scss"],
|
||||
"names": [],
|
||||
"file": "particles.css"
|
||||
}
|
||||
3
user/themes/antimatter/css-compiled/platform.css
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
/*# sourceMappingURL=platform.css.map */
|
||||
7
user/themes/antimatter/css-compiled/platform.css.map
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"version": 3,
|
||||
"mappings": "",
|
||||
"sources": [],
|
||||
"names": [],
|
||||
"file": "platform.css"
|
||||
}
|
||||
2318
user/themes/antimatter/css-compiled/template.css
Normal file
6
user/themes/antimatter/css-compiled/template.css.map
Normal file
1558
user/themes/antimatter/css-compiled/vendor/fontawesome/font-awesome.css
vendored
Normal file
7
user/themes/antimatter/css-compiled/vendor/fontawesome/font-awesome.css.map
vendored
Normal file
8
user/themes/antimatter/css/featherlight.min.css
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Featherlight - ultra slim jQuery lightbox
|
||||
* Version 0.4.9 - http://noelboss.github.io/featherlight/
|
||||
*
|
||||
* Copyright 2014, Noël Raoul Bossart (http://www.noelboss.com)
|
||||
* MIT Licensed.
|
||||
**/
|
||||
@media all{.featherlight{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2;text-align:center;white-space:nowrap;cursor:pointer;background:#333;background:rgba(0,0,0,0)}.featherlight:last-of-type{background:rgba(0,0,0,.8)}.featherlight:before{content:'';display:inline-block;height:100%;vertical-align:middle;margin-right:-.25em}.featherlight .featherlight-content{position:relative;text-align:left;vertical-align:middle;display:inline-block;overflow:auto;padding:25px 25px 0;border-bottom:25px solid transparent;min-width:30%;margin-left:5%;margin-right:5%;max-height:95%;background:#fff;cursor:auto;white-space:normal}.featherlight .featherlight-inner{display:block}.featherlight .featherlight-close-icon{position:absolute;z-index:9999;top:0;right:0;line-height:25px;width:25px;cursor:pointer;text-align:center;font:Arial,sans-serif;background:#fff;background:rgba(255,255,255,.3);color:#000}.featherlight .featherlight-image{width:100%}.featherlight-iframe .featherlight-content{border-bottom:0;padding:0}.featherlight iframe{border:0}}@media only screen and (max-width:1024px){.featherlight .featherlight-content{margin-left:10px;margin-right:10px;max-height:98%;padding:10px 10px 0;border-bottom:10px solid transparent}}
|
||||
9
user/themes/antimatter/css/nucleus-ie10.css
Normal file
@@ -0,0 +1,9 @@
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
62
user/themes/antimatter/css/nucleus-ie9.css
Normal file
@@ -0,0 +1,62 @@
|
||||
/* IE9 Resets and Normalization */
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||