Merge branch 'release/0.9.34'

This commit is contained in:
Andy Miller
2015-08-04 16:53:32 -06:00
39 changed files with 1292 additions and 439 deletions

View File

@@ -1,3 +1,23 @@
# v0.9.34
## 08/04/2015
1. [](#new)
* Added new `cache_all` system setting + media `cache()` method
* Added base languages configuration
* Added property language to page to help plugins identify page language
* New `Utils::arrayFilterRecursive()` method
2. [](#improved)
* Improved Session handling to support site and admin independently
* Allow Twig variables to be modified in other events
* Blueprint updates in preparation for Admin plugin
* Changed `Inflector` from static to object and added multi-language support
* Support for admin override of a page's blueprints
3. [](#bugfix)
* Removed unused `use` in `VideoMedium` that was causing error
* Array fix in `User.authorise()` method
* Fix for typo in `translations_fallback`
* Fixed moving page to the root
# v0.9.33
## 07/21/2015

View File

@@ -9,9 +9,9 @@
"php": ">=5.4.0",
"twig/twig": "~1.16",
"erusev/parsedown-extra": "~0.7",
"symfony/yaml": "2.7.*",
"symfony/console": "2.7.*",
"symfony/event-dispatcher": "2.7.*",
"symfony/yaml": "2.7.3",
"symfony/console": "2.7.3",
"symfony/event-dispatcher": "2.7.3",
"doctrine/cache": "~1.4",
"maximebf/debugbar": "dev-master",
"filp/whoops": "1.2.*@dev",

View File

@@ -13,60 +13,101 @@ form:
label: Site Title
size: large
placeholder: "Site wide title"
help: Default title for your site
help: "Default title for your site, often used in themes"
author.name:
type: text
size: large
label: Default Author
help: "A default author name, often used in themes or page content"
author.email:
type: text
size: large
label: Default Email
help: "A default email to reference in themes or pages"
validate:
type: email
taxonomies:
type: text
type: selectize
size: large
label: Taxonomy Types
classes: fancy
help: "Taxonomy types must be defined here if you wish to use them in pages"
validate:
type: commalist
metadata:
type: array
label: Metadata
placeholder_key: Name
placeholder_value: Content
blog:
summary:
type: section
title: Blog
title: Page Summary
fields:
blog.route:
type: text
size: large
label: Blog URL
summary.enabled:
type: toggle
label: Enabled
highlight: 1
help: "Enable page summary (the summary returns the same as the page content)"
options:
1: Yes
0: No
validate:
type: bool
summary.size:
type: text
size: x-small
label: Summary Size
help: "The amount of characters of a page to use as a content summary"
validate:
type: int
min: 0
max: 65536
routes:
summary.format:
type: toggle
label: Format
classes: fancy
help: "short = use the first occurrence of delimiter or size; long = summary delimiter will be ignored"
highlight: short
options:
'short': 'Short'
'long': 'Long'
summary.delimiter:
type: text
size: x-small
label: Delimiter
help: "The summary delimiter (default '===')"
metadata:
type: section
title: Routes
title: Metadata
fields:
metadata:
type: array
label: Metadata
help: "Default metadata values that will be displayed on every page unless overridden by the page"
placeholder_key: Name
placeholder_value: Content
routes:
type: section
title: Redirects & Routes
fields:
redirects:
type: array
label: Custom Redirects
help: "routes to redirect to other pages. Standard Regex replacement is valid"
placeholder_key: /your/alias
placeholder_value: /your/redirect
routes:
type: array
label: Custom
label: Custom Routes
help: "routes to alias to other pages. Standard Regex replacement is valid"
placeholder_key: /your/alias
placeholder_value: /your/route

View File

@@ -14,7 +14,7 @@ form:
type: pages
size: medium
classes: fancy
label: Home Page
label: Home page
show_all: false
show_modular: false
show_root: false
@@ -23,61 +23,65 @@ form:
pages.theme:
type: themeselect
classes: fancy
selectize: true
size: medium
label: Default Theme
help: "Set the theme (defaults to 'default')"
pages.markdown.extra:
type: toggle
label: Markdown Extra
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
label: Default theme
help: "Set the default theme for Grav to use (default is Antimatter)"
pages.process:
type: checkboxes
label: Process
help: "Control how pages are processed. Can be set per-page rather than globally"
default: [markdown: true, twig: true]
options:
markdown: Markdown
twig: Twig
use: keys
timezone:
type: select
label: Timezone
size: medium
classes: fancy
help: "Override the default timezone the server"
@data-options: '\Grav\Common\Utils::timezones'
default: ''
options:
'': 'Default (Server Timezone)'
pages.dateformat.short:
type: select
size: medium
classes: fancy
label: Short Date Format
help: "Set the short date format"
default: 'jS M Y'
label: Short date format
help: "Set the short date format that can be used by themes"
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"
"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
size: medium
classes: fancy
label: Long Date Format
help: "Set the long date format"
label: Long date format
help: "Set the long date format that can be used by themes"
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"
"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.order.by:
type: select
size: medium
classes: fancy
label: Default Ordering
label: Default ordering
help: "Pages in a list will render using this order unless it is overridden"
options:
default: Default - based on folder name
folder: Folder - based on prefix-less folder name
@@ -86,9 +90,10 @@ form:
pages.order.dir:
type: toggle
label: Default Order Direction
label: Default order direction
highlight: asc
default: desc
help: "The direction of pages in a list"
options:
asc: Ascending
desc: Descending
@@ -96,15 +101,16 @@ form:
pages.list.count:
type: text
size: x-small
label: Default Item Count
help: "Default max pages count"
label: Default page count
help: "Default maximum pages count in a list"
validate:
type: number
min: 1
pages.publish_dates:
type: toggle
label: Date-based publishing
help: Automatically (un)publish posts based on their date
help: "Automatically (un)publish posts based on their date"
highlight: 1
options:
1: Yes
@@ -112,17 +118,47 @@ form:
validate:
type: bool
pages.events:
type: checkboxes
label: Events
help: "Enable or Disable specific events. Disabling these can break plugins"
default: [page: true, twig: true]
options:
page: Page Events
twig: Twig Events
use: keys
pages.redirect_default_route:
type: toggle
label: Redirect default route
help: "Automatically redirect to a page's default route"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
events:
languages:
type: section
title: Events
title: Languages
underline: true
fields:
pages.events.page:
languages.supported:
type: selectize
size: large
label: Supported
help: "Comma separated list of 2 letter language codes (for example 'en,fr,de')"
classes: fancy
validate:
type: commalist
languages.translations:
type: toggle
label: Page events
label: Translations enabled
help: "Support translations in Grav, plugins and extensions"
highlight: 1
options:
1: Yes
@@ -130,9 +166,10 @@ form:
validate:
type: bool
pages.events.twig:
languages.translations_fallback:
type: toggle
label: Twig events
label: Translations fallback
help: "Fallback through supported translations if active language doesn't exist"
highlight: 1
options:
1: Yes
@@ -140,6 +177,131 @@ form:
validate:
type: bool
languages.session_store_active:
type: toggle
label: Active language in session
help: "Support translations in Grav, plugins and extensions"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
languages.home_redirect.include_lang:
type: toggle
label: Home redirect include language
help: "Include language in home redirect (/en)"
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
languages.home_redirect.include_route:
type: toggle
label: Home redirect include route
help: "Include route in home redirect (/blog)"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
http_headers:
type: section
title: HTTP Headers
underline: true
fields:
pages.expires:
type: text
size: small
label: Expires
help: "Sets the expires header. The value is in seconds."
validate:
type: number
min: 1
pages.last_modified:
type: toggle
label: Last modified
help: "Sets the last modified header that can help optimize proxy and browser caching"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.etag:
type: toggle
label: ETag
help: "Sets the etag header to help identify when a page has been modified"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.vary_accept_encoding:
type: toggle
label: Vary accept encoding
help: "Sets the `Vary: Accept Encoding` header to help with proxy and CDN caching"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
markdown:
type: section
title: Markdown
underline: true
fields:
pages.markdown.extra:
type: toggle
label: Markdown extra
help: "Enable default support for Markdown Extra - https://michelf.ca/projects/php-markdown/extra/"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.markdown.auto_line_breaks:
type: toggle
label: Auto line breaks
help: "Enable support for automatic line breaks in markdown"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.markdown.auto_url_links:
type: toggle
label: Auto URL links
help: "Enable automatic conversion of URLs into HTML hyperlinks"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
pages.markdown.escape_markup:
type: toggle
label: Escape markup
help: "Escape markup tags into HTML entities"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
caching:
type: section
title: Caching
@@ -149,6 +311,7 @@ form:
cache.enabled:
type: toggle
label: Caching
help: "Global ON/OFF switch to enable/disable Grav caching"
highlight: 1
options:
1: Yes
@@ -160,7 +323,8 @@ form:
type: select
size: small
classes: fancy
label: Cache Check Method
label: Cache check method
help: "Select the method that Grav uses to check if page files have been modified."
options:
file: File
folder: Folder
@@ -171,6 +335,7 @@ form:
size: small
classes: fancy
label: Cache driver
help: "Choose which cache driver Grav should use. 'Auto Detect' attempts to find the best for you"
options:
auto: Auto detect
file: File
@@ -182,38 +347,29 @@ form:
cache.prefix:
type: text
size: x-small
label: Cache Prefix
label: Cache prefix
help: "An identifier for part of the Grav key. Don't change unless you know what your doing."
placeholder: "Derived from base URL (override by entering random string)"
cache.lifetime:
type: text
size: small
label: Lifetime
help: "Sets the cache lifetime in seconds. 0 = infinite"
validate:
type: number
cache.gzip:
type: toggle
label: GZIP compression
highlight: 1
label: Gzip compression
help: "Enable GZip compression of the Grav page for increased performance."
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
assets.enable_asset_timestamp:
type: toggle
label: Enable timestamps on assets
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
media.enable_media_timestamp:
type: toggle
label: Enable timestamps on media
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
twig:
type: section
@@ -224,6 +380,7 @@ form:
twig.cache:
type: toggle
label: Twig caching
help: "Control the Twig caching mechanism. Leave this enabled for best performance."
highlight: 1
options:
1: Yes
@@ -234,7 +391,8 @@ form:
twig.debug:
type: toggle
label: Twig debug
highlight: 1
help: "Allows the option of not loading the Twig Debugger extension"
highlight: 0
options:
1: Yes
0: No
@@ -244,6 +402,7 @@ form:
twig.auto_reload:
type: toggle
label: Detect changes
help: "Twig will automatically recompile the Twig cache if it detects any changes in Twig templates"
highlight: 1
options:
1: Yes
@@ -254,7 +413,8 @@ form:
twig.autoescape:
type: toggle
label: Autoescape variables
highlight: 1
help: "Autoescapes all variables. This will break your site most likely"
highlight: 0
options:
1: Yes
0: No
@@ -269,8 +429,9 @@ form:
fields:
assets.css_pipeline:
type: toggle
label: CSS Pipeline
highlight: 1
label: CSS pipeline
help: "The CSS pipeline is the unification of multiple CSS resources into one file"
highlight: 0
options:
1: Yes
0: No
@@ -279,7 +440,8 @@ form:
assets.css_minify:
type: toggle
label: CSS Minify
label: CSS minify
help: "Minify the CSS during pipelining"
highlight: 1
options:
1: Yes
@@ -289,8 +451,9 @@ form:
assets.css_minify_windows:
type: toggle
label: CSS Minify Windows Override
highlight: 1
label: CSS minify Windows override
help: "Minify Override for Windows platforms. False by default due to ThreadStackSize"
highlight: 0
options:
1: Yes
0: No
@@ -299,7 +462,8 @@ form:
assets.css_rewrite:
type: toggle
label: CSS Rewrite
label: CSS rewrite
help: "Rewrite any CSS relative URLs during pipelining"
highlight: 1
options:
1: Yes
@@ -309,8 +473,9 @@ form:
assets.js_pipeline:
type: toggle
label: JavaScript Pipeline
highlight: 01
label: JavaScript pipeline
help: "The JS pipeline is the unification of multiple JS resources into one file"
highlight: 0
options:
1: Yes
0: No
@@ -319,7 +484,8 @@ form:
assets.js_minify:
type: toggle
label: JavaScript Minify
label: JavaScript minify
help: "Minify the JS during pipelining"
highlight: 1
options:
1: Yes
@@ -327,6 +493,23 @@ form:
validate:
type: bool
assets.enable_asset_timestamp:
type: toggle
label: Enable timestamps on assets
help: "Enable asset timestamps"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
assets.collections:
type: array
label: Collections
placeholder_key: collection_name
placeholder_value: collection_path
errors:
type: section
title: Error handler
@@ -336,6 +519,7 @@ form:
errors.display:
type: toggle
label: Display errors
help: "Display full backtrace-style error page"
highlight: 1
options:
1: Yes
@@ -346,6 +530,7 @@ form:
errors.log:
type: toggle
label: Log errors
help: "Log errors to /logs folder"
highlight: 1
options:
1: Yes
@@ -362,56 +547,18 @@ form:
debugger.enabled:
type: toggle
label: Debugger
highlight: 1
help: "Enable Grav debugger and following settings"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
debugger.mode:
type: select
size: small
classes: fancy
label: Mode
options:
detect: Auto-Detect
development: Development
production: Production
debugger.strict:
debugger.twig:
type: toggle
label: Strict
highlight: 1
options:
1: Yes
0: No
validate:
type: bool
debugger.max_depth:
type: select
size: small
classes: fancy
label: Detail Level
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
label: Debug Twig
help: "Enable debugging of Twig templates"
highlight: 1
options:
1: Yes
@@ -421,7 +568,8 @@ form:
debugger.shutdown.close_connection:
type: toggle
label: Shutdown Close Connection
label: Shutdown close connection
help: "Close the connection before calling onShutdown(). false for debugging"
highlight: 1
options:
1: Yes
@@ -438,16 +586,29 @@ form:
images.default_image_quality:
type: text
label: Default image quality
help: "Default image quality to use when resampling or caching images (85%)"
classes: x-small
validate:
type: number
min: 1
max: 100
images.cache_all:
type: toggle
label: Cache all images
help: "Run all images through Grav's cache system even if they have no media manipulations"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
images.debug:
type: toggle
label: Image debug watermark
highlight: 1
help: "Show an overlay over images indicating the pixel depth of the image when working with retina for example"
highlight: 0
options:
1: Yes
0: No
@@ -457,30 +618,80 @@ form:
media.upload_limit:
type: text
label: File upload limit
help: "Set maximum upload size in bytes (0 is unlimited)"
classes: small
validate:
type: number
system:
media.enable_media_timestamp:
type: toggle
label: Enable timestamps on media
help: "Appends a timestamp based on last modified date to each media item"
highlight: 0
options:
1: Yes
0: No
validate:
type: bool
session:
type: section
title: System
title: Session
underline: true
fields:
timezone:
type: select
label: Timezone
classes: fancy
@data-options: '\Grav\Common\Utils::timezones'
default: ''
session.enabled:
type: toggle
label: Enabled
help: "Enable session support within Grav"
highlight: 1
options:
'': '- None -'
1: Yes
0: No
validate:
type: bool
session.timeout:
type: text
size: small
label: Timeout
help: "Sets the session timeout in seconds"
validate:
type: number
min: 1
session.name:
type: text
size: small
label: Name
help: "An identifier used to form the name of the session cookie"
advanced:
type: section
title: Advanced
underline: true
fields:
absolute_urls:
type: toggle
label: Absolute URLs
highlight: 0
help: "Absolute or relative URLs for `base_url`"
options:
1: Yes
0: No
validate:
type: bool
param_sep:
type: select
label: Parameter separator
classes: fancy
help: "Separater for passed parameters that can be changed for Apache on Windows"
default: ''
options:
':': ': (default)'
';': '; (use this for apache on Windows)'
';': '; (for Apache running on Windows)'

View File

@@ -0,0 +1,278 @@
title: Default
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
type:
type: hidden
label: Page Type
default: default
tabs:
type: tabs
active: 1
fields:
content:
type: tab
title: Content
fields:
header.title:
type: text
style: vertical
label: Title
validate:
required: true
content:
type: markdown
label: Content
validate:
type: textarea
uploads:
type: uploads
label: Page Media
options:
type: tab
title: Options
fields:
publishing:
type: section
title: Publishing
underline: true
fields:
header.published:
type: toggle
label: Published
help: "By default, a page is published unless you explicitly set published: false or via a publish_date being in the future, or unpublish_date in the past"
highlight: 1
size: medium
options:
'': Global
1: Yes
0: No
validate:
type: bool
header.date:
type: datetime
label: Date
toggleable: true
help: "The date variable allows you to specifically set a date associated with this page."
header.published_date:
type: datetime
label: Published Date
toggleable: true
help: "Can provide a date to automatically trigger publication."
header.unpublished_date:
type: datetime
label: Unublished Date
toggleable: true
help: "can provide a date to automatically trigger un-publication."
meta:
type: section
title: Metadata
underline: true
fields:
header.metadata.description:
type: textarea
toggleable: true
label: Description
default:
validate:
max: 155
header.metadata.keywords:
type: text
toggleable: true
label: Keywords
validate:
max: 120
header.metadata.author:
type: text
toggleable: true
label: Author
validate:
max: 120
header.metadata.robots:
type: checkboxes
toggleable: true
label: Robots
options:
noindex: No index
nofollow: No follow
use: keys
taxonomies:
type: section
title: Taxonomies
underline: true
fields:
header.taxonomy:
type: taxonomy
label: Taxonomy
multiple: true
validate:
type: array
advanced:
type: tab
title: Advanced
fields:
columns:
type: columns
fields:
column1:
type: column
fields:
folder:
type: text
label: Folder Name
validate:
type: slug
route:
type: select
label: Parent
classes: fancy
@data-options: '\Grav\Common\Page\Pages::parents'
@data-default: '\Grav\Plugin\admin::route'
options:
'/': '- Root -'
type:
type: select
classes: fancy
label: Display Template
default: default
@data-options: '\Grav\Common\Page\Pages::types'
column2:
type: column
fields:
order:
type: order
label: Ordering
sitemap:
overrides:
type: section
title: Overrides
underline: true
fields:
header.menu:
type: text
label: Menu
toggleable: true
help: "The string to be used in a menu. If not set, <b>Title</b> will be used."
header.slug:
type: text
label: Slug
toggleable: true
help: "The slug variable allows you to specifically set the page's portion of the URL"
validate:
message: A slug must contain only lowercase alphanumeric characters and dashes
rule: slug
header.process:
type: checkboxes
label: Process
toggleable: true
@config-default: system.pages.process
default:
markdown: true
twig: false
options:
markdown: Markdown
twig: Twig
use: keys
header.child_type:
type: select
toggleable: true
label: Default Child Type
default: default
placeholder: Use Global
@data-options: '\Grav\Common\Page\Pages::types'
header.visible:
type: toggle
label: Visible
help: "Determines if a page is visible in the navigation."
highlight: 1
options:
'': Global
1: Enabled
0: Disabled
validate:
type: bool
header.routable:
type: toggle
label: Routable
help: If this page is reachable by a URL
highlight: 1
default: ''
options:
'': Global
1: Enabled
0: Disabled
validate:
type: bool
header.cache_enable:
type: toggle
label: Caching
highlight: 1
options:
'': Global
1: Enabled
0: Disabled
validate:
type: bool
header.order_by:
type: hidden
header.order_manual:
type: hidden
validate:
type: commalist
blueprint:
type: blueprint

View File

@@ -0,0 +1,46 @@
title: Modular
@extends:
type: default
context: blueprints://pages
form:
fields:
tabs:
type: tabs
active: 1
fields:
content:
fields:
header.content.items:
type: select
label: Items
default: @self.modular
options:
@self.modular: Children
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.process:
type: ignore
content:
type: ignore
uploads:
type: ignore

View File

@@ -52,3 +52,7 @@ form:
default: 1
validate:
type: bool
blueprint:
type: blueprint

View File

@@ -82,3 +82,5 @@ form:
type: order
label: Ordering
blueprint:
type: blueprint

View File

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

View File

@@ -46,3 +46,7 @@ form:
@data-options: '\Grav\Common\Page\Pages::types'
validate:
required: true
blueprint:
type: blueprint

View File

@@ -1,42 +0,0 @@
rules:
slug:
pattern: "[a-z][a-z0-9_\-]+"
min: 2
max: 80
form:
validation: loose
fields:
title:
type: text
label: Title
validate:
required: true
folder:
type: text
label: Folder
validate:
type: slug
required: true
route:
type: select
label: Parent
classes: fancy
@data-options: '\Grav\Common\Page\Pages::parents'
@data-default: '\Grav\Plugin\admin::route'
options:
'/': '- Root -'
validate:
required: true
type:
type: select
classes: fancy
label: Display Template
default: default
@data-options: '\Grav\Common\Page\Pages::types'
validate:
required: true

View File

@@ -82,3 +82,5 @@ form:
type: order
label: Ordering
blueprint:
type: blueprint

View File

@@ -15,10 +15,12 @@ form:
readonly: true
email:
type: text
type: email
size: large
label: Email
validate:
type: email
message: Must be a valid email address
required: true
password:
@@ -27,6 +29,8 @@ form:
label: Password
validate:
required: true
message: Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters
pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}'
fullname:
type: text

View File

@@ -3,7 +3,14 @@ timezone: '' # Valid values: http://php.net/manual/en/
param_sep: ':' # Parameter separator, use ';' for Apache on windows
languages:
translations: true # Enable translations by default
supported: [] # List of languages supported. eg: [en, fr, de]
translations: true # Enable translations by default
translations_fallback: true # Fallback through supported translations if active lang doesn't exist
session_store_active: false # Store active language in session
home_redirect:
include_lang: true # Include language in home redirect (/en)
include_route: false # Include route in home redirect (/blog)
home:
alias: '/home' # Default path for home, ie /
@@ -80,6 +87,7 @@ debugger:
images:
default_image_quality: 85 # Default image quality to use when resampling images (85%)
cache_all: false # Cache all image by default
debug: false # Show an overlay over images indicating the pixel depth of the image when working with retina for example
media:

View File

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

58
system/languages/en.yaml Normal file
View File

@@ -0,0 +1,58 @@
INFLECTOR_PLURALS:
'/(quiz)$/i': '\1zes'
'/^(ox)$/i': '\1en'
'/([m|l])ouse$/i': '\1ice'
'/(matr|vert|ind)ix|ex$/i': '\1ices'
'/(x|ch|ss|sh)$/i': '\1es'
'/([^aeiouy]|qu)ies$/i': '\1y'
'/([^aeiouy]|qu)y$/i': '\1ies'
'/(hive)$/i': '\1s'
'/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
'/sis$/i': 'ses'
'/([ti])um$/i': '\1a'
'/(buffal|tomat)o$/i': '\1oes'
'/(bu)s$/i': '\1ses'
'/(alias|status)/i': '\1es'
'/(octop|vir)us$/i': '\1i'
'/(ax|test)is$/i': '\1es'
'/s$/i': 's'
'/$/': 's'
INFLECTOR_SINGULAR:
'/(quiz)zes$/i': '\1'
'/(matr)ices$/i': '\1ix'
'/(vert|ind)ices$/i': '\1ex'
'/^(ox)en/i': '\1'
'/(alias|status)es$/i': '\1'
'/([octop|vir])i$/i': '\1us'
'/(cris|ax|test)es$/i': '\1is'
'/(shoe)s$/i': '\1'
'/(o)es$/i': '\1'
'/(bus)es$/i': '\1'
'/([m|l])ice$/i': '\1ouse'
'/(x|ch|ss|sh)es$/i': '\1'
'/(m)ovies$/i': '\1ovie'
'/(s)eries$/i': '\1eries'
'/([^aeiouy]|qu)ies$/i': '\1y'
'/([lr])ves$/i': '\1f'
'/(tive)s$/i': '\1'
'/(hive)s$/i': '\1'
'/([^f])ves$/i': '\1fe'
'/(^analy)ses$/i': '\1sis'
'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
'/([ti])a$/i': '\1um'
'/(n)ews$/i': '\1ews'
'/s$/i': ''
INFLECTOR_UNCOUNTABLE: ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep']
INFLECTOR_IRREGULAR:
'person': 'people'
'man': 'men'
'child': 'children'
'sex': 'sexes'
'move': 'moves'
INFLECTOR_ORDINALS:
'default': 'th'
'first': 'st'
'second': 'nd'
'third': 'rd'

View File

@@ -120,7 +120,6 @@ class Config extends Data
$setup['streams']['schemes'] += $this->streams;
$setup = $this->autoDetectEnvironmentConfig($setup);
$this->messages[] = $setup['streams']['schemes']['config']['prefixes'][''];
$this->setup = $setup;
parent::__construct($setup);
@@ -202,24 +201,20 @@ class Config extends Data
// Generate checksum according to the configuration settings.
if (!$checkConfig) {
$this->messages[] = 'Check configuration timestamps from system.yaml files.';
// Just check changes in system.yaml files and ignore all the other files.
$cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : [];
} else {
$this->messages[] = 'Check configuration timestamps from all configuration files.';
// Check changes in all configuration files.
$cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup);
}
if ($checkBlueprints) {
$this->messages[] = 'Check blueprint timestamps from all blueprint files.';
$cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup);
} else {
$cb = [];
}
if ($checkLanguages) {
$this->messages[] = 'Check language timestamps from all language files.';
$cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup);
} else {
$cl = [];
@@ -341,6 +336,11 @@ class Config extends Data
$this->items = $cache['data'];
}
/**
* @param $languages
* @param $plugins
* @param null $filename
*/
protected function loadCompiledLanguages($languages, $plugins, $filename = null)
{
$checksum = md5(json_encode($languages));
@@ -366,11 +366,24 @@ class Config extends Data
// Load languages.
$this->languages = new Languages;
foreach ($languageFiles as $files) {
$this->loadLanguagesFiles($files);
if (isset($languageFiles['user/plugins'])) {
foreach ((array) $languageFiles['user/plugins'] as $plugin => $item) {
$lang_file = CompiledYamlFile::instance($item['file']);
$content = $lang_file->content();
foreach ((array) $content as $lang => $value) {
$this->languages->join($lang, $value, '/');
}
}
}
$this->languages->reformat();
if (isset($languageFiles['system/languages'])) {
foreach ((array) $languageFiles['system/languages'] as $lang => $item) {
$lang_file = CompiledYamlFile::instance($item['file']);
$content = $lang_file->content();
$this->languages->join($lang, $content, '/');
}
}
$cache = [
'@class' => $class,
@@ -415,14 +428,6 @@ class Config extends Data
}
}
public function loadLanguagesFiles(array $files)
{
foreach ($files as $name => $item) {
$file = CompiledYamlFile::instance($item['file']);
$this->languages->join($name, $file->content(), '/');
}
}
/**
* Initialize resource locator by using the configuration.
*

View File

@@ -143,7 +143,7 @@ class ConfigFinder
$filename = "{$path}/{$name}/$find";
if (file_exists($filename)) {
$list["plugins"] = ['file' => $filename, 'modified' => filemtime($filename)];
$list[$name] = ['file' => $filename, 'modified' => filemtime($filename)];
}
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace Grav\Common\Data;
use Grav\Common\GravTrait;
use RocketTheme\Toolbox\ArrayTraits\Export;
/**
@@ -11,7 +12,7 @@ use RocketTheme\Toolbox\ArrayTraits\Export;
*/
class Blueprint
{
use Export, DataMutatorTrait;
use Export, DataMutatorTrait, GravTrait;
public $name;
@@ -75,7 +76,7 @@ class Blueprint
try {
$this->validateArray($data, $this->nested);
} catch (\RuntimeException $e) {
throw new \RuntimeException(sprintf('Page validation failed: %s', $e->getMessage()));
throw new \RuntimeException(sprintf('<b>Validation failed:</b> %s', $e->getMessage()));
}
}
@@ -117,7 +118,17 @@ class Blueprint
{
// Initialize data
$this->fields();
return $this->extraArray($data, $this->nested, $prefix);
$rules = $this->nested;
// Drill down to prefix level
if (!empty($prefix)) {
$parts = explode('.', trim($prefix, '.'));
foreach ($parts as $part) {
$rules = isset($rules[$part]) ? $rules[$part] : [];
}
}
return $this->extraArray($data, $rules, $prefix);
}
/**
@@ -286,7 +297,7 @@ class Blueprint
// 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);
$array += $this->ExtraArray($field, $val, $prefix . $key . '.');
} else {
// Undefined/extra item.
$array[$prefix.$key] = $field;
@@ -313,11 +324,11 @@ class Blueprint
$field['name'] = $prefix . $key;
$field += $params;
if (isset($field['fields'])) {
if (isset($field['fields']) && $field['type'] !== 'list') {
// Recursively get all the nested fields.
$newParams = array_intersect_key($this->filter, $field);
$this->parseFormFields($field['fields'], $newParams, $prefix, $current[$key]['fields']);
} else {
} else if ($field['type'] !== 'ignore') {
// Add rule.
$this->rules[$prefix . $key] = &$field;
$this->addProperty($prefix . $key);
@@ -362,10 +373,20 @@ class Blueprint
}
}
}
elseif (substr($name, 0, 8) == '@config-') {
$property = substr($name, 8);
$default = isset($field[$property]) ? $field[$property] : null;
$config = self::getGrav()['config']->get($value, $default);
if (!is_null($config)) {
$field[$property] = $config;
}
}
}
// Initialize predefined validation rule.
if (isset($field['validate']['rule'])) {
if (isset($field['validate']['rule']) && $field['type'] !== 'ignore') {
$field['validate'] += $this->getRule($field['validate']['rule']);
}
}

View File

@@ -2,6 +2,7 @@
namespace Grav\Common\Data;
use Grav\Common\File\CompiledYamlFile;
use Grav\Common\GravTrait;
/**
* Blueprints class keeps track on blueprint instances.
@@ -11,6 +12,8 @@ use Grav\Common\File\CompiledYamlFile;
*/
class Blueprints
{
use GravTrait;
protected $search;
protected $types;
protected $instances = array();
@@ -55,8 +58,20 @@ class Blueprints
if (isset($blueprints['@extends'])) {
// Extend blueprint by other blueprints.
$extends = (array) $blueprints['@extends'];
foreach ($extends as $extendType) {
$blueprint->extend($this->get($extendType));
if (is_string(key($extends))) {
$extends = [ $extends ];
}
foreach ($extends as $extendConfig) {
$extendType = !is_string($extendConfig) ? empty($extendConfig['type']) ? false : $extendConfig['type'] : $extendConfig;
if (!$extendType) {
continue;
}
$context = is_string($extendConfig) || empty($extendConfig['context']) ? $this : new self(self::getGrav()['locator']->findResource($extendConfig['context']));
$blueprint->extend($context->get($extendType));
}
}

View File

@@ -28,14 +28,16 @@ class Validation
// Validate type with fallback type text.
$type = (string) isset($field['validate']['type']) ? $field['validate']['type'] : $field['type'];
$method = 'type'.strtr($type, '-', '_');
$name = ucfirst($field['label'] ? $field['label'] : $field['name']);
$message = (string) isset($field['validate']['message']) ? $field['validate']['message'] : 'Invalid input in ' . $name;
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}");
throw new \RuntimeException($message);
}
// Check individual rules
@@ -43,8 +45,9 @@ class Validation
$method = 'validate'.strtr($rule, '-', '_');
if (method_exists(__CLASS__, $method)) {
$success = self::$method($value, $params);
if (!$success) {
throw new \RuntimeException('Failed');
throw new \RuntimeException($message);
}
}
}
@@ -489,6 +492,7 @@ class Validation
{
$values = (array) $value;
$options = isset($field['options']) ? array_keys($field['options']) : array();
$multi = isset($field['multiple']) ? $field['multiple'] : false;
if ($options) {
$useKey = isset($field['use']) && $field['use'] == 'keys';
@@ -497,9 +501,39 @@ class Validation
}
}
if ($multi) {
foreach ($values as $key => $value) {
$values[$key] = explode(',', $value[0]);
}
}
return $values;
}
public static function typeList($value, array $params, array $field)
{
if (!is_array($value)) {
return false;
}
if (isset($field['fields'])) {
foreach ($value as $key => $item) {
foreach ($field['fields'] as $subKey => $subField) {
$subKey = trim($subKey, '.');
$subValue = isset($item[$subKey]) ? $item[$subKey] : null;
self::validate($subValue, $subField);
}
}
}
return true;
}
protected static function filterList($value, array $params, array $field)
{
return (array) $value;
}
/**
* Custom input: ignore (will not validate)
*
@@ -513,6 +547,11 @@ class Validation
return true;
}
public static function filterIgnore($value, array $params, array $field)
{
return $value;
}
// HTML5 attributes (min, max and range are handled inside the types)
public static function validateRequired($value, $params)

View File

@@ -25,7 +25,7 @@ trait CompiledFile
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
if ($var === null && $this->raw === null && $this->content === null) {
$key = md5($this->filename);
$file = PhpFile::instance(CACHE_DIR . "/compiled/files/{$key}{$this->extension}.php");
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
$modified = $this->modified();
if (!$modified) {

View File

@@ -349,6 +349,7 @@ class GPM extends Iterator
public function findPackages($searches = [])
{
$packages = ['total' => 0, 'not_found' => []];
$inflector = new Inflector();
foreach ($searches as $search) {
$repository = '';
@@ -380,7 +381,7 @@ class GPM extends Iterator
}
$not_found = new \stdClass();
$not_found->name = Inflector::camelize($search);
$not_found->name = $inflector->camelize($search);
$not_found->slug = $search;
$not_found->package_type = $type;
$not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]);

View File

@@ -159,6 +159,8 @@ class Grav extends Container
$container->register(new StreamsServiceProvider);
$container->register(new ConfigServiceProvider);
$container['inflector'] = new Inflector();
$container['debugger']->stopTimer('_init');
return $container;
@@ -172,8 +174,8 @@ class Grav extends Container
// Initialize configuration.
$debugger->startTimer('_config', 'Configuration');
$this['config']->init();
$this['session']->init();
$this['uri']->init();
$this['session']->init();
$this['errors']->resetHandlers();
$debugger->init();
$this['config']->debug();

View File

@@ -4,6 +4,7 @@ namespace Grav\Common;
/**
* This file was originally part of the Akelos Framework
*/
use Grav\Common\Language\Language;
/**
* Inflector for pluralize and singularize English nouns.
@@ -20,65 +21,55 @@ namespace Grav\Common;
class Inflector
{
use GravTrait;
protected $plural;
protected $singular;
protected $uncountable;
protected $irregular;
protected $ordinals;
public function init()
{
if (empty($this->plural)) {
$language = self::getGrav()['language'];
$this->plural = $language->translate('INFLECTOR_PLURALS', null, true);
$this->singular = $language->translate('INFLECTOR_SINGULAR', null, true);
$this->uncountable = $language->translate('INFLECTOR_UNCOUNTABLE', null, true);
$this->irregular = $language->translate('INFLECTOR_IRREGULAR', null, true);
$this->ordinals = $language->translate('INFLECTOR_ORDINALS', null, true);
}
}
/**
* 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)
public function pluralize($word, $count = 2)
{
$this->init();
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) {
foreach ($this->uncountable as $_uncountable) {
if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) {
return $word;
}
}
foreach ($irregular as $_plural => $_singular) {
foreach ($this->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) {
foreach ($this->plural as $rule => $replacement) {
if (preg_match($rule, $word)) {
return preg_replace($rule, $replacement, $word);
}
@@ -94,62 +85,28 @@ class Inflector
* @param int $count
* @return string Singular noun.
*/
public static function singularize($word, $count = 1)
public function singularize($word, $count = 1)
{
$this->init();
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) {
foreach ($this->uncountable as $_uncountable) {
if (substr($lowercased_word, (-1*strlen($_uncountable))) == $_uncountable) {
return $word;
}
}
foreach ($irregular as $_plural => $_singular) {
foreach ($this->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) {
foreach ($this->singular as $rule => $replacement) {
if (preg_match($rule, $word)) {
return preg_replace($rule, $replacement, $word);
}
@@ -162,24 +119,22 @@ class Inflector
* Converts an underscored or CamelCase word into a English
* sentence.
*
* The titleize static public function converts text like "WelcomePage",
* The titleize 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 = '')
public function titleize($word, $uppercase = '')
{
$uppercase = $uppercase == 'first' ? 'ucfirst' : 'ucwords';
return $uppercase(static::humanize(static::underscorize($word)));
return $uppercase($this->humanize($this->underscorize($word)));
}
/**
@@ -189,13 +144,11 @@ class Inflector
* 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)
public function camelize($word)
{
return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word)));
}
@@ -208,12 +161,10 @@ class Inflector
*
* 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)
public 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);
@@ -229,12 +180,10 @@ class Inflector
*
* 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)
public 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);
@@ -252,14 +201,12 @@ class Inflector
* 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 = '')
public function humanize($word, $uppercase = '')
{
$uppercase = $uppercase == 'all' ? 'ucwords' : 'ucfirst';
return $uppercase(str_replace('_', ' ', preg_replace('/_id$/', '', $word)));
@@ -272,15 +219,13 @@ class Inflector
* 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)
public function variablize($word)
{
$word = static::camelize($word);
$word = $this->camelize($word);
return strtolower($word[0]).substr($word, 1);
}
@@ -290,15 +235,13 @@ class Inflector
*
* 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)
public function tableize($class_name)
{
return static::pluralize(static::underscore($class_name));
return $this->pluralize($this->underscore($class_name));
}
/**
@@ -307,15 +250,13 @@ class Inflector
*
* 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)
public function classify($table_name)
{
return static::camelize(static::singularize($table_name));
return $this->camelize($this->singularize($table_name));
}
/**
@@ -323,34 +264,34 @@ class Inflector
*
* 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)
public function ordinalize($number)
{
$this->init();
if (in_array(($number % 100), range(11, 13))) {
return $number.'th';
return $number.$this->ordinals['default'];
} else {
switch (($number % 10)) {
case 1:
return $number.'st';
return $number.$this->ordinals['first'];
break;
case 2:
return $number.'nd';
return $number.$this->ordinals['second'];
break;
case 3:
return $number.'rd';
return $number.$this->ordinals['third'];
break;
default:
return $number.'th';
return $number.$this->ordinals['default'];
break;
}
}
}
public static function monthize($days)
public function monthize($days)
{
$now = new \DateTime();
$end = new \DateTime();

View File

@@ -276,13 +276,15 @@ class Language
/**
* Translate a key and possibly arguments into a string using current lang and fallbacks
*
* @param $args first argument is the lookup key value
* other arguments can be passed and replaced in the translation with sprintf syntax
* @param $args first argument is the lookup key value
* other arguments can be passed and replaced in the translation with sprintf syntax
* @param Array $languages
* @param bool $array_support
* @param bool $html_out
*
* @return string
*/
public function translate($args, Array $languages = null)
public function translate($args, Array $languages = null, $array_support = false, $html_out = false)
{
if (is_array($args)) {
$lookup = array_shift($args);
@@ -293,9 +295,12 @@ class Language
if ($this->config->get('system.languages.translations', true)) {
if ($this->enabled() && $lookup) {
if (isset($this->grav['admin'])) {
$languages = ['en'];
} elseif ($this->enabled() && $lookup) {
if (empty($languages)) {
if ($this->config->get('system.languages.translations.fallback', true)) {
if ($this->config->get('system.languages.translations_fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = (array)$this->getDefault();
@@ -306,7 +311,7 @@ class Language
}
foreach ((array)$languages as $lang) {
$translation = $this->getTranslation($lang, $lookup);
$translation = $this->getTranslation($lang, $lookup, $array_support);
if ($translation) {
if (count($args) >= 1) {
@@ -318,7 +323,11 @@ class Language
}
}
return '<span class="untranslated">' . $lookup . '</span>';
if ($html_out) {
return '<span class="untranslated">' . $lookup . '</span>';
} else {
return $lookup;
}
}
/**
@@ -327,15 +336,16 @@ class Language
* @param $key
* @param $index
* @param null $languages
* @param bool $html_out
*
* @return string
*/
public function translateArray($key, $index, $languages = null)
public function translateArray($key, $index, $languages = null, $html_out = false)
{
if ($this->config->get('system.languages.translations', true)) {
if ($this->enabled() && $key) {
if (empty($languages)) {
if ($this->config->get('system.languages.translations.fallback', true)) {
if ($this->config->get('system.languages.translations_fallback', true)) {
$languages = $this->getFallbackLanguages();
} else {
$languages = (array)$this->getDefault();
@@ -353,21 +363,26 @@ class Language
}
}
return '<span class="untranslated">' . $key . '[' . $index . ']</span>';
if ($html_out) {
return '<span class="untranslated">' . $key . '[' . $index . ']</span>';
} else {
return $key . '[' . $index . ']';
}
}
/**
* Lookup the translation text for a given lang and key
*
* @param $lang lang code
* @param $key key to lookup with
* @param $lang lang code
* @param $key key to lookup with
* @param bool $array_support
*
* @return string
*/
public function getTranslation($lang, $key)
public function getTranslation($lang, $key, $array_support = false)
{
$translation = $this->config->getLanguages()->get($lang . '.' . $key, null);
if (is_array($translation)) {
if (!$array_support && is_array($translation)) {
return (string)array_shift($translation);
}

View File

@@ -2,6 +2,7 @@
namespace Grav\Common\Page\Medium;
use Grav\Common\GravTrait;
use Gregwar\Image\Exceptions\GenerationError;
use RocketTheme\Toolbox\Event\Event;
class ImageFile extends \Gregwar\Image\Image

View File

@@ -66,17 +66,23 @@ class ImageMedium extends Medium
{
parent::__construct($items, $blueprint);
$config = self::$grav['config'];
$image_info = getimagesize($this->get('filepath'));
$this->def('width', $image_info[0]);
$this->def('height', $image_info[1]);
$this->def('mime', $image_info['mime']);
$this->def('debug', self::$grav['config']->get('system.images.debug'));
$this->def('debug', $config->get('system.images.debug'));
$this->set('thumbnails.media', $this->get('filepath'));
$this->default_quality = self::$grav['config']->get('system.images.default_image_quality', 85);
$this->default_quality = $config->get('system.images.default_image_quality', 85);
$this->reset();
if ($config->get('system.images.cache_all', false)) {
$this->cache();
}
}
/**
@@ -129,6 +135,19 @@ class ImageMedium extends Medium
return self::$grav['base_url'] . $output . $this->querystring() . $this->urlHash();
}
/**
* Simply processes with no extra methods. Useful for triggering events.
*
* @return $this
*/
public function cache()
{
if (!$this->image) {
$this->image();
}
return $this;
}
/**
* Return srcset string for this Medium and its alternatives.

View File

@@ -7,7 +7,6 @@ use Grav\Common\Grav;
use Grav\Common\GravTrait;
use Grav\Common\Data\Blueprint;
use Grav\Common\Data\Data;
use Gregwar\Image\Image as ImageFile;
class VideoMedium extends Medium
{

View File

@@ -56,6 +56,7 @@ class Page
protected $items;
protected $header;
protected $frontmatter;
protected $language;
protected $content;
protected $summary;
protected $raw_content;
@@ -127,6 +128,10 @@ class Page
} else {
$this->extension($extension);
}
// Exract page language from page extension
$language = trim(basename($this->extension(), 'md'), '.') ?: null;
$this->language($language);
}
/**
@@ -217,6 +222,9 @@ class Page
if (isset($this->header->title)) {
$this->title = trim($this->header->title);
}
if (isset($this->header->language)) {
$this->language = trim($this->header->language);
}
if (isset($this->header->template)) {
$this->template = trim($this->header->template);
}
@@ -281,6 +289,22 @@ class Page
return $this->header;
}
/**
* Get page language
*
* @param $var
*
* @return mixed
*/
public function language($var = null)
{
if ($var !== null) {
$this->language = $var;
}
return $this->language;
}
/**
* Modify a header value directly
*
@@ -539,7 +563,7 @@ class Page
return preg_replace($regex, '', $this->folder);
}
if ($name == 'type') {
return basename($this->name(), '.md');
return $this->template();
}
if ($name == 'media') {
return $this->media()->all();
@@ -605,22 +629,6 @@ class Page
return null;
}
/**
* Get page extension
*
* @param $var
*
* @return mixed
*/
public function extension($var = null)
{
if ($var !== null) {
$this->extension = $var;
}
return $this->extension;
}
/**
* Save page if there's a file assigned to it.
* @param bool $reorder Internal use.
@@ -658,9 +666,12 @@ class Page
if ($parent->path()) {
$clone->path($parent->path() . '/' . $clone->folder());
}
// TODO: make sure we always have the route.
if ($parent->route()) {
$clone->route($parent->route() . '/'. $clone->slug());
} else {
$clone->route(self::getGrav()['pages']->root()->route() . '/'. $clone->slug());
}
return $clone;
@@ -693,7 +704,33 @@ class Page
/** @var Pages $pages */
$pages = self::getGrav()['pages'];
return $pages->blueprints($this->template());
$blueprint = $pages->blueprints($this->blueprintName());
$fields = $blueprint->fields();
// override if you only want 'normal' mode
if (empty($fields) && self::getGrav()['admin'] && self::getGrav()['config']->get('plugins.admin.edit_mode', 'auto') == 'normal') {
$blueprint = $pages->blueprints('default');
}
// override if you only want 'expert' mode
if (!empty($fields) && self::getGrav()['admin'] && self::getGrav()['config']->get('plugins.admin.edit_mode', 'auto') == 'expert') {
$blueprint = $pages->blueprints('');
}
return $blueprint;
}
/**
* Get the blueprint name for this page. Use the blueprint form field if set
*
* @return string
*/
public function blueprintName()
{
$blueprint_name = filter_input(INPUT_POST, 'blueprint', FILTER_SANITIZE_STRING) ?: $this->template();
return $blueprint_name;
}
/**
@@ -725,7 +762,7 @@ class Page
public function extra()
{
$blueprints = $this->blueprints();
return $blueprints->extra($this->toArray(), 'header.');
return $blueprints->extra($this->toArray()['header'], 'header.');
}
/**
@@ -824,11 +861,28 @@ class Page
$this->template = $var;
}
if (empty($this->template)) {
$this->template = ($this->modular() ? 'modular/' : '') . str_replace($this->extension, '', $this->name());
$this->template = ($this->modular() ? 'modular/' : '') . str_replace($this->extension(), '', $this->name());
}
return $this->template;
}
/**
* Gets and sets the extension field.
*
* @param null $var
* @return null|string
*/
public function extension($var = null)
{
if ($var !== null) {
$this->extension = $var;
}
if (empty($this->extension)) {
$this->extension = '.' . pathinfo($this->name(), PATHINFO_EXTENSION);
}
return $this->extension;
}
/**
* Gets and sets the expires field. If not set will return the default
*
@@ -1005,24 +1059,29 @@ class Page
if (null === $this->metadata) {
$header_tag_http_equivs = ['content-type', 'default-style', 'refresh'];
$this->metadata = array();
$page_header = $this->header;
// Set the Generator tag
$this->metadata['generator'] = array('name'=>'generator', 'content'=>'GravCMS ' . GRAV_VERSION);
// Safety check to ensure we have a header
if ($page_header) {
if (isset($this->header->metadata)) {
$page_header = $this->header->metadata;
// Merge any site.metadata settings in with page metadata
$defaults = (array) self::getGrav()['config']->get('site.metadata');
if (isset($page_header->metadata)) {
$page_header->metadata = array_merge($defaults, $page_header->metadata);
if (isset($page_header)) {
$page_header = array_merge($defaults, $page_header);
} else {
$page_header->metadata = $defaults;
$page_header = $defaults;
}
// Build an array of meta objects..
foreach ((array)$page_header->metadata as $key => $value) {
foreach ((array)$page_header as $key => $value) {
// If this is a property type metadata: "og", "twitter", "facebook" etc
if (is_array($value)) {
foreach ($value as $property => $prop_value) {

View File

@@ -404,8 +404,8 @@ class Pages
{
if (!self::$types) {
self::$types = new Types();
self::$types->scanBlueprints('theme://blueprints/');
self::$types->scanTemplates('theme://templates/');
file_exists('theme://blueprints/') && self::$types->scanBlueprints('theme://blueprints/');
file_exists('theme://templates/') && self::$types->scanTemplates('theme://templates/');
$event = new Event();
$event->types = self::$types;

View File

@@ -13,27 +13,24 @@ class Types implements \ArrayAccess, \Iterator, \Countable
use ArrayAccess, Constructor, Iterator, Countable, Export;
protected $items;
protected $systemBlueprints;
public function register($type, $blueprint = null)
{
if (!$blueprint && $this->systemBlueprints && isset($this->systemBlueprints[$type])) {
$useBlueprint = $this->systemBlueprints[$type];
} else {
$useBlueprint = $blueprint;
}
if ($blueprint || empty($this->items[$type])) {
$this->items[$type] = $blueprint;
$this->items[$type] = $useBlueprint;
}
}
public function scanBlueprints($path)
{
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|'
],
'key' => 'SubPathName',
'value' => 'PathName',
];
$this->items = Folder::all($path, $options) + $this->items;
$this->items = $this->findBlueprints($path) + $this->items;
}
public function scanTemplates($path)
@@ -48,6 +45,10 @@ class Types implements \ArrayAccess, \Iterator, \Countable
'recursive' => false
];
if (!$this->systemBlueprints) {
$this->systemBlueprints = $this->findBlueprints('blueprints://pages');
}
foreach (Folder::all($path, $options) as $type) {
$this->register($type);
}
@@ -78,9 +79,24 @@ class Types implements \ArrayAccess, \Iterator, \Countable
if (strpos($name, 'modular/') !== 0) {
continue;
}
$list[basename($name)] = trim(ucfirst(strtr(basename($name), '_', ' ')));
$list[$name] = trim(ucfirst(strtr(basename($name), '_', ' ')));
}
ksort($list);
return $list;
}
private function findBlueprints($path)
{
$options = [
'compare' => 'Filename',
'pattern' => '|\.yaml$|',
'filters' => [
'key' => '|\.yaml$|'
],
'key' => 'SubPathName',
'value' => 'PathName',
];
return Folder::all($path, $options);
}
}

View File

@@ -32,6 +32,8 @@ class Plugins extends Iterator
$config = self::getGrav()['config'];
$plugins = (array) $config->get('plugins');
$inflector = self::getGrav()['inflector'];
/** @var EventDispatcher $events */
$events = self::getGrav()['events'];
@@ -52,7 +54,7 @@ class Plugins extends Iterator
$pluginClassFormat = [
'Grav\\Plugin\\'.ucfirst($plugin).'Plugin',
'Grav\\Plugin\\'.Inflector::camelize($plugin).'Plugin'
'Grav\\Plugin\\'.$inflector->camelize($plugin).'Plugin'
];
$pluginClassName = false;

View File

@@ -21,15 +21,27 @@ class Session extends \RocketTheme\Toolbox\Session\Session
$config = $this->grav['config'];
if ($config->get('system.session.enabled')) {
// Only activate admin if we're inside the admin path.
$is_admin = false;
$route = $config->get('plugins.admin.route');
$base = '/' . trim($route, '/');
if (substr($uri->route(), 0, strlen($base)) == $base) {
$is_admin = true;
}
$session_timeout = $config->get('system.session.timeout', 1800);
$session_path = $config->get('system.session.path', '/' . ltrim($uri->rootUrl(false), '/'));
// Define session service.
parent::__construct(
$config->get('system.session.timeout', 1800),
$config->get('system.session.path', '/' . ltrim($uri->rootUrl(false), '/'))
$session_timeout,
$session_path
);
$site_identifier = $config->get('site.title', 'unkown');
$this->setName($config->get('system.session.name', 'grav_site') . '_' . substr(md5($site_identifier), 0, 7));
$site_identifier = $config->get('site.title', 'unknown');
$this->setName($config->get('system.session.name', 'grav_site') . '_' . substr(md5($site_identifier), 0, 7) . ($is_admin ? '_admin' : ''));
$this->start();
setcookie(session_name(), session_id(), time() + $session_timeout, $session_path);
}
}
}

View File

@@ -141,15 +141,16 @@ class Themes extends Iterator
$locator = $grav['locator'];
$file = $locator('theme://theme.php') ?: $locator("theme://{$name}.php");
$inflector = $grav['inflector'];
if ($file) {
// Local variables available in the file: $grav, $config, $name, $file
$class = include $file;
if (!is_object($class)) {
$themeClassFormat = [
'Grav\\Theme\\'.ucfirst($name),
'Grav\\Theme\\'.Inflector::camelize($name)
'Grav\\Theme\\'.$inflector->camelize($name)
];
$themeClassName = false;

View File

@@ -301,13 +301,14 @@ class Twig
$this->grav->fireEvent('onTwigSiteVariables');
$pages = $this->grav['pages'];
$page = $this->grav['page'];
$content = $page->content();
$twig_vars = $this->twig_vars;
$twig_vars['pages'] = $pages->root();
$twig_vars['page'] = $page;
$twig_vars['header'] = $page->header();
$twig_vars['content'] = $page->content();
$twig_vars['content'] = $content;
$ext = '.' . ($format ? $format : 'html') . TWIG_EXT;
// determine if params are set, if so disable twig cache

View File

@@ -78,6 +78,7 @@ class TwigExtension extends \Twig_Extension
new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
new \Twig_SimpleFunction('array', [$this, 'arrayFunc']),
new \Twig_simpleFunction('t', [$this, 'translate']),
new \Twig_simpleFunction('ta', [$this, 'translateArray'])
];
@@ -194,16 +195,18 @@ class TwigExtension extends \Twig_Extension
// TODO: check this and fix the docblock if needed.
$action = $action.'ize';
$inflector = $this->grav['inflector'];
if (in_array(
$action,
['titleize','camelize','underscorize','hyphenize', 'humanize','ordinalize','monthize']
)) {
return Inflector::$action($data);
return $inflector->$action($data);
} elseif (in_array($action, ['pluralize','singularize'])) {
if ($count) {
return Inflector::$action($data, $count);
return $inflector->$action($data, $count);
} else {
return Inflector::$action($data);
return $inflector->$action($data);
}
} else {
return $data;
@@ -479,6 +482,11 @@ class TwigExtension extends \Twig_Extension
return Utils::generateRandomString($count);
}
public function arrayFunc($value)
{
return (array) $value;
}
public function translateFunc()
{
return $this->grav['language']->translate(func_get_args());

View File

@@ -9,6 +9,9 @@ use Grav\Common\GravTrait;
/**
* User object
*
* @property mixed authenticated
* @property mixed password
* @property bool|string hashed_password
* @author RocketTheme
* @license MIT
*/
@@ -57,14 +60,13 @@ class User extends Data
// Plain-text is still stored
if ($this->password) {
if ($password !== $this->password) {
// Plain-text passwords do not match, we know we should fail but execute
// verify to protect us from timing attacks and return false regardless of
// the result
Authentication::verify($password, self::getGrav()['config']->get('system.security.default_hash'));
return false;
} else {
} else {
// Plain-text does match, we can update the hash and proceed
$save = true;
@@ -97,6 +99,10 @@ class User extends Data
*/
public function authorise($action)
{
if (empty($this->items)) {
return false;
}
return $this->get("access.{$action}") === true;
}
}

View File

@@ -13,8 +13,9 @@ abstract class Utils
use GravTrait;
/**
* @param string $haystack
* @param string $needle
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function startsWith($haystack, $needle)
@@ -27,14 +28,17 @@ abstract class Utils
return $status;
}
}
return $status;
}
return $needle === '' || strpos($haystack, $needle) === 0;
}
/**
* @param string $haystack
* @param string $needle
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function endsWith($haystack, $needle)
@@ -47,14 +51,17 @@ abstract class Utils
return $status;
}
}
return $status;
}
return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
}
/**
* @param string $haystack
* @param string $needle
* @param string $haystack
* @param string $needle
*
* @return bool
*/
public static function contains($haystack, $needle)
@@ -67,11 +74,12 @@ abstract class Utils
*
* @param object $obj1
* @param object $obj2
*
* @return object
*/
public static function mergeObjects($obj1, $obj2)
{
return (object) array_merge((array) $obj1, (array) $obj2);
return (object)array_merge((array)$obj1, (array)$obj2);
}
/**
@@ -82,6 +90,7 @@ abstract class Utils
* @param string $ending
* @param bool $exact
* @param bool $considerHtml
*
* @return string
*/
public static function truncateHtml($text, $length = 100, $ending = '...', $exact = false, $considerHtml = true)
@@ -100,34 +109,41 @@ abstract class Utils
// 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])) {
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 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]));
}
}
// 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) {
$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)) {
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) {
if ($entity[1] + 1 - $entities_length <= $left) {
$left--;
$entities_length += strlen($entity[0]);
} else {
@@ -136,7 +152,7 @@ abstract class Utils
}
}
}
$truncate .= substr($line_matchings[2], 0, $left+$entities_length);
$truncate .= substr($line_matchings[2], 0, $left + $entities_length);
// maximum length is reached, so get off the loop
break;
} else {
@@ -172,6 +188,7 @@ abstract class Utils
$truncate .= '</' . $tag . '>';
}
}
return $truncate;
}
@@ -208,7 +225,7 @@ abstract class Utils
if ($force_download) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$file_parts['basename']);
header('Content-Disposition: attachment; filename=' . $file_parts['basename']);
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
@@ -243,14 +260,14 @@ abstract class Utils
* Return the mimetype based on filename
*
* @param $extension Extension of file (eg .txt)
*
* @return string
*/
public static function getMimeType($extension)
{
$extension = strtolower($extension);
switch($extension)
{
switch ($extension) {
case "js":
return "application/x-javascript";
@@ -348,6 +365,7 @@ abstract class Utils
* Normalize path by processing relative `.` and `..` syntax and merging path
*
* @param $path
*
* @return string
*/
public static function normalizePath($path)
@@ -366,6 +384,7 @@ abstract class Utils
array_push($ret, $segment);
}
}
return $root . implode('/', $ret);
}
@@ -383,10 +402,9 @@ abstract class Utils
asort($offsets);
$timezone_list = array();
foreach( $offsets as $timezone => $offset )
{
foreach ($offsets as $timezone => $offset) {
$offset_prefix = $offset < 0 ? '-' : '+';
$offset_formatted = gmdate( 'H:i', abs($offset) );
$offset_formatted = gmdate('H:i', abs($offset));
$pretty_offset = "UTC${offset_prefix}${offset_formatted}";
@@ -396,4 +414,23 @@ abstract class Utils
return $timezone_list;
}
public static function arrayFilterRecursive(Array $source, $fn)
{
$result = array();
foreach ($source as $key => $value)
{
if (is_array($value))
{
$result[$key] = static::arrayFilterRecursive($value, $fn);
continue;
}
if ($fn($key, $value))
{
$result[$key] = $value; // KEEP
continue;
}
}
return $result;
}
}