Merge branch 'develop' of https://github.com/getgrav/grav into feature/multi-config

Conflicts:
	composer.json
	system/config/streams.yaml
	system/src/Grav/Common/Page/Page.php
	system/src/Grav/Common/Theme.php
	system/src/Grav/Common/Themes.php
	system/src/Grav/Component/Filesystem/ResourceLocator.php
	vendor/autoload.php
	vendor/composer/autoload_classmap.php
	vendor/composer/autoload_files.php
	vendor/composer/autoload_real.php
	vendor/composer/installed.json
This commit is contained in:
Matias Griese
2014-09-03 10:33:02 +03:00
43 changed files with 854 additions and 794 deletions

View File

@@ -1,26 +1,36 @@
<IfModule mod_rewrite.c>
RewriteEngine On
# access site
##
# If you are getting 404 errors on subpages, you may have to uncomment the RewriteBase entry
# You should change the '/' to your appropriate subfolder. For example if you have
# your Grav install at the root of your site '/' should work, else it might be something
# along the lines of: RewriteBase /<your_sub_folder>
##
# RewriteBase /
# Access site
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]
RewriteRule .* index.php [L]
# block various user files from being accessed directly
# Block various user files from being accessed directly
RewriteRule ^user/accounts/(.*)$ error [R=301,L]
RewriteRule ^user/config/(.*)$ error [R=301,L]
RewriteRule ^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ error [R=301,L]
# block cache
# Block cache/
RewriteRule ^cache/(.*) error [R=301,L]
# block bin
# Block bin/
RewriteRule ^bin/(.*)$ error [R=301,L]
# block system
# Block system/
RewriteRule ^system/(.*)$ error [R=301,L]
# block vendor
# Block vendor/
RewriteRule ^vendor/(.*)$ error [R=301,L]
</IfModule>

View File

