diff --git a/CHANGELOG.md b/CHANGELOG.md
index f449a7e83..0a30b4e3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/composer.json b/composer.json
index 2203028c0..775a77125 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
diff --git a/system/blueprints/config/site.yaml b/system/blueprints/config/site.yaml
index 604be106d..9c74d59d0 100644
--- a/system/blueprints/config/site.yaml
+++ b/system/blueprints/config/site.yaml
@@ -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
diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml
index 071a5c1b2..ab1fead2c 100644
--- a/system/blueprints/config/system.yaml
+++ b/system/blueprints/config/system.yaml
@@ -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)'
diff --git a/system/blueprints/pages/default.yaml b/system/blueprints/pages/default.yaml
new file mode 100644
index 000000000..64a4bd029
--- /dev/null
+++ b/system/blueprints/pages/default.yaml
@@ -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, Title 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
diff --git a/system/blueprints/pages/modular.yaml b/system/blueprints/pages/modular.yaml
new file mode 100644
index 000000000..e90f1d2ee
--- /dev/null
+++ b/system/blueprints/pages/modular.yaml
@@ -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
diff --git a/system/blueprints/pages/modular_new.yaml b/system/blueprints/pages/modular_new.yaml
index 8951b8899..fe366439a 100644
--- a/system/blueprints/pages/modular_new.yaml
+++ b/system/blueprints/pages/modular_new.yaml
@@ -52,3 +52,7 @@ form:
default: 1
validate:
type: bool
+
+
+ blueprint:
+ type: blueprint
diff --git a/system/blueprints/pages/modular_raw.yaml b/system/blueprints/pages/modular_raw.yaml
index f7877772f..c22efe421 100644
--- a/system/blueprints/pages/modular_raw.yaml
+++ b/system/blueprints/pages/modular_raw.yaml
@@ -82,3 +82,5 @@ form:
type: order
label: Ordering
+ blueprint:
+ type: blueprint
diff --git a/system/blueprints/pages/move.yaml b/system/blueprints/pages/move.yaml
new file mode 100644
index 000000000..cd90dea8d
--- /dev/null
+++ b/system/blueprints/pages/move.yaml
@@ -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 -'
diff --git a/system/blueprints/pages/new.yaml b/system/blueprints/pages/new.yaml
index 3d554bff1..b48000f2c 100644
--- a/system/blueprints/pages/new.yaml
+++ b/system/blueprints/pages/new.yaml
@@ -46,3 +46,7 @@ form:
@data-options: '\Grav\Common\Page\Pages::types'
validate:
required: true
+
+
+ blueprint:
+ type: blueprint
diff --git a/system/blueprints/pages/page.yaml b/system/blueprints/pages/page.yaml
deleted file mode 100644
index 81948231b..000000000
--- a/system/blueprints/pages/page.yaml
+++ /dev/null
@@ -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
diff --git a/system/blueprints/pages/raw.yaml b/system/blueprints/pages/raw.yaml
index 311cf77e3..c4f91ac3f 100644
--- a/system/blueprints/pages/raw.yaml
+++ b/system/blueprints/pages/raw.yaml
@@ -82,3 +82,5 @@ form:
type: order
label: Ordering
+ blueprint:
+ type: blueprint
diff --git a/system/blueprints/user/account.yaml b/system/blueprints/user/account.yaml
index 7ed36145a..7f9dfc9dc 100644
--- a/system/blueprints/user/account.yaml
+++ b/system/blueprints/user/account.yaml
@@ -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
diff --git a/system/config/system.yaml b/system/config/system.yaml
index 17ef03d8a..77edb1d93 100644
--- a/system/config/system.yaml
+++ b/system/config/system.yaml
@@ -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:
diff --git a/system/defines.php b/system/defines.php
index 851848604..707e7498f 100644
--- a/system/defines.php
+++ b/system/defines.php
@@ -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
diff --git a/system/languages/en.yaml b/system/languages/en.yaml
new file mode 100644
index 000000000..7ee1ca42b
--- /dev/null
+++ b/system/languages/en.yaml
@@ -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'
+
+
diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php
index 646abee27..98a7b3da8 100644
--- a/system/src/Grav/Common/Config/Config.php
+++ b/system/src/Grav/Common/Config/Config.php
@@ -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.
*
diff --git a/system/src/Grav/Common/Config/ConfigFinder.php b/system/src/Grav/Common/Config/ConfigFinder.php
index 7d79b765b..46069410d 100644
--- a/system/src/Grav/Common/Config/ConfigFinder.php
+++ b/system/src/Grav/Common/Config/ConfigFinder.php
@@ -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)];
}
}
}
diff --git a/system/src/Grav/Common/Data/Blueprint.php b/system/src/Grav/Common/Data/Blueprint.php
index 19448bc58..d92976ddd 100644
--- a/system/src/Grav/Common/Data/Blueprint.php
+++ b/system/src/Grav/Common/Data/Blueprint.php
@@ -1,6 +1,7 @@
validateArray($data, $this->nested);
} catch (\RuntimeException $e) {
- throw new \RuntimeException(sprintf('Page validation failed: %s', $e->getMessage()));
+ throw new \RuntimeException(sprintf('Validation failed: %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']);
}
}
diff --git a/system/src/Grav/Common/Data/Blueprints.php b/system/src/Grav/Common/Data/Blueprints.php
index a92f235f2..a024c87f7 100644
--- a/system/src/Grav/Common/Data/Blueprints.php
+++ b/system/src/Grav/Common/Data/Blueprints.php
@@ -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));
}
}
diff --git a/system/src/Grav/Common/Data/Validation.php b/system/src/Grav/Common/Data/Validation.php
index bd1a34d10..30db908f7 100644
--- a/system/src/Grav/Common/Data/Validation.php
+++ b/system/src/Grav/Common/Data/Validation.php
@@ -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)
diff --git a/system/src/Grav/Common/File/CompiledFile.php b/system/src/Grav/Common/File/CompiledFile.php
index d9cf58d42..27fffc50b 100644
--- a/system/src/Grav/Common/File/CompiledFile.php
+++ b/system/src/Grav/Common/File/CompiledFile.php
@@ -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) {
diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php
index a6eefce5a..1118b2955 100644
--- a/system/src/Grav/Common/GPM/GPM.php
+++ b/system/src/Grav/Common/GPM/GPM.php
@@ -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]);
diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php
index c145192e4..5e4737411 100644
--- a/system/src/Grav/Common/Grav.php
+++ b/system/src/Grav/Common/Grav.php
@@ -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();
diff --git a/system/src/Grav/Common/Inflector.php b/system/src/Grav/Common/Inflector.php
index d707448c4..ff1030ef7 100644
--- a/system/src/Grav/Common/Inflector.php
+++ b/system/src/Grav/Common/Inflector.php
@@ -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();
diff --git a/system/src/Grav/Common/Language/Language.php b/system/src/Grav/Common/Language/Language.php
index 9c19f59b6..ea644e8af 100644
--- a/system/src/Grav/Common/Language/Language.php
+++ b/system/src/Grav/Common/Language/Language.php
@@ -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 '' . $lookup . '';
+ if ($html_out) {
+ return '' . $lookup . '';
+ } 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 '' . $key . '[' . $index . ']';
+ if ($html_out) {
+ return '' . $key . '[' . $index . ']';
+ } 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);
}
diff --git a/system/src/Grav/Common/Page/Medium/ImageFile.php b/system/src/Grav/Common/Page/Medium/ImageFile.php
index 9514653f2..bb9ce59aa 100644
--- a/system/src/Grav/Common/Page/Medium/ImageFile.php
+++ b/system/src/Grav/Common/Page/Medium/ImageFile.php
@@ -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
diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php
index 05f75da8d..17fd32151 100644
--- a/system/src/Grav/Common/Page/Medium/ImageMedium.php
+++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php
@@ -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.
diff --git a/system/src/Grav/Common/Page/Medium/VideoMedium.php b/system/src/Grav/Common/Page/Medium/VideoMedium.php
index e67d3fbfc..acc967293 100644
--- a/system/src/Grav/Common/Page/Medium/VideoMedium.php
+++ b/system/src/Grav/Common/Page/Medium/VideoMedium.php
@@ -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
{
diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php
index 7bf743fd3..8bcc84fa4 100644
--- a/system/src/Grav/Common/Page/Page.php
+++ b/system/src/Grav/Common/Page/Page.php
@@ -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) {
diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php
index c8872c483..d8cad6e07 100644
--- a/system/src/Grav/Common/Page/Pages.php
+++ b/system/src/Grav/Common/Page/Pages.php
@@ -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;
diff --git a/system/src/Grav/Common/Page/Types.php b/system/src/Grav/Common/Page/Types.php
index 418faba9c..f6407cadd 100644
--- a/system/src/Grav/Common/Page/Types.php
+++ b/system/src/Grav/Common/Page/Types.php
@@ -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);
+ }
}
diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php
index 77a2afa2e..dd5fb74a0 100644
--- a/system/src/Grav/Common/Plugins.php
+++ b/system/src/Grav/Common/Plugins.php
@@ -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;
diff --git a/system/src/Grav/Common/Session.php b/system/src/Grav/Common/Session.php
index cdc2e9958..c587b6332 100644
--- a/system/src/Grav/Common/Session.php
+++ b/system/src/Grav/Common/Session.php
@@ -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);
}
}
}
diff --git a/system/src/Grav/Common/Themes.php b/system/src/Grav/Common/Themes.php
index b27a32c87..516a9d2fb 100644
--- a/system/src/Grav/Common/Themes.php
+++ b/system/src/Grav/Common/Themes.php
@@ -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;
diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php
index 10d8c6539..d8f790962 100644
--- a/system/src/Grav/Common/Twig/Twig.php
+++ b/system/src/Grav/Common/Twig/Twig.php
@@ -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
diff --git a/system/src/Grav/Common/Twig/TwigExtension.php b/system/src/Grav/Common/Twig/TwigExtension.php
index f2cba017f..7addcc223 100644
--- a/system/src/Grav/Common/Twig/TwigExtension.php
+++ b/system/src/Grav/Common/Twig/TwigExtension.php
@@ -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());
diff --git a/system/src/Grav/Common/User/User.php b/system/src/Grav/Common/User/User.php
index b727fa19b..6528d9b0c 100644
--- a/system/src/Grav/Common/User/User.php
+++ b/system/src/Grav/Common/User/User.php
@@ -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;
}
}
diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php
index 7262c498c..5c38f97a9 100644
--- a/system/src/Grav/Common/Utils.php
+++ b/system/src/Grav/Common/Utils.php
@@ -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;
+ }
}