This commit is contained in:
Djamil Legato
2014-08-02 12:11:33 -07:00
parent a833ada6ff
commit 230a2b594e
610 changed files with 63359 additions and 0 deletions

21
bin/grav Executable file
View 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
View 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
View 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
View 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);
}
});

View File

@@ -0,0 +1,5 @@
title: Assets
validation: loose
form:
fields:

View 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'

View 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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,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;
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

View 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('&nbsp; ', ($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;
}
}
}

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

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

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

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

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

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

View File

@@ -0,0 +1,6 @@
<?php
namespace Grav\Common;
class Theme
{
}

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

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

View 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 = "&hellip;")
{
// 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);
}
}

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

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

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

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

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

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

View 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.

View 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

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

View File

@@ -0,0 +1,6 @@
name: 404 Not Found
description: 404 Not found
enabled: true
routes:
404: '/error'

View 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.

View File

@@ -0,0 +1,3 @@
<h1>Error {{ page.header.code }}</h1>
<p>{{ page.content }}</p>

View 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 */

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

View File

@@ -0,0 +1,2 @@
enabled: true
built_in_css: true

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

View File

@@ -0,0 +1,9 @@
<?php
namespace Grav\Theme;
use Grav\Common\Theme;
class Antimatter extends Theme
{
}

View File

@@ -0,0 +1,2 @@
enabled: true
color: blue

View 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

View 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

View 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: ''

View 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'

View File

@@ -0,0 +1,2 @@
title: Nopad
@extends: default

View 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:

View 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

View 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 */

File diff suppressed because one or more lines are too long

View 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 */

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

View File

@@ -0,0 +1,3 @@
/*# sourceMappingURL=platform.css.map */

View File

@@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "",
"sources": [],
"names": [],
"file": "platform.css"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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

View File

@@ -0,0 +1,9 @@
button {
overflow: visible;
}
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box;
padding: 0;
}

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

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