@@ -7,7 +7,12 @@ The underlying architecture of Grav has been designed to use well-established an
* [Twig Templating](http://twig.sensiolabs.org/): for powerful control of the user interface
* [Markdown](http://en.wikipedia.org/wiki/Markdown): for easy content creation
* [YAML](http://yaml.org): for simple configuration
* [Doctrine Cache](http://docs.doctrine-project.org/en/2.0.x/reference/caching.html): layer for incredible performance
* [Parsedown](http://parsedown.org/): for fast Markdown and Mardown Extra support
* [Doctrine Cache](http://docs.doctrine-project.org/en/2.0.x/reference/caching.html): layer for performance
* [Pimple Dependency Injection Container](http://pimple.sensiolabs.org/): for extensibility and maintainability
* [Symfony Event Dispacher](http://symfony.com/doc/current/components/event_dispatcher/introduction.html): for plugin event handling
* [Symfony Console](http://symfony.com/doc/current/components/console/introduction.html): for CLI interface
* [Gregwar Image Library](https://github.com/Gregwar/Image): for dynamic image manipulation
# QuickStart

View File

@@ -1 +1 @@
0.8.0
0.9.1

View File

@@ -1,6 +1,10 @@
#!/usr/bin/env php
<?php
if (version_compare($ver = PHP_VERSION, $req = '5.4.0', '<')) {
exit(sprintf("You are running PHP %s, but Grav needs at least PHP %s to run.\n", $ver, $req));
}
use Symfony\Component\Console\Application;
$autoload = __DIR__ . '/../vendor/autoload.php';

View File

@@ -8,7 +8,7 @@
"require": {
"php": ">=5.4.0",
"twig/twig": "~1.16",
"erusev/parsedown-extra": "~0.2",
"erusev/parsedown-extra": "dev-master",
"symfony/yaml": "~2.5",
"symfony/console": "~2.5",
"symfony/event-dispatcher": "~2.5",
@@ -16,8 +16,8 @@
"tracy/tracy": "~2.2",
"gregwar/image": "~2.0",
"ircmaxell/password-compat": "1.0.*",
"mrclay/minify": "~2.2",
"ornicar/php-user-agent": "1.0.*",
"mrclay/minify": "dev-master",
"donatj/phpuseragentparser": "dev-master",
"pimple/pimple": "~3.0",
"rockettheme/toolbox": "dev-develop"
},
@@ -25,11 +25,15 @@
{
"type": "vcs",
"url": "https://github.com/rockettheme/toolbox"
},
{
"type": "vcs",
"url": "https://github.com/rhukster/minify"
}
],
"autoload": {
"psr-4": {
"Grav\\": "system/src/"
"Grav\\": "system/src/Grav"
},
"files": ["system/defines.php"]
},

View File

@@ -1,13 +1,39 @@
schemes:
plugin:
type: ReadOnlyStream
paths:
- user/plugins
- system/plugins
asset:
type: ReadOnlyStream
paths:
- assets
cache:
type: ReadOnlyStream
paths:
- cache
log:
type: ReadOnlyStream
paths:
- logs
image:
type: ReadOnlyStream
paths:
- user/images
user:
type: ReadOnlyStream
paths:
- user
# asset:
# type: ReadOnlyStream
# paths:
# - assets
page:
type: ReadOnlyStream
paths:
- user/pages
account:
type: ReadOnlyStream
@@ -19,20 +45,8 @@ schemes:
paths:
- user/data
page:
theme:
type: ReadOnlyStream
prefixes:
'/':
- user/pages
image:
prefixes:
'/':
- user/images
- system/images
theme:
prefixes:
'/':
- user/themes
- system/themes

View File

@@ -16,13 +16,13 @@ pages:
markdown: true # Process Markdown
twig: false # Process Twig
events:
page: false # Enable page level events
page: true # 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
method: file # Method to check for updates in pages: file|folder|none
driver: auto # One of: auto|file|apc|xcache|memcache|memcached|wincache
prefix: 'g' # Cache prefix string (prevents cache conflicts)
@@ -32,15 +32,16 @@ twig:
auto_reload: true # Refresh cache on changes
autoescape: false # Autoescape Twig vars
assets: # Configuration for Assets Manager (JS, CSS)
css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
css_minify: true # Minify the CSS during pipelining
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
assets: # Configuration for Assets Manager (JS, CSS)
css_pipeline: false # The CSS pipeline is the unification of multiple CSS resources into one file
css_minify: true # Minify the CSS during pipelining
css_rewrite: true # Rewrite any CSS relative URLs during pipelining
js_pipeline: false # The JS pipeline is the unification of multiple JS resources into one file
js_minify: true # Minify the JS during pipelining
debugger:
enabled: false # Enable Grav debugger and following settings
mode: detect # Mode tracy Debugger should be set to when enabled: detect|development|production
strict: false # Throw fatal error also on PHP warnings and notices
max_depth: 10 # How many nested levels to display for objects or arrays
log:

View File

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

View File

@@ -0,0 +1,36 @@
<?php
namespace Grav\Common;
/**
* Simple wrapper for the very simple parse_user_agent() function
*/
class Browser {
protected $useragent;
public function __construct()
{
$this->useragent = parse_user_agent();
}
public function getBrowser()
{
return strtolower($this->useragent['browser']);
}
public function getPlatform()
{
return strtolower($this->useragent['platform']);
}
public function getLongVersion()
{
return $this->useragent['version'];
}
public function getVersion()
{
$version = explode('.', $this->getLongVersion());
return intval($version[0]);
}
}

View File

@@ -66,6 +66,9 @@ class Cache extends Getters
$this->key = substr(md5(($prefix ? $prefix : 'g') . $uri->rootUrl(true) . $this->config->key . GRAV_VERSION), 2, 8);
$this->driver = $this->getCacheDriver();
// Set the cache namespace to our unique key
$this->driver->setNamespace($this->key);
}
/**
@@ -132,7 +135,6 @@ class Cache extends Getters
public function fetch($id)
{
if ($this->enabled) {
$id = $this->key . $id;
return $this->driver->fetch($id);
} else {
return false;
@@ -149,7 +151,6 @@ class Cache extends Getters
public function save($id, $data, $lifetime = null)
{
if ($this->enabled) {
$id = $this->key . $id;
$this->driver->save($id, $data, $lifetime);
}
}

View File

@@ -11,6 +11,7 @@ class Debugger
{
const PRODUCTION = TracyDebugger::PRODUCTION;
const DEVELOPMENT = TracyDebugger::DEVELOPMENT;
const DETECT = TracyDebugger::DETECT;
public function __construct($mode = self::PRODUCTION)
{
@@ -27,6 +28,7 @@ class Debugger
/** @var Config $config */
$config = $grav['config'];
$mode = $config->get('system.debugger.mode');
TracyDebugger::$logDirectory = $config->get('system.debugger.log.enabled') ? LOG_DIR : null;
TracyDebugger::$maxDepth = $config->get('system.debugger.max_depth');
@@ -39,7 +41,16 @@ class Debugger
if (function_exists('ini_set')) {
ini_set('display_errors', true);
}
TracyDebugger::$productionMode = TracyDebugger::DEVELOPMENT;
if ($mode == strtolower('detect')) {
TracyDebugger::$productionMode = self::DETECT;
} elseif ($mode == strtolower('production')) {
TracyDebugger::$productionMode = self::PRODUCTION;
} else {
TracyDebugger::$productionMode = self::DEVELOPMENT;
}
}
}

View File

@@ -99,8 +99,8 @@ class Grav extends Container
$container['output'] = function ($c) {
return $c['twig']->processSite($c['uri']->extension());
};
$container['user_agent'] = function ($c) {
return new \phpUserAgent();
$container['browser'] = function ($c) {
return new Browser();
};
$container->register(new StreamsServiceProvider);

View File

@@ -0,0 +1,13 @@
<?php
namespace Grav\Common\Markdown;
class Markdown extends \Parsedown
{
use MarkdownGravLinkTrait;
function __construct($page)
{
$this->page = $page;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Grav\Common\Markdown;
class MarkdownExtra extends \ParsedownExtra
{
use MarkdownGravLinkTrait;
function __construct($page)
{
parent::__construct();
$this->page = $page;
}
}

View File

@@ -0,0 +1,147 @@
<?php
namespace Grav\Common\Markdown;
use Grav\Common\Debugger;
use Grav\Common\GravTrait;
/**
* A trait to add some custom processing to the identifyLink() method in Parsedown and ParsedownExtra
*/
trait MarkdownGravLinkTrait
{
use GravTrait;
protected function identifyLink($Excerpt)
{
// Run the parent method to get the actual results
$Excerpt = parent::identifyLink($Excerpt);
$actions = array();
$this->base_url = trim(self::$grav['config']->get('system.base_url_relative'));
// if this is a link
if (isset($Excerpt['element']['attributes']['href'])) {
$url = parse_url(htmlspecialchars_decode($Excerpt['element']['attributes']['href']));
// if there is no host set but there is a path, the file is local
if (!isset($url['host']) && isset($url['path'])) {
// convert the URl is required
$Excerpt['element']['attributes']['href'] = $this->convertUrl($url['path']);
}
}
// if this is an image
if (isset($Excerpt['element']['attributes']['src'])) {
$alt = isset($Excerpt['element']['attributes']['alt']) ? $Excerpt['element']['attributes']['alt'] : '';
$title = isset($Excerpt['element']['attributes']['title']) ? $Excerpt['element']['attributes']['title'] : '';
//get the url and parse it
$url = parse_url(htmlspecialchars_decode($Excerpt['element']['attributes']['src']));
// if there is no host set but there is a path, the file is local
if (!isset($url['host']) && isset($url['path'])) {
// get the media objects for this page
$media = $this->page->media();
// if there is a media file that matches the path referenced..
if (isset($media->images()[$url['path']])) {
// get the medium object
$medium = $media->images()[$url['path']];
// if there is a query, then parse it and build action calls
if (isset($url['query'])) {
parse_str($url['query'], $actions);
}
// loop through actions for the image and call them
foreach ($actions as $action => $params) {
// as long as it's not an html, url or ligtbox action
if (!in_array($action, ['html','url','lightbox'])) {
call_user_func_array(array(&$medium, $action), explode(',', $params));
}
}
// Get the URL for regular images, or an array of bits needed to put together
// the lightbox HTML
if (!isset($actions['lightbox'])) {
$src = $medium->url();
} else {
$src = $medium->lightboxRaw();
}
// set the src element with the new generated url
if (!isset($actions['lightbox']) && !is_array($src)) {
$Excerpt['element']['attributes']['src'] = $src;
} else {
// Create the custom lightbox element
$Element = array(
'name' => 'a',
'attributes' => array('rel' => $src['a_rel'], 'href' => $src['a_url']),
'handler' => 'element',
'text' => array(
'name' => 'img',
'attributes' => array('src' => $src['img_url'], 'alt' => $alt, 'title' => $title)
),
);
// Set the lightbox element on the Excerpt
$Excerpt['element'] = $Element;
}
} else {
// not a current page media file, see if it needs converting to relative
$Excerpt['element']['attributes']['src'] = $this->convertUrl($url['path']);
}
}
}
return $Excerpt;
}
/**
* Converts links from absolute '/' or relative (../..) to a grav friendly format
* @param string $markdown_url the URL as it was written in the markdown
* @return string the more friendly formatted url
*/
protected function convertUrl($markdown_url)
{
// if absolue and starts with a base_url move on
if ($this->base_url == '' || strpos($markdown_url, $this->base_url) === 0) {
$new_url = $markdown_url;
// if its absolute with /
} elseif (strpos($markdown_url, '/') === 0) {
$new_url = rtrim($this->base_url, '/') . $markdown_url;
} else {
$relative_path = rtrim($this->base_url, '/') . $this->page->route();
// If this is a 'real' filepath clean it up
if (file_exists($this->page->path().'/'.$markdown_url)) {
$relative_path = rtrim($this->base_url, '/') .
preg_replace('/\/([\d]+.)/', '/',
str_replace(PAGES_DIR, '/', $this->page->path()));
$markdown_url = preg_replace('/^([\d]+.)/', '',
preg_replace('/\/([\d]+.)/', '/',
trim(preg_replace('/[^\/]+(\.md$)/', '', $markdown_url), '/')));
}
// else its a relative path already
$newpath = array();
$paths = explode('/', $markdown_url);
// remove the updirectory references (..)
foreach ($paths as $path) {
if ($path == '..') {
$relative_path = dirname($relative_path);
} else {
$newpath[] = $path;
}
}
// build the new url
$new_url = $relative_path . '/' . implode('/', $newpath);
}
return $new_url;
}
}

View File

@@ -202,6 +202,15 @@ class Medium extends Data
return $this->link($width, $height);
}
public function lightboxRaw($width = null, $height = null)
{
$url = $this->url();
$this->link($width, $height);
$lightbox_url = self::$grav['config']->get('system.base_url_relative') . '/'. $this->linkTarget;
return array('a_url' => $lightbox_url, 'a_rel' => 'lightbox', 'img_url' => $url);
}
/**
* Return link HTML for the medium.
*

View File

@@ -9,6 +9,8 @@ use Grav\Common\Twig;
use Grav\Common\Uri;
use Grav\Common\Grav;
use Grav\Common\Taxonomy;
use Grav\Common\Markdown\Markdown;
use Grav\Common\Markdown\MarkdownExtra;
use Grav\Component\Data\Blueprint;
use Grav\Component\Filesystem\File;
use Grav\Component\Filesystem\Folder;
@@ -68,6 +70,7 @@ class Page
protected $modular_twig;
protected $process;
protected $summary_size;
protected $markdown_extra;
/**
* @var Page Unmodified (original) version of the page. Used for copying and moving the page.
@@ -103,7 +106,7 @@ class Page
public function init($file)
{
$this->filePath($file->getPathName());
$this->modified(filemtime($file->getPath()));
$this->modified($file->getMTime());
$this->id($this->modified().md5($this->filePath()));
$this->header();
$this->slug();
@@ -199,6 +202,9 @@ class Page
if (isset($this->header->date)) {
$this->date = strtotime($this->header->date);
}
if (isset($this->header->markdown_extra)) {
$this->markdown_extra = (bool)$this->header->markdown_extra;
}
if (isset($this->header->taxonomy)) {
foreach ($this->header->taxonomy as $taxonomy => $taxitems) {
$this->taxonomy[$taxonomy] = (array)$taxitems;
@@ -212,7 +218,9 @@ class Page
$this->process[$process] = $status;
}
}
}
return $this->header;
}
@@ -272,6 +280,9 @@ class Page
// If no content, process it
if ($this->content === null) {
// Get media
$this->media();
// Load cached content
/** @var Cache $cache */
$cache = self::$grav['cache'];
@@ -319,7 +330,6 @@ class Page
$this->content = $content;
$this->media();
}
return $this->content;
@@ -1315,7 +1325,7 @@ class Page
}
}
$config->set('system.cache.enabled', false);
$config->set('system.cache.enabled', false); // TODO: Do we still need this?
}
}
// TODO: END OF MOVE
@@ -1513,10 +1523,12 @@ class Page
{
/** @var Config $config */
$config = self::$grav['config'];
if ($config->get('system.pages.markdown_extra')) {
$parsedown = new \ParsedownExtra();
// get the appropriate setting for markdown extra
if (isset($this->markdown_extra) ? $this->markdown_extra : $config->get('system.pages.markdown_extra')) {
$parsedown = new MarkdownExtra($this);
} else {
$parsedown = new \Parsedown();
$parsedown = new Markdown($this);
}
$content = $parsedown->parse($content);
return $content;

View File

@@ -349,8 +349,22 @@ class Pages
$cache = $this->grav['cache'];
/** @var Taxonomy $taxonomy */
$taxonomy = $this->grav['taxonomy'];
$last_modified = $config->get('system.cache.check.pages', true)
? Folder::lastModified(PAGES_DIR) : 0;
$last_modified = 0;
// how should we check for last modified? Default is by file
switch (strtolower($config->get('system.cache.check.method', 'file'))) {
case 'none':
case 'off':
$last_modified = 0;
break;
case 'folder':
$last_modified = Folder::lastModifiedFolder(PAGES_DIR);
break;
default:
$last_modified = Folder::lastModifiedFile(PAGES_DIR);
}
$page_cache_id = md5(USER_DIR.$last_modified);
list($this->instances, $this->routes, $this->children, $taxonomy_map, $this->sort) = $cache->fetch($page_cache_id);
@@ -405,10 +419,17 @@ class Pages
throw new \RuntimeException('Fatal error when creating page instances.');
}
$last_modified = 0;
/** @var \DirectoryIterator $file */
foreach ($iterator as $file) {
$name = $file->getFilename();
$date = $file->getMTime();
if ($date > $last_modified) {
$last_modified = $date;
}
if ($file->isFile() && Utils::endsWith($name, CONTENT_EXT)) {
$page->init($file);
@@ -444,8 +465,14 @@ class Pages
$this->grav->fireEvent('onFolderProcessed', new Event(['page' => $page]));
}
}
}
// Override the modified and ID so that it takes the latest change
// into account
$page->modified($last_modified);
$page->id($last_modified.md5($page->filePath()));
// Sort based on Defaults or Page Overridden sort order
$this->children[$page->path()] = $this->sort($page);
@@ -532,8 +559,14 @@ class Pages
}
}
// Sort by the new list.
asort($list);
// handle special case when order_by is random
if ($order_by == 'random') {
$list = $this->array_shuffle($list);
} else {
// else just sort the list according to specified key
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)) {
@@ -561,4 +594,17 @@ class Pages
$this->sort[$path][$order_by][$key] = $info;
}
}
// Shuffles and associative array
protected function array_shuffle($list) {
$keys = array_keys($list);
shuffle($keys);
$new = array();
foreach($keys as $key) {
$new[$key] = $list[$key];
}
return $new;
}
}

View File

@@ -62,8 +62,10 @@ class Plugins extends Iterator
}
}
$instance = $this->grav['themes']->load();
$instance->configure();
/** @var Themes $themes */
$themes = $this->grav['themes'];
$themes->configure();
$instance = $themes->load();
if ($instance instanceof EventSubscriberInterface) {
$events->addSubscriber($instance);
}
@@ -86,7 +88,7 @@ class Plugins extends Iterator
static public function all()
{
$list = array();
$iterator = new \DirectoryIterator('plugin://');
$iterator = new \DirectoryIterator('plugin:///');
/** @var \DirectoryIterator $directory */
foreach ($iterator as $directory) {

View File

@@ -1,10 +1,6 @@
<?php
namespace Grav\Common;
use Grav\Common\Config\Config;
use Grav\Component\Filesystem\File\Yaml;
use Grav\Component\Filesystem\ResourceLocator;
class Theme extends Plugin
{
public $name;
@@ -22,49 +18,4 @@ class Theme extends Plugin
parent::__construct($grav, $config);
}
public function configure() {
$this->loadConfiguration();
/** @var ResourceLocator $locator */
$locator = $this->grav['locator'];
// TODO: move
$registered = stream_get_wrappers();
$schemes = $this->config->get(
"themes.{$this->name}.streams.scheme",
['theme' => ['paths' => ["user/themes/{$this->name}"]]]
);
foreach ($schemes as $scheme => $config) {
if (isset($config['paths'])) {
$locator->addPath($scheme, '', $config['paths']);
}
if (isset($config['prefixes'])) {
foreach ($config['prefixes'] as $prefix => $paths) {
$locator->addPath($scheme, $prefix, $paths);
}
}
if (in_array($scheme, $registered)) {
stream_wrapper_unregister($scheme);
}
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
if ($type[0] != '\\') {
$type = '\\Grav\\Component\\Filesystem\\StreamWrapper\\' . $type;
}
if (!stream_wrapper_register($scheme, $type)) {
throw new \InvalidArgumentException("Stream '{$type}' could not be initialized.");
}
}
}
protected function loadConfiguration()
{
$themeConfig = Yaml::instance(THEMES_DIR . "{$this->name}/{$this->name}.yaml")->content();
$this->config->merge(['themes' => [$this->name => $themeConfig]]);
}
}

View File

@@ -4,6 +4,8 @@ namespace Grav\Common;
use Grav\Component\Data\Blueprints;
use Grav\Component\Data\Data;
use Grav\Component\Filesystem\File;
use Grav\Common\Filesystem\File\Yaml;
use Grav\Component\Filesystem\ResourceLocator;
/**
* The Themes object holds an array of all the theme objects that Grav knows about.
@@ -61,23 +63,25 @@ class Themes
throw new \RuntimeException('Theme name not provided.');
}
$blueprints = new Blueprints("theme://{$name}");
$blueprints = new Blueprints("theme:///{$name}");
$blueprint = $blueprints->get('blueprints');
$blueprint->name = $name;
/** @var Config $config */
$config = $this->grav['config'];
// Find thumbnail.
$thumb = THEMES_DIR . "{$name}/thumbnail.jpg";
$thumb = "theme:///{$name}/thumbnail.jpg";
if (file_exists($thumb)) {
// TODO: use real URL with base path.
$blueprint->set('thumbnail', "/user/themes/{$name}/thumbnail.jpg");
$blueprint->set('thumbnail', $config->get('system.base_url_relative') . "/user/themes/{$name}/thumbnail.jpg");
}
// Load default configuration.
$file = File\Yaml::instance("theme://{$name}.yaml");
$file = Yaml::instance("theme:///{$name}/{$name}.yaml");
$obj = new Data($file->content(), $blueprint);
// Override with user configuration.
$file = File\Yaml::instance("user://config/themes/{$name}.yaml");
$file = Yaml::instance("user://config/themes/{$name}.yaml");
$obj->merge($file->content());
// Save configuration always to user/config.
@@ -86,20 +90,31 @@ class Themes
return $obj;
}
public function load($name = null)
public function current($name = null)
{
$grav = $this->grav;
/** @var Config $config */
$config = $grav['config'];
$config = $this->grav['config'];
if (!$name) {
$name = $config->get('system.pages.theme');
}
$path = THEMES_DIR . $name;
$file = "{$path}/{$name}.php";
return $name;
}
if (file_exists($file)) {
public function load($name = null)
{
$name = $this->current($name);
$grav = $this->grav;
/** @var Config $config */
$config = $grav['config'];
/** @var ResourceLocator $locator */
$locator = $grav['locator'];
$file = $locator("theme://theme.php") ?: $locator("theme://{$name}.php");
if ($file) {
// Local variables available in the file: $grav, $config, $name, $path, $file
$class = include $file;
@@ -118,4 +133,49 @@ class Themes
return $class;
}
public function configure($name = null) {
$name = $this->current($name);
/** @var Config $config */
$config = $this->grav['config'];
$themeConfig = Yaml::instance(THEMES_DIR . "{$name}/{$name}.yaml")->content();
$config->merge(['themes' => [$name => $themeConfig]]);
/** @var ResourceLocator $locator */
$locator = $this->grav['locator'];
// TODO: move
$registered = stream_get_wrappers();
$schemes = $config->get(
"themes.{$name}.streams.schemes",
['theme' => ['paths' => ["user/themes/{$name}"]]]
);
foreach ($schemes as $scheme => $config) {
if (isset($config['paths'])) {
$locator->addPath($scheme, '', $config['paths']);
}
if (isset($config['prefixes'])) {
foreach ($config['prefixes'] as $prefix => $paths) {
$locator->addPath($scheme, $prefix, $paths);
}
}
if (in_array($scheme, $registered)) {
stream_wrapper_unregister($scheme);
}
$type = !empty($config['type']) ? $config['type'] : 'ReadOnlyStream';
if ($type[0] != '\\') {
$type = '\\Grav\\Component\\Filesystem\\StreamWrapper\\' . $type;
}
if (!stream_wrapper_register($scheme, $type)) {
throw new \InvalidArgumentException("Stream '{$type}' could not be initialized.");
}
}
}
}

View File

@@ -2,6 +2,7 @@
namespace Grav\Common;
use \Grav\Common\Page\Page;
use Grav\Component\Filesystem\ResourceLocator;
/**
* The Twig object handles all the Twig template rendering for Grav. It's a singleton object
@@ -66,8 +67,10 @@ class Twig
if (!isset($this->twig)) {
/** @var Config $config */
$config = $this->grav['config'];
/** @var ResourceLocator $locator */
$locator = $this->grav['locator'];
$this->twig_paths = array(THEMES_DIR . $config->get('system.pages.theme') . '/templates');
$this->twig_paths = $locator->findResources('theme://templates');
$this->grav->fireEvent('onTwigTemplatePaths');
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
@@ -76,7 +79,7 @@ class Twig
$params = $config->get('system.twig');
if (!empty($params['cache'])) {
$params['cache'] = CACHE_DIR;
$params['cache'] = $locator->findResource('cache://');
}
$this->twig = new \Twig_Environment($loader_chain, $params);
@@ -106,12 +109,12 @@ class Twig
'base_dir' => rtrim(ROOT_DIR, '/'),
'base_url_absolute' => $baseUrlAbsolute,
'base_url_relative' => $baseUrlRelative,
'theme_dir' => THEMES_DIR . $theme,
'theme_dir' => $locator->findResource('theme://'),
'theme_url' => $themeUrl,
'site' => $config->get('site'),
'assets' => $this->grav['assets'],
'taxonomy' => $this->grav['taxonomy'],
'user_agent' => $this->grav['user_agent'],
'browser' => $this->grav['browser'],
);
}
@@ -181,24 +184,6 @@ class Twig
return $output;
}
/**
* @param string $string string to render.
* @param array $vars Optional variables
* @return string
*/
public function processString($string, array $vars = array())
{
// override the twig header vars for local resolution
$this->grav->fireEvent('onTwigStringVariables');
$vars += $this->twig_vars;
$name = '@Var:' . $string;
$this->setTemplate($name, $string);
$output = $this->twig->render($name, $vars);
return $output;
}
/**
* 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.

View File

@@ -1,5 +1,6 @@
<?php
namespace Grav\Common;
use Grav\Component\Filesystem\ResourceLocator;
/**
* The Twig extension adds some filters and functions that are useful for Grav
@@ -44,7 +45,8 @@ class TwigExtension extends \Twig_Extension
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('repeat', array($this, 'repeatFunc'))
new \Twig_SimpleFunction('repeat', array($this, 'repeatFunc')),
new \Twig_SimpleFunction('url', array($this, 'urlFunc'))
);
}
@@ -195,4 +197,22 @@ class TwigExtension extends \Twig_Extension
{
return str_repeat($input, $multiplier);
}
/**
* Return URL to the resource.
*
* @param string $input
* @param bool $domain
* @return string
*/
public function urlFunc($input, $domain = false)
{
$grav = Grav::instance();
/** @var ResourceLocator $locator */
$locator = $grav['locator'];
/** @var Uri $uri */
$uri = $grav['uri'];
return $uri->rootUrl($domain) . $locator->findResource($input, false);
}
}

View File

@@ -82,7 +82,7 @@ class Uri
// remove the extension if there is one set
$parts = pathinfo($uri);
if (strpos($parts['basename'], '.')) {
if (preg_match("/\.(txt|xml|html|json|rss|atom)$/", $parts['basename'])) {
$uri = rtrim($parts['dirname'], '/').'/'.$parts['filename'];
$this->extension = $parts['extension'];
}

View File

@@ -11,6 +11,8 @@ use Grav\Component\Data\Data;
*/
class User extends Data
{
protected $password;
/**
* Authenticate user.
*

View File

@@ -109,7 +109,7 @@ class Markdown extends General
$var = preg_replace("/(\r\n|\r)/", "\n", $var);
// Parse header.
preg_match("/---\n(.+?)\n---(\n\n|$)/uism", $this->raw(), $m);
preg_match("/---\n(.+?)\n---(\n\n|$)/uism", $var, $m);
$content['header'] = isset($m[1]) ? YamlParser::parse(preg_replace("/\n\t/", "\n ", $m[1])) : array();
// Strip header to get content.

View File

@@ -15,13 +15,13 @@ abstract class Folder
* @param string $path
* @return int
*/
public static function lastModified($path)
public static function lastModifiedFolder($path)
{
$last_modified = 0;
$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();
@@ -43,6 +43,34 @@ abstract class Folder
return $to;
}
/**
* Recursively find the last modified time under given path by file.
*
* @param string $path
* @return int
*/
public static function lastModifiedFile($path)
{
$last_modified = 0;
$dirItr = new \RecursiveDirectoryIterator($path);
$filterItr = new GravRecursiveFilterIterator($dirItr);
$itr = new \RecursiveIteratorIterator($filterItr, \RecursiveIteratorIterator::SELF_FIRST);
/** @var \RecursiveDirectoryIterator $file */
foreach ($itr as $file) {
if (!$file->isDir()) {
$file_modified = $file->getMTime();
if ($file_modified > $last_modified) {
$last_modified = $file_modified;
}
}
}
return $last_modified;
}
/**
* Return recursive list of all files and directories under given path.
*
@@ -237,3 +265,19 @@ abstract class Folder
}
}
}
class GravRecursiveFilterIterator extends \RecursiveFilterIterator {
public static $FILTERS = array(
'.', '..', '.DS_Store'
);
public function accept() {
return !in_array(
$this->current()->getFilename(),
self::$FILTERS,
true
);
}
}

View File

@@ -87,13 +87,14 @@ class ResourceLocator
$scheme = 'file';
}
if (!$file || $uri[0] == ':') {
throw new \InvalidArgumentException('Invalid resource URI');
}
if (!isset($this->schemes[$scheme])) {
throw new \InvalidArgumentException("Invalid resource {$scheme}://");
}
if (!$file && $scheme == 'file') {
$file = getcwd();
}
return [$file, $scheme];
}

View File

@@ -26,6 +26,13 @@ class CleanCommand extends Command {
'user/plugins/email/vendor/swiftmailer/swiftmailer/notes',
'user/plugins/email/vendor/swiftmailer/swiftmailer/doc',
'user/themes/antimatter/.sass-cache',
'vendor/donatj/phpuseragentparser/.git',
'vendor/donatj/phpuseragentparser/.gitignore',
'vendor/donatj/phpuseragentparser/.travis.yml',
'vendor/donatj/phpuseragentparser/composer.json',
'vendor/donatj/phpuseragentparser/phpunit.xml.dist',
'vendor/donatj/phpuseragentparser/Tests',
'vendor/donatj/phpuseragentparser/Tools',
'vendor/doctrine/cache/.travis.yml',
'vendor/doctrine/cache/build.properties',
'vendor/doctrine/cache/build.xml',
@@ -74,11 +81,6 @@ class CleanCommand extends Command {
'vendor/mrclay/minify/min/quick-test.css',
'vendor/mrclay/minify/min/quick-test.js',
'vendor/mrclay/minify/min/utils.php',
'vendor/ornicar/php-user-agent/.git',
'vendor/ornicar/php-user-agent/.gitignore',
'vendor/ornicar/php-user-agent/composer.json',
'vendor/ornicar/php-user-agent/prove.php',
'vendor/ornicar/php-user-agent/test',
'vendor/pimple/pimple/.gitignore',
'vendor/pimple/pimple/.travis.yml',
'vendor/pimple/pimple/composer.json',

View File

@@ -3,18 +3,15 @@ home:
pages:
theme: antimatter
markdown_extra: true
markdown_extra: false
process:
markdown: true
twig: false
events:
page: false
twig: true
cache:
enabled: true
check:
pages: true
method: file
driver: auto
prefix: 'g'
@@ -25,7 +22,7 @@ twig:
autoescape: false
assets:
css_pipeline: false
css_pipeline: true
css_minify: true
css_rewrite: true
js_pipeline: false

View File

@@ -0,0 +1,5 @@
---
title: Test
---
# Testing

View File

@@ -1,6 +1,7 @@
The MIT License
===============
Copyright (c) 2010 Thibault Duplessis
Copyright (c) 2013 Jesse G. Donat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.

View File

@@ -0,0 +1,110 @@
# PHP User Agent Parser
[![Latest Stable Version](https://poser.pugx.org/donatj/phpuseragentparser/v/stable.png)](https://packagist.org/packages/donatj/phpuseragentparser) [![Total Downloads](https://poser.pugx.org/donatj/phpuseragentparser/downloads.png)](https://packagist.org/packages/donatj/phpuseragentparser) [![Latest Unstable Version](https://poser.pugx.org/donatj/phpuseragentparser/v/unstable.png)](https://packagist.org/packages/donatj/phpuseragentparser) [![License](https://poser.pugx.org/donatj/phpuseragentparser/license.png)](https://packagist.org/packages/donatj/phpuseragentparser)
[![Build Status](https://travis-ci.org/donatj/PhpUserAgent.png?branch=master)](https://travis-ci.org/donatj/PhpUserAgent)
[![HHVM Status](http://hhvm.h4cc.de/badge/donatj/phpuseragentparser.png)](http://hhvm.h4cc.de/package/donatj/phpuseragentparser) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/donatj/PhpUserAgent/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/donatj/PhpUserAgent/?branch=master)
## What It Is
A simple, streamlined PHP user-agent parser!
Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
## Why Use This
You have your choice in user-agent parsers. This one detects **all modern browsers** in a very light, quick, understandable fashion.
It is less than 150 lines of code, and consists of just two regular expressions!
It can also correctly identify exotic versions of IE others fail on.
It offers 100% unit test coverage, is installable via Composer, and is very easy to use.
## What It Doesn't Do
### OS Versions
User-agent strings **are not** a reliable source of OS Version!
- Many agents simply don't send the information.
- Others provide varying levels of accuracy.
- Parsing Windows versions alone almost nearly doubles the size of the code.
I'm much more interested in keeping this thing *tiny* and accurate than adding niché features and would rather focus on things that can be **done well**.
All that said, there is the start of a [branch to do it](https://github.com/donatj/PhpUserAgent/tree/os_version_detection) I created for a client if you want to poke it, I update it from time to time, but frankly if you need to *reliably detect OS Version*, using user-agent isn't the way to do it. I'd go with JavaScript.
## Requirements
- PHP 5.3.0+
## Installing
PHP User Agent is available through Packagist via Composer.
```json
{
"require": {
"donatj/phpuseragentparser": "*"
}
}
```
## Sample Usage
```php
$ua_info = parse_user_agent();
/*
array(
'platform' => '[Detected Platform]',
'browser' => '[Detected Browser]',
'version' => '[Detected Browser Version]',
);
*/
```
## Currently Detected Platforms
- Desktop
- Windows
- Linux
- Macintosh
- Chrome OS
- Mobile
- Android
- iPhone
- iPad
- Windows Phone OS
- Kindle
- Kindle Fire
- BlackBerry
- Playbook
- Console
- Nintendo 3DS
- Nintendo Wii
- Nintendo WiiU
- PlayStation 3
- PlayStation 4
- PlayStation Vita
- Xbox 360
- Xbox One
## Currently Detected Browsers
- Android Browser
- BlackBerry Browser
- Camino
- Kindle / Silk
- Firefox / Iceweasel
- Safari
- Internet Explorer
- IEMobile
- Chrome
- Opera
- Midori
- Lynx
- Wget
- Curl
More information is available at [Donat Studios](http://donatstudios.com/PHP-Parser-HTTP_USER_AGENT).

View File

@@ -0,0 +1,144 @@
<?php
/**
* Parses a user agent string into its important parts
*
* @author Jesse G. Donat <donatj@gmail.com>
* @link https://github.com/donatj/PhpUserAgent
* @link http://donatstudios.com/PHP-Parser-HTTP_USER_AGENT
* @param string|null $u_agent User agent string to parse or null. Uses $_SERVER['HTTP_USER_AGENT'] on NULL
* @throws InvalidArgumentException on not having a proper user agent to parse.
* @return array an array with browser, version and platform keys
*/
function parse_user_agent( $u_agent = null ) {
if( is_null($u_agent) ) {
if( isset($_SERVER['HTTP_USER_AGENT']) ) {
$u_agent = $_SERVER['HTTP_USER_AGENT'];
} else {
throw new \InvalidArgumentException('parse_user_agent requires a user agent');
}
}
$platform = null;
$browser = null;
$version = null;
$empty = array( 'platform' => $platform, 'browser' => $browser, 'version' => $version );
if( !$u_agent ) return $empty;
if( preg_match('/\((.*?)\)/im', $u_agent, $parent_matches) ) {
preg_match_all('/(?P<platform>BB\d+;|Android|CrOS|iPhone|iPad|Linux|Macintosh|Windows(\ Phone)?|Silk|linux-gnu|BlackBerry|PlayBook|Nintendo\ (WiiU?|3DS)|Xbox(\ One)?)
(?:\ [^;]*)?
(?:;|$)/imx', $parent_matches[1], $result, PREG_PATTERN_ORDER);
$priority = array( 'Android', 'Xbox One', 'Xbox' );
$result['platform'] = array_unique($result['platform']);
if( count($result['platform']) > 1 ) {
if( $keys = array_intersect($priority, $result['platform']) ) {
$platform = reset($keys);
} else {
$platform = $result['platform'][0];
}
} elseif( isset($result['platform'][0]) ) {
$platform = $result['platform'][0];
}
}
if( $platform == 'linux-gnu' ) {
$platform = 'Linux';
} elseif( $platform == 'CrOS' ) {
$platform = 'Chrome OS';
}
preg_match_all('%(?P<browser>Camino|Kindle(\ Fire\ Build)?|Firefox|Iceweasel|Safari|MSIE|Trident/.*rv|AppleWebKit|Chrome|IEMobile|Opera|OPR|Silk|Lynx|Midori|Version|Wget|curl|NintendoBrowser|PLAYSTATION\ (\d|Vita)+)
(?:\)?;?)
(?:(?:[:/ ])(?P<version>[0-9A-Z.]+)|/(?:[A-Z]*))%ix',
$u_agent, $result, PREG_PATTERN_ORDER);
// If nothing matched, return null (to avoid undefined index errors)
if( !isset($result['browser'][0]) || !isset($result['version'][0]) ) {
return $empty;
}
$browser = $result['browser'][0];
$version = $result['version'][0];
$find = function ( $search, &$key ) use ( $result ) {
$xkey = array_search(strtolower($search), array_map('strtolower', $result['browser']));
if( $xkey !== false ) {
$key = $xkey;
return true;
}
return false;
};
$key = 0;
if( $browser == 'Iceweasel' ) {
$browser = 'Firefox';
} elseif( $find('Playstation Vita', $key) ) {
$platform = 'PlayStation Vita';
$browser = 'Browser';
} elseif( $find('Kindle Fire Build', $key) || $find('Silk', $key) ) {
$browser = $result['browser'][$key] == 'Silk' ? 'Silk' : 'Kindle';
$platform = 'Kindle Fire';
if( !($version = $result['version'][$key]) || !is_numeric($version[0]) ) {
$version = $result['version'][array_search('Version', $result['browser'])];
}
} elseif( $find('NintendoBrowser', $key) || $platform == 'Nintendo 3DS' ) {
$browser = 'NintendoBrowser';
$version = $result['version'][$key];
} elseif( $find('Kindle', $key) ) {
$browser = $result['browser'][$key];
$platform = 'Kindle';
$version = $result['version'][$key];
} elseif( $find('OPR', $key) ) {
$browser = 'Opera Next';
$version = $result['version'][$key];
} elseif( $find('Opera', $key) ) {
$browser = 'Opera';
$find('Version', $key);
$version = $result['version'][$key];
} elseif( $find('Midori', $key) ) {
$browser = 'Midori';
$version = $result['version'][$key];
} elseif( $browser == 'MSIE' || strpos($browser, 'Trident') !== false ) {
if( $find('IEMobile', $key) ) {
$browser = 'IEMobile';
} else {
$browser = 'MSIE';
$key = 0;
}
$version = $result['version'][$key];
} elseif( $find('Chrome', $key) ) {
$browser = 'Chrome';
$version = $result['version'][$key];
} elseif( $browser == 'AppleWebKit' ) {
if( ($platform == 'Android' && !($key = 0)) ) {
$browser = 'Android Browser';
} elseif( strpos($platform, 'BB') === 0 ) {
$browser = 'BlackBerry Browser';
$platform = 'BlackBerry';
} elseif( $platform == 'BlackBerry' || $platform == 'PlayBook' ) {
$browser = 'BlackBerry Browser';
} elseif( $find('Safari', $key) ) {
$browser = 'Safari';
}
$find('Version', $key);
$version = $result['version'][$key];
} elseif( $key = preg_grep('/playstation \d/i', array_map('strtolower', $result['browser'])) ) {
$key = reset($key);
$platform = 'PlayStation ' . preg_replace('/[^\d]/i', '', $key);
$browser = 'NetFront';
}
return array( 'platform' => $platform, 'browser' => $browser, 'version' => $version );
}

View File

@@ -1,5 +1,8 @@
Minify Release History
(master)
* Builder styled with Bootstrap (thanks to help from acidvertigo)
Version 2.2.0
* Fix handling of RegEx in certain situations in JSMin
* Thanks to Vovan-VE for reporting this

View File

@@ -325,6 +325,10 @@ class CSSmin
// @media screen and (-webkit-min-device-pixel-ratio:0){
$css = preg_replace('/\band\(/i', 'and (', $css);
// Put the space back in for @support tag
// @supports (display: flex) and @supports not (display: flex)
$css = preg_replace('/\b(supports|not)\(/i', '$1 (', $css);
// Remove the spaces after the things that should not have spaces after them.
$css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css);
@@ -772,4 +776,4 @@ class CSSmin
return (int) $size;
}
}
}

View File

@@ -70,7 +70,7 @@ class Minify_CSS_UriRewriter {
// rewrite
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
,array(self::$className, '_processUriCB'), $css);
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
$css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
,array(self::$className, '_processUriCB'), $css);
return $css;
@@ -94,7 +94,7 @@ class Minify_CSS_UriRewriter {
// append
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
,array(self::$className, '_processUriCB'), $css);
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
$css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
,array(self::$className, '_processUriCB'), $css);
self::$_prependPath = null;

View File

@@ -98,6 +98,9 @@ class Minify_Cache_File {
{
if ($this->_locking) {
$fp = fopen($this->_path . '/' . $id, 'rb');
if (!$fp) {
return false;
}
flock($fp, LOCK_SH);
$ret = stream_get_contents($fp);
flock($fp, LOCK_UN);

View File

@@ -33,6 +33,11 @@
*/
class Minify_ClosureCompiler {
const OPTION_CHARSET = 'charset';
const OPTION_COMPILATION_LEVEL = 'compilation_level';
public static $isDebug = false;
/**
* Filepath of the Closure Compiler jar file. This must be set before
* calling minifyJs().
@@ -65,18 +70,28 @@ class Minify_ClosureCompiler {
* @see https://code.google.com/p/closure-compiler/source/browse/trunk/README
*
* @return string
*
* @throws Minify_ClosureCompiler_Exception
*/
public static function minify($js, $options = array())
{
self::_prepare();
if (! ($tmpFile = tempnam(self::$tempDir, 'cc_'))) {
throw new Exception('Minify_ClosureCompiler : could not create temp file in "'.self::$tempDir.'".');
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : could not create temp file in "'.self::$tempDir.'".');
}
file_put_contents($tmpFile, $js);
exec(self::_getCmd($options, $tmpFile), $output, $result_code);
$cmd = self::_getCmd($options, $tmpFile);
exec($cmd, $output, $result_code);
unlink($tmpFile);
if ($result_code != 0) {
throw new Exception('Minify_ClosureCompiler : Closure Compiler execution failed.');
$message = 'Minify_ClosureCompiler : Closure Compiler execution failed.';
if (self::$isDebug) {
exec($cmd . ' 2>&1', $error);
if ($error) {
$message .= "\nReason:\n" . join("\n", $error);
}
}
throw new Minify_ClosureCompiler_Exception($message);
}
return implode("\n", $output);
}
@@ -85,17 +100,18 @@ class Minify_ClosureCompiler {
{
$o = array_merge(
array(
'charset' => 'utf-8',
'compilation_level' => 'SIMPLE_OPTIMIZATIONS',
self::OPTION_CHARSET => 'utf-8',
self::OPTION_COMPILATION_LEVEL => 'SIMPLE_OPTIMIZATIONS',
),
$userOptions
);
$charsetOption = $o[self::OPTION_CHARSET];
$cmd = self::$javaExecutable . ' -jar ' . escapeshellarg(self::$jarFile)
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset'])
? " --charset {$o['charset']}"
. (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $charsetOption)
? " --charset {$charsetOption}"
: '');
foreach (array('compilation_level') as $opt) {
foreach (array(self::OPTION_COMPILATION_LEVEL) as $opt) {
if ($o[$opt]) {
$cmd .= " --{$opt} ". escapeshellarg($o[$opt]);
}
@@ -106,18 +122,18 @@ class Minify_ClosureCompiler {
private static function _prepare()
{
if (! is_file(self::$jarFile)) {
throw new Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not a valid file.');
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not a valid file.');
}
if (! is_readable(self::$jarFile)) {
throw new Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not readable.');
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $jarFile('.self::$jarFile.') is not readable.');
}
if (! is_dir(self::$tempDir)) {
throw new Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not a valid direcotry.');
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not a valid direcotry.');
}
if (! is_writable(self::$tempDir)) {
throw new Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not writable.');
throw new Minify_ClosureCompiler_Exception('Minify_ClosureCompiler : $tempDir('.self::$tempDir.') is not writable.');
}
}
}
/* vim:ts=4:sw=4:et */
class Minify_ClosureCompiler_Exception extends Exception {}

View File

@@ -1,9 +0,0 @@
# CHANGELOG
### 1.0.0 (2013-08-09)
* Add a version following semver spec.
### v1.0 - 2010-04-13
* create initial 1.0 version

View File

@@ -1,92 +0,0 @@
# PHP User Agent
Browser detection in PHP5.
Uses a simple and fast algorithm to recognize major browsers.
## Overview
``` php
$userAgent = new phpUserAgent();
$userAgent->getBrowserName() // firefox
$userAgent->getBrowserVersion() // 3.6
$userAgent->getOperatingSystem() // linux
$userAgent->getEngine() // gecko
```
### Why you should use it
PHP provides a native function to detect user browser: [get_browser()](http://us2.php.net/manual/en/function.get-browser.php).
get_browser() requires the "browscap.ini" file which is 300KB+.
Loading and processing this file impact script performance.
And sometimes, the production server just doesn't provide browscap.ini.
Although get_browser() surely provides excellent detection results, in most
cases a much simpler method can be just as effective.
php-user-agent has the advantage of being compact and easy to extend.
It is performant as well, since it doesn't do any iteration or recursion.
## Usage
``` php
// include classes or rely on Composer autoloader
require_once '/path/to/php-user-agent/phpUserAgent.php';
require_once '/path/to/php-user-agent/phpUserAgentStringParser.php';
// Create a user agent
$userAgent = new phpUserAgent();
// Interrogate the user agent
$userAgent->getBrowserName() // firefox
$userAgent->getBrowserVersion() // 3.6
$userAgent->getOperatingSystem() // linux
$userAgent->getEngine() // gecko
```
## Advanced
### Custom user agent string
When you create a phpUserAgent object, the current user agent string is used.
You can specify another user agent string:
``` php
// use another user agent string
$userAgent = new phpUserAgent('msnbot/2.0b (+http://search.msn.com/msnbot.htm)');
$userAgent->getBrowserName() // msnbot
// use current user agent string
$userAgent = new phpUserAgent($_SERVER['HTTP_USER_AGENT');
// this is equivalent to:
$userAgent = new phpUserAgent();
```
### Custom parser class
By default, phpUserAgentStringParser is used to analyse the user agent string.
You can replace the parser instance and customize it to match your needs:
``` php
// create a custom user agent string parser
class myUserAgentStringParser extends phpUserAgentStringParser
{
// override methods
}
// inject the custom parser when creating a user agent:
$userAgent = new phpUserAgent(null, new myUserAgentStringParser());
```
## Run tests
You can run the unit tests on your server:
``` bash
$ php prove.php
```
## Contribute
If you found a browser of operating system this library fails to recognize,
feel free to submit an issue. Please provide the user agent string.
And well, if you also want to provide the patch, it's even better.

View File

@@ -1,194 +0,0 @@
<?php
/**
* Simple PHP User agent
*
* @link http://github.com/ornicar/php-user-agent
* @version 1.0
* @author Thibault Duplessis <thibault.duplessis at gmail dot com>
* @license MIT License
*
* Documentation: http://github.com/ornicar/php-user-agent/blob/master/README.markdown
* Tickets: http://github.com/ornicar/php-user-agent/issues
*/
class phpUserAgent
{
protected $userAgentString;
protected $browserName;
protected $browserVersion;
protected $operatingSystem;
protected $engine;
public function __construct($userAgentString = null, phpUserAgentStringParser $userAgentStringParser = null)
{
$this->configureFromUserAgentString($userAgentString, $userAgentStringParser);
}
/**
* Get the browser name
*
* @return string the browser name
*/
public function getBrowserName()
{
return $this->browserName;
}
/**
* Set the browser name
*
* @param string $name the browser name
*/
public function setBrowserName($name)
{
$this->browserName = $name;
}
/**
* Get the browser version
*
* @return string the browser version
*/
public function getBrowserVersion()
{
return $this->browserVersion;
}
/**
* Set the browser version
*
* @param string $version the browser version
*/
public function setBrowserVersion($version)
{
$this->browserVersion = $version;
}
/**
* Get the operating system name
*
* @return string the operating system name
*/
public function getOperatingSystem()
{
return $this->operatingSystem;
}
/**
* Set the operating system name
*
* @param string $operatingSystem the operating system name
*/
public function setOperatingSystem($operatingSystem)
{
$this->operatingSystem = $operatingSystem;
}
/**
* Get the engine name
*
* @return string the engine name
*/
public function getEngine()
{
return $this->engine;
}
/**
* Set the engine name
*
* @param string $operatingSystem the engine name
*/
public function setEngine($engine)
{
$this->engine = $engine;
}
/**
* Get the user agent string
*
* @return string the user agent string
*/
public function getUserAgentString()
{
return $this->userAgentString;
}
/**
* Set the user agent string
*
* @param string $userAgentString the user agent string
*/
public function setUserAgentString($userAgentString)
{
$this->userAgentString = $userAgentString;
}
/**
* Tell whether this user agent is unknown or not
*
* @return boolean true if this user agent is unknown, false otherwise
*/
public function isUnknown()
{
return empty($this->browserName);
}
/**
* @return string combined browser name and version
*/
public function getFullName()
{
return $this->getBrowserName().' '.$this->getBrowserVersion();
}
public function __toString()
{
return $this->getFullName();
}
/**
* Configure the user agent from a user agent string
* @param string $userAgentString the user agent string
* @param phpUserAgentStringParser $userAgentStringParser the parser used to parse the string
*/
public function configureFromUserAgentString($userAgentString, phpUserAgentStringParser $userAgentStringParser = null)
{
if(null === $userAgentStringParser)
{
$userAgentStringParser = new phpUserAgentStringParser();
}
$this->setUserAgentString($userAgentString);
$this->fromArray($userAgentStringParser->parse($userAgentString));
}
/**
* Convert the user agent to a data array
*
* @return array data
*/
public function toArray()
{
return array(
'browser_name' => $this->getBrowserName(),
'browser_version' => $this->getBrowserVersion(),
'operating_system' => $this->getOperatingSystem()
);
}
/**
* Configure the user agent from a data array
*
* @param array $data
*/
public function fromArray(array $data)
{
$this->setBrowserName($data['browser_name']);
$this->setBrowserVersion($data['browser_version']);
$this->setOperatingSystem($data['operating_system']);
$this->setEngine($data['engine']);
}
}

View File

@@ -1,321 +0,0 @@
<?php
/**
* Simple PHP User Agent string parser
*/
class phpUserAgentStringParser
{
/**
* Parse a user agent string.
*
* @param string $userAgentString defaults to $_SERVER['HTTP_USER_AGENT'] if empty
* @return array ( the user agent informations
* 'browser_name' => 'firefox',
* 'browser_version' => '3.6',
* 'operating_system' => 'linux'
* )
*/
public function parse($userAgentString = null)
{
// use current user agent string as default
if(!$userAgentString)
{
$userAgentString = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
}
// parse quickly (with medium accuracy)
$informations = $this->doParse($userAgentString);
// run some filters to increase accuracy
foreach($this->getFilters() as $filter)
{
$this->$filter($informations);
}
return $informations;
}
/**
* Detect quickly informations from the user agent string
*
* @param string $userAgentString user agent string
* @return array user agent informations array
*/
protected function doParse($userAgentString)
{
$userAgent = array(
'string' => $this->cleanUserAgentString($userAgentString),
'browser_name' => null,
'browser_version' => null,
'operating_system' => null,
'engine' => null
);
if(empty($userAgent['string']))
{
return $userAgent;
}
// build regex that matches phrases for known browsers
// (e.g. "Firefox/2.0" or "MSIE 6.0" (This only matches the major and minor
// version numbers. E.g. "2.0.0.6" is parsed as simply "2.0"
$pattern = '#('.join('|', $this->getKnownBrowsers()).')[/ ]+([0-9]+(?:\.[0-9]+)?)#';
// Find all phrases (or return empty array if none found)
if (preg_match_all($pattern, $userAgent['string'], $matches))
{
// Since some UAs have more than one phrase (e.g Firefox has a Gecko phrase,
// Opera 7,8 have a MSIE phrase), use the last one found (the right-most one
// in the UA). That's usually the most correct.
$i = count($matches[1])-1;
if (isset($matches[1][$i]))
{
$userAgent['browser_name'] = $matches[1][$i];
}
if (isset($matches[2][$i]))
{
$userAgent['browser_version'] = $matches[2][$i];
}
}
// Find operating system
$pattern = '#'.join('|', $this->getKnownOperatingSystems()).'#';
if (preg_match($pattern, $userAgent['string'], $match))
{
if (isset($match[0]))
{
$userAgent['operating_system'] = $match[0];
}
}
// Find engine
$pattern = '#'.join('|', $this->getKnownEngines()).'#';
if (preg_match($pattern, $userAgent['string'], $match))
{
if (isset($match[0]))
{
$userAgent['engine'] = $match[0];
}
}
return $userAgent;
}
/**
* Make user agent string lowercase, and replace browser aliases
*
* @param string $userAgentString the dirty user agent string
* @return string the clean user agent string
*/
public function cleanUserAgentString($userAgentString)
{
// clean up the string
$userAgentString = trim(strtolower($userAgentString));
// replace browser names with their aliases
$userAgentString = strtr($userAgentString, $this->getKnownBrowserAliases());
// replace operating system names with their aliases
$userAgentString = strtr($userAgentString, $this->getKnownOperatingSystemAliases());
// replace engine names with their aliases
$userAgentString = strtr($userAgentString, $this->getKnownEngineAliases());
return $userAgentString;
}
/**
* Get the list of filters that get called when parsing a user agent
*
* @return array list of valid callables
*/
public function getFilters()
{
return array(
'filterAndroid',
'filterGoogleChrome',
'filterSafariVersion',
'filterOperaVersion',
'filterYahoo',
'filterMsie',
);
}
/**
* Add a filter to be called when parsing a user agent
*
* @param string $filter name of the filter method
*/
public function addFilter($filter)
{
$this->filters += $filter;
}
/**
* Get known browsers
*
* @return array the browsers
*/
protected function getKnownBrowsers()
{
return array(
'msie',
'firefox',
'safari',
'webkit',
'opera',
'netscape',
'konqueror',
'gecko',
'chrome',
'googlebot',
'iphone',
'msnbot',
'applewebkit'
);
}
/**
* Get known browser aliases
*
* @return array the browser aliases
*/
protected function getKnownBrowserAliases()
{
return array(
'shiretoko' => 'firefox',
'namoroka' => 'firefox',
'shredder' => 'firefox',
'minefield' => 'firefox',
'granparadiso' => 'firefox'
);
}
/**
* Get known operating system
*
* @return array the operating systems
*/
protected function getKnownOperatingSystems()
{
return array(
'windows',
'macintosh',
'linux',
'freebsd',
'unix',
'iphone'
);
}
/**
* Get known operating system aliases
*
* @return array the operating system aliases
*/
protected function getKnownOperatingSystemAliases()
{
return array();
}
/**
* Get known engines
*
* @return array the engines
*/
protected function getKnownEngines()
{
return array(
'gecko',
'webkit',
'trident',
'presto'
);
}
/**
* Get known engines aliases
*
* @return array the engines aliases
*/
protected function getKnownEngineAliases()
{
return array();
}
/**
* Filters
*/
/**
* Google chrome has a safari like signature
*/
protected function filterGoogleChrome(array &$userAgent)
{
if ('safari' === $userAgent['browser_name'] && strpos($userAgent['string'], 'chrome/'))
{
$userAgent['browser_name'] = 'chrome';
$userAgent['browser_version'] = preg_replace('|.+chrome/([0-9]+(?:\.[0-9]+)?).+|', '$1', $userAgent['string']);
}
}
/**
* Safari version is not encoded "normally"
*/
protected function filterSafariVersion(array &$userAgent)
{
if ('safari' === $userAgent['browser_name'] && strpos($userAgent['string'], ' version/'))
{
$userAgent['browser_version'] = preg_replace('|.+\sversion/([0-9]+(?:\.[0-9]+)?).+|', '$1', $userAgent['string']);
}
}
/**
* Opera 10.00 (and higher) version number is located at the end
*/
protected function filterOperaVersion(array &$userAgent)
{
if('opera' === $userAgent['browser_name'] && strpos($userAgent['string'], ' version/'))
{
$userAgent['browser_version'] = preg_replace('|.+\sversion/([0-9]+\.[0-9]+)\s*.*|', '$1', $userAgent['string']);
}
}
/**
* Yahoo bot has a special user agent string
*/
protected function filterYahoo(array &$userAgent)
{
if (null === $userAgent['browser_name'] && strpos($userAgent['string'], 'yahoo! slurp'))
{
$userAgent['browser_name'] = 'yahoobot';
}
}
/**
* MSIE does not always declare its engine
*/
protected function filterMsie(array &$userAgent)
{
if ('msie' === $userAgent['browser_name'] && empty($userAgent['engine']))
{
$userAgent['engine'] = 'trident';
}
}
/**
* Android has a safari like signature
*/
protected function filterAndroid(array &$userAgent) {
if ('safari' === $userAgent['browser_name'] && strpos($userAgent['string'], 'android ')) {
$userAgent['browser_name'] = 'android';
$userAgent['operating_system'] = 'android';
$userAgent['browser_version'] = preg_replace('|.+android ([0-9]+(?:\.[0-9]+)+).+|', '$1', $userAgent['string']);
}
}
}