Merge branch '3.1'

Conflicts:
	README.md
	dev/install/install.php5
	forms/ConfirmedPasswordField.php
	tests/forms/FormTest.php
This commit is contained in:
Sam Minnee 2013-05-23 19:01:58 +12:00
commit d97ca43cd0
182 changed files with 8138 additions and 1414 deletions

5
.scrutinizer.yml Normal file
View File

@ -0,0 +1,5 @@
tools:
custom_commands:
-
scope: file
command: php tests/phpcs_runner.php %pathname%

View File

@ -24,14 +24,13 @@ matrix:
- env: PHPCS=1 CORE_RELEASE=master
before_script:
- pear install pear/PHP_CodeSniffer
- phpenv rehash
- git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support
- php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss
- cd ~/builds/ss
script:
- sh -c "if [ '$PHPCS' != '1' ]; then phpunit framework/tests; else phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs/ruleset.xml -np framework && phpcs --encoding=utf-8 --standard=framework/tests/phpcs/tabs.xml -np framework; fi"
- phpunit framework/tests
branches:
except:

View File

@ -23,7 +23,7 @@ If you would like to make changes to the SilverStripe core codebase, we have an
* [Server Requirements](http://doc.silverstripe.org/framework/en/installation/server-requirements)
* [Changelogs](http://doc.silverstripe.org/framework/en/changelogs/)
* [Bugtracker: Framework](https://github.com/silverstripe/sapphire/issues)
* [Bugtracker: Framework](https://github.com/silverstripe/silverstripe-framework/issues)
* [Bugtracker: CMS](https://github.com/silverstripe/silverstripe-cms/issues)
* [Bugtracker: Installer](https://github.com/silverstripe/silverstripe-installer/issues)
* [Forums](http://silverstripe.org/forums)

15
_config/i18n.yml Normal file
View File

@ -0,0 +1,15 @@
---
Name: basei18n
Before: '/i18n'
---
i18n:
module_priority:
- admin
- framework
- sapphire
---
Name: defaulti18n
---
i18n:
module_priority:
- other_modules

View File

@ -14,7 +14,7 @@ HtmlEditorConfig::get('cms')->setOptions(array(
'valid_elements' => "@[id|class|style|title],a[id|rel|rev|dir|tabindex|accesskey|type|name|href|target|title"
. "|class],-strong/-b[class],-em/-i[class],-strike[class],-u[class],#p[id|dir|class|align|style],-ol[class],"
. "-ul[class],-li[class],br,img[id|dir|longdesc|usemap|class|src|border|alt=|title|width|height|align|data*],"
. "-sub[class],-sup[class],-blockquote[dir|class],"
. "-sub[class],-sup[class],-blockquote[dir|class],-cite[dir|class|id|title],"
. "-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|dir|id|style],"
. "-tr[id|dir|class|rowspan|width|height|align|valign|bgcolor|background|bordercolor|style],"
. "tbody[id|class|style],thead[id|class|style],tfoot[id|class|style],"

View File

@ -6,8 +6,8 @@
* This is essentially an abstract class which should be subclassed.
* See {@link CMSMain} for a good example.
*
* @package cms
* @subpackage core
* @package framework
* @subpackage admin
*/
class LeftAndMain extends Controller implements PermissionProvider {
@ -197,6 +197,7 @@ class LeftAndMain extends Controller implements PermissionProvider {
parent::init();
Config::inst()->update('SSViewer', 'rewrite_hash_links', false);
Config::inst()->update('ContentNegotiator', 'enabled', false);
// set language
$member = Member::currentUser();
@ -1768,6 +1769,9 @@ class LeftAndMainMarkingFilter {
/**
* Allow overriding finished state for faux redirects.
*
* @package framework
* @subpackage admin
*/
class LeftAndMain_HTTPResponse extends SS_HTTPResponse {
@ -1787,7 +1791,10 @@ class LeftAndMain_HTTPResponse extends SS_HTTPResponse {
* Wrapper around objects being displayed in a tree.
* Caution: Volatile API.
*
* @todo Implement recursive tree node rendering
* @todo Implement recursive tree node rendering.
*
* @package framework
* @subpackage admin
*/
class LeftAndMain_TreeNode extends ViewableData {

View File

@ -2,8 +2,8 @@
/**
* Plug-ins for additional functionality in your LeftAndMain classes.
*
* @package cms
* @subpackage core
* @package framework
* @subpackage admin
*/
abstract class LeftAndMainExtension extends Extension {

View File

@ -26,8 +26,8 @@
*
* @uses SearchContext
*
* @package cms
* @subpackage core
* @package framework
* @subpackage admin
*/
abstract class ModelAdmin extends LeftAndMain {

View File

@ -120,7 +120,7 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif;
.cms h5 { font-size: 12px; }
.cms p { line-height: 16px; margin-bottom: 16px; }
.cms em { font-style: italic; }
.cms code { font-family: "Bitstream Vera Sans Mono", "Courier", monospace; }
.cms code { font-family: 'Bitstream Vera Sans Mono','Courier', monospace; }
/** This file defines CMS-specific customizations to the jQuery UI theme. Every rule should contain ONLY overwritten jQuery UI rules (with 'ui-' prefix). This file should be fairly short, as we're using our own custom jQuery UI theme already. TODO Add theme reference Use _style.scss to add more generic style information, and read the jQuery UI theming API: http://jqueryui.com/docs/Theming/API */
.ui-widget-content, .ui-widget { color: #444444; font-size: 12px; font-family: Arial, sans-serif; border: 0; }
@ -172,6 +172,7 @@ form.nostyle input.text, form.nostyle textarea, form.nostyle select, form.nostyl
form.stacked .field label, .field.stacked label { display: block; float: none; padding-bottom: 10px; }
form.stacked .field .middleColumn, .field.stacked .middleColumn { margin-left: 0px; clear: left; }
form.stacked .field .description, .field.stacked .description { margin-left: 0px; }
form.small .field label.left, .field.small label.left { width: 112px; }
form.small .field .middleColumn, .field.small .middleColumn { margin-left: 120px; }
@ -264,6 +265,9 @@ input.radio { margin-left: 0; }
.optionset.field { padding-top: 0; }
/** ---------------------------------------------------- HTML Text ---------------------------------------------------- */
.htmleditor label { display: block; float: none; padding-bottom: 10px; }
.htmleditor .middleColumn { margin-left: 0px; clear: left; }
.htmleditor .description { margin-left: 0px; }
.htmleditor textarea { visibility: hidden; }
.htmleditor .mceEditor input, .htmleditor .mceEditor select { width: auto; }
.htmleditor label.left { padding-bottom: 4px; }
@ -431,7 +435,7 @@ body.cms { overflow: hidden; }
.cms-content-actions, .cms-preview-controls { margin: 0; padding: 12px 12px; z-index: 0; border-top: 1px solid #cacacc; -webkit-box-shadow: 1px 0 0 #eceff1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; -moz-box-shadow: 1px 0 0 #eceff1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; box-shadow: 1px 0 0 #eceff1, rgba(248, 248, 248, 0.9) 0 1px 0px inset, rgba(201, 205, 206, 0.8) 0 0 1px; height: 28px; background-color: #eceff1; }
/** -------------------------------------------- Messages -------------------------------------------- */
.message { display: block; clear: both; margin: 8px 0; padding: 10px 12px; font-weight: normal; border: 1px #cccccc solid; background: #fff; background: rgba(255, 255, 255, 0.5); text-shadow: none; -webkit-border-radius: 3px 3px 3px 3px; -moz-border-radius: 3px 3px 3px 3px; -ms-border-radius: 3px 3px 3px 3px; -o-border-radius: 3px 3px 3px 3px; border-radius: 3px 3px 3px 3px; }
.message { display: block; clear: both; margin: 8px 0; padding: 10px 12px; font-weight: normal; border: 1px #ccc solid; background: #fff; background: rgba(255, 255, 255, 0.5); text-shadow: none; -webkit-border-radius: 3px 3px 3px 3px; -moz-border-radius: 3px 3px 3px 3px; -ms-border-radius: 3px 3px 3px 3px; -o-border-radius: 3px 3px 3px 3px; border-radius: 3px 3px 3px 3px; }
.message.notice { background-color: #f0f8fc; border-color: #93cde8; }
.message.warning { background-color: #fefbde; border-color: #e9d104; }
.message.error, .message.bad, .message.required, .message.validation { background-color: #fae8e9; border-color: #e68288; }
@ -481,7 +485,7 @@ body.cms { overflow: hidden; }
.cms-content-toolbar .chzn-container-single .chzn-single:hover { -webkit-box-shadow: 0 0 5px #b3b3b3; -moz-box-shadow: 0 0 5px #b3b3b3; box-shadow: 0 0 5px #b3b3b3; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ViZWJlYiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2QyZDJkMiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ebebeb), color-stop(100%, #d2d2d2)); background-image: -webkit-linear-gradient(#ebebeb, #d2d2d2); background-image: -moz-linear-gradient(#ebebeb, #d2d2d2); background-image: -o-linear-gradient(#ebebeb, #d2d2d2); background-image: linear-gradient(#ebebeb, #d2d2d2); }
.cms-content-toolbar .chzn-container-single .chzn-single:active { -webkit-box-shadow: inset 0 1px 3px #4d4d4d; -moz-box-shadow: inset 0 1px 3px #4d4d4d; box-shadow: inset 0 1px 3px #4d4d4d; }
.cms-content-toolbar .chzn-container-single .chzn-single span { padding-top: 1px; }
.cms-content-toolbar .chzn-container-single .chzn-single div { /*background:url(../images/btn-icon/settings.png) 5px 4px no-repeat;*/ border-left: none; width: 100%; }
.cms-content-toolbar .chzn-container-single .chzn-single div { background: url(../images/btn-icon/settings.png) 5px 4px no-repeat; border-left: none; width: 100%; }
.cms-content-toolbar .chzn-container-single .chzn-single div b { background: url(../images/sprites-32x32/menu-arrow-deselected-down.png) no-repeat 9px 11px; float: right; width: 24px; }
.cms-content-toolbar .ss-ui-button { margin-bottom: 8px; }
@ -515,7 +519,7 @@ body.cms { overflow: hidden; }
/** CMS Batch actions */
.cms-content-batchactions { float: left; position: relative; display: block; }
.cms-content-batchactions .view-mode-batchactions-wrapper { height: 18px; float: left; padding: 4px 6px; border: 1px solid #aaaaaa; margin-bottom: 8px; background-color: #D9D9D9; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #ffffff, #d9d9d9); background-image: -moz-linear-gradient(top, #ffffff, #d9d9d9); background-image: -o-linear-gradient(top, #ffffff, #d9d9d9); background-image: linear-gradient(top, #ffffff, #d9d9d9); border-top-left-radius: 4px; border-bottom-left-radius: 4px; }
.cms-content-batchactions .view-mode-batchactions-wrapper { height: 18px; float: left; padding: 4px 6px; border: 1px solid #aaa; margin-bottom: 8px; background-color: #D9D9D9; background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #ffffff, #d9d9d9); background-image: -moz-linear-gradient(top, #ffffff, #d9d9d9); background-image: -o-linear-gradient(top, #ffffff, #d9d9d9); background-image: linear-gradient(top, #ffffff, #d9d9d9); border-top-left-radius: 4px; border-bottom-left-radius: 4px; }
.cms-content-batchactions .view-mode-batchactions-wrapper input { vertical-align: middle; }
.cms-content-batchactions .view-mode-batchactions-wrapper label { vertical-align: middle; display: none; }
.cms-content-batchactions .view-mode-batchactions-wrapper fieldset, .cms-content-batchactions .view-mode-batchactions-wrapper .Actions { display: inline-block; }
@ -544,9 +548,9 @@ form.member-profile-form #FavouritePageID { margin-top: 8px; }
form.member-profile-form #CsvFile .middleColumn { background: none !important; }
form.member-profile-form .advanced h4 { margin-bottom: .5em; }
form.member-profile-form .Actions { text-align: left; border: 0; }
form.member-profile-form input.customFormat { width: 80px; border: 1px solid #cccccc !important; padding: 3px; display: inline-block; margin-left: 1em; }
form.member-profile-form input.customFormat { width: 80px; border: 1px solid #ccc !important; padding: 3px; display: inline-block; margin-left: 1em; }
form.member-profile-form .formattingHelpToggle { display: block; font-size: 11px; }
form.member-profile-form .formattingHelpText { margin: 5px 0 0 -5px; color: #333; padding: 5px 10px; background: #fff; border: 1px solid #cccccc; }
form.member-profile-form .formattingHelpText { margin: 5px 0 0 -5px; color: #333; padding: 5px 10px; background: #fff; border: 1px solid #ccc; }
form.member-profile-form .formattingHelpText ul { padding: 0; }
form.member-profile-form .formattingHelpText li { font-size: 11px; color: #333; margin-bottom: 2px; padding-bottom: 0; float: none; width: auto; }
form.member-profile-form #Groups .middleColumn { margin-left: 0; width: 100%; }
@ -556,7 +560,7 @@ form.member-profile-form #Permissions .optionset li { float: none; width: auto;
.memberdatetimeoptionset .description { font-style: normal; }
.memberdatetimeoptionset .toggle { font-size: 11px; }
.cms .cms-content { border-right: 1px solid #bbbbbb; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eceff1; width: 800px; z-index: 40; }
.cms .cms-content { border-right: 1px solid #BBB; -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eceff1; width: 800px; z-index: 40; }
.cms .cms-content-fields { overflow-y: auto; overflow-x: auto; background: #e6eaed; width: 100%; }
.cms .cms-content-fields #Root_Main .confirmedpassword { border-bottom: none; box-shadow: none; }
.cms .cms-content-fields #Root_Main .customFormat { max-width: 80px; }
@ -608,7 +612,7 @@ form.member-profile-form #Permissions .optionset li { float: none; width: auto;
.cms .ui-dialog .htmleditorfield-dialog { min-width: 570px; }
.cms .ui-dialog .ss-ui-dialog.ui-dialog-content { padding-top: 0px; }
.ui-dialog { background: url("../images/textures/bg_cms_main_content.png") repeat left top #f0f3f4; border: 3px solid black !important; border-radius: 8px; overflow: visible; padding: 0; }
.ui-dialog { background: url("../images/textures/bg_cms_main_content.png") repeat left top #f0f3f4; border: 3px solid #000 !important; border-radius: 8px; overflow: visible; padding: 0; }
.ui-dialog .ui-dialog-titlebar.ui-widget-header { font-size: 14px; padding: 0; border: none; background-color: transparent; background-image: url(../images/textures/cms_content_header.png); background-repeat: repeat; -webkit-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; -moz-box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; box-shadow: rgba(107, 120, 123, 0.5) 0 0 4px inset; }
.ui-dialog .ui-dialog-titlebar.ui-widget-header .ui-dialog-title { position: absolute; }
.ui-dialog .cms-dialog-content { background: url("../images/textures/bg_cms_main_content.png") repeat left top #f0f3f4; padding-bottom: 8px; padding-top: 0px; }
@ -723,7 +727,7 @@ form.import-form label.left { width: 250px; }
.cms .jstree li.disabled > a, .TreeDropdownField .treedropdownfield-panel .jstree li.disabled > a { color: #aaaaaa; }
.cms .jstree li > .jstree-icon, .TreeDropdownField .treedropdownfield-panel .jstree li > .jstree-icon { cursor: pointer; }
.cms .jstree ins, .TreeDropdownField .treedropdownfield-panel .jstree ins { display: inline-block; text-decoration: none; width: 18px; height: 18px; margin: 0 0 0 0; padding: 0; float: left; }
.cms .jstree a, .TreeDropdownField .treedropdownfield-panel .jstree a { display: inline-block; line-height: 16px; height: 16px; color: black; white-space: nowrap; text-decoration: none; padding: 1px 2px; margin: 0; border: 1px solid white; }
.cms .jstree a, .TreeDropdownField .treedropdownfield-panel .jstree a { display: inline-block; line-height: 16px; height: 16px; color: black; white-space: nowrap; text-decoration: none; padding: 1px 2px; margin: 0; border: 1px solid #fff; }
.cms .jstree a:focus, .cms .jstree a:active, .cms .jstree a:hover, .TreeDropdownField .treedropdownfield-panel .jstree a:focus, .TreeDropdownField .treedropdownfield-panel .jstree a:active, .TreeDropdownField .treedropdownfield-panel .jstree a:hover { outline: none; text-decoration: none; cursor: pointer; text-shadow: none; }
.cms .jstree a > ins, .TreeDropdownField .treedropdownfield-panel .jstree a > ins { height: 16px; width: 16px; }
.cms .jstree a > ins.jstree-checkbox, .TreeDropdownField .treedropdownfield-panel .jstree a > ins.jstree-checkbox { height: 19px; }
@ -732,7 +736,7 @@ form.import-form label.left { width: 250px; }
.cms .jstree .jstree-wholerow-real, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow-real { position: relative; z-index: 1; }
.cms .jstree .jstree-wholerow-real li, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow-real li { cursor: pointer; }
.cms .jstree .jstree-wholerow-real a, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow-real a { border-left-color: transparent !important; border-right-color: transparent !important; }
.cms .jstree .jstree-wholerow, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow, .cms .jstree .jstree-wholerow ul, .cms .jstree .jstree-wholerow li, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow ul, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow ul, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow li, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow li, .cms .jstree .jstree-wholerow a, .cms .jstree .jstree-wholerow a:hover, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow a, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a:hover, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow a:hover, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow ul, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow ul, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow li, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow li, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow ul, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow li, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow a, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow a:hover, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a:hover, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a:hover { margin: 0 !important; padding: 0 !important; }
.cms .jstree .jstree-wholerow, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow, .cms .jstree .jstree-wholerow ul, .cms .jstree .jstree-wholerow li, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow ul, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow ul, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow li, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow li, .cms .jstree .jstree-wholerow a, .cms .jstree .jstree-wholerow a:hover, .cms .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .cms .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow ul, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow li, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a:hover { margin: 0 !important; padding: 0 !important; }
.cms .jstree .jstree-wholerow, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow { position: relative; z-index: 0; height: 0; background: transparent !important; }
.cms .jstree .jstree-wholerow ul, .cms .jstree .jstree-wholerow li, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow ul, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow li { background: transparent !important; width: 100%; }
.cms .jstree .jstree-wholerow a, .cms .jstree .jstree-wholerow a:hover, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a, .TreeDropdownField .treedropdownfield-panel .jstree .jstree-wholerow a:hover { text-indent: -9999px !important; width: 100%; border-right-width: 0px !important; border-left-width: 0px !important; }
@ -751,12 +755,12 @@ form.import-form label.left { width: 250px; }
.cms .jstree-themeroller a, .TreeDropdownField .treedropdownfield-panel .jstree-themeroller a { padding: 0 2px; }
.cms .jstree-themeroller .ui-icon, .TreeDropdownField .treedropdownfield-panel .jstree-themeroller .ui-icon { overflow: visible; }
.cms .jstree-themeroller .jstree-no-icon, .TreeDropdownField .treedropdownfield-panel .jstree-themeroller .jstree-no-icon { display: none; }
.cms #jstree-marker, .cms .TreeDropdownField .treedropdownfield-panel #jstree-marker, .TreeDropdownField .treedropdownfield-panel .cms #jstree-marker, .cms #jstree-marker-line, .cms .TreeDropdownField .treedropdownfield-panel #jstree-marker-line, .TreeDropdownField .treedropdownfield-panel .cms #jstree-marker-line, .TreeDropdownField .treedropdownfield-panel .cms #jstree-marker, .cms .TreeDropdownField .treedropdownfield-panel #jstree-marker, .TreeDropdownField .treedropdownfield-panel #jstree-marker, .TreeDropdownField .treedropdownfield-panel .cms #jstree-marker-line, .cms .TreeDropdownField .treedropdownfield-panel #jstree-marker-line, .TreeDropdownField .treedropdownfield-panel #jstree-marker-line { padding: 0; margin: 0; overflow: hidden; position: absolute; top: -30px; background-repeat: no-repeat; display: none; }
.cms #jstree-marker, .cms .TreeDropdownField .treedropdownfield-panel #jstree-marker, .TreeDropdownField .treedropdownfield-panel .cms #jstree-marker, .cms #jstree-marker-line, .cms .TreeDropdownField .treedropdownfield-panel #jstree-marker-line, .TreeDropdownField .treedropdownfield-panel .cms #jstree-marker-line, .TreeDropdownField .treedropdownfield-panel #jstree-marker, .TreeDropdownField .treedropdownfield-panel #jstree-marker-line { padding: 0; margin: 0; overflow: hidden; position: absolute; top: -30px; background-repeat: no-repeat; display: none; }
.cms #jstree-marker, .TreeDropdownField .treedropdownfield-panel #jstree-marker { line-height: 10px; font-size: 12px; height: 12px; width: 8px; z-index: 10001; background-color: transparent; text-shadow: 1px 1px 1px white; color: black; }
.cms #jstree-marker-line, .TreeDropdownField .treedropdownfield-panel #jstree-marker-line { line-height: 0%; font-size: 1px; height: 1px; width: 100px; z-index: 10000; background-color: #456c43; cursor: pointer; border: 1px solid #eeeeee; border-left: 0; -moz-box-shadow: 0px 0px 2px #666666; -webkit-box-shadow: 0px 0px 2px #666666; box-shadow: 0px 0px 2px #666666; -moz-border-radius: 1px; border-radius: 1px; -webkit-border-radius: 1px; }
.cms #jstree-marker-line, .TreeDropdownField .treedropdownfield-panel #jstree-marker-line { line-height: 0%; font-size: 1px; height: 1px; width: 100px; z-index: 10000; background-color: #456c43; cursor: pointer; border: 1px solid #eeeeee; border-left: 0; -moz-box-shadow: 0px 0px 2px #666; -webkit-box-shadow: 0px 0px 2px #666; box-shadow: 0px 0px 2px #666; -moz-border-radius: 1px; border-radius: 1px; -webkit-border-radius: 1px; }
.cms #vakata-contextmenu, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu { display: block; visibility: hidden; left: 0; top: -200px; position: absolute; margin: 0; padding: 0; min-width: 180px; background: #FFF; border: 1px solid silver; z-index: 10000; *width: 180px; -webkit-box-shadow: 0 0 10px #cccccc; -moz-box-shadow: 0 0 10px #cccccc; box-shadow: 0 0 10px #cccccc; }
.cms #vakata-contextmenu::before, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu::before { content: ""; display: block; /* reduce the damage in FF3.0 */ position: absolute; top: -10px; left: 24px; width: 0; border-width: 0 6px 10px 6px; border-color: white transparent; border-style: solid; z-index: 10000; }
.cms #vakata-contextmenu::after, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu::after { content: ""; display: block; /* reduce the damage in FF3.0 */ position: absolute; top: -11px; left: 23px; width: 0; border-width: 0 7px 11px 7px; border-color: #cccccc transparent; border-style: solid; }
.cms #vakata-contextmenu::before, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu::before { content: ""; display: block; /* reduce the damage in FF3.0 */ position: absolute; top: -10px; left: 24px; width: 0; border-width: 0 6px 10px 6px; border-color: #FFF transparent; border-style: solid; z-index: 10000; }
.cms #vakata-contextmenu::after, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu::after { content: ""; display: block; /* reduce the damage in FF3.0 */ position: absolute; top: -11px; left: 23px; width: 0; border-width: 0 7px 11px 7px; border-color: #CCC transparent; border-style: solid; }
.cms #vakata-contextmenu ul, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu ul { min-width: 180px; *width: 180px; }
.cms #vakata-contextmenu ul, .cms #vakata-contextmenu li, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu ul, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu li { margin: 0; padding: 0; list-style-type: none; display: block; }
.cms #vakata-contextmenu li, .TreeDropdownField .treedropdownfield-panel #vakata-contextmenu li { line-height: 20px; min-height: 23px; position: relative; padding: 0px; }
@ -790,13 +794,13 @@ form.import-form label.left { width: 250px; }
.tree-holder.jstree-apple a, .tree-holder.jstree-apple a:link, .cms-tree.jstree-apple a, .cms-tree.jstree-apple a:link { color: #0073c1; padding: 3px 6px 3px 3px; border: none; display: inline-block; margin-right: 5px; }
.tree-holder.jstree-apple ins, .cms-tree.jstree-apple ins { background-color: transparent; background-image: url(../images/sitetree_ss_default_icons.png); }
.tree-holder.jstree-apple span.badge, .cms-tree.jstree-apple span.badge { clear: both; text-transform: uppercase; display: inline-block; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-right: 6px; margin-top: -1px; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; border-radius: 2px / 2px; }
.tree-holder.jstree-apple span.badge.modified, .tree-holder.jstree-apple span.badge.addedtodraft, .cms-tree.jstree-apple span.badge.modified, .cms-tree.jstree-apple span.badge.addedtodraft { color: #7E7470; border: 1px solid #c9b800; background-color: #FFF0BC; }
.tree-holder.jstree-apple span.badge.deletedonlive, .tree-holder.jstree-apple span.badge.removedfromdraft, .cms-tree.jstree-apple span.badge.deletedonlive, .cms-tree.jstree-apple span.badge.removedfromdraft { color: #636363; border: 1px solid #e49393; background-color: #F2DADB; }
.tree-holder.jstree-apple span.badge.workflow-approval, .cms-tree.jstree-apple span.badge.workflow-approval { color: #56660C; border: 1px solid #7c8816; background-color: #DAE79A; }
.tree-holder.jstree-apple span.comment-count, .cms-tree.jstree-apple span.comment-count { clear: both; position: relative; text-transform: uppercase; display: inline-block; overflow: visible; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-right: 6px; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; border-radius: 2px / 2px; color: #7E7470; border: 1px solid #c9b800; background-color: #FFF0BC; }
.tree-holder.jstree-apple span.comment-count span.comment-count:before, .tree-holder.jstree-apple span.comment-count .cms-tree.jstree-apple span.comment-count:before, .cms-tree.jstree-apple .tree-holder.jstree-apple span.comment-count span.comment-count:before, .tree-holder.jstree-apple span.comment-count span.comment-count:after, .tree-holder.jstree-apple span.comment-count .cms-tree.jstree-apple span.comment-count:after, .cms-tree.jstree-apple .tree-holder.jstree-apple span.comment-count span.comment-count:after, .cms-tree.jstree-apple span.comment-count .tree-holder.jstree-apple span.comment-count:before, .tree-holder.jstree-apple .cms-tree.jstree-apple span.comment-count span.comment-count:before, .cms-tree.jstree-apple span.comment-count span.comment-count:before, .cms-tree.jstree-apple span.comment-count .tree-holder.jstree-apple span.comment-count:after, .tree-holder.jstree-apple .cms-tree.jstree-apple span.comment-count span.comment-count:after, .cms-tree.jstree-apple span.comment-count span.comment-count:after { content: ""; position: absolute; border-style: solid; /* reduce the damage in FF3.0 */ display: block; width: 0; }
.tree-holder.jstree-apple span.comment-count:before, .cms-tree.jstree-apple span.comment-count:before { bottom: -4px; /* value = - border-top-width - border-bottom-width */ left: 3px; /* controls horizontal position */ border-width: 4px 4px 0; border-color: #c9b800 transparent; }
.tree-holder.jstree-apple span.comment-count:after, .cms-tree.jstree-apple span.comment-count:after { bottom: -3px; /* value = - border-top-width - border-bottom-width */ left: 4px; /* value = (:before left) + (:before border-left) - (:after border-left) */ border-width: 3px 3px 0; border-color: #fff0bc transparent; }
.tree-holder.jstree-apple span.badge.modified, .tree-holder.jstree-apple span.badge.addedtodraft, .cms-tree.jstree-apple span.badge.modified, .cms-tree.jstree-apple span.badge.addedtodraft { color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
.tree-holder.jstree-apple span.badge.deletedonlive, .tree-holder.jstree-apple span.badge.removedfromdraft, .cms-tree.jstree-apple span.badge.deletedonlive, .cms-tree.jstree-apple span.badge.removedfromdraft { color: #636363; border: 1px solid #E49393; background-color: #F2DADB; }
.tree-holder.jstree-apple span.badge.workflow-approval, .cms-tree.jstree-apple span.badge.workflow-approval { color: #56660C; border: 1px solid #7C8816; background-color: #DAE79A; }
.tree-holder.jstree-apple span.comment-count, .cms-tree.jstree-apple span.comment-count { clear: both; position: relative; text-transform: uppercase; display: inline-block; overflow: visible; padding: 0px 3px; font-size: 0.75em; line-height: 1em; margin-left: 3px; margin-right: 6px; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; border-radius: 2px / 2px; color: #7E7470; border: 1px solid #C9B800; background-color: #FFF0BC; }
.tree-holder.jstree-apple span.comment-count span.comment-count:before, .tree-holder.jstree-apple span.comment-count span.comment-count:after, .cms-tree.jstree-apple span.comment-count span.comment-count:before, .cms-tree.jstree-apple span.comment-count span.comment-count:after { content: ""; position: absolute; border-style: solid; /* reduce the damage in FF3.0 */ display: block; width: 0; }
.tree-holder.jstree-apple span.comment-count:before, .cms-tree.jstree-apple span.comment-count:before { bottom: -4px; /* value = - border-top-width - border-bottom-width */ left: 3px; /* controls horizontal position */ border-width: 4px 4px 0; border-color: #C9B800 transparent; }
.tree-holder.jstree-apple span.comment-count:after, .cms-tree.jstree-apple span.comment-count:after { bottom: -3px; /* value = - border-top-width - border-bottom-width */ left: 4px; /* value = (:before left) + (:before border-left) - (:after border-left) */ border-width: 3px 3px 0; border-color: #FFF0BC transparent; }
.tree-holder.jstree-apple .jstree-hovered, .cms-tree.jstree-apple .jstree-hovered { text-shadow: none; text-decoration: none; }
.tree-holder.jstree-apple .jstree-closed > ins, .cms-tree.jstree-apple .jstree-closed > ins { background-position: 0 0; }
.tree-holder.jstree-apple .jstree-open > ins, .cms-tree.jstree-apple .jstree-open > ins { background-position: -20px 0; }
@ -898,10 +902,10 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; }
.cms-content-controls .preview-selector .chzn-drop .chzn-results { width: 135px; }
.cms-content-controls .preview-selector .chzn-drop .chzn-results .result-selected { background: #eceff1; }
.cms-content-controls .preview-selector .chzn-container { width: auto !important; }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop { padding: 0; border-bottom: 1px solid #aaaaaa; margin-top: -5px; width: auto !important; }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop { padding: 0; border-bottom: 1px solid #aaa; margin-top: -5px; width: auto !important; }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop .chzn-search { display: none; }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop ul { padding: 0; margin: 0; }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop ul li { font-size: 12px; line-height: 16px; padding: 7px 16px 7px 6px; color: #0073c1; border-bottom: 1px solid #dddddd; background-color: #FFF; /* Description styling */ }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop ul li { font-size: 12px; line-height: 16px; padding: 7px 16px 7px 6px; color: #0073c1; border-bottom: 1px solid #DDD; background-color: #FFF; /* Description styling */ }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop ul li:before { margin-right: 2px; }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop ul li.description { padding-top: 5px; padding-bottom: 5px; }
.cms-content-controls .preview-selector .chzn-container.chzn-with-rise .chzn-drop ul li.description:before { margin-top: 5px; }
@ -923,7 +927,7 @@ li.class-ErrorPage > a .jstree-pageicon { background-position: 0 -112px; }
/* Styling for the preview screen sizes */
.cms-preview { background-color: #eceff1; height: 100%; width: 100%; }
.cms-preview .cms-preview-overlay { width: 100%; height: 100%; }
.cms-preview .preview-note { color: #CDD7DC; display: block; font-size: 22px; font-weight: bold; height: 82px; margin-top: -50px; margin-left: -150px; /* half of width */ position: absolute; text-align: center; text-shadow: 0 1px 0 white; top: 50%; left: 50%; width: 300px; }
.cms-preview .preview-note { color: #CDD7DC; display: block; font-size: 22px; font-weight: bold; height: 82px; margin-top: -50px; margin-left: -150px; /* half of width */ position: absolute; text-align: center; text-shadow: 0 1px 0 #fff; top: 50%; left: 50%; width: 300px; }
.cms-preview .preview-note span { background: url('../images/sprites-64x64-s88957ee578.png') 0 0 no-repeat; display: block; height: 41px; margin: 0 auto 20px; width: 50px; }
.cms-preview .preview-scroll { height: 100%; overflow: auto; position: relative; width: 100%; }
.cms-preview .preview-scroll .preview-device-outer { height: 100%; width: 100%; }
@ -968,7 +972,7 @@ visible. Added and removed with js in TabSet.js */ /***************************
.cms .ss-ui-action-tabset.multi { /* Style the tab panels */ }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav { -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; overflow: hidden; *zoom: 1; border: 1px solid #b3b3b3; float: left; overflow: visible; padding: 0; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav:active { outline: none; box-shadow: none; -webkit-box-shadow: none; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li { background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eaeaea; border: none; border-right: 1px solid #eeeeee; border-left: 1px solid #b3b3b3; margin: 0; overflow: visible; min-width: 110px; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li { background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjUwJSIgeTE9IjAlIiB4Mj0iNTAlIiB5Mj0iMTAwJSI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Q5ZDlkOSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA=='); background-size: 100%; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f8f8f8), color-stop(100%, #d9d9d9)); background-image: -webkit-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -moz-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: -o-linear-gradient(top, #f8f8f8, #d9d9d9); background-image: linear-gradient(top, #f8f8f8, #d9d9d9); -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; background: #eaeaea; border: none; border-right: 1px solid #eee; border-left: 1px solid #b3b3b3; margin: 0; overflow: visible; min-width: 110px; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li:active { outline: none; box-shadow: none; -webkit-box-shadow: none; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; background: #f8f8f8; border-bottom: none !important; }
.cms .ss-ui-action-tabset.multi ul.ui-tabs-nav li.ui-state-active a { -moz-border-radius-bottomleft: 0px; -webkit-border-bottom-left-radius: 0px; border-bottom-left-radius: 0px; -moz-border-radius-bottomright: 0px; -webkit-border-bottom-right-radius: 0px; border-bottom-right-radius: 0px; }
@ -1036,7 +1040,7 @@ visible. Added and removed with js in TabSet.js */ /***************************
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li a:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1163px no-repeat; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1215px no-repeat; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset ul.ui-tabs-nav li.ui-state-active a:hover:after { background: url('../images/sprites-32x32-sf6890c994e.png') 0 -1137px no-repeat; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; /* Restyle for smaller area*/ clear: both; display: block; background-color: #eceff1; border: 1px solid #cccccc; border-bottom: 1px solid #eceff1; margin: 0; margin-top: 2px; max-width: 250px; padding: 8px 0 2px; position: absolute; z-index: 1; min-width: 190px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel { overflow: hidden; *zoom: 1; -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; border-top-left-radius: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; border-top-right-radius: 3px; -moz-border-radius-bottomleft: 0; -webkit-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -moz-border-radius-bottomright: 0; -webkit-border-bottom-right-radius: 0; border-bottom-right-radius: 0; /* Restyle for smaller area*/ clear: both; display: block; background-color: #eceff1; border: 1px solid #ccc; border-bottom: 1px solid #eceff1; margin: 0; margin-top: 2px; max-width: 250px; padding: 8px 0 2px; position: absolute; z-index: 1; min-width: 190px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h3, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h4, .cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h5 { font-weight: bold; line-height: 16px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h3 { font-size: 13px; }
.cms .ss-ui-action-tabset.action-menus.ss-tabset .ui-tabs-panel h4 { font-size: 12px; margin: 5px 0; }

View File

@ -643,6 +643,10 @@ jQuery.noConflict();
if(!tabset.data('tabs')) return; // don't act on uninit'ed controls
// The tabs may have changed, notify the widget that it should update its internal state.
tabset.tabs('refresh');
// Make sure the intended tab is selected.
if(forcedTab.length) {
index = forcedTab.index();
} else if(overrideStates && overrideStates[tabsetId]) {

View File

@ -153,16 +153,7 @@ form.nostyle {
}
form.stacked .field, .field.stacked {
label {
display: block;
float: none;
padding-bottom: 10px;
}
.middleColumn {
margin-left: 0px;
clear: left;
}
@include form-field-stacked;
}
form.small .field, .field.small {
@ -645,6 +636,8 @@ input.radio {
.htmleditor {
@include form-field-stacked;
textarea {
visibility: hidden; // enabled by JS
}

View File

@ -117,6 +117,26 @@
transition-duration: $time;
}
//** ----------------------------------------------------
// * Show label and field content in their own lines,
// * to maximize the available horizontal space.
// * ----------------------------------------------------- */
@mixin form-field-stacked {
label {
display: block;
float: none;
padding-bottom: 10px;
}
.middleColumn {
margin-left: 0px;
clear: left;
}
.description {
margin-left: 0px;
}
}
/*Mixin used to generate slightly smaller text and forms
Used in side panels and action tabs

View File

@ -651,7 +651,7 @@ body.cms {
}
div {
/*background:url(../images/btn-icon/settings.png) 5px 4px no-repeat;*/
background:url(../images/btn-icon/settings.png) 5px 4px no-repeat;
border-left:none;
width:100%;
}

View File

@ -97,7 +97,7 @@ class JSONDataFormatter extends DataFormatter {
$innerParts[] = ArrayData::array_to_object(array(
"className" => $relClass,
"href" => "$href.json",
"id" => $obj->$fieldName
"id" => $item->$fieldName
));
}
$serobj->$relName = $innerParts;
@ -118,7 +118,7 @@ class JSONDataFormatter extends DataFormatter {
$innerParts[] = ArrayData::array_to_object(array(
"className" => $relClass,
"href" => "$href.json",
"id" => $obj->$fieldName
"id" => $item->$fieldName
));
}
$serobj->$relName = $innerParts;

262
cache/Cache.php vendored
View File

@ -1,22 +1,29 @@
<?php
/**
* SS_Cache provides a bunch of static functions wrapping the Zend_Cache system in something a little more
* easy to use with the SilverStripe config system.
* SS_Cache provides a bunch of static functions wrapping the Zend_Cache system
* in something a little more easy to use with the SilverStripe config system.
*
* A Zend_Cache has both a frontend (determines how to get the value to cache, and how to serialize it for storage)
* and a backend (handles the actual storage).
* A Zend_Cache has both a frontend (determines how to get the value to cache,
* and how to serialize it for storage) and a backend (handles the actual
* storage).
*
* Rather than require library code to specify the backend directly, cache consumers provide a name for the cache
* backend they want. The end developer can then specify which backend to use for each name in their project's
* _config.php. They can also use 'all' to provide a backend for all named caches
* Rather than require library code to specify the backend directly, cache
* consumers provide a name for the cache backend they want. The end developer
* can then specify which backend to use for each name in their project's
* _config.php. They can also use 'all' to provide a backend for all named
* caches.
*
* End developers provide a set of named backends, then pick the specific backend for each named cache. There is a
* default File cache set up as the 'default' named backend, which is assigned to 'all' named caches
* End developers provide a set of named backends, then pick the specific
* backend for each named cache. There is a default File cache set up as the
* 'default' named backend, which is assigned to 'all' named caches.
*
* USING A CACHE
* <h2>Using a cache</h2>
*
* $cache = SS_Cache::factory('foo') ; // foo is any name (try to be specific), and is used to get configuration &
* storage info
* <code>
* // foo is any name (try to be specific), and is used to get configuration
* // & storage info
* $cache = SS_Cache::factory('foo');
*
* if (!($result = $cache->load($cachekey))) {
* $result = caluate some how;
@ -24,108 +31,184 @@
* }
*
* return $result;
* </code>
*
* Normally there's no need to remove things from the cache - the cache backends clear out entries based on age &
* maximum allocated storage. If you include the version of the object in the cache key, even object changes don't
* need any invalidation
* Normally there's no need to remove things from the cache - the cache
* backends clear out entries based on age & maximum allocated storage. If you
* include the version of the object in the cache key, even object changes
* don't need any invalidation.
*
* DISABLING CACHING IN DEV MOVE
* <h2>Disabling cache in dev mode</h2>
*
* (in _config.php)
* <code>
* // _config.php
* if (Director::isDev()) {
* SS_Cache::set_cache_lifetime('any', -1, 100);
* //
* </code>
*
* if (Director::isDev()) SS_Cache::set_cache_lifetime('any', -1, 100);
* <h2>Using memcached as a store</h2>
*
* USING MEMCACHED AS STORE
*
* (in _config.php)
*
* SS_Cache::add_backend('primary_memcached', 'Memcached',
* array('host' => 'localhost', 'port' => 11211, 'persistent' => true, 'weight' => 1, 'timeout' => 5,
* 'retry_interval' => 15, 'status' => true, 'failure_callback' => '' )
* <code>
* // _config.php
* SS_Cache::add_backend(
* 'primary_memcached',
* 'Memcached',
* array(
* 'host' => 'localhost',
* 'port' => 11211,
* 'persistent' => true,
* 'weight' => 1,
* 'timeout' => 5,
* 'retry_interval' => 15,
* 'status' => true,
* 'failure_callback' => ''
* )
* );
*
* SS_Cache::pick_backend('primary_memcached', 'any', 10);
* // Aggregate needs a backend with tag support, which memcached doesn't provide
*
* // Aggregate needs a backend with tag support, which memcached doesn't
* // provide
* SS_Cache::pick_backend('default', 'aggregate', 20);
* </code>
*
* USING APC AND FILE AS TWO LEVEL STORE
*
* (in _config.php)
* <h2>Using APC as a store</h2>
*
* <code>
* SS_Cache::add_backend('two-level', 'TwoLevels', array(
* 'slow_backend' => 'File',
* 'fast_backend' => 'Apc',
* 'slow_backend_options' => array('cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache')
* 'slow_backend_options' => array(
* 'cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache'
* )
* ));
*
* // No need for special backend for aggregate - TwoLevels with a File slow backend supports tags
* // No need for special backend for aggregate - TwoLevels with a File slow
* // backend supports tags
* SS_Cache::pick_backend('two-level', 'any', 10);
* </code>
*
* <h2>Invalidate an element</h2>
*
* <code>
* $cache = SS_Cache::factory('foo');
* $cache->remove($cachekey);
* </code>
*
* <h2>Clear the cache</h2>
*
* This clears the entire backend, not just this named cache partition.
*
* <code>
* $cache = SS_Cache::factory('foo');
* $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
* </code>
*
* @author hfried
* @package framework
* @subpackage core
*/
class SS_Cache {
/**
* @var array $backends
*/
protected static $backends = array();
/**
* @var array $backend_picks
*/
protected static $backend_picks = array();
/**
* @var array $cache_lifetime
*/
protected static $cache_lifetime = array();
/**
* Initialize the 'default' named cache backend
* Initialize the 'default' named cache backend.
*/
protected static function init(){
if (!isset(self::$backends['default'])) {
$cachedir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache';
if (!is_dir($cachedir)) mkdir($cachedir);
if (!is_dir($cachedir)) {
mkdir($cachedir);
}
self::$backends['default'] = array(
'File',
array('cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache')
array(
'cache_dir' => $cachedir
)
);
self::$cache_lifetime['default'] = array(
'lifetime' => 600,
'priority' => 1
);
self::$cache_lifetime['default'] = array('lifetime' => 600, 'priority' => 1);
}
}
/**
* Add a new named cache backend
* Add a new named cache backend.
*
* @see http://framework.zend.com/manual/en/zend.cache.html
*
* @param string $name The name of this backend as a freeform string
* @param string $type The Zend_Cache backend ('File' or 'Sqlite' or ...)
* @param array $options The Zend_Cache backend options (see http://framework.zend.com/manual/en/zend.cache.html)
* @param array $options The Zend_Cache backend options
*
* @return none
*/
public static function add_backend($name, $type, $options=array()) {
public static function add_backend($name, $type, $options = array()) {
self::init();
self::$backends[$name] = array($type, $options);
}
/**
* Pick a named cache backend for a particular named cache
* Pick a named cache backend for a particular named cache.
*
* The priority call with the highest number will be the actual backend
* picked. A backend picked for a specific cache name will always be used
* instead of 'any' if it exists, no matter the priority.
*
* @param string $name The name of the backend, as passed as the first argument to add_backend
* @param string $for The name of the cache to pick this backend for (or 'any' for any backend)
* @param integer $priority The priority of this pick - the call with the highest number will be the actual
* backend picked. A backend picked for a specific cache name will always be used instead
* of 'any' if it exists, no matter the priority.
* @param integer $priority The priority of this pick
*
* @return none
*/
public static function pick_backend($name, $for, $priority=1) {
public static function pick_backend($name, $for, $priority = 1) {
self::init();
$current = -1;
if (isset(self::$backend_picks[$for])) $current = self::$backend_picks[$for]['priority'];
if ($priority >= $current) self::$backend_picks[$for] = array('name' => $name, 'priority' => $priority);
if (isset(self::$backend_picks[$for])) {
$current = self::$backend_picks[$for]['priority'];
}
if ($priority >= $current) {
self::$backend_picks[$for] = array(
'name' => $name,
'priority' => $priority
);
}
}
/**
* Return the cache lifetime for a particular named cache.
* @return array
*
* @param string $for
*
* @return string
*/
public static function get_cache_lifetime($for) {
return (isset(self::$cache_lifetime[$for])) ? self::$cache_lifetime[$for] : false;
if(isset(self::$cache_lifetime[$for])) {
return self::$cache_lifetime[$for];
}
return null;
}
/**
@ -140,52 +223,29 @@ class SS_Cache {
self::init();
$current = -1;
if (isset(self::$cache_lifetime[$for])) $current = self::$cache_lifetime[$for]['priority'];
if (isset(self::$cache_lifetime[$for])) {
$current = self::$cache_lifetime[$for]['priority'];
}
if ($priority >= $current) {
self::$cache_lifetime[$for] = array('lifetime' => $lifetime, 'priority' => $priority);
self::$cache_lifetime[$for] = array(
'lifetime' => $lifetime,
'priority' => $priority
);
}
}
/**
* Build a cache object
* @param string $for The name of the cache to build the cache object for
* @param string $frontend (optional) The type of Zend_Cache frontend to use. Output is almost always the best
* Build a cache object.
*
* @see http://framework.zend.com/manual/en/zend.cache.html
*
* @param string $for The name of the cache to build
* @param string $frontend (optional) The type of Zend_Cache frontend
* @param array $frontendOptions (optional) Any frontend options to use.
*
* @return The cache object. It has been partitioned so that actions on the object
* won't affect cache objects generated with a different $for value, even those using the same Zend_Backend
*
* -- Cache a calculation
*
* if (!($result = $cache->load($cachekey))) {
* $result = caluate some how;
* $cache->save($result);
* }
*
* return $result;
*
* -- Cache captured output
*
* if (!($cache->start($cachekey))) {
*
* // output everything as usual
* echo 'Hello world! ';
* echo 'This is cached ('.time().') ';
*
* $cache->end(); // output buffering ends
* }
*
* -- Invalidate an element
*
* $cache->remove($cachekey);
*
* -- Clear the cache (warning - this clears the entire backend, not just this named cache partition)
*
* $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
*
* See the Zend_Cache documentation at http://framework.zend.com/manual/en/zend.cache.html for more
*
* @return Zend_Cache_Frontend The cache object
*/
public static function factory($for, $frontend='Output', $frontendOptions=null) {
self::init();
@ -195,15 +255,19 @@ class SS_Cache {
$cache_lifetime = self::$cache_lifetime['default']['lifetime'];
$lifetime_priority = -1;
foreach (array('any', $for) as $name) {
if (isset(self::$backend_picks[$name]) && self::$backend_picks[$name]['priority'] > $backend_priority) {
$backend_name = self::$backend_picks[$name]['name'];
$backend_priority = self::$backend_picks[$name]['priority'];
foreach(array('any', $for) as $name) {
if(isset(self::$backend_picks[$name])) {
if(self::$backend_picks[$name]['priority'] > $backend_priority) {
$backend_name = self::$backend_picks[$name]['name'];
$backend_priority = self::$backend_picks[$name]['priority'];
}
}
if (isset(self::$cache_lifetime[$name]) && self::$cache_lifetime[$name]['priority'] > $lifetime_priority) {
$cache_lifetime = self::$cache_lifetime[$name]['lifetime'];
$lifetime_priority = self::$cache_lifetime[$name]['priority'];
if (isset(self::$cache_lifetime[$name])) {
if(self::$cache_lifetime[$name]['priority'] > $lifetime_priority) {
$cache_lifetime = self::$cache_lifetime[$name]['lifetime'];
$lifetime_priority = self::$cache_lifetime[$name]['priority'];
}
}
}
@ -211,12 +275,18 @@ class SS_Cache {
$basicOptions = array('cache_id_prefix' => $for);
if ($cache_lifetime >= 0) $basicOptions['lifetime'] = $cache_lifetime;
else $basicOptions['caching'] = false;
if ($cache_lifetime >= 0) {
$basicOptions['lifetime'] = $cache_lifetime;
} else {
$basicOptions['caching'] = false;
}
$frontendOptions = $frontendOptions ? array_merge($basicOptions, $frontendOptions) : $basicOptions;
require_once 'Zend/Cache.php';
return Zend_Cache::factory($frontend, $backend[0], $frontendOptions, $backend[1]);
return Zend_Cache::factory(
$frontend, $backend[0], $frontendOptions, $backend[1]
);
}
}

View File

@ -46,6 +46,12 @@ class ContentNegotiator extends Object {
*/
private static $enabled = false;
/**
* @config
* @var string
*/
private static $default_format = 'html';
/**
* Set the character set encoding for this page. By default it's utf-8, but you could change it to, say,
* windows-1252, to improve interoperability with extended characters being imported from windows excel.
@ -114,7 +120,7 @@ class ContentNegotiator extends Object {
);
$q = array();
if(headers_sent()) {
$chosenFormat = "html";
$chosenFormat = Config::inst()->get('ContentNegotiator', 'default_format');
} else if(isset($_GET['forceFormat'])) {
$chosenFormat = $_GET['forceFormat'];
@ -139,7 +145,7 @@ class ContentNegotiator extends Object {
krsort($q);
$chosenFormat = reset($q);
} else {
$chosenFormat = "html";
$chosenFormat = Config::inst()->get('ContentNegotiator', 'default_format');
}
}
}

View File

@ -418,33 +418,63 @@ class Director implements TemplateGlobalProvider {
}
/**
* Return the current protocol that the site is running under
* Return the current protocol that the site is running under.
*
* @return String
* @return string
*/
public static function protocol() {
if(isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])&&strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])=='https') {
return "https://";
return (self::is_https()) ? 'https://' : 'http://';
}
/**
* Return whether the site is running as under HTTPS.
*
* @return boolean
*/
public static function is_https() {
if(isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) {
if(strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https') {
return true;
}
}
return (isset($_SERVER['SSL']) || (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off'))
? 'https://' : 'http://';
if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
return true;
}
else if(isset($_SERVER['SSL'])) {
return true;
}
return false;
}
/**
* Returns the root URL for the site.
* It will be automatically calculated unless it is overridden with {@link setBaseURL()}.
*
* It will be automatically calculated unless it is overridden with
* {@link setBaseURL()}.
*
* @return string
*/
public static function baseURL() {
$alternate = Config::inst()->get('Director', 'alternate_base_url');
if($alternate) {
return $alternate;
} else {
$base = BASE_URL;
if($base == '/' || $base == '/.' || $base == '\\') $baseURL = '/';
else $baseURL = $base . '/';
if(defined('BASE_SCRIPT_URL')) return $baseURL . BASE_SCRIPT_URL;
else return $baseURL;
if($base == '/' || $base == '/.' || $base == '\\') {
$baseURL = '/';
} else {
$baseURL = $base . '/';
}
if(defined('BASE_SCRIPT_URL')) {
return $baseURL . BASE_SCRIPT_URL;
}
return $baseURL;
}
}
@ -579,7 +609,7 @@ class Director implements TemplateGlobalProvider {
// Check for more than one leading slash without a protocol.
// While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
// and hence a potential security risk. Single leading slashes are not an issue though.
|| preg_match('/\s*[\/]{2,}/', $url)
|| preg_match('%^\s*/{2,}%', $url)
|| (
// If a colon is found, check if it's part of a valid scheme definition
// (meaning its not preceded by a slash).
@ -737,9 +767,7 @@ class Director implements TemplateGlobalProvider {
$matched = true;
}
if($matched && (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off')
&& !(isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https')) {
if($matched && !self::is_https()) {
// if an domain is specified, redirect to that instead of the current domain
if($secureDomain) {
@ -755,6 +783,7 @@ class Director implements TemplateGlobalProvider {
return $destURL;
} else {
if(!headers_sent()) header("Location: $destURL");
die("<h1>Your browser is not accepting header redirects</h1>"
. "<p>Please <a href=\"$destURL\">click here</a>");
}

View File

@ -1,4 +1,5 @@
<?php
/**
* A class with HTTP-related helpers.
* Like Debug, this is more a bundle of methods than a class ;-)
@ -8,17 +9,30 @@
*/
class HTTP {
/**
* @var int $cache_age
*/
protected static $cache_age = 0;
/**
* @var timestamp $modification_date
*/
protected static $modification_date = null;
/**
* @var string $etag
*/
protected static $etag = null;
/**
* Turns a local system filename into a URL by comparing it to the script filename
* Turns a local system filename into a URL by comparing it to the script
* filename.
*
* @param string
*/
public static function filename2url($filename) {
$slashPos = -1;
while(($slashPos = strpos($filename, "/", $slashPos+1)) !== false) {
if(substr($filename, 0, $slashPos) == substr($_SERVER['SCRIPT_FILENAME'],0,$slashPos)) {
$commonLength = $slashPos;
@ -27,13 +41,19 @@ class HTTP {
}
}
$urlBase = substr($_SERVER['PHP_SELF'], 0, -(strlen($_SERVER['SCRIPT_FILENAME']) - $commonLength));
$urlBase = substr(
$_SERVER['PHP_SELF'],
0,
-(strlen($_SERVER['SCRIPT_FILENAME']) - $commonLength)
);
$url = $urlBase . substr($filename, $commonLength);
$protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') ? "https" : "http";
return "$protocol://". $_SERVER['HTTP_HOST'] . $url;
// Count the number of extra folders the script is in.
// $prefix = str_repeat("../", substr_count(substr($_SERVER[SCRIPT_FILENAME],$commonBaseLength)));
return "$protocol://". $_SERVER['HTTP_HOST'] . $url;
}
/**
@ -42,7 +62,10 @@ class HTTP {
public static function absoluteURLs($html) {
$html = str_replace('$CurrentPageURL', $_SERVER['REQUEST_URI'], $html);
return HTTP::urlRewriter($html, function($url) {
if(stripos($url, 'mailto:') === 0) return $url;
//no need to rewrite, if uri has a protocol (determined here by existence of reserved URI character ":")
if(preg_match('/^\w+:/', $url)){
return $url;
}
return Director::absoluteURL($url, true);
});
}

View File

@ -1,14 +1,21 @@
<?php
/**
* Handle the X-Pjax header that AJAX responses may provide, returning the
* fragment, or, in the case of non-AJAX form submissions, redirecting back to the submitter.
* fragment, or, in the case of non-AJAX form submissions, redirecting back
* to the submitter.
*
* X-Pjax ensures that users won't end up seeing the unstyled form HTML in
* their browser.
*
* X-Pjax ensures that users won't end up seeing the unstyled form HTML in their browser
* If a JS error prevents the Ajax overriding of form submissions from happening.
*
* It also provides better non-JS operation.
*
* Caution: This API is volatile, and might eventually be replaced by a generic
* action helper system for controllers.
*
* @package framework
* @subpackage control
*/
class PjaxResponseNegotiator {

View File

@ -1,10 +1,8 @@
<?php
/**
* Description of RequestProcessor
*
* @author marcus@silverstripe.com.au
* @license BSD License http://silverstripe.org/bsd-license/
* @package framework
* @subpackage control
*/
class RequestProcessor {

View File

@ -2,13 +2,10 @@
/**
* A class that proxies another, allowing various functionality to be
* injected
* injected.
*
* @author marcus@silverstripe.com.au
* @package framework
* @subpackage injector
*
* @license http://silverstripe.org/bsd-license/
*/
class AopProxyService {
public $beforeCall = array();

View File

@ -1,15 +1,13 @@
<?php
/**
* A BeforeCallAspect is run before a method is executed
* A BeforeCallAspect is run before a method is executed.
*
* This is a declared interface, but isn't actually required
* as PHP doesn't really care about types...
*
* @author Marcus Nyeholt <marcus@silverstripe.com.au>
* @package framework
* @subpackage injector
* @license BSD http://silverstripe.org/BSD-license
*/
interface BeforeCallAspect {

View File

@ -0,0 +1,26 @@
<?php
/**
* A class for creating new objects by the injector.
*
* @package framework
* @subpackage injector
*/
class InjectionCreator {
/**
* @param string $object
* A string representation of the class to create
* @param array $params
* An array of parameters to be passed to the constructor
*/
public function create($class, $params = array()) {
$reflector = new ReflectionClass($class);
if (count($params)) {
return $reflector->newInstanceArgs($params);
}
return $reflector->newInstance();
}
}

View File

@ -0,0 +1,15 @@
<?php
/**
* Used to locate configuration for a particular named service.
*
* If it isn't found, return null.
*
* @package framework
* @subpackage injector
*/
class ServiceConfigurationLocator {
public function locateConfigFor($name) {
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* @package framework
* @subpackage injector
*/
class SilverStripeInjectionCreator {
/**
*
* @param string $object
* A string representation of the class to create
* @param array $params
* An array of parameters to be passed to the constructor
*/
public function create($class, $params = array()) {
$class = Object::getCustomClass($class);
$reflector = new ReflectionClass($class);
return $reflector->newInstanceArgs($params);
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Use the SilverStripe configuration system to lookup config for a
* particular service.
*
* @package framework
* @subpackage injector
*/
class SilverStripeServiceConfigurationLocator {
private $configs = array();
public function locateConfigFor($name) {
if (isset($this->configs[$name])) {
return $this->configs[$name];
}
$config = Config::inst()->get('Injector', $name);
if ($config) {
$this->configs[$name] = $config;
return $config;
}
// do parent lookup if it's a class
if (class_exists($name)) {
$parents = array_reverse(array_keys(ClassInfo::ancestry($name)));
array_shift($parents);
foreach ($parents as $parent) {
// have we already got for this?
if (isset($this->configs[$parent])) {
return $this->configs[$parent];
}
$config = Config::inst()->get('Injector', $parent);
if ($config) {
$this->configs[$name] = $config;
return $config;
} else {
$this->configs[$parent] = false;
}
}
// there is no parent config, so we'll record that as false so we don't do the expensive
// lookup through parents again
$this->configs[$name] = false;
}
}
}

View File

@ -1,6 +1,8 @@
<?php
/**
* Library of static methods for manipulating arrays.
*
* @package framework
* @subpackage misc
*/
@ -43,7 +45,10 @@ class ArrayLib {
* @return array
*/
public static function invert($arr) {
if(!$arr) return false;
if(!$arr) {
return false;
}
$result = array();
foreach($arr as $columnName => $column) {
@ -56,7 +61,7 @@ class ArrayLib {
}
/**
* Return an array where the keys are all equal to the values
* Return an array where the keys are all equal to the values.
*
* @param $arr array
* @return array
@ -70,7 +75,8 @@ class ArrayLib {
*/
public static function array_values_recursive($arr) {
$lst = array();
foreach(array_keys($arr) as $k){
foreach(array_keys($arr) as $k) {
$v = $arr[$k];
if (is_scalar($v)) {
$lst[] = $v;
@ -80,92 +86,115 @@ class ArrayLib {
);
}
}
return $lst;
}
/**
* Filter an array by keys (useful for only allowing certain form-input to be saved).
* Filter an array by keys (useful for only allowing certain form-input to
* be saved).
*
* @param $arr array
* @param $keys array
*
* @return array
*/
public static function filter_keys($arr, $keys)
{
foreach ($arr as $key => $v) {
if (!in_array($key, $keys)) {
public static function filter_keys($arr, $keys) {
foreach($arr as $key => $v) {
if(!in_array($key, $keys)) {
unset($arr[$key]);
}
}
return $arr;
}
/**
* Determines if an array is associative by checking
* for existing keys via array_key_exists().
* Determines if an array is associative by checking for existing keys via
* array_key_exists().
*
* @see http://nz.php.net/manual/en/function.is-array.php#76188
*
* @param array $arr
*
* @return boolean
*/
public static function is_associative($arr) {
if(is_array($arr) && ! empty($arr)) {
for($iterator = count($arr) - 1; $iterator; $iterator--) {
if (!array_key_exists($iterator, $arr)) return true;
if (!array_key_exists($iterator, $arr)) {
return true;
}
}
return !array_key_exists(0, $arr);
}
return false;
}
/**
* Recursively searches an array $haystack for the value(s) $needle.
*
* Assumes that all values in $needle (if $needle is an array) are at
* the SAME level, not spread across multiple dimensions of the $haystack.
*
* @param mixed $needle
* @param array $haystack
* @param boolean $strict
*
* @return boolean
*/
public static function in_array_recursive($needle, $haystack, $strict = false) {
if(!is_array($haystack)) return false; // Not an array, we've gone as far as we can down this branch
if(!is_array($haystack)) {
return false;
}
if(in_array($needle, $haystack, $strict)) return true; // Is it in this level of the array?
else {
foreach($haystack as $obj) { // It's not, loop over the rest of this array
if(self::in_array_recursive($needle, $obj, $strict)) return true;
if(in_array($needle, $haystack, $strict)) {
return true;
} else {
foreach($haystack as $obj) {
if(self::in_array_recursive($needle, $obj, $strict)) {
return true;
}
}
}
return false; // Never found $needle :(
return false;
}
/**
* Recursively merges two or more arrays.
*
* Behaves similar to array_merge_recursive(), however it only merges values when both are arrays
* rather than creating a new array with both values, as the PHP version does. The same behaviour
* also occurs with numeric keys, to match that of what PHP does to generate $_REQUEST.
* Behaves similar to array_merge_recursive(), however it only merges
* values when both are arrays rather than creating a new array with
* both values, as the PHP version does. The same behaviour also occurs
* with numeric keys, to match that of what PHP does to generate $_REQUEST.
*
* @param array $array
*
* @param array $array, ...
* @return array
*/
public static function array_merge_recursive($array) {
$arrays = func_get_args();
$merged = array();
if(count($arrays) == 1) {
return $array;
}
while ($arrays) {
$array = array_shift($arrays);
if (!is_array($array)) {
trigger_error('ArrayLib::array_merge_recursive() encountered a non array argument', E_USER_WARNING);
return;
}
if (!$array) {
continue;
}
foreach ($array as $key => $value) {
if (is_array($value) && array_key_exists($key, $merged) && is_array($merged[$key])) {
$merged[$key] = ArrayLib::array_merge_recursive($merged[$key], $value);
@ -174,6 +203,30 @@ class ArrayLib {
}
}
}
return $merged;
}
/**
* Takes an multi dimension array and returns the flattened version.
*
* @param array $array
* @param boolean $preserveKeys
*
* @return array
*/
public static function flatten($array, $preserveKeys = true, &$out = array()) {
foreach($array as $key => $child) {
if(is_array($child)) {
$out = self::flatten($child, $preserveKeys, $out);
} else if($preserveKeys) {
$out[$key] = $child;
} else {
$out[] = $child;
}
}
return $out;
}
}

View File

@ -10,166 +10,215 @@
* - An array
* - A non-array value
*
* If the value is an array, each value in the array may also be one of those three types
* If the value is an array, each value in the array may also be one of those
* three types.
*
* A property can have a value specified in multiple locations, each of which have a hardcoded or explicit priority.
* We combine all these values together into a "composite" value using rules that depend on the priority order of the
* locations to give the final value, using these rules:
* A property can have a value specified in multiple locations, each of which
* have a hard coded or explicit priority. We combine all these values together
* into a "composite" value using rules that depend on the priority order of
* the locations to give the final value, using these rules:
*
* - If the value is an array, each array is added to the _beginning_ of the composite array in ascending priority
* order. If a higher priority item has a non-integer key which is the same as a lower priority item, the value of
* those items is merged using these same rules, and the result of the merge is located in the same location the
* higher priority item would be if there was no key clash. Other than in this key-clash situation, within the
* particular array, order is preserved.
* - If the value is an array, each array is added to the _beginning_ of the
* composite array in ascending priority order. If a higher priority item has
* a non-integer key which is the same as a lower priority item, the value of
* those items is merged using these same rules, and the result of the merge
* is located in the same location the higher priority item would be if there
* was no key clash. Other than in this key-clash situation, within the
* particular array, order is preserved.
*
* - If the value is not an array, the highest priority value is used without any attempt to merge
* - If the value is not an array, the highest priority value is used without
* any attempt to merge.
*
* It is an error to have mixed types of the same named property in different locations (but an error will not
* necessarily be raised due to optimisations in the lookup code)
* It is an error to have mixed types of the same named property in different
* locations (but an error will not necessarily be raised due to optimizations
* in the lookup code).
*
* The exception to this is "false-ish" values - empty arrays, empty strings, etc. When merging a non-false-ish value
* with a false-ish value, the result will be the non-false-ish value regardless of priority. When merging two
* The exception to this is "false-ish" values - empty arrays, empty strings,
* etc. When merging a non-false-ish value with a false-ish value, the result
* will be the non-false-ish value regardless of priority. When merging two
* false-ish values the result will be the higher priority false-ish value.
*
* The locations that configuration values are taken from in highest -> lowest priority order
* The locations that configuration values are taken from in highest -> lowest
* priority order.
*
* - Any values set via a call to Config#update
* - Any values set via a call to Config#update.
*
* - The configuration values taken from the YAML files in _config directories (internally sorted in before / after
* order, where the item that is latest is highest priority)
* - The configuration values taken from the YAML files in _config directories
* (internally sorted in before / after order, where the item that is latest
* is highest priority).
*
* - Any static set on an "additional static source" class (such as an extension) named the same as the name of the
* property
* - Any static set on an "additional static source" class (such as an
* extension) named the same as the name of the property.
*
* - Any static set on the class named the same as the name of the property
* - Any static set on the class named the same as the name of the property.
*
* - The composite configuration value of the parent class of this class
* - The composite configuration value of the parent class of this class.
*
* At some of these levels you can also set masks. These remove values from the composite value at their priority
* point rather than add. They are much simpler. They consist of a list of key / value pairs. When applied against the
* current composite value:
* At some of these levels you can also set masks. These remove values from the
* composite value at their priority point rather than add. They are much
* simpler. They consist of a list of key / value pairs. When applied against
* the current composite value:
*
* - If the composite value is a sequential array, any member of that array that matches any value in the mask is
* removed
* - If the composite value is a sequential array, any member of that array
* that matches any value in the mask is removed.
*
* - If the composite value is an associative array, any member of that array that matches both the key and value of
* any pair in the mask is removed
* - If the composite value is an associative array, any member of that array
* that matches both the key and value of any pair in the mask is removed.
*
* - If the composite value is not an array, if that value matches any value in the mask it is removed
* - If the composite value is not an array, if that value matches any value
* in the mask it is removed.
*
* @package framework
* @subpackage core
*/
class Config {
/**
* [A marker instance for the "anything" singleton value. Don't access directly, even in-class, always use
* self::anything()
* A marker instance for the "anything" singleton value. Don't access
* directly, even in-class, always use self::anything()
*
* @var Object
*/
static private $_anything = null;
private static $_anything = null;
/**
* Get a marker class instance that is used to do a "remove anything with this key" by adding
* $key => Config::anything() to the suppress array
* Get a marker class instance that is used to do a "remove anything with
* this key" by adding $key => Config::anything() to the suppress array
*
* @todo Does this follow the SS coding conventions? Config::get_anything_marker_instance() is a lot less elegant.
* @return Object
*/
static public function anything() {
if (self::$_anything === null) self::$_anything = new stdClass();
public static function anything() {
if (self::$_anything === null) {
self::$_anything = new stdClass();
}
return self::$_anything;
}
// -- Source options bitmask --
/**
* source options bitmask value - merge all parent configuration in as lowest priority
* source options bitmask value - merge all parent configuration in as
* lowest priority.
*
* @const
*/
const INHERITED = 0;
/**
* source options bitmask value - only get configuration set for this specific class, not any of it's parents
* source options bitmask value - only get configuration set for this
* specific class, not any of it's parents.
*
* @const
*/
const UNINHERITED = 1;
/**
* source options bitmask value - inherit, but stop on the first class that actually provides a value (event an
* empty value)
* source options bitmask value - inherit, but stop on the first class
* that actually provides a value (event an empty value).
*
* @const
*/
const FIRST_SET = 2;
/** @const source options bitmask value - do not use additional statics sources (such as extension) */
/**
* @const source options bitmask value - do not use additional statics
* sources (such as extension)
*/
const EXCLUDE_EXTRA_SOURCES = 4;
// -- get_value_type response enum --
/**
* Return flag for get_value_type indicating value is a scalar (or really just not-an-array, at least ATM)
* Return flag for get_value_type indicating value is a scalar (or really
* just not-an-array, at least ATM)
*
* @const
*/
const ISNT_ARRAY = 1;
/**
* Return flag for get_value_type indicating value is an array
* Return flag for get_value_type indicating value is an array.
* @const
*/
const IS_ARRAY = 2;
/**
* Get whether the value is an array or not. Used to be more complicated, but still nice sugar to have an enum
* to compare and not just a true/false value
* Get whether the value is an array or not. Used to be more complicated,
* but still nice sugar to have an enum to compare and not just a true /
* false value.
*
* @param $val any - The value
*
* @return int - One of ISNT_ARRAY or IS_ARRAY
*/
static protected function get_value_type($val) {
if (is_array($val)) return self::IS_ARRAY;
protected static function get_value_type($val) {
if (is_array($val)) {
return self::IS_ARRAY;
}
return self::ISNT_ARRAY;
}
/**
* What to do if there's a type mismatch
* What to do if there's a type mismatch.
*
* @throws UnexpectedValueException
*/
static protected function type_mismatch() {
protected static function type_mismatch() {
throw new UnexpectedValueException('Type mismatch in configuration. All values for a particular property must'
. ' contain the same type (or no value at all).');
}
/* @todo If we can, replace next static & static methods with DI once that's in */
static protected $instance;
/**
* @todo If we can, replace next static & static methods with DI once that's in
*/
protected static $instance;
/**
* Get the current active Config instance.
*
* Configs should not normally be manually created.
* In general use you will use this method to obtain the current Config instance.
*
* In general use you will use this method to obtain the current Config
* instance.
*
* @return Config
*/
static public function inst() {
if (!self::$instance) self::$instance = new Config();
public static function inst() {
if (!self::$instance) {
self::$instance = new Config();
}
return self::$instance;
}
/**
* Set the current active Config instance.
* Set the current active {@link Config} instance.
*
* Configs should not normally be manually created.
* A use case for replacing the active configuration set would be for creating an isolated environment for
* unit tests
* {@link Config} objects should not normally be manually created.
*
* A use case for replacing the active configuration set would be for
* creating an isolated environment for unit tests.
*
* @return Config
*/
static public function set_instance($instance) {
public static function set_instance($instance) {
self::$instance = $instance;
global $_SINGLETONS;
$_SINGLETONS['Config'] = $instance;
}
/**
* Make the newly active Config be a copy of the current active Config instance.
* Make the newly active {@link Config} be a copy of the current active
* {@link Config} instance.
*
* You can then make changes to the configuration by calling update and remove on the new
* value returned by Config::inst(), and then discard those changes later by calling unnest
* You can then make changes to the configuration by calling update and
* remove on the new value returned by Config::inst(), and then discard
* those changes later by calling unnest.
*/
static public function nest() {
public static function nest() {
$current = self::$instance;
$new = clone $current;
@ -178,17 +227,21 @@ class Config {
}
/**
* Change the active Config back to the Config instance the current active Config object
* was copied from
* Change the active Config back to the Config instance the current active
* Config object was copied from.
*/
static public function unnest() {
public static function unnest() {
self::set_instance(self::$instance->nestedFrom);
}
/**
* @var array
*/
protected $cache;
/**
* Each copy of the Config object need's it's own cache, so changes don't leak through to other instances
* Each copy of the Config object need's it's own cache, so changes don't
* leak through to other instances.
*/
public function __construct() {
$this->cache = new Config_LRU();
@ -198,21 +251,37 @@ class Config {
$this->cache = clone $this->cache;
}
/** @var Config - The config instance this one was copied from when Config::nest() was called */
/**
* @var Config - The config instance this one was copied from when
* Config::nest() was called.
*/
protected $nestedFrom = null;
/** @var [array] - Array of arrays. Each member is an nested array keyed as $class => $name => $value,
* where value is a config value to treat as the highest priority item */
/**
* @var array - Array of arrays. Each member is an nested array keyed as
* $class => $name => $value, where value is a config value to treat as
* the highest priority item.
*/
protected $overrides = array();
/** @var [array] - Array of arrays. Each member is an nested array keyed as $class => $name => $value,
* where value is a config value suppress from any lower priority item */
/**
* @var array $suppresses Array of arrays. Each member is an nested array
* keyed as $class => $name => $value, where value is a config value suppress
* from any lower priority item.
*/
protected $suppresses = array();
/**
* @var array
*/
protected $staticManifests = array();
/**
* @param SS_ConfigStaticManifest
*/
public function pushConfigStaticManifest(SS_ConfigStaticManifest $manifest) {
array_unshift($this->staticManifests, $manifest);
$this->cache->clean();
}
@ -596,6 +665,10 @@ class Config {
}
/**
* @package framework
* @subpackage core
*/
class Config_LRU {
const SIZE = 1000;
@ -701,25 +774,54 @@ class Config_LRU {
}
}
/**
* @package framework
* @subpackage core
*/
class Config_ForClass {
/**
* @var string $class
*/
protected $class;
/**
* @param string $class
*/
public function __construct($class) {
$this->class = $class;
}
/**
* @param string $name
*/
public function __get($name) {
return Config::inst()->get($this->class, $name);
}
/**
* @param string $name
* @param mixed $val
*/
public function __set($name, $val) {
return Config::inst()->update($this->class, $name, $val);
}
/**
* @param string $name
* @param int $sourceOptions
*
* @return array|scalar
*/
public function get($name, $sourceOptions = 0) {
return Config::inst()->get($this->class, $name, $sourceOptions);
}
/**
* @param string
*
* @return Config_ForClass
*/
public function forClass($class) {
return Config::inst()->forClass($class);
}

View File

@ -44,24 +44,46 @@
error_reporting(E_ALL | E_STRICT);
/**
* Include _ss_environment.php files
* Include _ss_environment.php file
*/
//define the name of the environment file
$envFile = '_ss_environment.php';
//define the dir to start scanning from (have to add the trailing slash)
$dir = '.';
//check this dir and every parent dir (until we hit the base of the drive)
do {
$dir = realpath($dir) . '/';
//if the file exists, then we include it, set relevant vars and break out
if (file_exists($dir . $envFile)) {
define('SS_ENVIRONMENT_FILE', $dir . $envFile);
include_once(SS_ENVIRONMENT_FILE);
break;
}
//here we need to check that the real path of the last dir and the next one are
// not the same, if they are, we have hit the root of the drive
} while (realpath($dir) != realpath($dir .= '../'));
//define the dirs to start scanning from (have to add the trailing slash)
// we're going to check the realpath AND the path as the script sees it
$dirsToCheck = array(
realpath('.'),
dirname($_SERVER['SCRIPT_FILENAME'])
);
//if they are the same, remove one of them
if ($dirsToCheck[0] == $dirsToCheck[1]) {
unset($dirsToCheck[1]);
}
foreach ($dirsToCheck as $dir) {
//check this dir and every parent dir (until we hit the base of the drive)
// or until we hit a dir we can't read
do {
//add the trailing slash we need to concatenate properly
$dir .= DIRECTORY_SEPARATOR;
//if it's readable, go ahead
if (@is_readable($dir)) {
//if the file exists, then we include it, set relevant vars and break out
if (file_exists($dir . $envFile)) {
define('SS_ENVIRONMENT_FILE', $dir . $envFile);
include_once(SS_ENVIRONMENT_FILE);
//break out of BOTH loops because we found the $envFile
break(2);
}
}
else {
//break out of the while loop, we can't read the dir
break;
}
//go up a directory
$dir = dirname($dir);
//here we need to check that the path of the last dir and the next one are
// not the same, if they are, we have hit the root of the drive
} while (dirname($dir) != $dir);
}
///////////////////////////////////////////////////////////////////////////////
// GLOBALS AND DEFINE SETTING

View File

@ -66,6 +66,52 @@ abstract class Object {
*/
protected $extension_instances = array();
/**
* List of callbacks to call prior to extensions having extend called on them,
* each grouped by methodName.
*
* @var array[callable]
*/
protected $beforeExtendCallbacks = array();
/**
* Allows user code to hook into Object::extend prior to control
* being delegated to extensions. Each callback will be reset
* once called.
*
* @param string $method The name of the method to hook into
* @param callable $callback The callback to execute
*/
protected function beforeExtending($method, $callback) {
if(empty($this->beforeExtendCallbacks[$method])) {
$this->beforeExtendCallbacks[$method] = array();
}
$this->beforeExtendCallbacks[$method][] = $callback;
}
/**
* List of callbacks to call after extensions having extend called on them,
* each grouped by methodName.
*
* @var array[callable]
*/
protected $afterExtendCallbacks = array();
/**
* Allows user code to hook into Object::extend after control
* being delegated to extensions. Each callback will be reset
* once called.
*
* @param string $method The name of the method to hook into
* @param callable $callback The callback to execute
*/
protected function afterExtending($method, $callback) {
if(empty($this->afterExtendCallbacks[$method])) {
$this->afterExtendCallbacks[$method] = array();
}
$this->afterExtendCallbacks[$method][] = $callback;
}
/**
* An implementation of the factory method, allows you to create an instance of a class
*
@ -211,7 +257,12 @@ abstract class Object {
}
} else {
if($tName == ')') {
if($tName == '[') {
// Add an empty array to the bucket
$bucket[] = array();
$bucketStack[] = &$bucket;
$bucket = &$bucket[sizeof($bucket)-1];
} elseif($tName == ')' || $tName == ']') {
// Pop-by-reference
$bucket = &$bucketStack[sizeof($bucketStack)-1];
array_pop($bucketStack);
@ -924,6 +975,14 @@ abstract class Object {
public function extend($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null) {
$values = array();
if(!empty($this->beforeExtendCallbacks[$method])) {
foreach(array_reverse($this->beforeExtendCallbacks[$method]) as $callback) {
$value = call_user_func($callback, $a1, $a2, $a3, $a4, $a5, $a6, $a7);
if($value !== null) $values[] = $value;
}
$this->beforeExtendCallbacks[$method] = array();
}
if($this->extension_instances) foreach($this->extension_instances as $instance) {
if(method_exists($instance, $method)) {
$instance->setOwner($this);
@ -933,6 +992,14 @@ abstract class Object {
}
}
if(!empty($this->afterExtendCallbacks[$method])) {
foreach(array_reverse($this->afterExtendCallbacks[$method]) as $callback) {
$value = call_user_func($callback, $a1, $a2, $a3, $a4, $a5, $a6, $a7);
if($value !== null) $values[] = $value;
}
$this->afterExtendCallbacks[$method] = array();
}
return $values;
}

View File

@ -248,19 +248,28 @@ class SS_ClassManifest {
/**
* Returns an array of module names mapped to their paths.
* "Modules" in SilverStripe are simply directories with a _config.php file.
*
* "Modules" in SilverStripe are simply directories with a _config.php
* file.
*
* @return array
*/
public function getModules() {
$modules = array();
foreach($this->configs as $configPath) {
$modules[basename(dirname($configPath))] = dirname($configPath);
if($this->configs) {
foreach($this->configs as $configPath) {
$modules[basename(dirname($configPath))] = dirname($configPath);
}
}
foreach($this->configDirs as $configDir) {
$path = preg_replace('/\/_config$/', '', dirname($configDir));
$modules[basename($path)] = $path;
if($this->configDirs) {
foreach($this->configDirs as $configDir) {
$path = preg_replace('/\/_config$/', '', dirname($configDir));
$modules[basename($path)] = $path;
}
}
return $modules;
}

View File

@ -282,10 +282,9 @@ class SS_ConfigStaticManifest_Parser {
$type = is_array($token) ? $token[0] : $token;
// Track array nesting depth
if($type == T_ARRAY) {
if($type == T_ARRAY || $type == '[') {
$depth += 1;
}
else if($type == ')') {
} elseif($type == ')' || $type == ']') {
$depth -= 1;
}

View File

@ -75,15 +75,15 @@ class SS_TemplateLoader {
}
if ($found = $this->getManifest()->getCandidateTemplate($template, $theme)) {
if ($type && isset($found[$type])) {
if ($type && isset($found[$type])) {
$found = array(
'main' => $found[$type]
);
}
}
$result = array_merge($found, $result);
$result = array_merge($found, $result);
}
}
}
return $result;
}

View File

@ -115,11 +115,11 @@ class SS_TemplateManifest {
if ($this->project && isset($candidates[$this->project])) {
$found = $candidates[$this->project];
} else if ($theme && isset($candidates['themes'][$theme])) {
$found = $candidates['themes'][$theme];
$found = array_merge($candidates, $candidates['themes'][$theme]);
} else {
unset($candidates['themes']);
$found = $candidates;
}
if(isset($found['themes'])) unset($found['themes']);
return $found;
}

View File

@ -1 +1,2 @@
.datetime .middleColumn .middleColumn { margin: 0; padding: 0; clear: none; float: left; }
.datetime .middleColumn .field { margin: 0; border-bottom: none; box-shadow: none; }

View File

@ -81,6 +81,7 @@ Used in side panels and action tabs
.cms table.ss-gridfield-table tr th.extra { position: relative; background: #637276; background: rgba(0, 0, 0, 0.7); padding: 5px; border-top: rgba(0, 0, 0, 0.2); }
.cms table.ss-gridfield-table tr th.extra input { height: 28px; }
.cms table.ss-gridfield-table tr th.extra button.ss-ui-button { padding: .3em; line-height: 1; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; position: relative; border-bottom-width: 0; -webkit-border-radius: 2px 2px; -moz-border-radius: 2px / 2px; border-radius: 2px / 2px; }
.cms table.ss-gridfield-table tr th.extra select { margin: 0; }
.cms table.ss-gridfield-table tr th.first { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; border-top-left-radius: 5px; }
.cms table.ss-gridfield-table tr th.last { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; border-top-right-radius: 5px; }
.cms table.ss-gridfield-table tr th button#action_gridfield_relationadd:hover { color: #444 !important; /* Not sure why IE think it needs this */ }

View File

@ -1,9 +1,13 @@
<?php
/**
* Class to handle parsing of CSV files, where the column headers are in the first row.
* The idea is that you pass it another object to handle the actual procesing of the data in the CSV file.
* Class to handle parsing of CSV files, where the column headers are in the
* first row.
*
* The idea is that you pass it another object to handle the actual processing
* of the data in the CSV file.
*
* Usage:
*
* <code>
* $parser = new CSVParser('myfile.csv');
* $parser->mapColumns(
@ -23,50 +27,80 @@
* @subpackage bulkloading
*/
class CSVParser extends Object implements Iterator {
/**
* @var string $filename
*/
protected $filename;
/**
* @var resource $fileHandle
*/
protected $fileHandle;
/**
* Map of source columns to output columns
* Once they get into this variable, all of the source columns are in lowercase
* Map of source columns to output columns.
*
* Once they get into this variable, all of the source columns are in
* lowercase.
*
* @var array
*/
protected $columnMap = array();
/**
* The header row used to map data in the CSV file
* To begin with, this is null. Once it has been set, data will get returned from the CSV file
* The header row used to map data in the CSV file.
*
* To begin with, this is null. Once it has been set, data will get
* returned from the CSV file.
*
* @var array
*/
protected $headerRow = null;
/**
* A custom header row provided by the caller
* A custom header row provided by the caller.
*
* @var array
*/
protected $providedHeaderRow = null;
/**
* The data of the current row
* The data of the current row.
*
* @var array
*/
protected $currentRow = null;
/**
* The current row number
* 1 is the first data row in the CSV file; the header row, if it exists, is ignored
* The current row number.
*
* 1 is the first data row in the CSV file; the header row, if it exists,
* is ignored.
*
* @var int
*/
protected $rowNum = 0;
/**
* The character for separating columns
* The character for separating columns.
*
* @var string
*/
protected $delimiter = ",";
/**
* The character for quoting colums
* The character for quoting columns.
*
* @var string
*/
protected $enclosure = '"';
/**
* Open a CSV file for parsing.
* You can use the object returned in a foreach loop to extract the data
*
* You can use the object returned in a foreach loop to extract the data.
*
* @param $filename The name of the file. If relative, it will be relative to the site's base dir
* @param $delimiter The character for seperating columns
* @param $enclosure The character for quoting or enclosing columns
@ -76,58 +110,70 @@ class CSVParser extends Object implements Iterator {
$this->filename = $filename;
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
parent::__construct();
}
/**
* Re-map columns in the CSV file.
* This can be useful for identifying synonyms in the file
* For example:
*
* This can be useful for identifying synonyms in the file. For example:
*
* <code>
* $csv->mapColumns(array(
* 'firstname' => 'FirstName',
* 'last name' => 'Surname',
* ));
* </code>
*
* @param array
*/
public function mapColumns($columnMap) {
if($columnMap) {
$lowerColumnMap = array();
foreach($columnMap as $k => $v) {
$lowerColumnMap[strtolower($k)] = $v;
}
$this->columnMap = array_merge($this->columnMap, $lowerColumnMap);
}
}
/**
* If your CSV file doesn't have a header row, then you can call this function to provide one.
* If you call this function, then the first row of the CSV will be included in the data returned.
* If your CSV file doesn't have a header row, then you can call this
* function to provide one.
*
* If you call this function, then the first row of the CSV will be
* included in the data returned.
*
* @param array
*/
public function provideHeaderRow($headerRow) {
$this->providedHeaderRow = $headerRow;
}
/**
* Open the CSV file for reading
* Open the CSV file for reading.
*/
protected function openFile() {
ini_set('auto_detect_line_endings',1);
$this->fileHandle = fopen($this->filename,'r');
if($this->providedHeaderRow) {
$this->headerRow = $this->remapHeader($this->providedHeaderRow);
}
}
/**
* Close the CSV file and re-set all of the internal variables
* Close the CSV file and re-set all of the internal variables.
*/
protected function closeFile() {
if($this->fileHandle) fclose($this->fileHandle);
$this->fileHandle = null;
if($this->fileHandle) {
fclose($this->fileHandle);
}
$this->fileHandle = null;
$this->rowNum = 0;
$this->currentRow = null;
$this->headerRow = null;
@ -135,20 +181,34 @@ class CSVParser extends Object implements Iterator {
/**
* Get a header row from the CSV file
* Get a header row from the CSV file.
*/
protected function fetchCSVHeader() {
$srcRow = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure);
$srcRow = fgetcsv(
$this->fileHandle,
0,
$this->delimiter,
$this->enclosure
);
$this->headerRow = $this->remapHeader($srcRow);
}
/**
* Map the contents of a header array using $this->mappedColumns
* Map the contents of a header array using $this->mappedColumns.
*
* @param array
*
* @return array
*/
protected function remapHeader($header) {
$mappedHeader = array();
foreach($header as $item) {
if(isset($this->columnMap[strtolower($item)])) $item = $this->columnMap[strtolower($item)];
if(isset($this->columnMap[strtolower($item)])) {
$item = $this->columnMap[strtolower($item)];
}
$mappedHeader[] = $item;
}
return $mappedHeader;
@ -156,23 +216,42 @@ class CSVParser extends Object implements Iterator {
/**
* Get a row from the CSV file and update $this->currentRow;
*
* @return array
*/
protected function fetchCSVRow() {
if(!$this->fileHandle) $this->openFile();
if(!$this->headerRow) $this->fetchCSVHeader();
if(!$this->fileHandle) {
$this->openFile();
}
if(!$this->headerRow) {
$this->fetchCSVHeader();
}
$this->rowNum++;
$srcRow = fgetcsv($this->fileHandle, 0, $this->delimiter, $this->enclosure);
$srcRow = fgetcsv(
$this->fileHandle,
0,
$this->delimiter,
$this->enclosure
);
if($srcRow) {
$row = array();
foreach($srcRow as $i => $value) {
// Allow escaping of quotes and commas in the data
$value = str_replace(
array('\\'.$this->enclosure,'\\'.$this->delimiter),
array($this->enclosure,$this->delimiter),$value);
array('\\'.$this->enclosure,'\\'.$this->delimiter),
array($this->enclosure, $this->delimiter),
$value
);
if(array_key_exists($i, $this->headerRow)) {
if($this->headerRow[$i]) $row[$this->headerRow[$i]] = $value;
if($this->headerRow[$i]) {
$row[$this->headerRow[$i]] = $value;
}
} else {
user_error("No heading for column $i on row $this->rowNum", E_USER_WARNING);
}
@ -182,6 +261,7 @@ class CSVParser extends Object implements Iterator {
} else {
$this->closeFile();
}
return $this->currentRow;
}
@ -221,6 +301,7 @@ class CSVParser extends Object implements Iterator {
*/
public function next() {
$this->fetchCSVRow();
return $this->currentRow;
}
@ -230,7 +311,4 @@ class CSVParser extends Object implements Iterator {
public function valid() {
return $this->currentRow ? true : false;
}
}

View File

@ -1,14 +1,18 @@
<?php
/**
* Utility class to facilitate complex CSV-imports by defining column-mappings and custom converters.
* Uses the fgetcsv() function to process CSV input. Accepts a file-handler as input.
* Utility class to facilitate complex CSV-imports by defining column-mappings
* and custom converters.
*
* Uses the fgetcsv() function to process CSV input. Accepts a file-handler as
* input.
*
* @see http://rfc.net/rfc4180.html
*
* @package framework
* @subpackage bulkloading
* @author Ingo Schommer, Silverstripe Ltd. (<myfirstname>@silverstripe.com)
*
* @todo Support for deleting existing records not matched in the import (through relation checks)
* @todo Support for deleting existing records not matched in the import
* (through relation checks)
*/
class CsvBulkLoader extends BulkLoader {
@ -27,7 +31,8 @@ class CsvBulkLoader extends BulkLoader {
public $enclosure = '"';
/**
* Identifies if the has a header row.
* Identifies if csv the has a header row.
*
* @var boolean
*/
public $hasHeaderRow = true;
@ -39,15 +44,37 @@ class CsvBulkLoader extends BulkLoader {
return $this->processAll($filepath, true);
}
/**
* @param string $filepath
* @param boolean $preview
*/
protected function processAll($filepath, $preview = false) {
$results = new BulkLoader_Result();
$csv = new CSVParser($filepath, $this->delimiter, $this->enclosure);
$csv = new CSVParser(
$filepath,
$this->delimiter,
$this->enclosure
);
// ColumnMap has two uses, depending on whether hasHeaderRow is set
if($this->columnMap) {
if($this->hasHeaderRow) $csv->mapColumns($this->columnMap);
else $csv->provideHeaderRow($this->columnMap);
// if the map goes to a callback, use the same key value as the map
// value, rather than function name as multiple keys may use the
// same callback
foreach($this->columnMap as $k => $v) {
if(strpos($v, "->") === 0) {
$map[$k] = $k;
} else {
$map[$k] = $v;
}
}
if($this->hasHeaderRow) {
$csv->mapColumns($map);
} else {
$csv->provideHeaderRow($map);
}
}
foreach($csv as $row) {
@ -59,7 +86,14 @@ class CsvBulkLoader extends BulkLoader {
/**
* @todo Better messages for relation checks and duplicate detection
* Note that columnMap isn't used
* Note that columnMap isn't used.
*
* @param array $record
* @param array $columnMap
* @param BulkLoader_Result $results
* @param boolean $preview
*
* @return int
*/
protected function processRecord($record, $columnMap, &$results, $preview = false) {
$class = $this->objectClass;
@ -105,22 +139,29 @@ class CsvBulkLoader extends BulkLoader {
$relationObj = $obj->getComponent($relationName);
if (!$preview) $relationObj->write();
$obj->{"{$relationName}ID"} = $relationObj->ID;
//write if we are not previewing
if (!$preview) {
$obj->write();
$obj->flushCache(); // avoid relation caching confusion
}
}
}
// second run: save data
foreach($record as $fieldName => $val) {
//break out of the loop if we are previewing
if ($preview) break;
if($this->isNullValue($val, $fieldName)) continue;
if(strpos($fieldName, '->') !== FALSE) {
$funcName = substr($fieldName, 2);
// break out of the loop if we are previewing
if ($preview) {
break;
}
// look up the mapping to see if this needs to map to callback
$mapped = $this->columnMap && isset($this->columnMap[$fieldName]);
if($mapped && strpos($this->columnMap[$fieldName], '->') === 0) {
$funcName = substr($this->columnMap[$fieldName], 2);
$this->$funcName($obj, $val, $record);
} else if($obj->hasMethod("import{$fieldName}")) {
$obj->{"import{$fieldName}"}($val, $record);
@ -154,24 +195,31 @@ class CsvBulkLoader extends BulkLoader {
}
/**
* Find an existing objects based on one or more uniqueness
* columns specified via {@link self::$duplicateChecks}
* Find an existing objects based on one or more uniqueness columns
* specified via {@link self::$duplicateChecks}.
*
* @param array $record CSV data column
* @return unknown
*
* @return mixed
*/
public function findExistingObject($record) {
$SNG_objectClass = singleton($this->objectClass);
// checking for existing records (only if not already found)
foreach($this->duplicateChecks as $fieldName => $duplicateCheck) {
if(is_string($duplicateCheck)) {
$SQL_fieldName = Convert::raw2sql($duplicateCheck);
if(!isset($record[$fieldName]) || empty($record[$fieldName])) { //skip current duplicate check if field value is empty
if(!isset($record[$SQL_fieldName]) || empty($record[$SQL_fieldName])) { //skip current duplicate check if field value is empty
continue;
}
$SQL_fieldValue = Convert::raw2sql($record[$fieldName]);
$SQL_fieldValue = Convert::raw2sql($record[$SQL_fieldName]);
$existingRecord = DataObject::get_one($this->objectClass, "\"$SQL_fieldName\" = '{$SQL_fieldValue}'");
if($existingRecord) return $existingRecord;
if($existingRecord) {
return $existingRecord;
}
} elseif(is_array($duplicateCheck) && isset($duplicateCheck['callback'])) {
if($this->hasMethod($duplicateCheck['callback'])) {
$existingRecord = $this->{$duplicateCheck['callback']}($record[$fieldName], $record);
@ -181,6 +229,7 @@ class CsvBulkLoader extends BulkLoader {
user_error("CsvBulkLoader::processRecord():"
. " {$duplicateCheck['callback']} not found on importer or object class.", E_USER_ERROR);
}
if($existingRecord) {
return $existingRecord;
}
@ -188,17 +237,17 @@ class CsvBulkLoader extends BulkLoader {
user_error('CsvBulkLoader::processRecord(): Wrong format for $duplicateChecks', E_USER_ERROR);
}
}
return false;
}
/**
* Determine wether any loaded files should be parsed
* with a header-row (otherwise we rely on {@link self::$columnMap}.
* Determine whether any loaded files should be parsed with a
* header-row (otherwise we rely on {@link self::$columnMap}.
*
* @return boolean
*/
public function hasHeaderRow() {
return ($this->hasHeaderRow || isset($this->columnMap));
}
}

View File

@ -360,7 +360,7 @@ class Debug {
}
if(!headers_sent()) {
$currController = Controller::curr();
$currController = Controller::has_curr() ? Controller::curr() : null;
// Ensure the error message complies with the HTTP 1.1 spec
$msg = strip_tags(str_replace(array("\n", "\r"), '', $friendlyErrorMessage));
if($currController) {

View File

@ -177,15 +177,23 @@ class DevelopmentAdmin extends Controller {
$generator = Injector::inst()->create('RandomGenerator');
$token = $generator->randomToken('sha1');
echo <<<TXT
Token: $token
Please add this to your mysite/_config.php with the following code:
Config::inst()->update('Security', 'token', '$token');
TXT;
$path = $this->request->getVar('path');
if($path) {
if(file_exists(BASE_PATH . '/' . $path)) {
echo sprintf(
"Configuration file '%s' exists, can't merge. Please choose a new file.\n",
BASE_PATH . '/' . $path
);
exit(1);
}
$yml = "Security:\n token: $token";
file_put_contents(BASE_PATH . '/' . $path, $yml);
echo "Configured token in $path\n";
} else {
echo "Generated new token. Please add the following code to your YAML configuration:\n\n";
echo "Security:\n";
echo " token: $token\n";
}
}
public function errors() {

View File

@ -28,27 +28,47 @@ if (function_exists('session_start')) {
session_start();
}
// Include environment files
$usingEnv = false;
$envFileExists = false;
/**
* Include _ss_environment.php file
*/
//define the name of the environment file
$envFile = '_ss_environment.php';
//define the dir to start scanning from
$dir = '.';
//check this dir and every parent dir (until we hit the base of the drive)
do {
$dir = realpath($dir) . '/';
//if the file exists, then we include it, set relevant vars and break out
if (file_exists($dir . $envFile)) {
include_once($dir . $envFile);
$envFileExists = true;
//legacy variable assignment
$usingEnv = true;
break;
}
//here we need to check that the real path of the last dir and the next one are
// not the same, if they are, we have hit the root of the drive
} while (realpath($dir) != realpath($dir .= '../'));
//define the dirs to start scanning from (have to add the trailing slash)
// we're going to check the realpath AND the path as the script sees it
$dirsToCheck = array(
realpath('.'),
dirname($_SERVER['SCRIPT_FILENAME'])
);
//if they are the same, remove one of them
if ($dirsToCheck[0] == $dirsToCheck[1]) {
unset($dirsToCheck[1]);
}
foreach ($dirsToCheck as $dir) {
//check this dir and every parent dir (until we hit the base of the drive)
// or until we hit a dir we can't read
do {
//add the trailing slash we need to concatenate properly
$dir .= DIRECTORY_SEPARATOR;
//if it's readable, go ahead
if (@is_readable($dir)) {
//if the file exists, then we include it, set relevant vars and break out
if (file_exists($dir . $envFile)) {
define('SS_ENVIRONMENT_FILE', $dir . $envFile);
include_once(SS_ENVIRONMENT_FILE);
//break out of BOTH loops because we found the $envFile
break(2);
}
}
else {
//break out of the while loop, we can't read the dir
break;
}
//go up a directory
$dir = dirname($dir);
//here we need to check that the path of the last dir and the next one are
// not the same, if they are, we have hit the root of the drive
} while (dirname($dir) != $dir);
}
if($envFileExists) {
if(!empty($_REQUEST['useEnv'])) {
@ -94,6 +114,7 @@ $locales = array(
'it_IT' => 'Italian (Italy)',
'ja_JP' => 'Japanese (Japan)',
'km_KH' => 'Khmer (Cambodia)',
'lc_XX' => 'LOLCAT',
'lv_LV' => 'Latvian (Latvia)',
'lt_LT' => 'Lithuanian (Lithuania)',
'ms_MY' => 'Malay (Malaysia)',
@ -426,7 +447,9 @@ class InstallRequirements {
$this->warning(array("Webserver Configuration", "URL rewriting support", "I can't tell whether any rewriting module is running. You may need to configure a rewriting rule yourself."));
}
$this->requireServerVariables(array('SCRIPT_NAME','HTTP_HOST','SCRIPT_FILENAME'), array("Webserver config", "Recognised webserver", "You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."));
$this->requireServerVariables(array('SCRIPT_NAME','HTTP_HOST','SCRIPT_FILENAME'), array("Webserver Configuration", "Recognised webserver", "You seem to be using an unsupported webserver. The server variables SCRIPT_NAME, HTTP_HOST, SCRIPT_FILENAME need to be set."));
$this->requirePostSupport(array("Webserver Configuration", "POST Support", 'I can\'t find $_POST, make sure POST is enabled.'));
// Check for GD support
if(!$this->requireFunction("imagecreatetruecolor", array("PHP Configuration", "GD2 support", "PHP must have GD version 2."))) {
@ -466,6 +489,10 @@ class InstallRequirements {
$this->suggestClass('finfo', array('PHP Configuration', 'fileinfo support', 'fileinfo should be enabled in PHP. SilverStripe uses it for MIME type detection of files. SilverStripe will still operate, but email attachments and sending files to browser (e.g. export data to CSV) may not work correctly without finfo.'));
$this->suggestFunction('curl_init', array('PHP Configuration', 'curl support', 'curl should be enabled in PHP. SilverStripe uses it for consuming web services via the RestfulService class and many modules rely on it.'));
$this->suggestClass('tidy', array('PHP Configuration', 'tidy support', 'Tidy provides a library of code to clean up your html. SilverStripe will operate fine without tidy but HTMLCleaner will not be effective.'));
$this->suggestPHPSetting('asp_tags', array(false,0,''), array('PHP Configuration', 'asp_tags option', 'This should be turned off as it can cause issues with SilverStripe'));
$this->suggestPHPSetting('magic_quotes_gpc', array(false,0,''), array('PHP Configuration', 'magic_quotes_gpc option', 'This should be turned off, as it can cause issues with cookies. More specifically, unserializing data stored in cookies.'));
$this->suggestPHPSetting('display_errors', array(false,0,''), array('PHP Configuration', 'display_errors option', 'Unless you\'re in a development environment, this should be turned off, as it can expose sensitive data to website users.'));
@ -478,6 +505,7 @@ class InstallRequirements {
function suggestPHPSetting($settingName, $settingValues, $testDetails) {
$this->testing($testDetails);
$val = ini_get($settingName);
if(!in_array($val, $settingValues) && $val != $settingValues) {
$testDetails[2] = "$settingName is set to '$val' in php.ini. $testDetails[2]";
@ -487,13 +515,23 @@ class InstallRequirements {
function suggestClass($class, $testDetails) {
$this->testing($testDetails);
if(!class_exists($class)) {
$this->warning($testDetails);
}
}
function suggestFunction($class, $testDetails) {
$this->testing($testDetails);
if(!function_exists($class)) {
$this->warning($testDetails);
}
}
function requireDateTimezone($testDetails) {
$this->testing($testDetails);
$result = ini_get('date.timezone') && in_array(ini_get('date.timezone'), timezone_identifiers_list());
if(!$result) {
$this->error($testDetails);
@ -606,7 +644,10 @@ class InstallRequirements {
function requireFunction($funcName, $testDetails) {
$this->testing($testDetails);
if(!function_exists($funcName)) $this->error($testDetails);
if(!function_exists($funcName)) {
$this->error($testDetails);
}
else return true;
}
@ -935,12 +976,17 @@ class InstallRequirements {
}
}
function requireServerVariables($varNames, $errorMessage) {
//$this->testing($testDetails);
function requireServerVariables($varNames, $testDetails) {
$this->testing($testDetails);
$missing = array();
foreach($varNames as $varName) {
if(!$_SERVER[$varName]) $missing[] = '$_SERVER[' . $varName . ']';
if(!isset($_SERVER[$varName]) || !$_SERVER[$varName]) {
$missing[] = '$_SERVER[' . $varName . ']';
}
}
if(!isset($missing)) {
if(!$missing) {
return true;
} else {
$testDetails[2] .= " (the following PHP variables are missing: " . implode(", ", $missing) . ")";
@ -948,6 +994,19 @@ class InstallRequirements {
}
}
function requirePostSupport($testDetails) {
$this->testing($testDetails);
if(!isset($_POST)) {
$this->error($testDetails);
return false;
}
return true;
}
function isRunningWebServer($testDetails) {
$this->testing($testDetails);
if($testDetails[3]) {

View File

@ -0,0 +1,5 @@
# 3.0.6 (Not yet released)
## Upgrading
* If you have created your own composite database fields, then you shoulcd amend the setValue() to allow the passing of an object (usually DataObject) as well as an array.

View File

@ -455,3 +455,6 @@ you can enable those warnings and future-proof your code already.
* Hard limit displayed pages in the CMS tree to `500`, and the number of direct children to `250`,
to avoid excessive resource usage. Configure through `Hierarchy.node_threshold_total` and `
Hierarchy.node_threshold_leaf`. Set to `0` to show tree unrestricted.
* `Object` now has `beforeExtending` and `afterExtending` to inject behaviour around method extension.
`DataObject` also has `beforeUpdateCMSFields` to insert fields between automatic scaffolding and extension
by `updateCMSFields`. See the [DataExtension Reference](/reference/dataextension) for more information.

View File

@ -126,7 +126,7 @@ and replace it with the following:
:::ss
<ul>
<% loop BookmarkedPages %>
<% loop $BookmarkedPages %>
<li><a href="admin/pages/edit/show/$ID">Edit "$Title"</a></li>
<% end_loop %>
</ul>

View File

@ -74,10 +74,10 @@ In this case, the `getTitleFirstLetter()` method defined earlier is used to brea
:::ss
<%-- Modules list grouped by TitleFirstLetter --%>
<h2>Modules</h2>
<% loop GroupedModules.GroupedBy(TitleFirstLetter) %>
<% loop $GroupedModules.GroupedBy(TitleFirstLetter) %>
<h3>$TitleFirstLetter</h3>
<ul>
<% loop Children %>
<% loop $Children %>
<li>$Title</li>
<% end_loop %>
</ul>
@ -133,10 +133,10 @@ The final step is the render this into the template using the [api:GroupedList->
:::ss
// Modules list grouped by the Month Posted
<h2>Modules</h2>
<% loop GroupedModulesByDate.GroupedBy(MonthCreated) %>
<% loop $GroupedModulesByDate.GroupedBy(MonthCreated) %>
<h3>$MonthCreated</h3>
<ul>
<% loop Children %>
<% loop $Children %>
<li>$Title ($Created.Nice)</li>
<% end_loop %>
</ul>

View File

@ -9,7 +9,7 @@ located in `themes/<mytheme>/templates/Page.ss`.
:::ss
<ul>
<% loop Menu(1) %>
<% loop $Menu(1) %>
<li>
<a href="$Link" title="Go to the $Title page" class="$LinkingMode">
<span>$MenuTitle</span>

View File

@ -36,7 +36,7 @@ The first step is to simply list the objects in the template:
:::ss
<ul>
<% loop PaginatedPages %>
<% loop $PaginatedPages %>
<li><a href="$Link">$Title</a></li>
<% end_loop %>
</ul>
@ -45,22 +45,22 @@ By default this will display 10 pages at a time. The next step is to add paginat
controls below this so the user can switch between pages:
:::ss
<% if PaginatedPages.MoreThanOnePage %>
<% if PaginatedPages.NotFirstPage %>
<% if $PaginatedPages.MoreThanOnePage %>
<% if $PaginatedPages.NotFirstPage %>
<a class="prev" href="$PaginatedPages.PrevLink">Prev</a>
<% end_if %>
<% loop PaginatedPages.Pages %>
<% if CurrentBool %>
<% loop $PaginatedPages.Pages %>
<% if $CurrentBool %>
$PageNum
<% else %>
<% if Link %>
<% if $Link %>
<a href="$Link">$PageNum</a>
<% else %>
...
<% end_if %>
<% end_if %>
<% end_loop %>
<% if PaginatedPages.NotLastPage %>
<% if $PaginatedPages.NotLastPage %>
<a class="next" href="$PaginatedPages.NextLink">Next</a>
<% end_if %>
<% end_if %>

View File

@ -66,7 +66,16 @@ Composer isn't only used to download SilverStripe CMS, it can also be used to ma
composer require silverstripe/forum:*
This command has two parts. First is `silverstripe/forum`. This is the name of the package. You can find other packages with the following command:
This will install the forum module in the latest compatible version.
By default, Composer updates other existing modules (like `framework` and `cms`),
and installs "dev" dependencies like PHPUnit. In case you don't need those dependencies,
use the following command instead:
composer require --no-update silverstripe/forum:*
composer update --no-dev
The `require` command has two parts. First is `silverstripe/forum`. This is the name of the package.
You can find other packages with the following command:
composer search silverstripe
@ -84,7 +93,7 @@ Except for the control code of the Voyager space probe, every piece of code in t
To get the latest updates of the modules in your project, run this command:
composer update
composer update --no-dev
Updates to the required modules will be installed, and the `composer.lock` file will get updated with the specific commits of each of those.

View File

@ -6,83 +6,147 @@ These instructions are also covered in less detail on the
The prerequisite is that you have already installed Nginx and you are
able to run PHP files via the FastCGI-wrapper from Nginx.
Now you need to set up a virtual host in Nginx with the following
configuration settings:
Now you need to set up a virtual host in Nginx with configuration settings
that are similar to those shown below.
<div class="notice" markdown='1'>
If you don't fully understand the configuration presented here, consult the
[nginx documentation](http://nginx.org/en/docs/).
Especially be aware of [accidental php-execution](https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/ "Don't trust the tutorials") when extending the configuration.
</div>
But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`:
server {
listen 80;
# SSL configuration (optional, but recommended for security)
include ssl
root /var/www/example.com;
index index.php index.html index.htm;
server_name example.com;
include silverstripe3;
include htaccess;
root /var/www/example.com;
# SSL configuration (optional, but recommended for security)
# (remember to actually force logins to use ssl)
include ssl
include silverstripe3.conf;
include htaccess.conf;
# rest of the server section is optional, but helpful
# maintenance page if it exists
error_page 503 @maintenance;
if (-f $document_root/maintenance.html ) {
return 503;
}
location @maintenance {
try_files /maintenance.html =503;
}
# always show SilverStripe's version of 500 error page
error_page 500 /assets/error-500.html;
# let the user's browser cache static files (e.g. 2 weeks)
expires 2w;
# in case your machine is slow, increase the timeout
# (also remembers php's own timeout settings)
#fastcgi_read_timeout 300s;
}
Here is the include file `silverstripe3`:
Here is the include file `silverstripe3.conf`:
location / {
try_files $uri @silverstripe;
}
# only needed for installation - disable this location (and remove the
# index.php and install.php files) after you installed SilverStripe
# (you did read the blogentry linked above, didn't you)
location ~ ^/(index|install).php {
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
include fastcgi.conf;
fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock;
}
# whitelist php files that are called directly and need to be interpreted
location = /framework/thirdparty/tinymce/tiny_mce_gzip.php {
include fastcgi.conf;
fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock;
}
location = /framework/thirdparty/tinymce-spellchecker/rpc.php {
include fastcgi.conf;
fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock;
}
location @silverstripe {
include fastcgi_params;
# Defend against arbitrary PHP code execution
# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
# More info:
# https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/
fastcgi_split_path_info ^(.+\.php)(/.+)$;
expires off;
include fastcgi.conf;
fastcgi_pass unix:/run/php-fpm/php-fpm-silverstripe.sock;
# note that specifying a fixed script already protects against execution
# of arbitrary files, but remember the advice above for any other rules
# you add yourself (monitoring, etc,....)
fastcgi_param SCRIPT_FILENAME $document_root/framework/main.php;
fastcgi_param SCRIPT_NAME /framework/main.php;
fastcgi_param QUERY_STRING url=$uri&$args;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_buffer_size 32k;
fastcgi_buffers 4 32k;
fastcgi_busy_buffers_size 64k;
# tuning is up to your expertise, but buffer_size needs to be >= 8k,
# otherwise you'll get "upstream sent too big header while reading
# response header from upstream" errors.
fastcgi_buffer_size 8k;
#fastcgi_buffers 4 32k;
#fastcgi_busy_buffers_size 64k;
}
<div class="warning" markdown='1'>
With only the above configuration, nginx would hand out any existing file
uninterpreted, so it would happily serve your precious configuration files,
including all your private api-keys and whatnot to any random visitor. So you
**must** restrict access further.
</div>
You don't need to use separate files, but it is easier to have the permissive
rules distinct from the restricting ones.
Here is the include file `htaccess`:
Here is the include file `htaccess.conf`:
# Don't serve up any .htaccess files
location ~ /\.ht {
deny all;
}
# Deny access to silverstripe-cache
location ~ ^/silverstripe-cache {
deny all;
}
# Don't execute scripts in the assets
# Don't try to find nonexisting stuff in assets (esp. don't pass through php)
location ^~ /assets/ {
try_files $uri $uri/ =404;
try_files $uri =404;
}
# Block access to yaml files
location ~ \.yml$ {
# Deny access to silverstripe-cache, vendor or composer.json/.lock
location ^~ /silverstripe-cache/ {
deny all;
}
location ^~ /vendor/ {
deny all;
}
location ~ /composer\.(json|lock) {
deny all;
}
# cms & framework .htaccess rules
location ~ ^/(cms|framework|mysite)/.*\.(php|php[345]|phtml|inc)$ {
# Don't serve up any "hidden" files or directories
# (starting with dot, like .htaccess or .git)
# also don't serve web.config files
location ~ /(\.|web\.config) {
deny all;
}
# Block access to yaml files (and don't forget about backup
# files that editors tend to leave behind)
location ~ \.(yml|bak|swp)$ {
deny all;
}
location ~ ~$ {
deny all;
}
# generally don't serve any php-like files
# (as they exist, they would be served as regular files, and not interpreted.
# But as those can contain configuration data, this is bad nevertheless)
# If needed, you can always whitelist entries.
location ~ \.(php|php[345]|phtml|inc)$ {
deny all;
}
location ~ ^/(cms|framework)/silverstripe_version$ {
deny all;
}
location ~ ^/framework/.*(main|static-main|rpc|tiny_mce_gzip)\.php$ {
allow all;
}
Here is the optional include file `ssl`:
@ -95,8 +159,9 @@ Here is the optional include file `ssl`:
The above configuration sets up a virtual host `example.com` with
rewrite rules suited for SilverStripe. The location block named
`@silverstripe` passes all php scripts to the FastCGI-wrapper via a Unix
socket. This example is from a site running Ubuntu with the php5-fpm
package.
`@silverstripe` passes all requests that aren't matched by one of the other
location rules (and cannot be satisfied by serving an existing file) to
SilverStripe framework's main.php script, that is run by the FastCGI-wrapper,
that in turn is accessed via a Unix socket.
Now you can proceed with the SilverStripe installation normally.

View File

@ -427,7 +427,7 @@ Put code into the classes in the following order (where applicable).
* Commonly used methods like `getCMSFields()`
* Accessor methods (`getMyField()` and `setMyField()`)
* Controller action methods
* Template data-access methods (methods that will be called by a `$MethodName` or `<% loop MethodName %>` construct in a template somewhere)
* Template data-access methods (methods that will be called by a `$MethodName` or `<% loop $MethodName %>` construct in a template somewhere)
* Object methods
### SQL Format

View File

@ -14,7 +14,7 @@ and a GitHub user account.
The easiest way of making a change to the documentation is by clicking the "Edit this page" link at
the bottom of the page you want to edit. Alternativly, you can find the appropriate .md file in
the [github.com/silverstripe/sapphire](https://github.com/silverstripe/sapphire/tree/3.0/docs/) repository
the [github.com/silverstripe/silverstripe-framework](https://github.com/silverstripe/silverstripe-framework/tree/3.0/docs/) repository
and press the "edit" button. You will need a GitHub account to do this. You should make the changes in the lowest branch they apply to.
* After you have made your change, describe it in the "commit summary" and "extended description" fields below, and press "Commit Changes".
@ -23,7 +23,7 @@ and press the "edit" button. You will need a GitHub account to do this. You sh
## Editing on your computer
If you prefer to edit the content on your local machine, you can "[fork](http://help.github.com/forking/)"
the [github.com/silverstripe/sapphire](http://github.com/silverstripe/sapphire)
the [github.com/silverstripe/silverstripe-framework](http://github.com/silverstripe/silverstripe-framework)
and [github.com/silverstripe/silverstripe-cms](http://github.com/silverstripe/silverstripe-cms)
repositories and send us "[pull requests](http://help.github.com/pull-requests/)". If you have
downloaded SilverStripe or a module, chances are that you already have these checkouts.

View File

@ -7,9 +7,9 @@
If you have discovered a bug in SilverStripe, we'd be glad to hear about it -
well written bug reports can be half of the solution already!
* [Framework Bugtracker](https://github.com/silverstripe/sapphire/issues)
* [Framework Bugtracker](https://github.com/silverstripe/silverstripe-framework/issues)
* [CMS Bugtracker](https://github.com/silverstripe/silverstripe-cms/issues)
* [Documentation Bugtracker](https://github.com/silverstripe/sapphire/issues)
* [Documentation Bugtracker](https://github.com/silverstripe/silverstripe-framework/issues)
* Search on [http://silverstripe.org/modules](http://silverstripe.org/modules) for module-specific bugtrackers
Before submitting a bug:
@ -25,7 +25,7 @@ Before submitting a bug:
If the issue does look like a new bug:
* [Create a new ticket](https://github.com/silverstripe/sapphire/issues/new)
* [Create a new ticket](https://github.com/silverstripe/silverstripe-framework/issues/new)
* Describe the steps required to reproduce your issue, and the expected outcome. Unit tests, screenshots and screencasts can help here.
* Describe your environment as detailed as possible: SilverStripe version, Browser, PHP version, Operating System, any installed SilverStripe modules.
* *(optional)* [Submit a pull request](/misc/contributing/code) which fixes the issue.

View File

@ -9,7 +9,7 @@ The current maintainer responsible for planning and performing releases is Ingo
## Release Planning
Our most up-to-date release plans are typically in the ["framework" milestone](https://github.com/silverstripe/sapphire/issues/milestones) and ["cms" milestone](https://github.com/silverstripe/silverstripe-cms/issues/milestones).
Our most up-to-date release plans are typically in the ["framework" milestone](https://github.com/silverstripe/silverstripe-framework/issues/milestones) and ["cms" milestone](https://github.com/silverstripe/silverstripe-cms/issues/milestones).
New features and API changes are typically discussed on the [core
mailinglist](http://groups.google.com/group/silverstripe-dev). They are prioritized by the core team as tickets on
github.com.

View File

@ -85,7 +85,7 @@ and committed to a special "translation-staging" branch on github.
You can download individual files by opening them on github.com (inside the `lang/` folder), and using the "Raw" view.
Place those files in the appropriate directories on a local silverstripe installation.
* ["translation-staging" branch for framework module](https://github.com/silverstripe/sapphire/tree/translation-staging)
* ["translation-staging" branch for framework module](https://github.com/silverstripe/silverstripe-framework/tree/translation-staging)
* ["translation-staging" branch for cms module](https://github.com/silverstripe/silverstripe-cms/tree/translation-staging)
# Related

View File

@ -78,6 +78,57 @@ The `$`fields parameter is passed by reference, as it is an object.
$fields->push(new UploadField('Image', 'Profile Image'));
}
### Adding/modifying fields prior to extensions
User code can intervene in the process of extending cms fields by using `beforeUpdateCMSFields`
in its implementation of `getCMSFields`. This can be useful in cases where user code will add
fields to a dataobject that should be present in the `$fields` parameter when passed to
`updateCMSFields` in extensions.
This method is preferred to disabling, enabling, and calling cms field extensions manually.
:::php
function getCMSFields() {
$this->beforeUpdateCMSFields(function($fields) {
// Include field which must be present when updateCMSFields is called on extensions
$fields->addFieldToTab("Root.Main", new TextField('Detail', 'Details', null, 255));
});
$fields = parent::getCMSFields();
// ... additional fields here
return $fields;
}
### Object extension injection points
`Object` now has two additional methods, `beforeExtending` and `afterExtending`, each of which takes a
method name and a callback to be executed immediately before and after `Object::extend()` is called on
extensions.
This is useful in many cases where working with modules such as `Translatable` which operate on
`DataObject` fields that must exist in the `FieldList` at the time that `$this->extend('UpdateCMSFields')`
is called.
<div class="notice" markdown='1'>
Please note that each callback is only ever called once, and then cleared, so multiple extensions
to the same function require that a callback is registered each time, if necessary.
</div>
Example: A class that wants to control default values during object initialisation. The code
needs to assign a value if not specified in self::$defaults, but before extensions have been called:
:::php
function __construct() {
$self = $this;
$this->beforeExtending('populateDefaults', function() uses ($self) {
if(empty($self->MyField)) {
$self->MyField = 'Value we want as a default if not specified in $defaults, but set before extensions';
}
});
parent::__construct();
}
### Custom database generation
Some extensions are designed to transparently add more sophisticated data-collection capabilities to your data object.

View File

@ -63,6 +63,8 @@ data management interfaces with very little custom coding.
You can also alter the fields of built-in and module `DataObject` classes through
your own `[DataExtension](/reference/dataextension)`, and a call to `[api:DataExtension->updateCMSFields()]`.
`[api::DataObject->beforeUpdateCMSFields()]` can also be used to interact with and add to automatically
scaffolded fields prior to being passed to extensions (See `[DataExtension](/reference/dataextension)`).
### Searchable Fields

View File

@ -68,7 +68,7 @@ and explanations on how the rules get processed for those methods.
See:
* [framework/_config/routes.yml](https://github.com/silverstripe/sapphire/blob/master/_config/routes.yml)
* [framework/_config/routes.yml](https://github.com/silverstripe/silverstripe-framework/blob/master/_config/routes.yml)
* [cms/_config/routes.yml](https://github.com/silverstripe/silverstripe-cms/blob/master/_config/routes.yml)

View File

@ -58,11 +58,10 @@ To help do this, SilverStripe introduces the concept of Aggregates. These calcul
on sets of `[api:DataObject]`s - the most useful for us being the Max aggregate.
For example, if we have a menu, we want that menu to update whenever _any_ page is edited, but would like to cache it
otherwise. By using aggregates, that's easy
otherwise. By using aggregates, we can do that like this:
:::ss
<% cached 'navigation', List(Page).max(LastEdited) %>
<% cached 'navigation', List(SiteTree).max(LastEdited) %>
If we have a block that shows a list of categories, we can make sure the cache updates every time a category is added or
edited
@ -70,14 +69,12 @@ edited
:::ss
<% cached 'categorylist', List(Category).max(LastEdited) %>
We can also calculate aggregates on relationships. A block that shows the current member's favourites needs to update
whenever the relationship Member::$has_many = array('Favourites' => Favourite') changes.
:::ss
<% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %>
## Cache key calculated in controller
That last example is a bit large, and is complicating our template up with icky logic. Better would be to extract that
@ -215,7 +212,7 @@ Failing example:
:::ss
<% cached LastEdited %>
<% loop Children %>
<% loop $Children %>
<% cached LastEdited %>
$Name
<% end_cached %>
@ -231,7 +228,7 @@ Can be re-written as:
<% cached LastEdited %>
<% cached Children.max(LastEdited) %>
<% loop Children %>
<% loop $Children %>
$Name
<% end_loop %>
<% end_cached %>

View File

@ -139,9 +139,9 @@ Results.PaginationSummary(4) defines how many pages the search will show in the
:::ss
<% if Results %>
<% if $Results %>
<ul>
<% loop Results %>
<% loop $Results %>
<li>$Title, $Autor</li>
<% end_loop %>
</ul>
@ -149,19 +149,19 @@ Results.PaginationSummary(4) defines how many pages the search will show in the
<p>Sorry, your search query did not return any results.</p>
<% end_if %>
<% if Results.MoreThanOnePage %>
<% if $Results.MoreThanOnePage %>
<div id="PageNumbers">
<p>
<% if Results.NotFirstPage %>
<% if $Results.NotFirstPage %>
<a class="prev" href="$Results.PrevLink" title="View the previous page">Prev</a>
<% end_if %>
<span>
<% loop Results.PaginationSummary(4) %>
<% if CurrentBool %>
<% loop $Results.PaginationSummary(4) %>
<% if $CurrentBool %>
$PageNum
<% else %>
<% if Link %>
<% if $Link %>
<a href="$Link" title="View page number $PageNum">$PageNum</a>
<% else %>
&hellip;
@ -170,7 +170,7 @@ Results.PaginationSummary(4) defines how many pages the search will show in the
<% end_loop %>
</span>
<% if Results.NotLastPage %>
<% if $Results.NotLastPage %>
<a class="next" href="$Results.NextLink" title="View the next page">Next</a>
<% end_if %>
</p>

View File

@ -1,32 +1,143 @@
# Shortcodes
# Shortcodes: Flexible Content Embedding
The Shortcode API is a way to replace simple bbcode-like tags within HTML. It is inspired by and very similar to
the [Wordpress implementation](http://codex.wordpress.org/Shortcode_API) of shortcodes.
## Overview
A guide to syntax
The `[api:ShortcodeParser]` API is simple parser that allows you to map specifically
formatted content to a callback to transform them into something else.
You might know this concept from forum software which don't allow you to insert
direct HTML, instead resorting to a custom syntax.
Unclosed - [shortcode]
Explicitly closed - [shortcode/]
With parameters, mixed quoting - [shortcode parameter=value parameter2='value2' parameter3="value3"]
Old style parameter separation - [shortcode,parameter=value,parameter2='value2',parameter3="value3"]
With contained content & closing tag - [shortcode]Enclosed Content[/shortcode]
Escaped (will output [just] [text] in response) - [[just] [[text]]
In the CMS, authors often want to insert content elements which go beyond
standard formatting, at an arbitrary position in their WYSIWYG editor.
Shortcodes are a semi-technical solution for this. A good example would
be embedding a 3D file viewer or a Google Map at a certain location.
Shortcode parsing is already hooked into HTMLText and HTMLVarchar fields when rendered into a template
Here's some syntax variations:
## Attribute and element scope
[my_shortcode]
[my_shortcode /]
[my_shortcode,myparameter="value"]
[my_shortcode,myparameter="value"]Enclosed Content[/my_shortcode]
## Usage
In its most basic form, you can invoke the `[api:ShortcodeParser]` directly:
:::php
ShortcodeParser::get_active()->parse($myvalue);
In addition, shortcodes are automatically parsed on any database field which is declared
as `[api:HTMLValue]` or `[api:HTMLText]`, when rendered into a template.
This means you can use shortcodes on common fields like `SiteTree.Content`,
and any other `[api:DataObject::$db]` definitions of these types.
In order to allow shortcodes in your own template placeholders,
ensure they're casted correctly:
:::php
class MyObject extends DataObject {
private static $db = array('Content' => 'HTMLText');
private static $casting = array('ContentHighlighted' => 'HTMLText');
public function ContentHighlighted($term) {
return str_replace($term, "<em>$term</em>", $this->Content);
}
}
There is currently no way to allow shortcodes directly in template markup
(as opposed to return values of template placeholders).
## Defining Custom Shortcodes
All you need to do to define a shortcode is to register a callback with the parser that will be called whenever a
shortcode is encountered. This callback will return a string to replace the shortcode with.
If the shortcode is used for template placeholders of type `HTMLText` or `HTMLVarchar`,
the returned value should be valid HTML
To register a shortcode you call:
ShortcodeParser::get('default')->register('my_shortcode', <callback>);
These parameters are passed to the callback:
- Any parameters attached to the shortcode as an associative array (keys are lower-case).
- Any content enclosed within the shortcode (if it is an enclosing shortcode). Note that any content within this
will not have been parsed, and can optionally be fed back into the parser.
- The ShortcodeParser instance used to parse the content.
- The shortcode tag name that was matched within the parsed content.
## Example: Google Maps Iframe by Address
To demonstrate how easy it is to build custom shortcodes, we'll build one to display
a Google Map based on a provided address. Format:
[googlemap,width=500,height=300]97-99 Courtenay Place, Wellington, New Zealand[/googlemap]
So we've got the address as "content" of our new `googlemap` shortcode tags,
plus some `width` and `height` arguments. We'll add defaults to those in our shortcode parser so they're optional.
:::php
ShortcodeParser::get('default')->register('googlemap', function($arguments, $address, $parser, $shortcode) {
$iframeUrl = sprintf(
'http://maps.google.com/maps?q=%s&amp;hnear=%s&amp;ie=UTF8&hq=&amp;t=m&amp;z=14&amp;output=embed',
urlencode($address),
urlencode($address)
);
$width = (isset($args['width']) && $args['width']) ? $args['width'] : 400;
$height = (isset($args['height']) && $args['height']) ? $args['height'] : 300;
return sprintf(
'<iframe width="%d" height="%d" src="%s" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>',
$width,
$height,
$iframeUrl
);
});
The hard bits are taken care of (parsing out the shortcodes), everything we need to do is a bit of string replacement.
CMS users still need to remember the specific syntax, but these shortcodes can form the basis
for more advanced editing interfaces (with visual placeholders). See the built-in `[embed]` shortcode as an example
for coupling shortcodes with a form to create and edit placeholders.
## Built-in Shortcodes
SilverStripe comes with several shortcode parsers already.
### Links
Internal page links keep references to their database IDs rather than
the URL, in order to make these links resilient against moving the target page to a different
location in the page tree. This is done through the `[sitetree_link]` shortcode, which
takes an `id` parameter. Example: `<a href="[sitetree_link,id=99]">`
Links to internal `File` database records work exactly the same, but with the `[file_link]` shortcode.
### Media (Photo, Video and Rich Content)
Many media formats can be embedded into websites through the `<object>`
tag, but some require plugins like Flash or special markup and attributes.
OEmbed is a standard to discover these formats based on a simple URL,
for example a Youtube link pasted into the "Insert Media" form of the CMS.
Since TinyMCE can't represent all these varations, we're showing a placeholder
instead, and storing the URL with a custom `[embed]` shortcode.
Example: `.[embed width=480 height=270 class=left thumbnail=http://i1.ytimg.com/vi/lmWeD-vZAMY/hqdefault.jpg?r=8767]http://www.youtube.com/watch?v=lmWeD-vZAMY[/embed]`
## Syntax
* Unclosed - `[shortcode]`
* Explicitly closed - `[shortcode/]`
* With parameters, mixed quoting - `[shortcode parameter=value parameter2='value2' parameter3="value3"]`
* Old style parameter separation - `[shortcode,parameter=value,parameter2='value2',parameter3="value3"]`
* With contained content & closing tag - `[shortcode]Enclosed Content[/shortcode]`
* Escaped (will output `[just] [text]` in response) - `[[just] [[text]]`
### Attribute and element scope
HTML with unprocessed shortcodes in it is still valid HTML. As a result, shortcodes can be in two places in HTML:
- In an attribute value, like so:
<a title="[title]">link</a>
- In an element's text, like so:
<p>
Some text [shortcode] more text
</p>
- In an attribute value, like so: `<a title="[title]">link</a>`
- In an element's text, like so: `<p>Some text [shortcode] more text</p>`
The first is called "element scope" use, the second "attribute scope"
@ -37,12 +148,9 @@ change the name of a tag. These usages are forbidden:
<a [titleattribute]>link</a>
Also note:
- you may need to escape text inside attributes `>` becomes `&gt;` etc
- you can include HTML tags inside a shortcode tag, but you need to be careful of nesting to ensure you don't
break the output
You may need to escape text inside attributes `>` becomes `&gt;`,
You can include HTML tags inside a shortcode tag, but you need to be careful of nesting to ensure you don't
break the output
Good:
@ -61,12 +169,12 @@ Bad:
[/shortcode]
</p>
## Location
### Location
Element scoped shortcodes have a special ability to move the location they are inserted at to comply with
HTML lexical rules. Take for example this basic paragraph tag:
<p><a href="#">Head [figure src="assets/a.jpg" caption="caption"] Tail</a></p>
<p><a href="#">Head [figure,src="assets/a.jpg",caption="caption"] Tail</a></p>
When converted naively would become
@ -83,43 +191,11 @@ When the location attribute is "leftAlone" or "center" then the DOM is split aro
<p><a href="#">Head </a></p><figure><img src="assets/a.jpg" /><figcaption>caption</figcaption></figure><p><a href="#"> Tail</a></p>
## Defining Custom Shortcodes
All you need to do to define a shortcode is to register a callback with the parser that will be called whenever a
shortcode is encountered. This callback will return a string to replace the shortcode with.
:::php
public static function my_shortcode_handler($attributes, $enclosedContent, $parser, $tagName) {
// This simple callback simply converts the shortcode to a span.
return "<span class=\"$tagName\">$enclosedContent</span>";
}
The parameters passed to the callback are, in order:
* Any parameters attached to the shortcode as an associative array (keys are lower-case).
* Any content enclosed within the shortcode (if it is an enclosing shortcode). Note that any content within this will
not have been parsed, and can optionally be fed back into the parser.
* The ShortcodeParser instance used to parse the content.
* The shortcode tag name that was matched within the parsed content.
For the shortcode to work, you need to register it with the `ShortcodeParser`. Assuming you've placed the
callback function in the `Page` class, you would need to make the following call from `_config.php`:
:::php
ShortcodeParser::get('default')->register(
'shortcode_tag_name',
array('Page', 'my_shortcode_handler')
);
An example result of installing such a shortcode would be that the string `[shortcode_tag_name]Testing
testing[/shortcode_tag_name]` in the page *Content* would be replaced with the `<span class="shortcode_tag_name">Testing
testing</span>`.
### Parameter values
Here is a summary of the callback parameter values based on some example shortcodes.
#### Short
Short
[my_shortcodes]
@ -128,7 +204,7 @@ Here is a summary of the callback parameter values based on some example shortco
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
#### Short with attributes
Short with attributes
[my_shortcode,attribute="foo",other="bar"]
@ -137,7 +213,7 @@ Here is a summary of the callback parameter values based on some example shortco
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
#### Long with attributes
Long with attributes
[my_shortcode,attribute="foo"]content[/my_shortcode]
@ -146,11 +222,6 @@ Here is a summary of the callback parameter values based on some example shortco
$parser => ShortcodeParser instance
$tagName => 'my_shortcode'
## Inbuilt Shortcodes
All internal links inserted via the CMS into a content field are in the form `<a href="[sitetree_link,id=n]">`. At
runtime this is replaced by a plain link to the page with the ID in question.
## Limitations
Since the shortcode parser is based on a simple regular expression it cannot properly handle nested shortcodes. For
@ -161,3 +232,7 @@ example the below code will not work as expected:
[/shortcode]
The parser will raise an error if it can not find a matching opening tag for any particular closing tag
## Related
* [Wordpress implementation](http://codex.wordpress.org/Shortcode_API)

View File

@ -15,7 +15,7 @@ You can access `[api:SiteConfig]` options from any SS template by using the func
// or
<% loop SiteConfig %>
<% loop $SiteConfig %>
$Title $AnotherField
<% end_loop %>

View File

@ -4,9 +4,9 @@ These are the main changes to the SiverStripe 3 template language.
## Control blocks: Loops vs. Scope
The `<% control var %>...<% end_control %>` in SilverStripe prior to version 3 has two different meanings. Firstly, if the control variable is a collection (e.g. DataList), then `<% control %>` iterates over that set. If it's a non-iteratable object, however, `<% control %>` introduces a new scope, which is used to render the inner template code. This dual-use is confusing to some people, and doesn't allow a collection of objects to be used as a scope.
The `<% control $var %>...<% end_control %>` in SilverStripe prior to version 3 has two different meanings. Firstly, if the control variable is a collection (e.g. DataList), then `<% control %>` iterates over that set. If it's a non-iteratable object, however, `<% control %>` introduces a new scope, which is used to render the inner template code. This dual-use is confusing to some people, and doesn't allow a collection of objects to be used as a scope.
In SilverStripe 3, the first usage (iteration) is replaced by `<% loop var %>`. The second usage (scoping) is replaced by `<% with var %>`
In SilverStripe 3, the first usage (iteration) is replaced by `<% loop $var %>`. The second usage (scoping) is replaced by `<% with $var %>`
## Literals in Expressions

View File

@ -26,9 +26,9 @@ Here is a very simple template:
<p>Welcome $FirstName $Surname.</p>
<% end_with %>
<% if Dishes %>
<% if $Dishes %>
<ul>
<% loop Dishes %>
<% loop $Dishes %>
<li>$Title ($Price.Nice)</li>
<% end_loop %>
</ul>
@ -106,7 +106,7 @@ The "include" tag can be particularly helpful for nested functionality. In this
a variable is true
:::ss
<% if CurrentMember %>
<% if $CurrentMember %>
<% include MembersOnlyInclude %>
<% end_if %>
@ -114,7 +114,7 @@ Includes can't directly access the parent scope of the scope active when the inc
pass arguments to the include, which are available on the scope top within the include
:::ss
<% with CurrentMember %>
<% with $CurrentMember %>
<% include MemberDetails PageTitle=$Top.Title, PageID=$Top.ID %>
<% end_with %>
@ -224,9 +224,15 @@ collection of items. For example:
This loops over the children of a page, and generates an unordered list showing
the `Title` property from each one. Note that `$Title` *inside* the loop refers
to the `Title` property on each object that is looped over, not the current page.
This is know as the `Scope` of the template. For more information about `Scope`
see the section below.
To refer to the current page's `Title` property inside the loop, you can do
`$Up.Title`. More about `Up` later.
`$Me` can be used to refer to the current object context the template is rendered
with.
### Position Indicators
Inside the loop scope, there are many variables at your disposal to determine the
@ -239,6 +245,50 @@ current position in the list and iteration:
* `$Pos`: The current position in the list (integer). Will start at 1.
* `$TotalItems`: Number of items in the list (integer)
### Altering the list
`<% loop %>` statements iterate over a `[api:DataList]` instance. As the
template has access to the list object, templates can call `[api:DataList]`
functions. For instance, see the following examples:
Providing a custom sort.
:::ss
<ul>
<% loop $Children.Sort(Title) %>
<li>$Title</li>
<% end_loop %>
</ul>
Limiting the number of items displayed.
:::ss
<ul>
<% loop $Children.Limit(10) %>
<li>$Title</li>
<% end_loop %>
</ul>
Reversing the loop.
:::ss
<ul>
<% loop $Children.Reverse %>
<li>$Title</li>
<% end_loop %>
</ul>
The `DataList` class also supports chaining methods. For example, to reverse
the list and output the last 3 items we would write:
:::ss
<ul>
<% loop $Children.Reverse.Limit(3) %>
<li>$Title</li>
<% end_loop %>
</ul>
### Modulus and MultipleOf
$Modulus and $MultipleOf can help to build column layouts.
@ -252,7 +302,7 @@ custom column names based on your loop statement. Note that this works for any
control statement (not just children).
:::ss
<% loop Children %>
<% loop $Children %>
<div class="column-{$Modulus(4)}">
...
</div>
@ -265,8 +315,8 @@ You can also use $MultipleOf(value, offset) to help build columned layouts. In
this case we want to add a <br> after every 3th item.
:::ss
<% loop Children %>
<% if MultipleOf(3) %>
<% loop $Children %>
<% if $MultipleOf(3) %>
<br>
<% end_if %>
<% end_loop %>
@ -289,11 +339,11 @@ the scope back to the previous level. Take the following example:
:::ss
$Title
--
<% loop Children %>
<% loop $Children %>
$Title
$Up.Title
--
<% loop Children %>
<% loop $Children %>
$Title
$Up.Title
<% end_loop %>
@ -321,12 +371,12 @@ include `$Top`:
:::ss
$Title
--
<% loop Children %>
<% loop $Children %>
$Title
$Up.Title
$Top.Title
--
<% loop Children %>
<% loop $Children %>
$Title
$Up.Title
$Top.Title
@ -459,11 +509,11 @@ It renders in the template as `<base href="http://www.mydomain.com" /><!--[if lt
Returns the currently logged in member, if there is one.
All of their details or any special Member page controls can be called on this.
Alternately, you can use `<% if CurrentMember %>` to detect whether someone has logged
Alternately, you can use `<% if $CurrentMember %>` to detect whether someone has logged
in.
:::ss
<% if CurrentMember %>
<% if $CurrentMember %>
Welcome Back, $CurrentMember.FirstName
<% end_if %>
@ -498,7 +548,7 @@ Your function could return a single value as above or it could be a subclass of
And now you could call these values by using
:::ss
<% with MyCustomValues %>
<% with $MyCustomValues %>
$Hi , $Name
<% end_with %>
// output "Kia Ora , John Smith"

View File

@ -83,7 +83,7 @@ WARNING: Currently the UploadField doesn't fully support has_many relations, so
### Overview
The field can either be configured on an instance level through `setConfig(<key>, <value>)`,
or globally by overriding the YAML defaults.
or globally by overriding the YAML defaults. See the [Configuration Reference](uploadfield#configuration-reference) section for possible values.
Example: mysite/_config/uploadfield.yml

View File

@ -79,7 +79,7 @@ In templates, casting helpers are available without the need for an `obj()` call
Example: Flagging an object of type `MyObject` (see above) if it's date is in the past.
:::ss
<% if MyObjectInstance.MyDate.InPast %>Outdated!<% end_if %>
<% if $MyObjectInstance.MyDate.InPast %>Outdated!<% end_if %>
## Casting HTML Text

View File

@ -419,7 +419,7 @@ but using the *obj()*-method or accessing through a template will cast the value
// $myPlayer->MembershipFee() returns a float (e.g. 123.45)
// $myPlayer->obj('MembershipFee') returns a object of type Currency
// In a template: <% loop MyPlayer %>MembershipFee.Nice<% end_loop %> returns a casted string (e.g. "$123.45")
// In a template: <% loop $MyPlayer %>MembershipFee.Nice<% end_loop %> returns a casted string (e.g. "$123.45")
public function getMembershipFee() {
return $this->Team()->BaseFee * $this->MembershipYears;
}

View File

@ -7,18 +7,18 @@ and handle the actions and data from a form.
A fully implemented form in SilverStripe includes a couple of classes that individually have separate concerns.
* Controller - Takes care of assemble the form and recieving data from it.
* Controller - Takes care of assembling the form and receiving data from it.
* Form - Holds sets of fields, actions and validators.
* FormField - Fields that recieves data or displays them, e.g input fields.
* FormField - Fields that receive data or displays them, e.g input fields.
* FormActions - Often submit buttons that executes actions.
* Validators - Validates the whole form, see [Form validation](form-validation.md) topic for more information.
* Validators - Validate the whole form, see [Form validation](form-validation.md) topic for more information.
Depending on your needs you can customize and override any of the above classes, however the defaults are often
sufficient.
## The Controller
Forms start at the controller. Here is an simple example on how to set up a form in a controller.
Forms start at the controller. Here is a simple example on how to set up a form in a controller.
**Page.php**
@ -264,7 +264,7 @@ basic customisation:
:::ss
<form $FormAttributes>
<% if Message %>
<% if $Message %>
<p id="{$FormName}_error" class="message $MessageType">$Message</p>
<% else %>
<p id="{$FormName}_error" class="message $MessageType" style="display: none"></p>
@ -284,9 +284,9 @@ basic customisation:
$Fields.dataFieldByName(SecurityID)
</fieldset>
<% if Actions %>
<% if $Actions %>
<div class="Actions">
<% loop Actions %>$Field<% end_loop %>
<% loop $Actions %>$Field<% end_loop %>
</div>
<% end_if %>
</form>
@ -297,7 +297,7 @@ for the type of field. Pass in the name of the field as the first parameter, as
template.
To find more methods, have a look at the `[api:Form]` class and `[api:FieldList]` class as there is a lot of different
methods of customising the form templates. An example is that you could use `<% loop Fields %>` instead of specifying
methods of customising the form templates. An example is that you could use `<% loop $Fields %>` instead of specifying
each field manually, as we've done above.
### Custom form field templates
@ -339,6 +339,14 @@ or set on a form field instance via anyone of these methods:
SilverStripe tries to protect users against *Cross-Site Request Forgery (CSRF)* by adding a hidden *SecurityID*
parameter to each form. See [secure-development](/topics/security) for details.
In addition, you should limit forms to the intended HTTP verb (mostly `GET` or `POST`)
to further reduce attack surface, by using `[api:Form->setStrictFormMethodCheck()]`.
:::php
$myForm->setFormMethod('POST');
$myForm->setStrictFormMethodCheck(true);
$myForm->setFormMethod('POST', true); // alternative short notation
### Remove existing fields
If you want to remove certain fields from your subclass:

View File

@ -223,6 +223,40 @@ If you want to run the text collector for just one module you can use the 'modul
You'll need to install PHPUnit to run the text collector (see [testing-guide](/topics/testing)).
</div>
## Module Priority
The order in which i18n strings are loaded from modules can be quite important, as it is pretty common for a site
developer to want to override the default i18n strings from time to time. Because of this, you will sometimes need to specify the loading priority of i18n modules.
By default, the language files are loaded from modules in this order:
* Your project (as defined in the `$project` global)
* admin
* framework
* All other modules
This default order is configured in `framework/_config/i18n.yml`. This file specifies two blocks of module ordering: `basei18n`, listing admin, and framework, and `defaulti18n` listing all other modules.
To create a custom module order, you need to specify a config fragment that inserts itself either after or before those items. For example, you may have a number of modules that have to come after the framework/admin, but before anyhting else. To do that, you would use this
---
Name: customi18n
Before: 'defaulti18n'
---
i18n:
module_priority:
- module1
- module2
- module3
The config option being set is `i18n.module_priority`, and it is a list of module names.
There are a few special cases:
* If not explicitly mentioned, your project is put as the first module.
* The module name `other_modules` can be used as a placeholder for all modules that aren't
specifically mentioned.
## Language definitions
Each module can have one language table per locale, stored by convention in the `lang/` subfolder.

View File

@ -24,6 +24,7 @@ It is where most documentation should live, and is the natural "second step" aft
* [Page Types](page-types): What is a "page type" and how do you create one?
* [Search](search): Searching for properties in the database as well as other documents
* [Security](security): How to develop secure SilverStripe applications with good code examples
* [Shortcodes](shortcodes): Use simple placeholders for powerful content replacements like multimedia embeds
* [Templates](templates): SilverStripe template syntax: Variables, loops, includes and much more
* [Testing](testing): Functional and Unit Testing with PHPUnit and SilverStripe's testing framework
* [Developing Themes](theme-development): Package templates, images and CSS to a reusable theme

View File

@ -378,7 +378,7 @@ Template:
:::ss
<ul>
<% loop Results %>
<% loop $Results %>
<li id="Result-$ID">$Title</li>
<% end_loop %>
</ul>

View File

@ -95,15 +95,15 @@ Note that pages with the `ShowInMenus` property set to FALSE will be filtered ou
### Children Loops
:::ss
<% loop Children %>...<% end_loop %>
<% loop $Children %>...<% end_loop %>
Will loop over all children of the current page context.
Helpful to create page-specific subnavigations.
Most likely, you'll want to use `<% loop Menu %>` for your main menus,
Most likely, you'll want to use `<% loop $Menu %>` for your main menus,
since its independent of the page context.
:::ss
<% loop ChildrenOf(<my-page-url>) %>...<% end_loop %>
<% loop $ChildrenOf(<my-page-url>) %>...<% end_loop %>
Will create a list of the children of the given page,
as identified by its `URLSegment` value. This can come in handy because its not dependent
@ -111,7 +111,7 @@ on the context of the current page. For example, it would allow you to list all
underneath a "staff" holder on any page, regardless if its on the top level or elsewhere.
:::ss
<% loop allChildren %>...<% end_loop %>
<% loop $allChildren %>...<% end_loop %>
This will show all children of a page even if the `ShowInMenus` property is set to FALSE.
@ -137,7 +137,7 @@ To simply retrieve the parent page of the current context (if existing), use the
### Access to a specific Page
:::ss
<% loop Page(my-page) %>...<% end_loop %>`
<% with $Page(my-page) %>...<% end_with %>`
"Page" will return a single page from the site tree, looking it up by URL. You can use it in the `<% loop %>` format.
Can't be called using `$Page(my-page).Title`.
@ -180,7 +180,7 @@ More common uses:
Example: Only show the menu item linked if its the current one:
:::ss
<% if LinkOrCurrent = current %>
<% if $LinkOrCurrent = current %>
$Title
<% else %>
<a href="$Link">$Title</a>
@ -199,9 +199,9 @@ Simply place a file with the same name in your `themes/<your-theme>/templates`
folder to customize its output. Here's the default template:
:::ss
<% if Pages %>
<% loop Pages %>
<% if Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> &raquo;<% end_if %>
<% if $Pages %>
<% loop $Pages %>
<% if $Last %>$Title.XML<% else %><a href="$Link">$MenuTitle.XML</a> &raquo;<% end_if %>
<% end_loop %>
<% end_if %>

View File

@ -253,7 +253,7 @@ of the CMS you have to take care of instanciation yourself:
:::ss
// File: mysite/templates/MyController.ss
$Form
<% with EditorToolbar %>
<% with $EditorToolbar %>
$MediaForm
$LinkForm
<% end_with %>

View File

@ -275,21 +275,14 @@ Some rules of thumb:
## Cross-Site Request Forgery (CSRF)
SilverStripe has built-in countermeasures against this type of identity theft for all form submissions. A form object
will automatically contain a *SecurityID* parameter which is generated as a secure hash on the server, connected to the
SilverStripe has built-in countermeasures against [CSRF](http://shiflett.org/articles/cross-site-request-forgeries) identity theft for all form submissions. A form object
will automatically contain a `SecurityID` parameter which is generated as a secure hash on the server, connected to the
currently active session of the user. If this form is submitted without this parameter, or if the parameter doesn't
match the hash stored in the users session, the request is discarded.
You can disable this behaviour through `[api:Form->disableSecurityToken()]`.
If you know what you're doing, you can disable this behaviour:
:::php
$myForm->disableSecurityToken();
See
[http://shiflett.org/articles/cross-site-request-forgeries](http://shiflett.org/articles/cross-site-request-forgeries)
It is also recommended to limit form submissions to the intended HTTP verb (mostly `GET` or `POST`)
through `[api:Form->setStrictFormMethodCheck()]`.
## Casting user input

View File

@ -72,9 +72,9 @@ our theme in action. The code for mine is below.
</div>
<div id="Navigation">
<% if Menu(1) %>
<% if $Menu(1) %>
<ul>
<% loop Menu(1) %>
<% loop $Menu(1) %>
<li><a href="$Link" title="Go to the $Title page" class="$LinkingMode">$MenuTitle</a></li>
<% end_loop %>
</ul>
@ -182,9 +182,9 @@ Next is a division for the main navigation. This may contain something like:
:::ss
<div id="Navigation">
<% if Menu(1) %>
<% if $Menu(1) %>
<ul>
<% loop Menu(1) %>
<% loop $Menu(1) %>
<li><a href="$Link" title="Go to the $Title page" class="$LinkingMode">$MenuTitle</a></li>
<% end_loop %>
</ul>
@ -198,8 +198,8 @@ Before stepping into a loop it's good practise to check if it exists first. This
important in manipulating SilverStripe templates, but in any programming language!
:::ss
<% if MyFunction %>
<% loop MyFunction %>
<% if $MyFunction %>
<% loop $MyFunction %>
$Title
<% end_loop %>
<% end_if %>

View File

@ -158,7 +158,7 @@ Open up *themes/simple/templates/Includes/Navigation.ss*
The Menu for our site is created using a **loop**. Loops allow us to iterate over a data set, and render each item using a sub-template.
:::ss
<% loop Menu(1) %>
<% loop $Menu(1) %>
returns a set of first level menu items. We can then use the template variable
*$MenuTitle* to show the title of the page we are linking to, *$Link* for the URL of the page and *$LinkingMode* to help style our menu with CSS (explained in more detail shortly).
@ -168,7 +168,7 @@ returns a set of first level menu items. We can then use the template variable
:::ss
<ul>
<% loop Menu(1) %>
<% loop $Menu(1) %>
<li class="$LinkingMode">
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
</li>
@ -225,7 +225,7 @@ Adding a second level menu is very similar to adding the first level menu. Open
:::ss
<ul>
<% loop Menu(2) %>
<% loop $Menu(2) %>
<li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span>
@ -245,10 +245,10 @@ Look again in the *Sidebar.ss* file and you will see that the menu is surrounded
like this:
:::ss
<% if Menu(2) %>
<% if $Menu(2) %>
...
<ul>
<% loop Menu(2) %>
<% loop $Menu(2) %>
<li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span>
@ -269,7 +269,7 @@ Now that we have two levels of navigation, it would also be useful to include so
Open up */themes/simple/templates/Includes/BreadCrumbs.ss* template and look at the following code:
:::ss
<% if Level(2) %>
<% if $Level(2) %>
<div id="Breadcrumbs">
$Breadcrumbs
</div>
@ -295,12 +295,12 @@ The following example runs an if statement, and a loop on *Children*, checking t
:::ss
<ul>
<% loop Menu(1) %>
<% loop $Menu(1) %>
<li class="$LinkingMode">
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
<% if Children %>
<% if $Children %>
<ul>
<% loop Children %>
<% loop $Children %>
<li class="$LinkingMode">
<a href="$Link" title="Go to the $Title.XML page">
<span class="arrow">&rarr;</span>

View File

@ -285,7 +285,7 @@ We'll now create a template for the article holder. We want our news section to
$Content
<div class="content">$Content</div>
</article>
<% loop Children %>
<% loop $Children %>
<article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
<p>$Content.FirstParagraph</p>
@ -313,7 +313,7 @@ Cut the code between "loop Children" in *ArticleHolder.ss** and replace it with
:::ss
...
<% loop Children %>
<% loop $Children %>
<% include ArticleTeaser %>
<% end_loop %>
...
@ -370,7 +370,7 @@ This function simply runs a database query that gets the latest news articles fr
<!-- ... -->
<div class="content">$Content</div>
</article>
<% loop LatestNews %>
<% loop $LatestNews %>
<% include ArticleTeaser %>
<% end_loop %>
@ -486,7 +486,7 @@ The staff section templates aren't too difficult to create, thanks to the utilit
$Content
<div class="content">$Content</div>
</article>
<% loop Children %>
<% loop $Children %>
<article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
$Photo.SetWidth(150)

View File

@ -325,11 +325,11 @@ The final step is to create the template to display our data. Change the 'Browse
:::ss
<div id="BrowserPoll">
<h2>Browser Poll</h2>
<% if BrowserPollForm %>
<% if $BrowserPollForm %>
$BrowserPollForm
<% else %>
<ul>
<% loop BrowserPollResults %>
<% loop $BrowserPollResults %>
<li>
<div class="browser">$Browser: $Percentage%</div>
<div class="bar" style="width:$Percentage%">&nbsp;</div>

View File

@ -33,7 +33,7 @@ To add the search form, we can add `$SearchForm` anywhere in our templates. In t
:::ss
...
<% if SearchForm %>
<% if $SearchForm %>
<span class="search-dropdown-icon">L</span>
<div class="search-bar">
$SearchForm
@ -98,16 +98,16 @@ class.
<div id="Content" class="searchResults">
<h1>$Title</h1>
<% if Query %>
<% if $Query %>
<p class="searchQuery"><strong>You searched for &quot;{$Query}&quot;</strong></p>
<% end_if %>
<% if Results %>
<% if $Results %>
<ul id="SearchResults">
<% loop Results %>
<% loop $Results %>
<li>
<a class="searchResultHeader" href="$Link">
<% if MenuTitle %>
<% if $MenuTitle %>
$MenuTitle
<% else %>
$Title
@ -124,17 +124,17 @@ class.
<p>Sorry, your search query did not return any results.</p>
<% end_if %>
<% if Results.MoreThanOnePage %>
<% if $Results.MoreThanOnePage %>
<div id="PageNumbers">
<% if Results.NotLastPage %>
<% if $Results.NotLastPage %>
<a class="next" href="$Results.NextLink" title="View the next page">Next</a>
<% end_if %>
<% if Results.NotFirstPage %>
<% if $Results.NotFirstPage %>
<a class="prev" href="$Results.PrevLink" title="View the previous page">Prev</a>
<% end_if %>
<span>
<% loop Results.Pages %>
<% if CurrentBool %>
<% loop $Results.Pages %>
<% if $CurrentBool %>
$PageNum
<% else %>
<a href="$Link" title="View page number $PageNum">$PageNum</a>

View File

@ -293,19 +293,19 @@ a named list of object.
</tr>
</thead>
<tbody>
<% loop Children %>
<% loop $Children %>
<tr>
<td>
<a href="$Link">$Title</a>
</td>
<td>
<% loop Students %>
$Name ($University)<% if Last !=1 %>,<% end_if %>
<% loop $Students %>
$Name ($University)<% if $Last !=1 %>,<% end_if %>
<% end_loop %>
</td>
<td>
<% loop Mentor %>
$Name<% if Last !=1 %>,<% end_if %>
<% loop $Mentor %>
$Name<% if $Last !=1 %>,<% end_if %>
<% end_loop %>
</td>
</tr>
@ -343,9 +343,9 @@ we can access the "Students" and "Mentors" relationships directly in the templat
<div class="content">
$Content
<h2>Students</h2>
<% if Students %>
<% if $Students %>
<ul>
<% loop Students %>
<% loop $Students %>
<li>$Name ($University)</li>
<% end_loop %>
</ul>
@ -353,9 +353,9 @@ we can access the "Students" and "Mentors" relationships directly in the templat
<p>No students found</p>
<% end_if %>
<h2>Mentors</h2>
<% if Mentors %>
<% if $Mentors %>
<ul>
<% loop Mentors %>
<% loop $Mentors %>
<li>$Name</li>
<% end_loop %>
</ul>
@ -392,7 +392,7 @@ To use this template, we need to add a new method to our student class:
Replace the student template code in both `Project.ss`
and `ProjectHolder.ss` templates with the new placeholder, `$Info`.
That's the code enclosed in `<% loop Students %>` and `<% end_loop %>`.
That's the code enclosed in `<% loop $Students %>` and `<% end_loop %>`.
With this pattern, you can increase code reuse across templates.
## Summary

View File

@ -936,7 +936,7 @@ class File extends DataObject {
if(!is_array($exts)) $exts = array($exts);
foreach($exts as $ext) {
if(!is_subclass_of($ext, 'File')) {
if(!is_subclass_of($class, 'File')) {
throw new InvalidArgumentException(
sprintf('Class "%s" (for extension "%s") is not a valid subclass of File', $class, $ext)
);

View File

@ -19,8 +19,27 @@ class Filesystem extends Object {
*/
private static $folder_create_mask = 02775;
/**
* @var int
*/
protected static $cache_folderModTime;
/**
* @config
*
* Array of file / folder regex expressions to exclude from the
* {@link Filesystem::sync()}
*
* @var array
*/
private static $sync_blacklisted_patterns = array(
"/^\./",
"/^_combinedfiles$/i",
"/^_resampled$/i",
"/^web.config/i",
"/^Thumbs(.)/"
);
/**
* Create a folder on the filesystem, recursively.
* Uses {@link Filesystem::$folder_create_mask} to set filesystem permissions.
@ -161,14 +180,32 @@ class Filesystem extends Object {
// Update the image tracking of all pages
if($syncLinkTracking) {
if(class_exists('SiteTree')) {
if(class_exists('Subsite')) $origDisableSubsiteFilter = Subsite::$disable_subsite_filter;
if(class_exists('Subsite')) Subsite::$disable_subsite_filter = true;
foreach(DataObject::get("SiteTree") as $page) {
// if subsites exist, go through each subsite and sync each subsite's pages.
// disabling the filter doesn't work reliably, because writing pages that share
// the same URLSegment between subsites will break, e.g. "home" between two
// sites will modify one of them to "home-2", thinking it's a duplicate. The
// check before a write is done in SiteTree::validURLSegment()
if(class_exists('Subsite')) {
// loop through each subsite ID, changing the subsite, then query it's pages
foreach(Subsite::get()->getIDList() as $id) {
Subsite::changeSubsite($id);
foreach(SiteTree::get() as $page) {
// syncLinkTracking is called by SiteTree::onBeforeWrite().
// Call it without affecting the page version, as this is an internal change.
$page->writeWithoutVersion();
}
}
// change back to the main site so the foreach below works
Subsite::changeSubsite(0);
}
foreach(SiteTree::get() as $page) {
// syncLinkTracking is called by SiteTree::onBeforeWrite().
// Call it without affecting the page version, as this is an internal change.
$page->writeWithoutVersion();
}
if(class_exists('Subsite')) Subsite::disable_subsite_filter($origDisableSubsiteFilter);
}
}

View File

@ -80,12 +80,14 @@ class Folder extends File {
}
/**
* Syncronise the file database with the actual content of the assets folder
* Synchronize the file database with the actual content of the assets
* folder.
*/
public function syncChildren() {
$parentID = (int)$this->ID; // parentID = 0 on the singleton, used as the 'root node';
$added = 0;
$deleted = 0;
$skipped = 0;
// First, merge any children that are duplicates
$duplicateChildrenNames = DB::query("SELECT \"Name\" FROM \"File\""
@ -113,6 +115,7 @@ class Folder extends File {
// We don't use DataObject so that things like subsites doesn't muck with this.
$dbChildren = DB::query("SELECT * FROM \"File\" WHERE \"ParentID\" = $parentID");
$hasDbChild = array();
if($dbChildren) {
foreach($dbChildren as $dbChild) {
$className = $dbChild['ClassName'];
@ -120,6 +123,7 @@ class Folder extends File {
$hasDbChild[$dbChild['Name']] = new $className($dbChild);
}
}
$unwantedDbChildren = $hasDbChild;
// if we're syncing a folder with no ID, we assume we're syncing the root assets folder
@ -135,13 +139,28 @@ class Folder extends File {
if(file_exists($baseDir)) {
$actualChildren = scandir($baseDir);
foreach($actualChildren as $actualChild) {
if($actualChild[0] == '.' || $actualChild[0] == '_' || substr($actualChild,0,6) == 'Thumbs'
|| $actualChild == 'web.config') {
$ignoreRules = Config::inst()->get('Filesystem', 'sync_blacklisted_patterns');
continue;
foreach($actualChildren as $actualChild) {
if($ignoreRules) {
$skip = false;
foreach($ignoreRules as $rule) {
if(preg_match($rule, $actualChild)) {
$skip = true;
break;
}
}
if($skip) {
$skipped++;
continue;
}
}
// A record with a bad class type doesn't deserve to exist. It must be purged!
if(isset($hasDbChild[$actualChild])) {
$child = $hasDbChild[$actualChild];
@ -166,6 +185,7 @@ class Folder extends File {
$childResult = $child->syncChildren();
$added += $childResult['added'];
$deleted += $childResult['deleted'];
$skipped += $childResult['skipped'];
}
// Clean up the child record from memory after use. Important!
@ -182,7 +202,11 @@ class Folder extends File {
DB::query("DELETE FROM \"File\" WHERE \"ID\" = $this->ID");
}
return array('added' => $added, 'deleted' => $deleted);
return array(
'added' => $added,
'deleted' => $deleted,
'skipped' => $skipped
);
}
/**

View File

@ -240,42 +240,26 @@ class ImagickBackend extends Imagick implements Image_Backend {
$width = round($width);
$height = round($height);
$geometry = $this->getImageGeometry();
$geo = $this->getImageGeometry();
// Check that a resize is actually necessary.
if ($width == $geometry["width"] && $height == $geometry["height"]) {
if ($width == $geo["width"] && $height == $geo["height"]) {
return $this;
}
if(!$backgroundColor){
$backgroundColor = new ImagickPixel('transparent');
}
$new = clone $this;
$new->setBackgroundColor($backgroundColor);
$destAR = $width / $height;
if ($geometry["width"] > 0 && $geometry["height"] > 0) {
// We can't divide by zero theres something wrong.
$srcAR = $this->width / $this->height;
// Destination narrower than the source
if($destAR < $srcAR) {
$srcY = 0;
$srcHeight = $this->height;
$srcWidth = round( $this->height * $destAR );
$srcX = round( ($this->width - $srcWidth) / 2 );
// Destination shorter than the source
} else {
$srcX = 0;
$srcWidth = $this->width;
$srcHeight = round( $this->width / $destAR );
$srcY = round( ($this->height - $srcHeight) / 2 );
}
$new->extentImage($width, $height, $destX, $destY);
if(($geo['width']/$width) < ($geo['height']/$height)){
$new->cropImage($geo['width'], floor($height*$geo['width']/$width), 0, (($geo['height']-($height*$geo['width']/$width))/2));
}else{
$new->cropImage(ceil($width*$geo['height']/$height), $geo['height'], (($geo['width']-($width*$geo['height']/$height))/2), 0);
}
$new->ThumbnailImage($width,$height,true);
return $new;
}
}

View File

@ -127,11 +127,8 @@ class Upload extends Controller {
$parentFolder = Folder::find_or_make($folderPath);
// Create a folder for uploading.
if(!file_exists(ASSETS_PATH)){
mkdir(ASSETS_PATH, Config::inst()->get('Filesystem', 'folder_create_mask'));
}
if(!file_exists(ASSETS_PATH . "/" . $folderPath)){
mkdir(ASSETS_PATH . "/" . $folderPath, Config::inst()->get('Filesystem', 'folder_create_mask'));
Filesystem::makeFolder(ASSETS_PATH . "/" . $folderPath);
}
// Generate default filename
@ -156,24 +153,24 @@ class Upload extends Controller {
// if filename already exists, version the filename (e.g. test.gif to test1.gif)
if(!$this->replaceFile) {
while(file_exists("$base/$relativeFilePath")) {
$i = isset($i) ? ($i+1) : 2;
$oldFilePath = $relativeFilePath;
// make sure archives retain valid extensions
if(substr($relativeFilePath, strlen($relativeFilePath) - strlen('.tar.gz')) == '.tar.gz' ||
substr($relativeFilePath, strlen($relativeFilePath) - strlen('.tar.bz2')) == '.tar.bz2') {
$relativeFilePath = preg_replace('/[0-9]*(\.tar\.[^.]+$)/', $i . '\\1', $relativeFilePath);
} else if (strpos($relativeFilePath, '.') !== false) {
$relativeFilePath = preg_replace('/[0-9]*(\.[^.]+$)/', $i . '\\1', $relativeFilePath);
} else if (strpos($relativeFilePath, '_') !== false) {
$relativeFilePath = preg_replace('/_([^_]+$)/', '_'.$i, $relativeFilePath);
} else {
$relativeFilePath .= '_'.$i;
}
if($oldFilePath == $relativeFilePath && $i > 2) {
user_error("Couldn't fix $relativeFilePath with $i tries", E_USER_ERROR);
}
while(file_exists("$base/$relativeFilePath")) {
$i = isset($i) ? ($i+1) : 2;
$oldFilePath = $relativeFilePath;
// make sure archives retain valid extensions
if(substr($relativeFilePath, strlen($relativeFilePath) - strlen('.tar.gz')) == '.tar.gz' ||
substr($relativeFilePath, strlen($relativeFilePath) - strlen('.tar.bz2')) == '.tar.bz2') {
$relativeFilePath = preg_replace('/[0-9]*(\.tar\.[^.]+$)/', $i . '\\1', $relativeFilePath);
} else if (strpos($relativeFilePath, '.') !== false) {
$relativeFilePath = preg_replace('/[0-9]*(\.[^.]+$)/', $i . '\\1', $relativeFilePath);
} else if (strpos($relativeFilePath, '_') !== false) {
$relativeFilePath = preg_replace('/_([^_]+$)/', '_'.$i, $relativeFilePath);
} else {
$relativeFilePath .= '_'.$i;
}
if($oldFilePath == $relativeFilePath && $i > 2) {
user_error("Couldn't fix $relativeFilePath with $i tries", E_USER_ERROR);
}
}
}
if(file_exists($tmpFile['tmp_name']) && copy($tmpFile['tmp_name'], "$base/$relativeFilePath")) {

View File

@ -1,8 +1,10 @@
<?php
/**
* Two masked input fields, checks for matching passwords.
* Optionally hides the fields by default and shows
* a link to toggle their visibility.
*
* Optionally hides the fields by default and shows a link to toggle their
* visibility.
*
* @package forms
* @subpackage fields-formattedinput
@ -39,11 +41,12 @@ class ConfirmedPasswordField extends FormField {
public $canBeEmpty = false;
/**
* If set to TRUE, the "password" and "confirm password"
* formfields will be hidden via CSS and JavaScript by default,
* and triggered by a link. An additional hidden field
* determines if showing the fields has been triggered,
* and just validates/saves the input in this case.
* If set to TRUE, the "password" and "confirm password" form fields will
* be hidden via CSS and JavaScript by default, and triggered by a link.
*
* An additional hidden field determines if showing the fields has been
* triggered and just validates/saves the input in this case.
*
* This behaviour works unobtrusively, without JavaScript enabled
* the fields show, validate and save by default.
*
@ -52,8 +55,7 @@ class ConfirmedPasswordField extends FormField {
protected $showOnClick = false;
/**
* Title for the link that triggers
* the visibility of password fields.
* Title for the link that triggers the visibility of password fields.
*
* @var string
*/
@ -93,6 +95,12 @@ class ConfirmedPasswordField extends FormField {
if($showOnClick) {
$this->children->push(new HiddenField("{$name}[_PasswordFieldVisible]"));
}
// disable auto complete
foreach($this->children as $child) {
$child->setAttribute('autocomplete', 'off');
}
$this->showOnClick = $showOnClick;
// we have labels for the subfields
@ -102,6 +110,11 @@ class ConfirmedPasswordField extends FormField {
$this->setValue($value);
}
/**
* @param array $properties
*
* @return string
*/
public function Field($properties = array()) {
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
Requirements::javascript(FRAMEWORK_DIR . '/javascript/ConfirmedPasswordField.js');
@ -129,11 +142,13 @@ class ConfirmedPasswordField extends FormField {
foreach($this->children as $field) {
$field->setDisabled($this->isDisabled());
$field->setReadonly($this->isReadonly());
if(count($this->attributes)) {
foreach($this->attributes as $name => $value) {
$field->setAttribute($name, $value);
}
}
$content .= $field->FieldHolder();
}
@ -154,65 +169,91 @@ class ConfirmedPasswordField extends FormField {
}
/**
* Can be empty is a flag that turns on/off empty field checking.
* Can be empty is a flag that turns on / off empty field checking.
*
* For example, set this to false (the default) when creating a user account,
* and true
* and true when displaying on an edit form.
*
* @param boolean $value
*
* @return ConfirmedPasswordField
*/
public function setCanBeEmpty($value) {
$this->canBeEmpty = (bool)$value;
return $this;
}
/**
* The title on the link which triggers display of the
* "password" and "confirm password" formfields.
* Only used if {@link setShowOnClick()} is set to TRUE.
* The title on the link which triggers display of the "password" and
* "confirm password" formfields. Only used if {@link setShowOnClick()}
* is set to TRUE.
*
* @param $title
* @param string $title
*
* @return ConfirmedPasswordField
*/
public function setShowOnClickTitle($title) {
$this->showOnClickTitle = $title;
return $this;
}
/**
* @return string
* @return string $title
*/
public function getShowOnClickTitle() {
return $this->showOnClickTitle;
}
/**
* @param string $title
*
* @return ConfirmedPasswordField
*/
public function setRightTitle($title) {
foreach($this->children as $field) {
$field->setRightTitle($title);
}
return $this;
}
/**
* @param array: 2 entrie array with the customised title for each of the 2 children.
* @param array $titles 2 entry array with the customized title for each
* of the 2 children.
*
* @return ConfirmedPasswordField
*/
public function setChildrenTitles($titles) {
if(is_array($titles)&&count($titles)==2){
if(is_array($titles) && count($titles) == 2) {
foreach($this->children as $field) {
if(isset($titles[0])){
if(isset($titles[0])) {
$field->setTitle($titles[0]);
array_shift($titles);
}
}
}
return $this;
}
/**
* Value is sometimes an array, and sometimes a single value, so we need to handle both cases
* Value is sometimes an array, and sometimes a single value, so we need
* to handle both cases.
*
* @param mixed $value
*
* @return ConfirmedPasswordField
*/
public function setValue($value) {
if(is_array($value)) {
if($value['_Password'] || (!$value['_Password'] && !$this->canBeEmpty)) {
$this->value = $value['_Password'];
}
if($this->showOnClick && isset($value['_PasswordFieldVisible'])){
if($this->showOnClick && isset($value['_PasswordFieldVisible'])) {
$this->children->fieldByName($this->getName() . '[_PasswordFieldVisible]')
->setValue($value['_PasswordFieldVisible']);
}
@ -221,29 +262,40 @@ class ConfirmedPasswordField extends FormField {
$this->value = $value;
}
}
$this->children->fieldByName($this->getName() . '[_Password]')->setValue($this->value);
$this->children->fieldByName($this->getName() . '[_ConfirmPassword]')->setValue($this->value);
$this->children->fieldByName($this->getName() . '[_Password]')
->setValue($this->value);
$this->children->fieldByName($this->getName() . '[_ConfirmPassword]')
->setValue($this->value);
return $this;
}
/**
* Determines if the field was actually
* shown on the clientside - if not,
* Determines if the field was actually shown on the client side - if not,
* we don't validate or save it.
*
* @return bool
* @return boolean
*/
public function isSaveable() {
$isVisible = $this->children->fieldByName($this->getName() . '[_PasswordFieldVisible]');
return (!$this->showOnClick || ($this->showOnClick && $isVisible && $isVisible->Value()));
}
/**
* @param Validator $validator
*
* @return boolean
*/
public function validate($validator) {
$name = $this->name;
// if field isn't visible, don't validate
if(!$this->isSaveable()) return true;
if(!$this->isSaveable()) {
return true;
}
$passwordField = $this->children->fieldByName($name.'[_Password]');
$passwordConfirmField = $this->children->fieldByName($name.'[_ConfirmPassword]');
@ -254,16 +306,26 @@ class ConfirmedPasswordField extends FormField {
// both password-fields should be the same
if($value != $passwordConfirmField->Value()) {
$validator->validationError($name, _t('Form.VALIDATIONPASSWORDSDONTMATCH',"Passwords don't match"),
"validation", false);
$validator->validationError(
$name,
_t('Form.VALIDATIONPASSWORDSDONTMATCH',"Passwords don't match"),
"validation",
false
);
return false;
}
if(!$this->canBeEmpty) {
// both password-fields shouldn't be empty
if(!$value || !$passwordConfirmField->Value()) {
$validator->validationError($name, _t('Form.VALIDATIONPASSWORDSNOTEMPTY', "Passwords can't be empty"),
"validation", false);
$validator->validationError(
$name,
_t('Form.VALIDATIONPASSWORDSNOTEMPTY', "Passwords can't be empty"),
"validation",
false
);
return false;
}
}
@ -310,21 +372,25 @@ class ConfirmedPasswordField extends FormField {
"validation",
false
);
return false;
}
}
return true;
}
/**
* Only save if field was shown on the client,
* and is not empty.
* Only save if field was shown on the client, and is not empty.
*
* @param DataObject $record
* @return bool
* @param DataObjectInterface $record
*
* @return boolean
*/
public function saveInto(DataObjectInterface $record) {
if(!$this->isSaveable()) return false;
if(!$this->isSaveable()) {
return false;
}
if(!($this->canBeEmpty && !$this->value)) {
parent::saveInto($record);
@ -332,7 +398,9 @@ class ConfirmedPasswordField extends FormField {
}
/**
* Makes a pretty readonly field with some stars in it
* Makes a read only field with some stars in it to replace the password
*
* @return ReadonlyField
*/
public function performReadonlyTransformation() {
$field = $this->castedCopy('ReadonlyField')

View File

@ -66,6 +66,11 @@ class Form extends RequestHandler {
protected $formMethod = "post";
/**
* @var boolean
*/
protected $strictFormMethodCheck = false;
protected static $current_action;
/**
@ -239,7 +244,22 @@ class Form extends RequestHandler {
* if the form is valid.
*/
public function httpSubmission($request) {
$vars = $request->requestVars();
// Strict method check
if($this->strictFormMethodCheck) {
// Throws an error if the method is bad...
if($this->formMethod != strtolower($request->httpMethod())) {
$response = Controller::curr()->getResponse();
$response->addHeader('Allow', $this->formMethod);
$this->httpError(405, _t("Form.BAD_METHOD", "This form requires a ".$this->formMethod." submission"));
}
// ...and only uses the vairables corresponding to that method type
$vars = $this->formMethod == 'get' ? $request->getVars() : $request->postVars();
} else {
$vars = $request->requestVars();
}
if(isset($funcName)) {
Form::set_current_action($funcName);
}
@ -794,12 +814,38 @@ class Form extends RequestHandler {
* Set the form method: GET, POST, PUT, DELETE.
*
* @param $method string
* @param $strict If non-null, pass value to {@link setStrictFormMethodCheck()}.
*/
public function setFormMethod($method) {
public function setFormMethod($method, $strict = null) {
$this->formMethod = strtolower($method);
if($strict !== null) $this->setStrictFormMethodCheck($strict);
return $this;
}
/**
* If set to true, enforce the matching of the form method.
*
* This will mean two things:
* - GET vars will be ignored by a POST form, and vice versa
* - A submission where the HTTP method used doesn't match the form will return a 400 error.
*
* If set to false (the default), then the form method is only used to construct the default
* form.
*
* @param $bool boolean
*/
public function setStrictFormMethodCheck($bool) {
$this->strictFormMethodCheck = (bool)$bool;
return $this;
}
/**
* @return boolean
*/
public function getStrictFormMethodCheck() {
return $this->strictFormMethodCheck;
}
/**
* Return the form's action attribute.
* This is build by adding an executeForm get variable to the parent controller's Link() value

View File

@ -412,7 +412,7 @@ class HtmlEditorField_Toolbar extends RequestHandler {
'<h4>' . sprintf($numericLabelTmpl, '1', _t('HtmlEditorField.ADDURL', 'Add URL')) . '</h4>'),
$remoteURL = new TextField('RemoteURL', 'http://'),
new LiteralField('addURLImage',
'<button class="action ui-action-constructive ui-button field add-url" data-icon="addMedia"></button>')
'<button class="action ui-action-constructive ui-button field add-url" data-icon="addMedia">'._t('HtmlEditorField.BUTTONADDURL', 'Add url').'</button>')
);
$remoteURL->addExtraClass('remoteurl');

View File

@ -1,30 +1,51 @@
<?php
/**
* Read-only complement of {@link DropdownField}.
* Shows the "human value" of the dropdown field for the currently selected value.
*
* Shows the "human value" of the dropdown field for the currently selected
* value.
*
* @package forms
* @subpackage fields-basic
*/
class LookupField extends DropdownField {
/**
* @var boolean $readonly
*/
protected $readonly = true;
/**
* Returns a readonly span containing the correct value.
*
* @param array $properties
*
* @return string
*/
public function Field($properties = array()) {
$source = $this->getSource();
// Normalize value to array to simplify further processing
$values = (is_array($this->value) || is_object($this->value)) ? $this->value : array(trim($this->value));
if(is_array($this->value) || is_object($this->value)) {
$values = $this->value;
} else {
$values = array(trim($this->value));
}
$mapped = array();
if($source instanceof SQLMap) {
foreach($values as $value) $mapped[] = $source->getItem($value);
} else if($source instanceof ArrayAccess || is_array($source)) {
foreach($values as $value) {
if(isset($source[$value])) $mapped[] = $source[$value];
$mapped[] = $source->getItem($value);
}
} else if($source instanceof ArrayAccess || is_array($source)) {
$source = ArrayLib::flatten($source);
foreach($values as $value) {
if(isset($source[$value])) {
$mapped[] = $source[$value];
}
}
} else {
$mapped = array();
@ -39,7 +60,11 @@ class LookupField extends DropdownField {
if($mapped) {
$attrValue = implode(', ', array_values($mapped));
if(!$this->dontEscape) $attrValue = Convert::raw2xml($attrValue);
if(!$this->dontEscape) {
$attrValue = Convert::raw2xml($attrValue);
}
$inputValue = implode(', ', array_values($values));
} else {
$attrValue = "<i>(none)</i>";
@ -51,17 +76,26 @@ class LookupField extends DropdownField {
"\" value=\"" . $inputValue . "\" />";
}
/**
* @return LookupField
*/
public function performReadonlyTransformation() {
$clone = clone $this;
return $clone;
}
/**
* @return string
*/
public function Type() {
return "lookup readonly";
}
/**
* Override parent behaviour by not merging arrays.
* Override parent behavior by not merging arrays.
*
* @return array
*/
public function getSource() {
return $this->source;

View File

@ -1,19 +1,31 @@
<?php
/**
* Text input field with validation for numeric values.
* Text input field with validation for numeric values. Supports validating
* the numeric value as to the {@link i18n::get_locale()} value.
*
* @package forms
* @subpackage fields-formattedinput
*/
class NumericField extends TextField{
class NumericField extends TextField {
public function Type() {
return 'numeric text';
}
/** PHP Validation **/
public function validate($validator){
if($this->value && !is_numeric(trim($this->value))){
public function validate($validator) {
if(!$this->value && !$validator->fieldIsRequired($this->name)) {
return true;
}
require_once THIRDPARTY_PATH."/Zend/Locale/Format.php";
$valid = Zend_Locale_Format::isNumber(
trim($this->value),
array('locale' => i18n::get_locale())
);
if(!$valid) {
$validator->validationError(
$this->name,
_t(
@ -22,10 +34,11 @@ class NumericField extends TextField{
),
"validation"
);
return false;
} else{
return true;
}
return true;
}
public function dataValue() {

View File

@ -93,6 +93,11 @@ class UploadField extends FileField {
* String values are interpreted as permission codes.
*/
'canAttachExisting' => "CMS_ACCESS_AssetAdmin",
/**
* @var boolean Shows the target folder for new uploads in the field UI.
* Disable to keep the internal filesystem structure hidden from users.
*/
'canPreviewFolder' => true,
/**
* @var boolean If a second file is uploaded, should it replace the existing one rather than throwing an errror?
* This only applies for has_one relationships, and only replaces the association

View File

@ -16,7 +16,7 @@
* @see SS_List
*
* @package framework
* @subpackage fields-relational
* @subpackage fields-gridfield
*/
class GridField extends FormField {
@ -55,6 +55,8 @@ class GridField extends FormField {
/**
* The components list
*
* @var array
*/
protected $components = array();
@ -68,9 +70,14 @@ class GridField extends FormField {
/**
* Map of callbacks for custom data fields
*
* @var array
*/
protected $customDataFields = array();
/**
* @var string
*/
protected $name = '';
/**
@ -222,12 +229,14 @@ class GridField extends FormField {
* Get the current GridState_Data or the GridState
*
* @param bool $getData - flag for returning the GridState_Data or the GridState
*
* @return GridState_data|GridState
*/
public function getState($getData=true) {
public function getState($getData = true) {
if($getData) {
return $this->state->getData();
}
return $this->state;
}
@ -465,11 +474,16 @@ class GridField extends FormField {
/**
* Add additional calculated data fields to be used on this GridField
* @param array $fields a map of fieldname to callback. The callback will bed passed the record as an argument.
*
* @param array $fields a map of fieldname to callback. The callback will
* be passed the record as an argument.
*/
public function addDataFields($fields) {
if($this->customDataFields) $this->customDataFields = array_merge($this->customDataFields, $fields);
else $this->customDataFields = $fields;
if($this->customDataFields) {
$this->customDataFields = array_merge($this->customDataFields, $fields);
} else {
$this->customDataFields = $fields;
}
}
/**
@ -580,9 +594,11 @@ class GridField extends FormField {
*/
protected function buildColumnDispatch() {
$this->columnDispatch = array();
foreach($this->getComponents() as $item) {
if($item instanceof GridField_ColumnProvider) {
$columns = $item->getColumnsHandled($this);
foreach($columns as $column) {
$this->columnDispatch[$column][] = $item;
}
@ -603,14 +619,19 @@ class GridField extends FormField {
// Update state from client
$state = $this->getState(false);
if(isset($fieldData['GridState'])) $state->setValue($fieldData['GridState']);
if(isset($fieldData['GridState'])) {
$state->setValue($fieldData['GridState']);
}
// Try to execute alter action
foreach($data as $k => $v) {
if(preg_match('/^action_gridFieldAlterAction\?StateID=(.*)/', $k, $matches)) {
$id = $matches[1];
$stateChange = Session::get($id);
$actionName = $stateChange['actionName'];
$args = isset($stateChange['args']) ? $stateChange['args'] : array();
$html = $this->handleAlterAction($actionName, $args, $data);
// A field can optionally return its own HTML
@ -670,7 +691,7 @@ class GridField extends FormField {
$this->setDataModel($model);
$fieldData = $this->request->requestVar($this->getName());
if($fieldData && $fieldData['GridState']) $this->getState(false)->setValue($fieldData['GridState']);
if($fieldData && isset($fieldData['GridState'])) $this->getState(false)->setValue($fieldData['GridState']);
foreach($this->getComponents() as $component) {
if(!($component instanceof GridField_URLHandler)) {
@ -740,41 +761,40 @@ class GridField extends FormField {
/**
* This class is the base class when you want to have an action that alters the state of the gridfield,
* rendered as a button element.
* This class is the base class when you want to have an action that alters
* the state of the {@link GridField}, rendered as a button element.
*
* @package framework
* @subpackage forms
*
* @subpackage fields-gridfield
*/
class GridField_FormAction extends FormAction {
/**
*
* @var GridField
*/
protected $gridField;
/**
*
* @var array
*/
protected $stateValues;
/**
*
* @var array
*/
//protected $stateFields = array();
protected $actionName;
protected $args = array();
/**
* @var string
*/
protected $actionName;
/**
* @var boolean
*/
public $useButtonTag = true;
/**
*
* @param GridField $gridField
* @param type $name
* @param type $label
@ -785,11 +805,13 @@ class GridField_FormAction extends FormAction {
$this->gridField = $gridField;
$this->actionName = $actionName;
$this->args = $args;
parent::__construct($name, $title);
}
/**
* urlencode encodes less characters in percent form than we need - we need everything that isn't a \w
* urlencode encodes less characters in percent form than we need - we
* need everything that isn't a \w.
*
* @param string $val
*/
@ -806,13 +828,17 @@ class GridField_FormAction extends FormAction {
return '%'.dechex(ord($match[0]));
}
/**
* @return array
*/
public function getAttributes() {
// Store state in session, and pass ID to client side
// Store state in session, and pass ID to client side.
$state = array(
'grid' => $this->getNameFromParent(),
'actionName' => $this->actionName,
'args' => $this->args,
);
$id = preg_replace('/[^\w]+/', '_', uniqid('', true));
Session::set($id, $state);
$actionData['StateID'] = $id;
@ -837,10 +863,12 @@ class GridField_FormAction extends FormAction {
protected function getNameFromParent() {
$base = $this->gridField;
$name = array();
do {
array_unshift($name, $base->getName());
$base = $base->getForm();
} while ($base && !($base instanceof Form));
return implode('.', $name);
}
}

View File

@ -1,14 +1,25 @@
<?php
/**
* This class is is responsible for adding objects to another object's has_many and many_many relation,
* as defined by the {@link RelationList} passed to the GridField constructor.
* Objects can be searched through an input field (partially matching one or more fields).
* This class is is responsible for adding objects to another object's has_many
* and many_many relation, as defined by the {@link RelationList} passed to the
* {@link GridField} constructor.
*
* Objects can be searched through an input field (partially matching one or
* more fields).
*
* Selecting from the results will add the object to the relation.
* Often used alongside {@link GridFieldDeleteAction} for detaching existing records from a relatinship.
* For easier setup, have a look at a sample configuration in {@link GridFieldConfig_RelationEditor}.
*
* Often used alongside {@link GridFieldDeleteAction} for detaching existing
* records from a relationship.
*
* For easier setup, have a look at a sample configuration in
* {@link GridFieldConfig_RelationEditor}.
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldAddExistingAutocompleter
implements GridField_HTMLProvider, GridField_ActionProvider, GridField_DataManipulator, GridField_URLHandler {
implements GridField_HTMLProvider, GridField_ActionProvider, GridField_DataManipulator, GridField_URLHandler {
/**
* Which template to use for rendering

View File

@ -1,10 +1,13 @@
<?php
/**
* This component provides a button for opening the add new form provided by {@link GridFieldDetailForm}.
* Only returns a button if {@link DataObject->canCreate()} for this record returns true.
* This component provides a button for opening the add new form provided by
* {@link GridFieldDetailForm}.
*
* Only returns a button if {@link DataObject->canCreate()} for this record
* returns true.
*
* @package framework
* @subpackage gridfield
* @subpackage fields-gridfield
*/
class GridFieldAddNewButton implements GridField_HTMLProvider {
@ -14,6 +17,7 @@ class GridFieldAddNewButton implements GridField_HTMLProvider {
public function setButtonName($name) {
$this->buttonName = $name;
return $this;
}
@ -23,7 +27,10 @@ class GridFieldAddNewButton implements GridField_HTMLProvider {
public function getHTMLFragments($gridField) {
$singleton = singleton($gridField->getModelClass());
if(!$singleton->canCreate()) return array();
if(!$singleton->canCreate()) {
return array();
}
if(!$this->buttonName) {
// provide a default button name, can be changed by calling {@link setButtonName()} on this component

View File

@ -1,14 +1,18 @@
<?php
/**
* Adding this class to a {@link GridFieldConfig} of a {@link GridField} adds a buttonrow to that field.
* Adding this class to a {@link GridFieldConfig} of a {@link GridField} adds
* a button row to that field.
*
* The button row provides a space for actions on this grid.
*
* This row provides two new HTML fragment spaces: 'toolbar-header-left' and 'toolbar-header-right'.
* This row provides two new HTML fragment spaces: 'toolbar-header-left' and
* 'toolbar-header-right'.
*
* @package framework
* @subpackage gridfield
* @subpackage fields-gridfield
*/
class GridFieldButtonRow implements GridField_HTMLProvider {
protected $targetFragment;
public function __construct($targetFragment = 'before') {
@ -20,6 +24,7 @@ class GridFieldButtonRow implements GridField_HTMLProvider {
"LeftFragment" => "\$DefineFragment(buttons-{$this->targetFragment}-left)",
"RightFragment" => "\$DefineFragment(buttons-{$this->targetFragment}-right)",
));
return array(
$this->targetFragment => $data->renderWith('GridFieldButtonRow')
);

View File

@ -2,41 +2,57 @@
/**
* Base interface for all components that can be added to GridField.
*
* @package framework
* @subpackage fields-gridfield
*/
interface GridFieldComponent {
}
/**
* A GridField manipulator that provides HTML for the header/footer rows, or for before/after the template
* A GridField manipulator that provides HTML for the header/footer rows, or f
* or before/after the template.
*
* @package framework
* @subpackage fields-gridfield
*/
interface GridField_HTMLProvider extends GridFieldComponent {
/**
* Returns a map where the keys are fragment names and the values are pieces of HTML to add to these fragments.
* Returns a map where the keys are fragment names and the values are
* pieces of HTML to add to these fragments.
*
* Here are 4 built-in fragments: 'header', 'footer', 'before', and 'after', but components may also specify
* fragments of their own.
* Here are 4 built-in fragments: 'header', 'footer', 'before', and
* 'after', but components may also specify fragments of their own.
*
* To specify a new fragment, specify a new fragment by including the text "$DefineFragment(fragmentname)" in the
* HTML that you return. Fragment names should only contain alphanumerics, -, and _.
* To specify a new fragment, specify a new fragment by including the
* text "$DefineFragment(fragmentname)" in the HTML that you return.
*
* If you attempt to return HTML for a fragment that doesn't exist, an exception will be thrown when the GridField
* is rendered.
* Fragment names should only contain alphanumerics, -, and _.
*
* @return Array
* If you attempt to return HTML for a fragment that doesn't exist, an
* exception will be thrown when the {@link GridField} is rendered.
*
* @return array
*/
public function getHTMLFragments($gridField);
}
/**
* Add a new column to the table display body, or modify existing columns.
*
* Used once per record/row.
*
* @package framework
* @subpackage fields-gridfield
*/
interface GridField_ColumnProvider extends GridFieldComponent {
/**
* Modify the list of columns displayed in the table.
* See {@link GridFieldDataColumns->getDisplayFields()} and {@link GridFieldDataColumns}.
*
* @see {@link GridFieldDataColumns->getDisplayFields()}
* @see {@link GridFieldDataColumns}.
*
* @param GridField $gridField
* @param array - List reference of all column names.
@ -83,16 +99,29 @@ interface GridField_ColumnProvider extends GridFieldComponent {
}
/**
* An action is defined by two things: an action name, and zero or more named arguments.
* An action is defined by two things: an action name, and zero or more named
* arguments.
*
* There is no built-in notion of a record-specific or column-specific action,
* but you may choose to define an argument such as ColumnName or RecordID in order to implement these.
* Does not provide interface elements to call those actions, see {@link GridField_FormAction}.
* but you may choose to define an argument such as ColumnName or RecordID in
* order to implement these.
*
* Does not provide interface elements to call those actions.
*
* @see {@link GridField_FormAction}.
*
* @package framework
* @subpackage fields-gridfield
*/
interface GridField_ActionProvider extends GridFieldComponent {
/**
* Return a list of the actions handled by this action provider.
* Used to identify the action later on through the $actionName parameter in {@link handleAction}.
* There is no namespacing on these actions, so you need to ensure that they don't conflict with other components.
*
* Used to identify the action later on through the $actionName parameter
* in {@link handleAction}.
*
* There is no namespacing on these actions, so you need to ensure that
* they don't conflict with other components.
*
* @param GridField
* @return Array with action identifier strings.
@ -100,9 +129,10 @@ interface GridField_ActionProvider extends GridFieldComponent {
public function getActions($gridField);
/**
* Handle an action on the given grid field.
* Calls ALL components for every action handled, so the component
* needs to ensure it only accepts actions it is actually supposed to handle.
* Handle an action on the given {@link GridField}.
*
* Calls ALL components for every action handled, so the component needs
* to ensure it only accepts actions it is actually supposed to handle.
*
* @param GridField
* @param String Action identifier, see {@link getActions()}.
@ -114,13 +144,20 @@ interface GridField_ActionProvider extends GridFieldComponent {
/**
* Can modify the data list.
* For example, a paginating component can apply a limit, or a sorting component can apply a sort.
* Generally, the data manipulator will make use of to `GridState` variables to decide
* how to modify the data list (see {@link GridState}).
*
* For example, a paginating component can apply a limit, or a sorting
* component can apply a sort.
*
* Generally, the data manipulator will make use of to {@link GridState}
* variables to decide how to modify the {@link DataList}.
*
* @package framework
* @subpackage fields-gridfield
*/
interface GridField_DataManipulator extends GridFieldComponent {
/**
* Manipulate the datalist as needed by this grid modifier.
* Manipulate the {@link DataList} as needed by this grid modifier.
*
* @param GridField
* @param SS_List
@ -130,21 +167,37 @@ interface GridField_DataManipulator extends GridFieldComponent {
}
/**
* Sometimes an action isn't enough: you need to provide additional support URLs for the grid.
* These URLs may return user-visible content, for example a pop-up form for editing a record's details,
* or they may be support URLs for front-end functionality.
* For example a URL that will return JSON-formatted data for a javascript grid control.
* Sometimes an action isn't enough: you need to provide additional support
* URLs for the {@link GridField}.
*
* These URLs may return user-visible content, for example a pop-up form for
* editing a record's details, or they may be support URLs for front-end
* functionality.
*
* For example a URL that will return JSON-formatted data for a javascript
* grid control.
*
* @package framework
* @subpackage fields-gridfield
*/
interface GridField_URLHandler extends GridFieldComponent {
/**
* Return URLs to be handled by this grid field, in an array the same form as $url_handlers.
* Handler methods will be called on the component, rather than the grid field.
* Return URLs to be handled by this grid field, in an array the same form
* as $url_handlers.
*
* Handler methods will be called on the component, rather than the
* {@link GridField}.
*/
public function getURLHandlers($gridField);
}
/**
* A component which is used to handle when a grid field is saved into a record.
* A component which is used to handle when a {@link GridField} is saved into
* a record.
*
* @package framework
* @subpackage fields-gridfield
*/
interface GridField_SaveHandler extends GridFieldComponent {

View File

@ -1,21 +1,27 @@
<?php
/**
* Encapsulates a collection of components following the {@link GridFieldComponent} interface.
* While the {@link GridField} itself has some configuration in the form of setters,
* most of the details are dealt with through components.
* Encapsulates a collection of components following the
* {@link GridFieldComponent} interface. While the {@link GridField} itself
* has some configuration in the form of setters, most of the details are
* dealt with through components.
*
* For example, you would add a {@link GridFieldPaginator} component to enable
* pagination on the listed records, and configure it through {@link GridFieldPaginator->setItemsPerPage()}.
* pagination on the listed records, and configure it through
* {@link GridFieldPaginator->setItemsPerPage()}.
*
* In order to reduce the amount of custom code required, the framework
* provides some default configurations for common use cases:
*
* In order to reduce the amount of custom code required, the framework provides
* some default configurations for common use cases:
* - {@link GridFieldConfig_Base} (added by default to GridField)
* - {@link GridFieldConfig_RecordEditor}
* - {@link GridFieldConfig_RelationEditor}
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldConfig {
/**
*
* @var ArrayList
*/
protected $components = null;
@ -129,12 +135,15 @@ class GridFieldConfig {
}
/**
* A simple readonly, paginated view of records,
* with sortable and searchable headers.
* A simple readonly, paginated view of records, with sortable and searchable
* headers.
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldConfig_Base extends GridFieldConfig {
/**
*
* @param int $itemsPerPage - How many items per page should show up
*/
public function __construct($itemsPerPage=null) {
@ -153,6 +162,9 @@ class GridFieldConfig_Base extends GridFieldConfig {
/**
* Allows viewing readonly details of individual records.
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldConfig_RecordViewer extends GridFieldConfig_Base {
@ -166,7 +178,8 @@ class GridFieldConfig_RecordViewer extends GridFieldConfig_Base {
}
/**
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldConfig_RecordEditor extends GridFieldConfig {
/**
@ -195,22 +208,28 @@ class GridFieldConfig_RecordEditor extends GridFieldConfig {
/**
* Similar to {@link GridFieldConfig_RecordEditor}, but adds features
* to work on has-many or many-many relationships.
* Allows to search for existing records to add to the relationship,
* detach listed records from the relationship (rather than removing them from the database),
* and automatically add newly created records to it.
* Similar to {@link GridFieldConfig_RecordEditor}, but adds features to work
* on has-many or many-many relationships.
*
* Allows to search for existing records to add to the relationship, detach
* listed records from the relationship (rather than removing them from the
* database), and automatically add newly created records to it.
*
* To further configure the field, use {@link getComponentByType()}, for
* example to change the field to search.
*
* To further configure the field, use {@link getComponentByType()},
* for example to change the field to search.
* <code>
* GridFieldConfig_RelationEditor::create()
* ->getComponentByType('GridFieldAddExistingAutocompleter')->setSearchFields('MyField');
* ->getComponentByType('GridFieldAddExistingAutocompleter')
* ->setSearchFields('MyField');
* </code>
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldConfig_RelationEditor extends GridFieldConfig {
/**
*
* @param int $itemsPerPage - How many items per page should show up
*/
public function __construct($itemsPerPage=null) {

View File

@ -1,17 +1,20 @@
<?php
/**
*
* @see GridField
*
* @package framework
* @subpackage fields-relational
* @subpackage fields-gridfield
*/
class GridFieldDataColumns implements GridField_ColumnProvider {
/** @var array */
/**
* @var array
*/
public $fieldCasting = array();
/** @var array */
/**
* @var array
*/
public $fieldFormatting = array();
/**

View File

@ -1,24 +1,31 @@
<?php
/**
* This class is a {@link GridField} component that adds a delete action for objects.
* This class is a {@link GridField} component that adds a delete action for
* objects.
*
* This component also supports unlinking a relation instead of deleting the
* object.
*
* This component also supports unlinking a relation instead of deleting the object.
* Use the {@link $removeRelation} property set in the constructor.
*
* <code>
* $action = new GridFieldDeleteAction(); // delete objects permanently
* $action = new GridFieldDeleteAction(true); // removes the relation to object, instead of deleting
*
* // removes the relation to object instead of deleting
* $action = new GridFieldDeleteAction(true);
* </code>
*
* @package framework
* @subpackage gridfield
* @subpackage fields-gridfield
*/
class GridFieldDeleteAction implements GridField_ColumnProvider, GridField_ActionProvider {
/**
* If this is set to true, this actionprovider will remove the object from the list, instead of
* deleting. In the case of a has one, has many or many many list it will uncouple the item from
* the list.
* If this is set to true, this {@link GridField_ActionProvider} will
* remove the object from the list, instead of deleting.
*
* In the case of a has one, has many or many many list it will uncouple
* the item from the list.
*
* @var boolean
*/

View File

@ -2,13 +2,19 @@
/**
* Provides view and edit forms at GridField-specific URLs.
*
* These can be placed into pop-ups by an appropriate front-end.
* Usually added to a grid field alongside of {@link GridFieldEditButton}
* which takes care of linking the individual rows to their edit view.
*
* Usually added to a {@link GridField} alongside of a
* {@link GridFieldEditButton} which takes care of linking the
* individual rows to their edit view.
*
* The URLs provided will be off the following form:
* - <FormURL>/field/<GridFieldName>/item/<RecordID>
* - <FormURL>/field/<GridFieldName>/item/<RecordID>/edit
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldDetailForm implements GridField_URLHandler {
@ -183,6 +189,10 @@ class GridFieldDetailForm implements GridField_URLHandler {
}
}
/**
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldDetailForm_ItemRequest extends RequestHandler {
/**

View File

@ -1,10 +1,17 @@
<?php
/**
* Provides the entry point to editing a single record presented by the grid.
* Doesn't show an edit view on its own or modifies the record, but rather relies on routing conventions
* established in {@link getColumnContent()}. The default routing applies to
* the {@link GridFieldDetailForm} component, which has to be added separately
* to the grid field configuration.
* Provides the entry point to editing a single record presented by the
* {@link GridField}.
*
* Doesn't show an edit view on its own or modifies the record, but rather
* relies on routing conventions established in {@link getColumnContent()}.
*
* The default routing applies to the {@link GridFieldDetailForm} component,
* which has to be added separately to the {@link GridField} configuration.
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldEditButton implements GridField_ColumnProvider {
@ -55,7 +62,7 @@ class GridFieldEditButton implements GridField_ColumnProvider {
}
/**
* Which GridField actions are this component handling
* Which GridField actions are this component handling.
*
* @param GridField $gridField
* @return array
@ -65,10 +72,10 @@ class GridFieldEditButton implements GridField_ColumnProvider {
}
/**
*
* @param GridField $gridField
* @param DataObject $record
* @param string $columnName
*
* @return string - the HTML for the column
*/
public function getColumnContent($gridField, $record, $columnName) {
@ -83,12 +90,13 @@ class GridFieldEditButton implements GridField_ColumnProvider {
}
/**
* Handle the actions and apply any changes to the GridField
* Handle the actions and apply any changes to the GridField.
*
* @param GridField $gridField
* @param string $actionName
* @param mixed $arguments
* @param array $data - form data
*
* @return void
*/
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {

View File

@ -1,12 +1,12 @@
<?php
/**
* @package framework
* @subpackage gridfield
*/
/**
* Adds an "Export list" button to the bottom of a GridField.
* Adds an "Export list" button to the bottom of a {@link GridField}.
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionProvider, GridField_URLHandler {
/**
@ -119,7 +119,7 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP
$fileData .= "\n";
}
$items = $gridField->getList();
$items = $gridField->getManipulatedList();
// @todo should GridFieldComponents change behaviour based on whether others are available in the config?
foreach($gridField->getConfig()->getComponents() as $component){

View File

@ -1,11 +1,12 @@
<?php
/**
* GridFieldFilterHeader alters the gridfield with some filtering fields in the header of each column
* GridFieldFilterHeader alters the {@link GridField} with some filtering
* fields in the header of each column.
*
* @see GridField
*
* @package framework
* @subpackage fields-relational
* @subpackage fields-gridfield
*/
class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {

View File

@ -1,13 +1,19 @@
<?php
/**
* Adding this class to a {@link GridFieldConfig} of a {@link GridField} adds a footer bar to that field.
* The footer looks just like the {@link GridFieldPaginator} control, except without the pagination controls.
* It only display the "Viewing 1-8 of 8" status text and (optionally) a configurable status message.
* Adding this class to a {@link GridFieldConfig} of a {@link GridField} adds
* a footer bar to that field.
*
* The purpose of this class is to have a footer that can round off GridField without having to use pagination.
* The footer looks just like the {@link GridFieldPaginator} control, except
* without the pagination controls.
*
* It only display the "Viewing 1-8 of 8" status text and (optionally) a
* configurable status message.
*
* The purpose of this class is to have a footer that can round off
* {@link GridField} without having to use pagination.
*
* @package framework
* @subpackage gridfield
* @subpackage fields-gridfield
*/
class GridFieldFooter implements GridField_HTMLProvider {
@ -21,7 +27,9 @@ class GridFieldFooter implements GridField_HTMLProvider {
* @param string $message - a message to display in the footer
*/
public function __construct($message = null) {
if($message) $this->message = $message;
if($message) {
$this->message = $message;
}
}
@ -36,7 +44,12 @@ class GridFieldFooter implements GridField_HTMLProvider {
));
return array(
'footer' => $forTemplate->renderWith('GridFieldFooter', array('Colspan'=>count($gridField->getColumns()))),
'footer' => $forTemplate->renderWith(
'GridFieldFooter',
array(
'Colspan' => count($gridField->getColumns())
)
)
);
}
}

View File

@ -1,10 +1,13 @@
<?php
/**
* Adds a "level up" link to a GridField table, which is useful
* when viewing hierarchical data. Requires the managed record
* to have a "getParent()" method or has_one relationship called "Parent".
* Adds a "level up" link to a GridField table, which is useful when viewing
* hierarchical data. Requires the managed record to have a "getParent()"
* method or has_one relationship called "Parent".
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldLevelup extends Object implements GridField_HTMLProvider{
class GridFieldLevelup extends Object implements GridField_HTMLProvider {
/**
* @var integer - the record id of the level up to

View File

@ -4,10 +4,10 @@
* GridFieldPage displays a simple current page count summary.
* E.g. "View 1 - 15 of 32"
*
* Depends on GridFieldPaginator being added to the same gridfield
* Depends on {@link GridFieldPaginator} being added to the {@link GridField}.
*
* @package framework
* @subpackage fields-relational
* @subpackage fields-gridfield
*/
class GridFieldPageCount implements GridField_HTMLProvider {
/**

View File

@ -1,10 +1,10 @@
<?php
/**
* GridFieldPaginator paginates the gridfields list and adds controlls to the
* bottom of the gridfield.
* GridFieldPaginator paginates the {@link GridField} list and adds controls
* to the bottom of the {@link GridField}.
*
* @package framework
* @subpackage fields-relational
* @subpackage fields-gridfield
*/
class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {

View File

@ -1,17 +1,18 @@
<?php
/**
* @package framework
* @subpackage gridfield
*/
/**
* Adds an "Print" button to the bottom or top of a GridField.
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionProvider, GridField_URLHandler {
/**
* @var array Map of a property name on the printed objects, with values being the column title in the CSV file.
* Note that titles are only used when {@link $csvHasHeader} is set to TRUE.
* @var array Map of a property name on the printed objects, with values
* being the column title in the CSV file.
*
* Note that titles are only used when {@link $csvHasHeader} is set to TRUE
*/
protected $printColumns;
@ -21,7 +22,9 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
protected $printHasHeader = true;
/**
* Fragment to write the button to
* Fragment to write the button to.
*
* @var string
*/
protected $targetFragment;
@ -36,6 +39,10 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
/**
* Place the print button in a <p> tag below the field
*
* @param GridField
*
* @return array
*/
public function getHTMLFragments($gridField) {
$button = new GridField_FormAction(
@ -45,21 +52,34 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
'print',
null
);
$button->setAttribute('data-icon', 'grid_print');
$button->addExtraClass('gridfield-button-print');
//$button->addExtraClass('no-ajax');
return array(
$this->targetFragment => '<p class="grid-print-button">' . $button->Field() . '</p>',
);
}
/**
* print is an action button
* Print is an action button.
*
* @param GridField
*
* @return array
*/
public function getActions($gridField) {
return array('print');
}
/**
* Handle the print action.
*
* @param GridField
* @param string
* @param array
* @param array
*/
public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
if($actionName == 'print') {
return $this->handlePrint($gridField);
@ -67,7 +87,10 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
}
/**
* it is also a URL
* Print is accessible via the url
*
* @param GridField
* @return array
*/
public function getURLHandlers($gridField) {
return array(
@ -82,15 +105,20 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
set_time_limit(60);
Requirements::clear();
Requirements::css(FRAMEWORK_DIR . '/css/GridField_print.css');
if($data = $this->generatePrintData($gridField)){
return $data->renderWith("GridField_print");
}
}
/**
* Export core.
*/
public function generatePrintData($gridField) {
* Return the columns to print
*
* @param GridField
*
* @return array
*/
protected function getPrintColumnsForGridField(GridField $gridField) {
if($this->printColumns) {
$printColumns = $this->printColumns;
} else if($dataCols = $gridField->getConfig()->getComponentByType('GridFieldDataColumns')) {
@ -99,73 +127,92 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
$printColumns = singleton($gridField->getModelClass())->summaryFields();
}
$header = null;
if($this->printHasHeader){
$header = new ArrayList();
foreach($printColumns as $field => $label){
$header->push(
new ArrayData(array(
"CellString" => $label,
))
);
}
}
return $printColumns;
}
$items = $gridField->getList();
foreach($gridField->getConfig()->getComponents() as $component){
if($component instanceof GridFieldFilterHeader || $component instanceof GridFieldSortableHeader) {
$items = $component->getManipulatedData($gridField, $items);
}
}
$itemRows = new ArrayList();
foreach($items as $item) {
$itemRow = new ArrayList();
foreach($printColumns as $field => $label) {
$value = $gridField->getDataFieldValue($item, $field);
$itemRow->push(
new ArrayData(array(
"CellString" => $value,
))
);
}
$itemRows->push(new ArrayData(
array(
"ItemRow" => $itemRow
)
));
$item->destroy();
}
//get title for the print view
/**
* Return the title of the printed page
*
* @param GridField
*
* @return array
*/
public function getTitle(GridField $gridField) {
$form = $gridField->getForm();
$currentController = Controller::curr();
$title = '';
if(method_exists($currentController, 'Title')) {
$title = $currentController->Title();
}else{
if($currentController->Title){
} else {
if ($currentController->Title) {
$title = $currentController->Title;
}else{
} else {
if($form->Name()){
$title = $form->Name();
}
}
}
if($fieldTitle = $gridField->Title()){
if($title) $title .= " - ";
if($fieldTitle = $gridField->Title()) {
if($title) {
$title .= " - ";
}
$title .= $fieldTitle;
}
$ret = new ArrayData(
array(
"Title" => $title,
"Header" => $header,
"ItemRows" => $itemRows,
"Datetime" => SS_Datetime::now(),
"Member" => Member::currentUser(),
)
);
return $title;
}
/**
* Export core.
*
* @param GridField
*/
public function generatePrintData(GridField $gridField) {
$printColumns = $this->getPrintColumnsForGridField($gridField);
$header = null;
if($this->printHasHeader) {
$header = new ArrayList();
foreach($printColumns as $field => $label){
$header->push(new ArrayData(array(
"CellString" => $label,
)));
}
}
$items = $gridField->getManipulatedList();
$itemRows = new ArrayList();
foreach($items as $item) {
$itemRow = new ArrayList();
foreach($printColumns as $field => $label) {
$value = $gridField->getDataFieldValue($item, $field);
$itemRow->push(new ArrayData(array(
"CellString" => $value,
)));
}
$itemRows->push(new ArrayData(array(
"ItemRow" => $itemRow
)));
$item->destroy();
}
$ret = new ArrayData(array(
"Title" => $this->getTitle($gridField),
"Header" => $header,
"ItemRows" => $itemRows,
"Datetime" => SS_Datetime::now(),
"Member" => Member::currentUser(),
));
return $ret;
}
@ -182,6 +229,7 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
*/
public function setPrintColumns($cols) {
$this->printColumns = $cols;
return $this;
}
@ -197,6 +245,7 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
*/
public function setPrintHasHeader($bool) {
$this->printHasHeader = $bool;
return $this;
}
}

View File

@ -1,11 +1,13 @@
<?php
/**
* GridFieldSortableHeader adds column headers to a gridfield that can also sort the columns
* GridFieldSortableHeader adds column headers to a {@link GridField} that can
* also sort the columns.
*
* @see GridField
*
* @package framework
* @subpackage fields-relational
* @subpackage fields-gridfield
*/
class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider {
@ -14,7 +16,9 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
*/
protected $throwExceptionOnBadDataType = true;
/** @var array */
/**
* @var array
*/
public $fieldSorting = array();
/**
@ -83,10 +87,12 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
$state = $gridField->State->GridFieldSortableHeader;
$columns = $gridField->getColumns();
$currentColumn = 0;
foreach($columns as $columnField) {
$currentColumn++;
$metadata = $gridField->getColumnMetadata($columnField);
$title = $metadata['title'];
if(isset($this->fieldSorting[$columnField]) && $this->fieldSorting[$columnField]) {
$columnField = $this->fieldSorting[$columnField];
}

View File

@ -1,12 +1,16 @@
<?php
/**
* Adding this class to a {@link GridFieldConfig} of a {@link GridField} adds a header title to that field.
* Adding this class to a {@link GridFieldConfig} of a {@link GridField} adds
* a header title to that field.
*
* The header serves to display the name of the data the GridField is showing.
*
* @package framework
* @subpackage gridfield
* @subpackage fields-gridfield
*/
class GridFieldToolbarHeader implements GridField_HTMLProvider {
public function getHTMLFragments( $gridField) {
return array(
'header' => $gridField->renderWith('GridFieldToolbarHeader')

View File

@ -1,9 +1,11 @@
<?php
/**
* A button that allows a user to view readonly details of a record. This is
* disabled by default and intended for use in readonly grid fields.
* disabled by default and intended for use in readonly {@link GridField}
* instances.
*
* @package framework
* @subpackage fields-gridfield
*/
class GridFieldViewButton implements GridField_ColumnProvider {
@ -31,5 +33,4 @@ class GridFieldViewButton implements GridField_ColumnProvider {
public function getColumnMetadata($gridField, $col) {
return array('title' => null);
}
}

View File

@ -1,33 +1,26 @@
<?php
/**
* This class is a snapshot of the current status of a gridfield.
* This class is a snapshot of the current status of a {@link GridField}.
*
* It's main use is to be inserted into a Form as a HiddenField
* It's designed to be inserted into a Form as a HiddenField and passed through
* to actions such as the {@link GridField_FormAction}.
*
* @see GridField
*
* @package framework
* @subpackage fields-relational
* @subpackage fields-gridfield
*/
class GridState extends HiddenField {
/** @var GridField */
/**
* @var GridField
*/
protected $grid;
protected $gridStateData = null;
/**
*
* @param type $d
* @return type
* @var GridState_Data
*/
public static function array_to_object($d) {
if(is_array($d)) {
return (object) array_map(array('GridState', 'array_to_object'), $d);
} else {
return $d;
}
}
protected $data = null;
/**
*
@ -43,40 +36,63 @@ class GridState extends HiddenField {
}
/**
*
* @param type $value
* @param mixed $d
* @return object
*/
public function setValue($value) {
if (is_string($value)) {
$this->gridStateData = new GridState_Data(json_decode($value, true));
public static function array_to_object($d) {
if(is_array($d)) {
return (object) array_map(array('GridState', 'array_to_object'), $d);
}
parent::setValue($value);
}
public function getData() {
if(!$this->gridStateData) $this->gridStateData = new GridState_Data;
return $this->gridStateData;
return $d;
}
/**
*
* @return type
* @param mixed $value
*/
public function setValue($value) {
if (is_string($value)) {
$this->data = new GridState_Data(json_decode($value, true));
}
parent::setValue($value);
}
/**
* @var GridState_Data
*/
public function getData() {
if(!$this->data) {
$this->data = new GridState_Data();
}
return $this->data;
}
/**
* @return DataList
*/
public function getList() {
return $this->grid->getList();
}
/** @return string */
/**
* Returns a json encoded string representation of this state.
*
* @return string
*/
public function Value() {
if(!$this->gridStateData) {
if(!$this->data) {
return json_encode(array());
}
return json_encode($this->gridStateData->toArray());
return json_encode($this->data->toArray());
}
/**
* Returns a json encoded string representation of this state.
*
* @return type
* @return string
*/
public function dataValue() {
return $this->Value();
@ -100,9 +116,19 @@ class GridState extends HiddenField {
}
/**
* Simple set of data, similar to stdClass, but without the notice-level errors
* Simple set of data, similar to stdClass, but without the notice-level
* errors.
*
* @see GridState
*
* @package framework
* @subpackage fields-gridfield
*/
class GridState_Data {
/**
* @var array
*/
protected $data;
public function __construct($data = array()) {
@ -110,42 +136,53 @@ class GridState_Data {
}
public function __get($name) {
if(!isset($this->data[$name])) $this->data[$name] = new GridState_Data;
if(is_array($this->data[$name])) $this->data[$name] = new GridState_Data($this->data[$name]);
if(!isset($this->data[$name])) {
$this->data[$name] = new GridState_Data();
} else if(is_array($this->data[$name])) {
$this->data[$name] = new GridState_Data($this->data[$name]);
}
return $this->data[$name];
}
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __isset($name) {
return isset($this->data[$name]);
}
public function __toString() {
if(!$this->data) return "";
else return json_encode($this->toArray());
if(!$this->data) {
return "";
}
return json_encode($this->toArray());
}
public function toArray() {
$output = array();
foreach($this->data as $k => $v) {
$output[$k] = (is_object($v) && method_exists($v, 'toArray')) ? $v->toArray() : $v;
}
return $output;
}
}
/**
* @see GridState
*
* @package framework
* @subpackage fields-gridfield
*/
class GridState_Component implements GridField_HTMLProvider {
public function getHTMLFragments($gridField) {
$forTemplate = new ArrayData(array());
$forTemplate->Fields = new ArrayList;
return array(
'before' => $gridField->getState(false)->Field()
);
}
}

View File

@ -2473,19 +2473,32 @@ class i18n extends Object implements TemplateGlobalProvider {
if($cache) $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
}
// Sort modules by inclusion priority, then alphabetically
// TODO Should be handled by priority flags within modules
$prios = array('sapphire' => 10, 'framework' => 10, 'admin' => 11, 'cms' => 12, project() => 90);
// Get list of module => path pairs, and then just the names
$modules = SS_ClassLoader::instance()->getManifest()->getModules();
ksort($modules);
uksort(
$modules,
function($a, $b) use(&$prios) {
$prioA = (isset($prios[$a])) ? $prios[$a] : 50;
$prioB = (isset($prios[$b])) ? $prios[$b] : 50;
return ($prioA > $prioB);
}
);
$moduleNames = array_keys($modules);
// Remove the "project" module from the list - we'll add it back specially later if needed
global $project;
if (($idx = array_search($project, $moduleNames)) !== false) array_splice($moduleNames, $idx, 1);
// Get the order from the config syste,
$order = Config::inst()->get('i18n', 'module_priority');
// Find all modules that don't have their order specified by the config system
$unspecified = array_diff($moduleNames, $order);
// If the placeholder "other_modules" exists in the order array, replace it by the unspecified modules
if (($idx = array_search('other_modules', $order)) !== false) array_splice($order, $idx, 1, $unspecified);
// Otherwise just jam them on the front
else array_splice($order, 0, 0, $unspecified);
// Put the project module back in at the begining if it wasn't specified by the config system
if (!in_array($project, $order)) array_unshift($order, $project);
$sortedModules = array();
foreach ($order as $module) {
if (isset($modules[$module])) $sortedModules[$module] = $modules[$module];
}
// Loop in reverse order, meaning the translator with the highest priority goes first
$translators = array_reverse(self::get_translators(), true);
@ -2494,7 +2507,7 @@ class i18n extends Object implements TemplateGlobalProvider {
$adapter = $translator->getAdapter();
// Load translations from modules
foreach($modules as $module) {
foreach($sortedModules as $module) {
$filename = $adapter->getFilenameForLocale($locale);
$filepath = "{$module}/lang/" . $filename;

View File

@ -252,7 +252,7 @@ class i18nTextCollector extends Object {
$inConcat = true;
} elseif($inTransFn && $token == ',') {
$inConcat = false;
} elseif($inTransFn && ($token == ')' || $finalTokenDueToArray)) {
} elseif($inTransFn && ($token == ')' || $finalTokenDueToArray || $token == '[')) {
// finalize definition
$inTransFn = false;
$inConcat = false;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 B

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 372 B

View File

@ -1,5 +1,5 @@
(function ($) {
$('.confirmedpassword .showOnClick a').live('click', function () {
$(document).on('click', '.confirmedpassword .showOnClick a', function () {
var $container = $('.showOnClickContainer', $(this).parent());
$container.toggle('fast', function() {

View File

@ -157,16 +157,27 @@
},
onclick: function(e){
var btn = this.closest(':button'), grid = this.getGridField(),
form = this.closest('form'), data = form.find(':input').serialize();
form = this.closest('form'), data = form.find(':input.gridstate').serialize();;
// Add current button
data += '&' + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val());
data += "&" + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val());
// Include any GET parameters from the current URL, as the view state might depend on it.
// For example, a list prefiltered through external search criteria might be passed to GridField.
if(window.location.search) data = window.location.search.replace(/^\?/, '') + '&' + data;
// Include any GET parameters from the current URL, as the view
// state might depend on it.
// For example, a list prefiltered through external search criteria
// might be passed to GridField.
if(window.location.search) {
data = window.location.search.replace(/^\?/, '') + '&' + data;
}
// decide whether we should use ? or & to connect the URL
var connector = grid.data('url').indexOf('?') == -1 ? '?' : '&';
var url = $.path.makeUrlAbsolute(
grid.data('url') + connector + data,
$('base').attr('href')
);
var url = $.path.makeUrlAbsolute(grid.data('url') + '?' + data, $('base').attr('href'));
var newWindow = window.open(url);
return false;
@ -188,22 +199,33 @@
/**
* Prevents actions from causing an ajax reload of the field.
* Useful e.g. for actions which rely on HTTP response headers being interpreted nativel
* by the browser, like file download triggers.
*
* Useful e.g. for actions which rely on HTTP response headers being
* interpreted natively by the browser, like file download triggers.
*/
$('.ss-gridfield .action.no-ajax').entwine({
onclick: function(e){
var self = this, btn = this.closest(':button'), grid = this.getGridField(),
form = this.closest('form'), data = form.find(':input').serialize();
form = this.closest('form'), data = form.find(':input.gridstate').serialize();
// Add current button
data += '&' + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val());
data += "&" + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val());
// Include any GET parameters from the current URL, as the view state might depend on it.
// For example, a list prefiltered through external search criteria might be passed to GridField.
if(window.location.search) data = window.location.search.replace(/^\?/, '') + '&' + data;
// Include any GET parameters from the current URL, as the view
// state might depend on it. For example, a list pre-filtered
// through external search criteria might be passed to GridField.
if(window.location.search) {
data = window.location.search.replace(/^\?/, '') + '&' + data;
}
// decide whether we should use ? or & to connect the URL
var connector = grid.data('url').indexOf('?') == -1 ? '?' : '&';
window.location.href = $.path.makeUrlAbsolute(
grid.data('url') + connector + data,
$('base').attr('href')
);
window.location.href = $.path.makeUrlAbsolute(grid.data('url') + '?' + data, $('base').attr('href'));
return false;
}
});
@ -340,7 +362,5 @@
}
}
});
});
}(jQuery));

View File

@ -716,7 +716,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
email: RegExp.$1,
Description: title
};
} else if(href.match(/^(assets\/.*)$/) || href.match(/^\[file_link\s*(?:%20)?id=([0-9]+)\]?(#.*)?$/)) {
} else if(href.match(/^(assets\/.*)$/) || href.match(/^\[file_link\s*(?:\s*|%20|,)?id=([0-9]+)\]?(#.*)?$/)) {
return {
LinkType: 'file',
file: RegExp.$1,
@ -1126,7 +1126,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
updateFromNode: function(node) {
this.find(':input[name=AltText]').val(node.attr('alt'));
this.find(':input[name=Title]').val(node.attr('title'));
this.find(':input[name=CSSClass]').val(node.attr('class')).attr('disabled', 'disabled');
this.find(':input[name=CSSClass]').val(node.attr('class'));
this.find(':input[name=Width]').val(node.width());
this.find(':input[name=Height]').val(node.height());
this.find(':input[name=CaptionText]').val(node.siblings('.caption:first').text());

134
lang/lc_XX.yml Normal file
View File

@ -0,0 +1,134 @@
lc_XX:
BasicAuth:
ENTERINFO: "PLZ ENTR UR USERNAYM N A PASWORD."
ERRORNOTADMIN: "DAT USR IZ NOT AN ADMINISTRATOR."
ERRORNOTREC: "DAT USERNAYM / PASWORD ISNT RECOGNISD SUZ 2 HEAR"
ChangePasswordEmail.ss:
CHANGEPASSWORDTEXT1: "U CHANGD UR PASWORD 4"
CHANGEPASSWORDTEXT2: "U CAN NAO USE TEH FOLLOWIN CREDENSHALS 2 LOG IN:"
HELLO: "OHI! HOWRU2DAI?"
ComplexTableField.ss:
ADDITEM: "ADD"
SORTASC: "SORT ASCENDIN"
SORTDESC: "SORT DESCENDIN"
ComplexTableField_popup.ss:
NEXT: "NEXT (GOGOGO)"
PREVIOUS: "PREVIOUZ"
Date:
DAY: "DAI"
DAYS: "DAIZ"
HOUR: "HOUR"
HOURS: "HOURZ"
MIN: "MIN"
MINS: "MINZ"
MONTH: "MONTH"
MONTHS: "MONTHX"
SEC: "SEC"
SECS: "SECZ"
YEAR: "YEAR"
YEARS: "YEARZ"
DateField:
DropdownField:
CHOOSE: "(pik)"
EmailField:
VALIDATION: "PLZ ENTR AN EMAIL ADDRES."
ForgotPasswordEmail.ss:
HELLO: "OHI! HOWRU2DAI?"
Form:
FIELDISREQUIRED: "%s IS REQUIRED"
VALIDATIONCREDITNUMBER: "PLZ ENSURE U HAS ENTERD TEH %s CREDIT CARD NUMBR RITE CUZ WE NEED UR MONEY RITE NOW."
VALIDATIONFAILED: "VALIDASHUN FAILD1!!1!!!!!!1 WAT NOW?"
VALIDATIONNOTUNIQUE: "TEH VALUE ENTERD R NOT UNIQUE"
VALIDATIONPASSWORDSDONTMATCH: "SRY BOSS, PASWORDZ DOAN MATCH"
VALIDATIONPASSWORDSNOTEMPTY: "PASWORDZ CANT BE EMPTY DOOD"
VALIDATIONSTRONGPASSWORD: "PASWORDZ MUST HAS AT LEAST WAN DIGIT AN WAN ALFANUMERIC CHARACTR."
VALIDCURRENCY: "PLZ ENTR VALID CURRENCY. CUD U?"
HtmlEditorField:
BUTTONINSERTLINK: "INSERT LINK"
BUTTONREMOVELINK: "REMOOV LINK"
CSSCLASS: "ALIGNMENT / STYLE"
CSSCLASSCENTER: "SENTERD, ON ITZ OWN."
CSSCLASSLEFT: "ON TEH LEFT, WIF TEXT WRAPPIN AROUND."
CSSCLASSRIGHT: "ON TEH RITE, WIF TEXT WRAPPIN AROUND."
EMAIL: "EMAIL ADDRESZ"
FILE: "FIEL"
FOLDER: "TEH FOLDER"
IMAGE: "INSERT PIKSHUR"
IMAGEDIMENSIONS: "DIMENSHUNZ"
IMAGEHEIGHTPX: "HEIGHT"
IMAGEWIDTHPX: "WIDTH"
LINK: "INSERT/EDIT LINK 4 HIGHLIGHTD TEXT"
LINKDESCR: "LINK DESCRIPSHUN"
LINKEMAIL: "EMAIL ADDRESZ"
LINKEXTERNAL: "ANODER WEBSIET"
LINKFILE: "DOWNLOAD FIEL"
LINKINTERNAL: "PAEG ON DIS SIET"
LINKOPENNEWWIN: "OPEN LINK IN NEW WINDOW?"
LINKTO: "LINK 2"
PAGE: "PAEG"
URL: "URL N STUFF"
Image_iframe.ss:
TITLE: "PIKSHUR UPLOADIN IFRAME"
Member:
ADDRESS: "ADDRESZ"
BUTTONCHANGEPASSWORD: "CHANGE PASWORD"
BUTTONLOGIN: "LOG IN"
BUTTONLOGINOTHER: "LOG IN AS SOMEONE ELSE"
BUTTONLOSTPASSWORD: "I HAS LOST MAH PASWORD"
CONFIRMNEWPASSWORD: "CUD U CONFIRM NEW PASWORD PLZ"
CONFIRMPASSWORD: "CONFIRM PASWORD"
CONTACTINFO: "SUM CONTACT STUFF"
EMAIL: "EMAIL"
EMAILSIGNUPINTRO1: "TEH LOLCAT SEZ OMG BIGTHX 2 U N U R A NEW MEMBR 4 NOW N UR DETAILZ R LISTD BELOW FOR THAT IF U WANTZ THEM IN SUM TIME"
EMAILSIGNUPINTRO2: "U CAN LOGIN TO TEH WEBSITE USINS TEH CREDENTIALZ LISTED BELOW"
EMAILSIGNUPSUBJECT: "TEH LOLCAT TANX U 4 SIGNINS UP"
ERRORNEWPASSWORD: "UR HAS ENTERD UR NEW PASWORD DIFFERENTLY, PLZ TRY AGAIN, K?"
ERRORPASSWORDNOTMATCH: "UR CURRENT PASWORD DOEZ NOT MATCH, PLZ TRY AGAIN, K?"
ERRORWRONGCRED: "DAT IS NOT SEEMZ 2 BE TEH RITE E-MAIL ADDRES OR PASWORD. PLZ TRY AGAIN, K?"
FIRSTNAME: "FURST NAYM"
GREETING: "WELKUM"
INTERFACELANG: "INTRFACELANGUEEG"
LOGGEDINAS: "URE LOGGD IN AS %s."
MOBILE: "MOBAIL"
NAME: "NAYM"
NEWPASSWORD: "NEW PASWORD"
PASSWORD: "PASWORD"
PHONE: "FONE"
REMEMBERME: "REMEMBR ME NEXT TIME, K?"
SUBJECTPASSWORDCHANGED: "UR PASWORD HAS BEEN CHANGD"
SUBJECTPASSWORDRESET: "YORE PASWORD RESET LINK"
SURNAME: "SHURNAYM"
VALIDATIONMEMBEREXISTS: "THAR ALREADY EXISTZ MEMBR WIF DIS EMAILZ"
WELCOMEBACK: "WELCUM BACK, %s"
YOUROLDPASSWORD: "UR OLD PASWORD"
MemberAuthenticator:
TITLE: "E-MAIL N PASWORD"
NumericField:
VALIDATION: "'%s' IZ NOT NUMBR, ONLY NUMBERS CAN BE ACCEPTD 4 DIS FIELD"
PhoneNumberField:
VALIDATION: "PLZ ENTR VALID FONE NUMBR WE WUNT DISTURB 2 MUCH JUS A BIT"
Security:
ALREADYLOGGEDIN: "U SHALL NOT PASS!! IF U HAS ANOTHR AKOWNT DAT CAN ACCES DAT PAEG, U CAN LOG IN BELOW."
BUTTONSEND: "SEND ME TEH PASWORD RESETZ LINK"
CHANGEPASSWORDBELOW: "U CAN CHANGE UR PASWORD BELOW"
CHANGEPASSWORDHEADER: "CHANGE UR PASWORD"
ENTERNEWPASSWORD: "PLEEZ ENTR NEW PASSWORD. KTHX."
ERRORPASSWORDPERMISSION: "U MUST BE LOGGD IN IN ORDR 2 CHANGE UR PASWORD!"
LOGGEDOUT: "U HAS BEEN LOGGD OUT. IF U WUD LIEK 2 LOG IN AGAIN, ENTR UR CREDENTIALS BELOW."
NOTEPAGESECURED: "TEH PAGE IZ SECURD. ENTR UR CREDENSHALS BELOW AN WE WILL SEND U RITE ALONG."
NOTERESETPASSWORD: "ENTR UR E-MAIL ADDRES AN WE R GONA SEND U LINK WIF WHICH U CAN RESET UR PASWORD"
PASSWORDSENTHEADER: "I R SENT TEH PASWORD RESET LINK TO '%s'"
PASSWORDSENTTEXT: "THXALOT! I R SENT TEH PASWORD RESET LINK TO '%s'"
SimpleImageField:
NOUPLOAD: "DEREZ NO PIKSHUR UPLOADED"
SiteTree:
TABMAIN: "MAIN"
TableField:
ISREQUIRED: "IN %s '%s' IS KINDA REQUIRED"
TableField.ss:
ToggleCompositeField.ss:
HIDE: "HAYD"
SHOW: "SHOW"
ToggleField:
LESS: "lesz"
MORE: "mur"

View File

@ -990,7 +990,10 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
*/
public function removeByID($itemID) {
$item = $this->byID($itemID);
if($item) return $item->delete();
if($item) {
return $item->delete();
}
}
/**

View File

@ -1,45 +1,73 @@
<?php
/**
* Representation of a DataModel - a collection of DataLists for each different data type.
* Representation of a DataModel - a collection of DataLists for each different
* data type.
*
* Usage:
*
* <code>
* $model = new DataModel;
* $mainMenu = $model->SiteTree->where('"ParentID" = 0 AND "ShowInMenus" = 1');
* </code>
*
* @package framework
* @subpackage model
*/
class DataModel {
/**
* @var DataModel
*/
protected static $inst;
/**
* @var array $customDataLists
*/
protected $customDataLists = array();
/**
* Get the global DataModel.
*
* @return DataModel
*/
public static function inst() {
if(!self::$inst) self::$inst = new self;
if(!self::$inst) {
self::$inst = new self;
}
return self::$inst;
}
/**
* Set the global DataModel, used when data is requested from static methods.
* Set the global DataModel, used when data is requested from static
* methods.
*
* @return DataModel
*/
public static function set_inst(DataModel $inst) {
self::$inst = $inst;
}
////////////////////////////////////////////////////////////////////////
protected $customDataLists = array();
/**
* @param string
*
* @return DataList
*/
public function __get($class) {
if(isset($this->customDataLists[$class])) {
return clone $this->customDataLists[$class];
} else {
$list = DataList::create($class);
$list->setDataModel($this);
return $list;
}
}
/**
* @param string
* @param DataList
*/
public function __set($class, $item) {
$item = clone $item;
$item->setDataModel($this);

View File

@ -749,6 +749,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $this->record;
}
/**
* Return all currently fetched database fields.
*
* This function is similar to toMap() but doesn't trigger the lazy-loading of all unfetched fields.
* Obviously, this makes it a lot faster.
*
* @return array The data as a map.
*/
public function getQueriedDatabaseFields() {
return $this->record;
}
/**
* Update a number of fields on this object, given a map of the desired changes.
*
@ -1123,7 +1135,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
// No changes made
if($this->changed) {
if($this->changed || $forceWrite) {
foreach($this->getClassAncestry() as $ancestor) {
if(self::has_own_table($ancestor))
$ancestry[] = $ancestor;
@ -1133,13 +1145,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
if(!$forceInsert) unset($this->changed['ID']);
$hasChanges = false;
foreach($this->changed as $fieldName => $changed) {
if($changed) {
$hasChanges = true;
break;
if (!$forceWrite) {
foreach ($this->changed as $fieldName => $changed) {
if ($changed) {
$hasChanges = true;
break;
}
}
}
if($hasChanges || $forceWrite || !$this->record['ID']) {
// New records have their insert into the base data table done first, so that they can pass the
@ -1420,7 +1433,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$joinField = $this->getRemoteJoinField($componentName, 'has_many');
$result = new HasManyList($componentClass, $joinField);
$result = HasManyList::create($componentClass, $joinField);
if($this->model) $result->setDataModel($this->model);
$result = $result->forForeignID($this->ID);
@ -1544,7 +1557,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $this->unsavedRelations[$componentName];
}
$result = Injector::inst()->create('ManyManyList', $componentClass, $table, $componentField, $parentField,
$result = ManyManyList::create($componentClass, $table, $componentField, $parentField,
$this->many_many_extraFields($componentName));
if($this->model) $result->setDataModel($this->model);
@ -1986,6 +1999,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $fs->getFieldList();
}
/**
* Allows user code to hook into DataObject::getCMSFields prior to updateCMSFields
* being called on extensions
*
* @param callable $callback The callback to execute
*/
protected function beforeUpdateCMSFields($callback) {
$this->beforeExtending('updateCMSFields', $callback);
}
/**
* Centerpiece of every data administration interface in Silverstripe,
* which returns a {@link FieldList} suitable for a {@link Form} object.
@ -2325,9 +2348,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
/**
* Returns true if the given field exists
* in a database column on any of the objects tables,
* or as a dynamic getter with get<fieldName>().
* Returns true if the given field exists in a database column on any of
* the objects tables and optionally look up a dynamic getter with
* get<fieldName>().
*
* @param string $field Name of the field
* @return boolean True if the given field exists
@ -2635,22 +2658,31 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
/**
* Traverses to a DBField referenced by relationships between data objects.
* The path to the related field is specified with dot separated syntax (eg: Parent.Child.Child.FieldName)
*
* @param $fieldPath string
* @return DBField
* The path to the related field is specified with dot separated syntax
* (eg: Parent.Child.Child.FieldName).
*
* @param string $fieldPath
*
* @return mixed DBField of the field on the object or a DataList instance.
*/
public function relObject($fieldPath) {
$object = null;
if(strpos($fieldPath, '.') !== false) {
$parts = explode('.', $fieldPath);
$fieldName = array_pop($parts);
// Traverse dot syntax
$component = $this;
foreach($parts as $relation) {
if($component instanceof SS_List) {
if(method_exists($component,$relation)) $component = $component->$relation();
else $component = $component->relation($relation);
if(method_exists($component,$relation)) {
$component = $component->$relation();
} else {
$component = $component->relation($relation);
}
} else {
$component = $component->$relation();
}
@ -2662,12 +2694,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$object = $this->dbObject($fieldPath);
}
if (!($object instanceof DBField) && !($object instanceof DataList)) {
// Todo: come up with a broader range of exception objects to describe differnet kinds of errors
// programatically
throw new Exception("Unable to traverse to related object field [$fieldPath] on [$this->class]");
}
return $object;
}
@ -3108,21 +3134,31 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
/**
* Get the default searchable fields for this object,
* as defined in the $searchable_fields list. If searchable
* fields are not defined on the data object, uses a default
* selection of summary fields.
* Get the default searchable fields for this object, as defined in the
* $searchable_fields list. If searchable fields are not defined on the
* data object, uses a default selection of summary fields.
*
* @return array
*/
public function searchableFields() {
// can have mixed format, need to make consistent in most verbose form
$fields = $this->stat('searchable_fields');
$labels = $this->fieldLabels();
// fallback to summary fields
if(!$fields) $fields = array_keys($this->summaryFields());
if(!$fields) {
$summaryFields = array_keys($this->summaryFields());
$fields = array();
// remove the custom getters as the search should not include.
if($summaryFields) {
foreach($summaryFields as $key => $name) {
if($this->hasDatabaseField($name) || $this->relObject($name)) {
$fields[] = $name;
}
}
}
}
// we need to make sure the format is unified before
// augmenting fields, so extensions can apply consistent checks

View File

@ -14,7 +14,7 @@
class DataQuery {
/**
* @var String
* @var string
*/
protected $dataClass;
@ -756,11 +756,16 @@ class DataQuery {
/**
* Represents a subgroup inside a WHERE clause in a {@link DataQuery}
*
* Stores the clauses for the subgroup inside a specific {@link SQLQuery} object.
* Stores the clauses for the subgroup inside a specific {@link SQLQuery}
* object.
*
* All non-where methods call their DataQuery versions, which uses the base
* query object.
*
* @package framework
*/
class DataQuery_SubGroup extends DataQuery {
protected $whereQuery;
public function __construct(DataQuery $base, $connective) {
@ -790,6 +795,7 @@ class DataQuery_SubGroup extends DataQuery {
if($filter) {
$this->whereQuery->addWhere($filter);
}
return $this;
}
@ -806,6 +812,7 @@ class DataQuery_SubGroup extends DataQuery {
if($filter) {
$this->whereQuery->addWhereAny($filter);
}
return $this;
}
@ -814,8 +821,14 @@ class DataQuery_SubGroup extends DataQuery {
// We always need to have something so we don't end up with something like '... AND () AND ...'
return '1=1';
}
$sql = DB::getConn()->sqlWhereToString($this->whereQuery->getWhere(), $this->whereQuery->getConnective());
$sql = DB::getConn()->sqlWhereToString(
$this->whereQuery->getWhere(),
$this->whereQuery->getConnective()
);
$sql = preg_replace('[^\s*WHERE\s*]', '', $sql);
return $sql;
}
}

View File

@ -210,16 +210,16 @@ abstract class SS_Database {
public function endSchemaUpdate() {
foreach($this->schemaUpdateTransaction as $tableName => $changes) {
switch($changes['command']) {
case 'create':
case 'create':
$this->createTable($tableName, $changes['newFields'], $changes['newIndexes'], $changes['options'],
@$changes['advancedOptions']);
break;
break;
case 'alter':
$this->alterTable($tableName, $changes['newFields'], $changes['newIndexes'],
case 'alter':
$this->alterTable($tableName, $changes['newFields'], $changes['newIndexes'],
$changes['alteredFields'], $changes['alteredIndexes'], $changes['alteredOptions'],
@$changes['advancedOptions']);
break;
break;
}
}
$this->schemaUpdateTransaction = null;
@ -835,8 +835,8 @@ abstract class SS_Database {
public function sqlLimitToString($limit) {
$clause = '';
// Pass limit as array or SQL string value
if(is_array($limit)) {
// Pass limit as array or SQL string value
if(is_array($limit)) {
if(!array_key_exists('limit', $limit)) {
throw new InvalidArgumentException('Database::sqlLimitToString(): Wrong format for $limit: '
. var_export($limit, true));
@ -846,11 +846,11 @@ abstract class SS_Database {
&& is_numeric($limit['limit'])) {
$combinedLimit = $limit['start'] ? "$limit[limit] OFFSET $limit[start]" : "$limit[limit]";
} elseif(isset($limit['limit']) && is_numeric($limit['limit'])) {
$combinedLimit = (int) $limit['limit'];
} else {
$combinedLimit = false;
}
} elseif(isset($limit['limit']) && is_numeric($limit['limit'])) {
$combinedLimit = (int)$limit['limit'];
} else {
$combinedLimit = false;
}
if(!empty($combinedLimit)) $clause .= ' LIMIT ' . $combinedLimit;
} else {
$clause .= ' LIMIT ' . $limit;
@ -868,9 +868,9 @@ abstract class SS_Database {
public function sqlQueryToString(SQLQuery $query) {
if($query->getDelete()) {
$text = 'DELETE ';
} else {
} else {
$text = $this->sqlSelectToString($query->getSelect(), $query->getDistinct());
}
}
if($query->getFrom()) $text .= $this->sqlFromToString($query->getFrom());
if($query->getWhere()) $text .= $this->sqlWhereToString($query->getWhere(), $query->getConnective());
@ -1007,9 +1007,9 @@ abstract class SS_Database {
*/
public function supportsLocks() {
return false;
}
}
/**
/**
* Returns if the lock is available.
* See {@link supportsLocks()} to check if locking is generally supported.
*
@ -1157,7 +1157,7 @@ abstract class SS_Query implements Iterator {
$result .= "<tr>";
foreach($record as $k => $v) {
$result .= "<th>" . Convert::raw2xml($k) . "</th> ";
}
}
$result .= "</tr> \n";
}
@ -1234,7 +1234,7 @@ abstract class SS_Query implements Iterator {
*/
public function valid() {
if(!$this->queryHasBegun) $this->next();
return $this->currentRecord !== false;
return $this->currentRecord !== false;
}
/**

View File

@ -1,9 +1,13 @@
<?php
/**
* Subclass of {@link DataList} representing a has_many relation
* Subclass of {@link DataList} representing a has_many relation.
*
* @package framework
* @subpackage model
*/
class HasManyList extends RelationList {
protected $foreignKey;
/**
@ -18,6 +22,7 @@ class HasManyList extends RelationList {
*/
public function __construct($dataClass, $foreignKey) {
parent::__construct($dataClass);
$this->foreignKey = $foreignKey;
}
@ -36,7 +41,9 @@ class HasManyList extends RelationList {
/**
* Adds the item to this relation.
*
* It does so by setting the relationFilters.
*
* @param $item The DataObject to be added, or its ID
*/
public function add($item) {
@ -66,11 +73,14 @@ class HasManyList extends RelationList {
/**
* Remove an item from this relation.
*
* Doesn't actually remove the item, it just clears the foreign key value.
* @param $itemID The ID of the item to be removed
*
* @param $itemID The ID of the item to be removed.
*/
public function removeByID($itemID) {
$item = $this->byID($itemID);
return $this->remove($item);
}

View File

@ -148,8 +148,12 @@ class Image extends File {
/**
* File names are filtered through {@link FileNameFilter}, see class documentation
* on how to influence this behaviour.
*
* @deprecated 3.2
*/
public function loadUploadedImage($tmpFile) {
Deprecation::notice('3.2', 'Use the Upload::loadIntoFile()');
if(!is_array($tmpFile)) {
user_error("Image::loadUploadedImage() Not passed an array. Most likely, the form hasn't got the right"
. "enctype", E_USER_ERROR);

View File

@ -1,7 +1,10 @@
<?php
/**
* Subclass of {@link DataList} representing a many_many relation
* Subclass of {@link DataList} representing a many_many relation.
*
* @package framework
* @subpackage model
*/
class ManyManyList extends RelationList {

View File

@ -2,9 +2,11 @@
/**
* A DataList that represents a relation.
*
* Adds the notion of a foreign ID that can be optionally set.
*
* @todo Is this additional class really necessary?
* @package framework
* @subpackage model
*/
abstract class RelationList extends DataList {
@ -13,8 +15,10 @@ abstract class RelationList extends DataList {
}
/**
* Returns a copy of this list with the ManyMany relationship linked to the given foreign ID.
* @param $id An ID or an array of IDs.
* Returns a copy of this list with the ManyMany relationship linked to
* the given foreign ID.
*
* @param int|array $id An ID or an array of IDs.
*/
public function forForeignID($id) {
// Turn a 1-element array into a simple value
@ -43,7 +47,9 @@ abstract class RelationList extends DataList {
}
/**
* Returns a where clause that filters the members of this relationship to just the related items
* Returns a where clause that filters the members of this relationship to
* just the related items.
*
* @param $id (optional) An ID or an array of IDs - if not provided, will use the current ids as per getForeignID
*/
abstract protected function foreignIDFilter($id = null);

View File

@ -29,9 +29,9 @@ class URLSegmentFilter extends Object {
private static $default_replacements = array(
'/&amp;/u' => '-and-',
'/&/u' => '-and-',
'/\s/u' => '-', // remove whitespace
'/\s|\+/u' => '-', // remove whitespace/plus
'/[_.]+/u' => '-', // underscores and dots to dashes
'/[^A-Za-z0-9+\-]+/u' => '', // remove non-ASCII chars, only allow alphanumeric and dashes
'/[^A-Za-z0-9\-]+/u' => '', // remove non-ASCII chars, only allow alphanumeric and dashes
'/[\-]{2,}/u' => '-', // remove duplicate dashes
'/^[\-_]/u' => '', // Remove all leading dashes or underscores
);
@ -69,8 +69,8 @@ class URLSegmentFilter extends Object {
$replacements = $this->getReplacements();
// Unset automated removal of non-ASCII characters, and don't try to transliterate
if($this->getAllowMultibyte() && isset($replacements['/[^A-Za-z0-9+\-]+/u'])) {
unset($replacements['/[^A-Za-z0-9+\-]+/u']);
if($this->getAllowMultibyte() && isset($replacements['/[^A-Za-z0-9\-]+/u'])) {
unset($replacements['/[^A-Za-z0-9\-]+/u']);
}
foreach($replacements as $regex => $replace) {

View File

@ -1,19 +1,23 @@
<?php
/**
* An ArrayList that represents an unsaved relation.
* An {@link ArrayList} that represents an unsaved relation.
*
* has_many and many_many relations cannot be saved until after the DataObject they're
* on has been written. This List pretends to be a RelationList and stores the related
* objects in memory.
* has_many and many_many relations cannot be saved until after the DataObject
* they're on has been written. This List pretends to be a RelationList and
* stores the related objects in memory.
*
* It can store both saved objects (as IDs) or unsaved objects (as instances of
* $dataClass). Unsaved objects are then written when the list is saved into an instance
* of RelationList.
* It can store both saved objects (as IDs) or unsaved objects (as instances
* of $dataClass). Unsaved objects are then written when the list is saved
* into an instance of {@link RelationList}.
*
* Most methods that alter the list of objects throw LogicExceptions.
*
* @package framework
* @subpackage model
*/
class UnsavedRelationList extends ArrayList {
/**
* The DataObject class name that this relation is on
*

View File

@ -1,11 +1,15 @@
<?php
/**
* The Versioned extension allows your DataObjects to have several versions, allowing
* you to rollback changes and view history. An example of this is the pages used in the CMS.
* The Versioned extension allows your DataObjects to have several versions,
* allowing you to rollback changes and view history. An example of this is
* the pages used in the CMS.
*
* @package framework
* @subpackage model
*/
class Versioned extends DataExtension {
/**
* An array of possible stages.
* @var array
@ -39,6 +43,11 @@ class Versioned extends DataExtension {
*/
protected static $cache_versionnumber;
/**
* @var string
*/
protected static $reading_mode = null;
/**
* @var Boolean Flag which is temporarily changed during the write() process
* to influence augmentWrite() behaviour. If set to TRUE, no new version will be created
@ -63,6 +72,20 @@ class Versioned extends DataExtension {
"PublisherID" => "Int"
);
/**
* @var array
*/
private static $db = array(
'Version' => 'Int'
);
/**
* Keep track of the archive tables that have been created.
*
* @var array
*/
private static $archive_tables = array();
/**
* Additional database indexes for the new
* "_versions" table. Used in {@link augmentDatabase()}.
@ -78,7 +101,25 @@ class Versioned extends DataExtension {
);
/**
* Reset static configuration variables to their default values
* An array of DataObject extensions that may require versioning for extra tables
* The array value is a set of suffixes to form these table names, assuming a preceding '_'.
* E.g. if Extension1 creates a new table 'Class_suffix1'
* and Extension2 the tables 'Class_suffix2' and 'Class_suffix3':
*
* $versionableExtensions = array(
* 'Extension1' => 'suffix1',
* 'Extension2' => array('suffix2', 'suffix3'),
* );
*
* Make sure your extension has a static $enabled-property that determines if it is
* processed by Versioned.
*
* @var array
*/
protected static $versionableExtensions = array('Translatable' => 'lang');
/**
* Reset static configuration variables to their default values.
*/
public static function reset() {
self::$reading_mode = '';
@ -88,36 +129,33 @@ class Versioned extends DataExtension {
/**
* Construct a new Versioned object.
*
* @var array $stages The different stages the versioned object can be.
* The first stage is consiedered the 'default' stage, the last stage is
* The first stage is considered the 'default' stage, the last stage is
* considered the 'live' stage.
*/
public function __construct($stages=array('Stage','Live')) {
public function __construct($stages = array('Stage','Live')) {
parent::__construct();
if(!is_array($stages)) {
$stages = func_get_args();
}
$this->stages = $stages;
$this->defaultStage = reset($stages);
$this->liveStage = array_pop($stages);
}
private static $db = array(
'Version' => 'Int'
);
public static function get_extra_config($class, $extension, $args) {
array(
'has_many' => array('Versions' => $class)
);
}
/**
* Amend freshly created DataQuery objects with versioned-specific information
* Amend freshly created DataQuery objects with versioned-specific
* information.
*
* @param SQLQuery
* @param DataQuery
*/
public function augmentDataQueryCreation(SQLQuery &$query, DataQuery &$dataQuery) {
$parts = explode('.', Versioned::get_reading_mode());
if($parts[0] == 'Archive') {
$dataQuery->setQueryParam('Versioned.mode', 'archive');
$dataQuery->setQueryParam('Versioned.date', $parts[1]);
@ -147,6 +185,10 @@ class Versioned extends DataExtension {
case 'archive':
$date = $dataQuery->getQueryParam('Versioned.date');
foreach($query->getFrom() as $table => $dummy) {
if(!DB::getConn()->hasTable($table . '_versions')) {
continue;
}
$query->renameTable($table, $table . '_versions');
$query->replaceText("\"{$table}_versions\".\"ID\"", "\"{$table}_versions\".\"RecordID\"");
$query->replaceText("`{$table}_versions`.`ID`", "`{$table}_versions`.`RecordID`");
@ -161,7 +203,6 @@ class Versioned extends DataExtension {
$query->addWhere("\"{$table}_versions\".\"Version\" = \"{$baseTable}_versions\".\"Version\"");
}
}
// Link to the version archived on that date
$safeDate = Convert::raw2sql($date);
$query->addWhere(
@ -274,12 +315,13 @@ class Versioned extends DataExtension {
}
/**
* For lazy loaded fields requiring extra sql manipulation, ie versioning
* For lazy loaded fields requiring extra sql manipulation, ie versioning.
*
* @param SQLQuery $query
* @param DataQuery $dataQuery
* @param DataObject $dataObject
*/
function augmentLoadLazyFields(SQLQuery &$query, DataQuery &$dataQuery = null, $dataObject) {
public function augmentLoadLazyFields(SQLQuery &$query, DataQuery &$dataQuery = null, $dataObject) {
// The VersionedMode local variable ensures that this decorator only applies to
// queries that have originated from the Versioned object, and have the Versioned
// metadata set on the query object. This prevents regular queries from
@ -300,13 +342,10 @@ class Versioned extends DataExtension {
}
}
/**
* Keep track of the archive tables that have been created
*/
private static $archive_tables = array();
/**
* Called by {@link SapphireTest} when the database is reset.
*
* @todo Reduce the coupling between this and SapphireTest, somehow.
*/
public static function on_db_reset() {
@ -321,24 +360,6 @@ class Versioned extends DataExtension {
self::$archive_tables = array();
}
/**
* An array of DataObject extensions that may require versioning for extra tables
* The array value is a set of suffixes to form these table names, assuming a preceding '_'.
* E.g. if Extension1 creates a new table 'Class_suffix1'
* and Extension2 the tables 'Class_suffix2' and 'Class_suffix3':
*
* $versionableExtensions = array(
* 'Extension1' => 'suffix1',
* 'Extension2' => array('suffix2', 'suffix3'),
* );
*
* Make sure your extension has a static $enabled-property that determines if it is
* processed by Versioned.
*
* @var array
*/
protected static $versionableExtensions = array('Translatable' => 'lang');
public function augmentDatabase() {
$classTable = $this->owner->class;
@ -504,6 +525,7 @@ class Versioned extends DataExtension {
/**
* Augment a write-record request.
*
* @param SQLQuery $manipulation Query to augment.
*/
public function augmentWrite(&$manipulation) {
@ -599,10 +621,15 @@ class Versioned extends DataExtension {
}
// Clear the migration flag
if($this->migratingVersion) $this->migrateVersion(null);
if($this->migratingVersion) {
$this->migrateVersion(null);
}
// Add the new version # back into the data object, for accessing after this write
if(isset($thisVersion)) $this->owner->Version = str_replace("'","",$thisVersion);
// Add the new version # back into the data object, for accessing
// after this write
if(isset($thisVersion)) {
$this->owner->Version = str_replace("'","", $thisVersion);
}
}
/**
@ -613,23 +640,30 @@ class Versioned extends DataExtension {
*/
public function writeWithoutVersion() {
$this->_nextWriteWithoutVersion = true;
return $this->owner->write();
}
/**
*
*/
public function onAfterWrite() {
$this->_nextWriteWithoutVersion = false;
}
/**
* If a write was skipped, then we need to ensure that we don't leave a migrateVersion()
* value lying around for the next write.
* If a write was skipped, then we need to ensure that we don't leave a
* migrateVersion() value lying around for the next write.
*
*
*/
public function onAfterSkippedWrite() {
$this->migrateVersion(null);
}
/**
* Determine if a table is supporting the Versioned extensions (e.g. $table_versions does exists)
* Determine if a table is supporting the Versioned extensions (e.g.
* $table_versions does exists).
*
* @param string $table Table name
* @return boolean
@ -641,20 +675,29 @@ class Versioned extends DataExtension {
}
/**
* Check if a certain table has the 'Version' field
* Check if a certain table has the 'Version' field.
*
* @param string $table Table name
*
* @return boolean Returns false if the field isn't in the table, true otherwise
*/
public function hasVersionField($table) {
$rPos = strrpos($table,'_');
if(($rPos !== false) && in_array(substr($table,$rPos), $this->stages)) {
$tableWithoutStage = substr($table,0,$rPos);
} else {
$tableWithoutStage = $table;
}
return ('DataObject' == get_parent_class($tableWithoutStage));
}
/**
* @param string $table
*
* @return string
*/
public function extendWithSuffix($table) {
foreach (Versioned::$versionableExtensions as $versionableExtension => $suffixes) {
if ($this->owner->hasExtension($versionableExtension)) {
@ -664,13 +707,13 @@ class Versioned extends DataExtension {
$ext->clearOwner();
}
}
return $table;
}
//-----------------------------------------------------------------------------------------------//
/**
* Get the latest published DataObject.
*
* @return DataObject
*/
public function latestPublished() {
@ -687,6 +730,7 @@ class Versioned extends DataExtension {
/**
* Move a database record from one stage to the other.
*
* @param fromStage Place to copy from. Can be either a stage name or a version number.
* @param toStage Place to copy to. Must be a stage name.
* @param createNewVersion Set this to true to create a new version number. By default, the existing version
@ -738,6 +782,7 @@ class Versioned extends DataExtension {
/**
* Set the migrating version.
*
* @param string $version The version.
*/
public function migrateVersion($version) {
@ -746,7 +791,9 @@ class Versioned extends DataExtension {
/**
* Compare two stages to see if they're different.
*
* Only checks the version numbers, not the actual content.
*
* @param string $stage1 The first stage to check.
* @param string $stage2
*/
@ -758,11 +805,14 @@ class Versioned extends DataExtension {
return true;
}
// We test for equality - if one of the versions doesn't exist, this will be false
//TODO: DB Abstraction: if statement here:
// We test for equality - if one of the versions doesn't exist, this
// will be false.
// TODO: DB Abstraction: if statement here:
$stagesAreEqual = DB::query("SELECT CASE WHEN \"$table1\".\"Version\"=\"$table2\".\"Version\""
. " THEN 1 ELSE 0 END FROM \"$table1\" INNER JOIN \"$table2\" ON \"$table1\".\"ID\" = \"$table2\".\"ID\""
. " AND \"$table1\".\"ID\" = {$this->owner->ID}")->value();
return !$stagesAreEqual;
}
@ -830,8 +880,10 @@ class Versioned extends DataExtension {
/**
* Compare two version, and return the diff between them.
*
* @param string $from The version to compare from.
* @param string $to The version to compare to.
*
* @return DataObject
*/
public function compareVersions($from, $to) {
@ -839,26 +891,39 @@ class Versioned extends DataExtension {
$toRecord = Versioned::get_version($this->owner->class, $this->owner->ID, $to);
$diff = new DataDifferencer($fromRecord, $toRecord);
return $diff->diffedData();
}
/**
* Return the base table - the class that directly extends DataObject.
*
* @return string
*/
public function baseTable($stage = null) {
$tableClasses = ClassInfo::dataClassesFor($this->owner->class);
$baseClass = array_shift($tableClasses);
return (!$stage || $stage == $this->defaultStage) ? $baseClass : $baseClass . "_$stage";
if(!$stage || $stage == $this->defaultStage) {
return $baseClass;
}
return $baseClass . "_$stage";
}
//-----------------------------------------------------------------------------------------------//
/**
* Choose the stage the site is currently on.
* If $_GET['stage'] is set, then it will use that stage, and store it in the session.
* if $_GET['archiveDate'] is set, it will use that date, and store it in the session.
* If neither of these are set, it checks the session, otherwise the stage is set to 'Live'.
*
* If $_GET['stage'] is set, then it will use that stage, and store it in
* the session.
*
* if $_GET['archiveDate'] is set, it will use that date, and store it in
* the session.
*
* If neither of these are set, it checks the session, otherwise the stage
* is set to 'Live'.
*/
public static function choose_site_stage() {
if(isset($_GET['stage'])) {
@ -897,6 +962,8 @@ class Versioned extends DataExtension {
/**
* Set the current reading mode.
*
* @param string $mode
*/
public static function set_reading_mode($mode) {
Versioned::$reading_mode = $mode;
@ -904,6 +971,7 @@ class Versioned extends DataExtension {
/**
* Get the current reading mode.
*
* @return string
*/
public static function get_reading_mode() {
@ -912,6 +980,7 @@ class Versioned extends DataExtension {
/**
* Get the name of the 'live' stage.
*
* @return string
*/
public static function get_live_stage() {
@ -920,15 +989,20 @@ class Versioned extends DataExtension {
/**
* Get the current reading stage.
*
* @return string
*/
public static function current_stage() {
$parts = explode('.', Versioned::get_reading_mode());
if($parts[0] == 'Stage') return $parts[1];
if($parts[0] == 'Stage') {
return $parts[1];
}
}
/**
* Get the current archive date.
*
* @return string
*/
public static function current_archived_date() {
@ -938,6 +1012,7 @@ class Versioned extends DataExtension {
/**
* Set the reading stage.
*
* @param string $stage New reading stage.
*/
public static function reading_stage($stage) {
@ -946,6 +1021,7 @@ class Versioned extends DataExtension {
/**
* Set the reading archive date.
*
* @param string $date New reading archived date.
*/
public static function reading_archived_date($date) {
@ -961,11 +1037,13 @@ class Versioned extends DataExtension {
* @param string $filter A filter to be inserted into the WHERE clause.
* @param boolean $cache Use caching.
* @param string $orderby A sort expression to be inserted into the ORDER BY clause.
*
* @return DataObject
*/
public static function get_one_by_stage($class, $stage, $filter = '', $cache = true, $sort = '') {
// TODO: No identity cache operating
$items = self::get_by_stage($class, $stage, $filter, $sort, null, 1);
return $items->First();
}
@ -976,6 +1054,7 @@ class Versioned extends DataExtension {
* @param string $stage
* @param int $id
* @param boolean $cache
*
* @return int
*/
public static function get_versionnumber_by_stage($class, $stage, $id, $cache = true) {
@ -992,10 +1071,14 @@ class Versioned extends DataExtension {
// cache value (if required)
if($cache) {
if(!isset(self::$cache_versionnumber[$baseClass])) self::$cache_versionnumber[$baseClass] = array();
if(!isset(self::$cache_versionnumber[$baseClass])) {
self::$cache_versionnumber[$baseClass] = array();
}
if(!isset(self::$cache_versionnumber[$baseClass][$stage])) {
self::$cache_versionnumber[$baseClass][$stage] = array();
}
self::$cache_versionnumber[$baseClass][$stage][$id] = $version;
}
@ -1003,17 +1086,26 @@ class Versioned extends DataExtension {
}
/**
* Pre-populate the cache for Versioned::get_versionnumber_by_stage() for a list of record IDs,
* for more efficient database querying. If $idList is null, then every page will be pre-cached.
* Pre-populate the cache for Versioned::get_versionnumber_by_stage() for
* a list of record IDs, for more efficient database querying. If $idList
* is null, then every page will be pre-cached.
*
* @param string $class
* @param string $stage
* @param array $idList
*/
public static function prepopulate_versionnumber_cache($class, $stage, $idList = null) {
$filter = "";
if($idList) {
// Validate the ID list
foreach($idList as $id) if(!is_numeric($id)) {
user_error("Bad ID passed to Versioned::prepopulate_versionnumber_cache() in \$idList: " . $id,
foreach($idList as $id) {
if(!is_numeric($id)) {
user_error("Bad ID passed to Versioned::prepopulate_versionnumber_cache() in \$idList: " . $id,
E_USER_ERROR);
}
}
$filter = "WHERE \"ID\" IN(" .implode(", ", $idList) . ")";
}
@ -1021,6 +1113,7 @@ class Versioned extends DataExtension {
$stageTable = ($stage == 'Stage') ? $baseClass : "{$baseClass}_{$stage}";
$versions = DB::query("SELECT \"ID\", \"Version\" FROM \"$stageTable\" $filter")->map();
foreach($versions as $id => $version) {
self::$cache_versionnumber[$baseClass][$stage][$id] = $version;
}
@ -1036,6 +1129,7 @@ class Versioned extends DataExtension {
* @param string $join Deprecated, use leftJoin($table, $joinClause) instead
* @param int $limit A limit on the number of records returned from the database.
* @param string $containerClass The container class for the result set (default is DataList)
*
* @return SS_List
*/
public static function get_by_stage($class, $stage, $filter = '', $sort = '', $join = '', $limit = '',
@ -1048,6 +1142,11 @@ class Versioned extends DataExtension {
));
}
/**
* @param string $stage
*
* @return int
*/
public function deleteFromStage($stage) {
$oldMode = Versioned::get_reading_mode();
Versioned::reading_stage($stage);
@ -1062,11 +1161,17 @@ class Versioned extends DataExtension {
return $result;
}
/**
* @param string $stage
* @param boolean $forceInsert
*/
public function writeToStage($stage, $forceInsert = false) {
$oldMode = Versioned::get_reading_mode();
Versioned::reading_stage($stage);
$result = $this->owner->write(false, $forceInsert);
Versioned::set_reading_mode($oldMode);
return $result;
}
@ -1074,7 +1179,7 @@ class Versioned extends DataExtension {
* Roll the draft version of this page to match the published page.
* Caution: Doesn't overwrite the object properties with the rolled back version.
*
* @param $version Either the string 'Live' or a version number
* @param int $version Either the string 'Live' or a version number
*/
public function doRollbackTo($version) {
$this->owner->extend('onBeforeRollback', $version);
@ -1107,7 +1212,7 @@ class Versioned extends DataExtension {
* @see get_latest_version()
* @see latestPublished
*
* @return bool
* @return boolean
*/
public function isLatestVersion() {
$version = self::get_latest_version($this->owner->class, $this->owner->ID);
@ -1120,6 +1225,10 @@ class Versioned extends DataExtension {
* version of each page stored in the (class)_versions tables.
*
* In particular, this will query deleted records as well as active ones.
*
* @param string $class
* @param string $filter
* @param string $sort
*/
public static function get_including_deleted($class, $filter = "", $sort = "") {
$list = DataList::create($class)
@ -1132,8 +1241,14 @@ class Versioned extends DataExtension {
/**
* Return the specific version of the given id.
* Caution: The record is retrieved as a DataObject, but saving back modifications
* via write() will create a new version, rather than modifying the existing one.
*
* Caution: The record is retrieved as a DataObject, but saving back
* modifications via write() will create a new version, rather than
* modifying the existing one.
*
* @param string $class
* @param int $id
* @param int $version
*
* @return DataObject
*/
@ -1148,7 +1263,11 @@ class Versioned extends DataExtension {
}
/**
* Return a list of all versions for a given id
* Return a list of all versions for a given id.
*
* @param string $class
* @param int $id
*
* @return DataList
*/
public static function get_all_versions($class, $id) {
@ -1160,25 +1279,44 @@ class Versioned extends DataExtension {
return $list;
}
/**
* @param Controller $controller
*/
public function contentcontrollerInit($controller) {
self::choose_site_stage();
}
/**
* @param Controller $controller
*/
public function modelascontrollerInit($controller) {
self::choose_site_stage();
}
protected static $reading_mode = null;
/**
* @param array $labels
*/
public function updateFieldLabels(&$labels) {
$labels['Versions'] = _t('Versioned.has_many_Versions', 'Versions', 'Past Versions of this page');
}
/**
* @param FieldList
*/
public function updateCMSFields(FieldList $fields) {
// remove the version field from the CMS as this should be left
// entirely up to the extension (not the cms user).
$fields->removeByName('Version');
}
public function flushCache() {
self::$cache_versionnumber = array();
}
/**
* Return a piece of text to keep DataObject cache keys appropriately specific
* Return a piece of text to keep DataObject cache keys appropriately specific.
*
* @return string
*/
public function cacheKeyComponent() {
return 'versionedmode-'.self::get_reading_mode();
@ -1194,7 +1332,14 @@ class Versioned extends DataExtension {
* @see Versioned
*/
class Versioned_Version extends ViewableData {
/**
* @var array
*/
protected $record;
/**
* @var DataObject
*/
protected $object;
public function __construct($record) {
@ -1208,23 +1353,36 @@ class Versioned_Version extends ViewableData {
parent::__construct();
}
/**
* @return string
*/
public function PublishedClass() {
return $this->record['WasPublished'] ? 'published' : 'internal';
}
/**
* @return Member
*/
public function Author() {
return DataObject::get_by_id("Member", $this->record['AuthorID']);
return Member::get()->byId($this->record['AuthorID']);
}
/**
* @return Member
*/
public function Publisher() {
if( !$this->record['WasPublished'] )
if (!$this->record['WasPublished']) {
return null;
}
return DataObject::get_by_id("Member", $this->record['PublisherID']);
return Member::get()->byId($this->record['PublisherID']);
}
/**
* @return boolean
*/
public function Published() {
return !empty( $this->record['WasPublished'] );
return !empty($this->record['WasPublished']);
}
/**
@ -1240,17 +1398,23 @@ class Versioned_Version extends ViewableData {
// Traverse dot syntax
foreach($parts as $relation) {
if($component instanceof SS_List) {
if(method_exists($component,$relation)) $component = $component->$relation();
else $component = $component->relation($relation);
if(method_exists($component,$relation)) {
$component = $component->$relation();
} else {
$component = $component->relation($relation);
}
} else {
$component = $component->$relation();
}
}
}
}
// Unlike has-one's, these "relations" can return false
if($component) {
if ($component->hasMethod($fieldName)) return $component->$fieldName();
if ($component->hasMethod($fieldName)) {
return $component->$fieldName();
}
return $component->$fieldName;
}
}

View File

@ -118,7 +118,7 @@ interface CompositeDBField {
* parameter.
*
* @param DBField|array $value
* @param array $record Map of values loaded from the database
* @param DataObject|array $record An array or object that this field is part of
* @param boolean $markChanged Indicate wether this field should be marked changed.
* Set to FALSE if you are initializing this field after construction, rather
* than setting a new value.

View File

@ -64,7 +64,7 @@ class Money extends DBField implements CompositeDBField {
);
public function __construct($name = null) {
$this->currencyLib = new Zend_Currency(null, i18n::default_locale());
$this->currencyLib = new Zend_Currency(null, i18n::get_locale());
parent::__construct($name);
}
@ -103,6 +103,11 @@ class Money extends DBField implements CompositeDBField {
}
public function setValue($value, $record = null, $markChanged = true) {
// Convert an object to an array
if($record && $record instanceof DataObject) {
$record = $record->getQueriedDatabaseFields();
}
// @todo Allow resetting value to NULL through Money $value field
if ($value instanceof Money && $value->exists()) {
$this->setCurrency($value->getCurrency(), $markChanged);

View File

@ -1,15 +1,24 @@
<?php
/**
* Format of the Oembed config. Autodiscover allows discovery of all URLs.
* Endpoint set to true means autodiscovery for this specific provider is allowed
* (even if autodiscovery in general has been disabled).
*
* Endpoint set to true means autodiscovery for this specific provider is
* allowed (even if autodiscovery in general has been disabled).
*
* <code>
*
* name: Oembed
* ---
* Oembed:
* providers:
* {pattern}:
* {endpoint}/true
* 'http://*.youtube.com/watch*':
* 'http://www.youtube.com/oembed/'
* autodiscover:
* true/false
* true
* </code>
*
* @package framework
* @subpackage oembed
*/
class Oembed {
@ -178,6 +187,10 @@ class Oembed {
}
}
/**
* @package framework
* @subpackage oembed
*/
class Oembed_Result extends ViewableData {
/**
* JSON data fetched from the Oembed URL.

View File

@ -1,9 +1,11 @@
<?php
/**
* A simple parser that allows you to map BBCode-like "shortcodes" to an arbitrary callback.
* It is a simple regex based parser that allows you to replace simple bbcode-like tags
* within a HTMLText or HTMLVarchar field when rendered into a template. The API is inspired by and very similar to the
* [Wordpress implementation](http://codex.wordpress.org/Shortcode_API) of shortcodes.
*
* See the documentation at docs/reference/shortcodes.md .
*
* @see http://doc.silverstripe.org/framework/en/topics/shortcodes
* @package framework
* @subpackage misc
*/
@ -218,7 +220,7 @@ class ShortcodeParser {
foreach ($attrmatches as $attr) {
list($whole, $name, $value) = array_values(array_filter($attr));
$attrs[$name] = $value;
}
}
}
// And store the indexes, tag details, etc
@ -233,7 +235,7 @@ class ShortcodeParser {
'escaped' => !empty($match['oesc'][0]) || !empty($match['cesc1'][0]) || !empty($match['cesc2'][0])
);
}
}
}
$i = count($tags);
while($i--) {
@ -243,7 +245,7 @@ class ShortcodeParser {
if ($i == 0) {
$err = 'Close tag "'.$tags[$i]['close'].'" is the first found tag, so has no related open tag';
}
}
else if (!$tags[$i-1]['open']) {
$err = 'Close tag "'.$tags[$i]['close'].'" preceded by another close tag "'.$tags[$i-1]['close'].'"';
}
@ -345,9 +347,9 @@ class ShortcodeParser {
}
}
return $content;
return $content;
});
}
}
}
}
@ -365,10 +367,10 @@ class ShortcodeParser {
$content = $this->replaceTagsWithText($content, $tags, function($idx, $tag) use ($markerClass) {
return '<img class="'.$markerClass.'" data-tagid="'.$idx.'" />';
});
}
}
return array($content, $tags);
}
}
protected function findParentsForMarkers($nodes) {
$parents = array();
@ -378,15 +380,15 @@ class ShortcodeParser {
do {
$parent = $parent->parentNode;
}
}
while($parent instanceof DOMElement && !in_array(strtolower($parent->tagName), self::$block_level_elements));
$node->setAttribute('data-parentid', count($parents));
$parents[] = $parent;
}
}
return $parents;
}
}
const BEFORE = 'before';
const AFTER = 'after';
@ -421,10 +423,10 @@ class ShortcodeParser {
protected function moveMarkerToCompliantHome($node, $parent, $location) {
// Move before block parent
if($location == self::BEFORE) {
if (isset($parent->parentNode))
$parent->parentNode->insertBefore($node, $parent);
}
// Move after block parent
else if($location == self::AFTER) {
} else if($location == self::AFTER) {
// Move after block parent
$this->insertAfter($node, $parent);
}
// Split parent at node

View File

@ -4,3 +4,8 @@
clear: none;
float: left;
}
.datetime .middleColumn .field {
margin: 0;
border-bottom: none;
box-shadow: none;
}

View File

@ -382,9 +382,11 @@ $gf_grid_x: 16px;
background: rgba(#000, 0.7);
padding: 5px;
border-top: $gf_colour_text_shadow;
input {
height:28px; //height of input field - to match design.
}
button.ss-ui-button {
padding: .3em;
line-height: 1;
@ -393,6 +395,10 @@ $gf_grid_x: 16px;
border-bottom-width: 0;
@include border-radius(2px, 2px);
}
select {
margin: 0;
}
}
&.first {
@include border-top-left-radius($gf_border_radius);

View File

@ -117,7 +117,7 @@ class Group extends DataObject {
}
}
});
$memberList = GridField::create('Members',false, $this->Members(), $config)->addExtraClass('members_grid');
$memberList = GridField::create('Members',false, $this->DirectMembers(), $config)->addExtraClass('members_grid');
// @todo Implement permission checking on GridField
//$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
$fields->addFieldToTab('Root.Members', $memberList);

View File

@ -988,7 +988,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
* @todo Push all this logic into Member_GroupSet's getIterator()?
*/
public function Groups() {
$groups = Injector::inst()->create('Member_GroupSet', 'Group', 'Group_Members', 'GroupID', 'MemberID');
$groups = Member_GroupSet::create('Group', 'Group_Members', 'GroupID', 'MemberID');
$groups = $groups->forForeignID($this->ID);
$this->extend('updateGroups', $groups);
@ -1424,6 +1424,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
$config = HtmlEditorConfig::get($group->HtmlEditorConfig);
if($config && $config->getOption('priority') > $currentPriority) {
$currentName = $configName;
$currentPriority = $config->getOption('priority');
}
}
}

View File

@ -89,6 +89,9 @@ class MemberLoginForm extends LoginForm {
$fields->push(new HiddenField('BackURL', 'BackURL', $backURL));
}
// Reduce attack surface by enforcing POST requests
$this->setFormMethod('POST', true);
parent::__construct($controller, $name, $fields, $actions);
// Focus on the email input when the page is loaded

View File

@ -222,17 +222,20 @@ class Security extends Controller {
$messageSet = array('default' => $messageSet);
}
$member = Member::currentUser();
// Work out the right message to show
if(Member::currentUser()) {
if($member && $member->exists()) {
$response = ($controller) ? $controller->getResponse() : new SS_HTTPResponse();
$response->setStatusCode(403);
//If 'alreadyLoggedIn' is not specified in the array, then use the default
//which should have been specified in the lines above
if(isset($messageSet['alreadyLoggedIn']))
$message=$messageSet['alreadyLoggedIn'];
else
$message=$messageSet['default'];
if(isset($messageSet['alreadyLoggedIn'])) {
$message = $messageSet['alreadyLoggedIn'];
} else {
$message = $messageSet['default'];
}
// Somewhat hackish way to render a login form with an error message.
$me = new Security();
@ -242,6 +245,9 @@ class Security extends Controller {
$formText = $me->login();
$response->setBody($formText);
$controller->extend('permissionDenied', $member);
return $response;
} else {
$message = $messageSet['default'];
@ -647,7 +653,7 @@ class Security extends Controller {
* @return Form Returns the lost password form
*/
public function ChangePasswordForm() {
return new ChangePasswordForm($this, 'ChangePasswordForm');
return Object::create('ChangePasswordForm', $this, 'ChangePasswordForm');
}
/**

View File

@ -10,9 +10,15 @@
<tr><% loop Header %><th>$CellString</th><% end_loop %></tr>
</thead>
<tbody>
<% if ItemRows %>
<% loop ItemRows %>
<tr><% loop ItemRow %><td>$CellString</td><% end_loop %></tr>
<% end_loop %>
<% else %>
<tr>
<td colspan="$Header.Count"><p><% _t('GridField.NoItemsFound', 'No items found') %></p></td>
</tr>
<% end_if %>
</tbody>
</table>
<p>

View File

@ -44,13 +44,16 @@
</div>
<% end_if %>
<div class="ss-uploadfield-item-info">
<label class="ss-uploadfield-item-name"><b>
<label class="ss-uploadfield-item-name">
<% if $multiple %>
<% _t('UploadField.ATTACHFILES', 'Attach files') %>
<b><% _t('UploadField.ATTACHFILES', 'Attach files') %></b>
<% else %>
<% _t('UploadField.ATTACHFILE', 'Attach a file') %>
<b><% _t('UploadField.ATTACHFILE', 'Attach a file') %></b>
<% end_if %>
</b></label>
<% if getConfig('canPreviewFolder') %>
<small>(<%t UploadField.UPLOADSINTO 'saves into /{path}' path=$FolderName %>)</small>
<% end_if %>
</label>
<% if canUpload %>
<label class="ss-uploadfield-fromcomputer ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Upload from your computer') %>" data-icon="drive-upload">
<% _t('UploadField.FROMCOMPUTER', 'From your computer') %>

View File

@ -1,2 +1,2 @@
<input $AttributesHTML />
<input type="hidden" name="MAX_FILE_SIZE" value="$MaxFileSize" />
<input $AttributesHTML />

View File

@ -9,6 +9,8 @@ class DirectorTest extends SapphireTest {
protected static $originalRequestURI;
protected $originalProtocolHeaders = array();
public function setUp() {
parent::setUp();
@ -24,6 +26,16 @@ class DirectorTest extends SapphireTest {
'Locale' => 'en_NZ'
)
));
$headers = array(
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
);
foreach($headers as $header) {
if(isset($_SERVER[$header])) {
$this->originalProtocolHeaders[$header] = $_SERVER[$header];
}
}
}
public function tearDown() {
@ -32,6 +44,12 @@ class DirectorTest extends SapphireTest {
// Reinstate the original REQUEST_URI after it was modified by some tests
$_SERVER['REQUEST_URI'] = self::$originalRequestURI;
if($this->originalProtocolHeaders) {
foreach($this->originalProtocolHeaders as $header => $value) {
$_SERVER[$header] = $value;
}
}
parent::tearDown();
}
@ -285,6 +303,43 @@ class DirectorTest extends SapphireTest {
$this->assertEquals(404, Director::test('no-route')->getStatusCode());
}
public function testIsHttps() {
// nothing available
$headers = array(
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
);
foreach($headers as $header) {
if(isset($_SERVER[$header])) {
unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
}
}
$this->assertFalse(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'https';
$this->assertTrue(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'http';
$this->assertFalse(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'ftp';
$this->assertFalse(Director::is_https());
// https via HTTPS
$_SERVER['HTTPS'] = 'true';
$this->assertTrue(Director::is_https());
$_SERVER['HTTPS'] = '1';
$this->assertTrue(Director::is_https());
$_SERVER['HTTPS'] = 'off';
$this->assertFalse(Director::is_https());
// https via SSL
$_SERVER['SSL'] = '';
$this->assertTrue(Director::is_https());
}
}
class DirectorTestRequest_Controller extends Controller implements TestOnly {

View File

@ -173,6 +173,14 @@ class HTTPTest extends SapphireTest {
HTTP::absoluteURLs('<div background="./themes/silverstripe/images/nav-bg-repeat-2.png">SS Blog</div>')
);
//check dot segments
// Assumption: dots are not removed
//if they were, the url should be: http://www.silverstripe.org/abc
$test->assertEquals(
'<a href="http://www.silverstripe.org/test/page/../../abc">Test</a>',
HTTP::absoluteURLs('<a href="test/page/../../abc">Test</a>')
);
// image
$test->assertEquals(
'<img src=\'http://www.silverstripe.org/themes/silverstripe/images/logo-org.png\' />',
@ -187,16 +195,33 @@ class HTTPTest extends SapphireTest {
});
}
public function testEmailLinks() {
/**
* Make sure URI schemes are not rewritten
*/
public function testURISchemes() {
$this->withBaseURL('http://www.silverstripe.org/', function($test){
// links
// mailto
$test->assertEquals(
'<a href=\'mailto:admin@silverstripe.org\'>Email Us</a>',
HTTP::absoluteURLs('<a href=\'mailto:admin@silverstripe.org\'>Email Us</a>')
HTTP::absoluteURLs('<a href=\'mailto:admin@silverstripe.org\'>Email Us</a>'),
'Email links are not rewritten'
);
// data uri
$test->assertEquals(
'<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />',
HTTP::absoluteURLs('<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />'),
'Data URI links are not rewritten'
);
// call
$test->assertEquals(
'<a href="callto:12345678" />',
HTTP::absoluteURLs('<a href="callto:12345678" />'),
'Call to links are not rewritten'
);
});
}
/**

View File

@ -1,9 +1,11 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class ArrayLibTest extends SapphireTest {
public function testInvert() {
$arr = array(
'row1' => array(
@ -187,4 +189,31 @@ class ArrayLibTest extends SapphireTest {
'Numeric keys should behave like string keys'
);
}
public function testFlatten() {
$options = array(
'1' => 'one',
'2' => 'two'
);
$expected = $options;
$this->assertEquals($expected, ArrayLib::flatten($options));
$options = array(
'1' => array(
'2' => 'two',
'3' => 'three'
),
'4' => 'four'
);
$expected = array(
'2' => 'two',
'3' => 'three',
'4' => 'four'
);
$this->assertEquals($expected, ArrayLib::flatten($options));
}
}

View File

@ -16,8 +16,10 @@ class CoreTest extends SapphireTest {
}
public function testGetTempPathInProject() {
$user = getTempFolderUsername();
if(file_exists($this->tempPath)) {
$this->assertEquals(getTempFolder(BASE_PATH), $this->tempPath);
$this->assertEquals(getTempFolder(BASE_PATH), $this->tempPath . '/' . $user);
} else {
$user = getTempFolderUsername();

View File

@ -395,6 +395,18 @@ class ObjectTest extends SapphireTest {
Object::parse_class_spec(
"Enum(array('Accepted', 'Pending', 'Declined', array('UnsubmittedA','UnsubmittedB')), 'Unsubmitted')")
);
// 5.4 Shorthand Array
$this->assertEquals(
array('Enum',array(array('Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted')),
Object::parse_class_spec("Enum(['Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted']")
);
// 5.4 Nested shorthand array
$this->assertEquals(
array('Enum',array(array('Accepted', 'Pending', 'Declined', array('UnsubmittedA','UnsubmittedB')),
'Unsubmitted')),
Object::parse_class_spec(
"Enum(['Accepted', 'Pending', 'Declined', ['UnsubmittedA','UnsubmittedB']], 'Unsubmitted')")
);
// Namespaced class
$this->assertEquals(
array('Test\MyClass', array()),

View File

@ -163,4 +163,23 @@ DOC;
$statics = $this->parseSelf()->getStatics();
$this->assertNull(@$statics[__CLASS__]['static_method']);
}
public function testParsingShortArray() {
if(version_compare(PHP_VERSION, '5.4', '<')) {
$this->markTestSkipped('This test requires PHP 5.4 or higher');
return;
}
$parser = new SS_ConfigStaticManifest_Parser(__DIR__ . '/ConfigStaticManifestTest/ConfigStaticManifestTestMyObject.php');
$parser->parse();
$statics = $parser->getStatics();
$expectedValue = array(
'Name' => 'Varchar',
'Description' => 'Text',
);
$this->assertEquals($expectedValue, $statics['ConfigStaticManifestTestMyObject']['db']['value']);
}
}

View File

@ -0,0 +1,8 @@
<?php
class ConfigStaticManifestTestMyObject implements TestOnly {
static private $db = [
'Name' => 'Varchar',
'Description' => 'Text',
];
}

View File

@ -42,6 +42,13 @@ class TemplateLoaderTest extends SapphireTest {
);
$this->assertEquals($expectCustomPage, $loader->findTemplates(array('CustomPage', 'Page')));
// 'main' template only exists in theme, and 'Layout' template only exists in module
$expectCustomThemePage = array(
'main' => "$base/themes/theme/templates/CustomThemePage.ss",
'Layout' => "$base/module/templates/Layout/CustomThemePage.ss"
);
$this->assertEquals($expectCustomThemePage, $loader->findTemplates(array('CustomThemePage', 'Page'), 'theme'));
}
public function testFindTemplatesApplicationOverridesModule() {

View File

@ -47,6 +47,13 @@ class TemplateManifestTest extends SapphireTest {
'subfolder' => array(
'main' => "{$this->base}/module/subfolder/templates/Subfolder.ss"
),
'customthemepage' => array (
'Layout' => "{$this->base}/module/templates/Layout/CustomThemePage.ss",
'themes' =>
array(
'theme' => array('main' => "{$this->base}/themes/theme/templates/CustomThemePage.ss",)
)
),
'include' => array('themes' => array(
'theme' => array(
'Includes' => "{$this->base}/themes/theme/templates/Includes/Include.ss"

View File

@ -1,6 +1,12 @@
<?php
/**
* @package framework
* @package tests
*/
class CSVParserTest extends SapphireTest {
public function testParsingWithHeaders() {
/* By default, a CSV file will be interpreted as having headers */
$csv = new CSVParser($this->getCurrentRelativePath() . '/CsvBulkLoaderTest_PlayersWithHeader.csv');
@ -87,5 +93,4 @@ class CSVParserTest extends SapphireTest {
$this->assertEquals(array("Birthday","31/01/1988","31/01/1982","31/01/1882","31/06/1982"), $birthdays);
$this->assertEquals(array('IsRegistered', '1', '0', '1', '1'), $registered);
}
}

View File

@ -1,10 +1,11 @@
<?php
/**
* @package tests
*
* @todo Test with columnn headers and custom mappings
* @package framework
* @subpackage tests
*/
class CsvBulkLoaderTest extends SapphireTest {
protected static $fixture_file = 'CsvBulkLoaderTest.yml';
protected $extraDataObjects = array(
@ -171,8 +172,10 @@ class CsvBulkLoaderTest extends SapphireTest {
// HACK need to update the loaded record from the database
$player = DataObject::get_by_id('CsvBulkLoaderTest_Player', $player->ID);
$this->assertEquals($player->FirstName, 'JohnUpdated', 'Test updating of existing records works');
$this->assertEquals($player->Biography, 'He\'s a good guy',
'Test retaining of previous information on duplicate when overwriting with blank field');
// null values are valid imported
// $this->assertEquals($player->Biography, 'He\'s a good guy',
// 'Test retaining of previous information on duplicate when overwriting with blank field');
}
public function testLoadWithCustomImportMethods() {
@ -192,6 +195,25 @@ class CsvBulkLoaderTest extends SapphireTest {
$this->assertEquals($player->IsRegistered, "1");
}
public function testLoadWithCustomImportMethodDuplicateMap() {
$loader = new CsvBulkLoaderTest_CustomLoader('CsvBulkLoaderTest_Player');
$filepath = $this->getCurrentAbsolutePath() . '/CsvBulkLoaderTest_PlayersWithHeader.csv';
$loader->columnMap = array(
'FirstName' => '->updatePlayer',
'Biography' => '->updatePlayer',
'Birthday' => 'Birthday',
'IsRegistered' => 'IsRegistered'
);
$results = $loader->load($filepath);
$createdPlayers = $results->Created();
$player = $createdPlayers->First();
$this->assertEquals($player->FirstName, "John. He's a good guy. ");
}
protected function getLineCount(&$file) {
$i = 0;
while(fgets($file) !== false) $i++;
@ -205,6 +227,10 @@ class CsvBulkLoaderTest_CustomLoader extends CsvBulkLoader implements TestOnly {
public function importFirstName(&$obj, $val, $record) {
$obj->FirstName = "Customized {$val}";
}
public function updatePlayer(&$obj, $val, $record) {
$obj->FirstName .= $val . '. ';
}
}
class CsvBulkLoaderTest_Team extends DataObject implements TestOnly {
@ -252,6 +278,7 @@ class CsvBulkLoaderTest_Player extends DataObject implements TestOnly {
}
}
class CsvBulkLoaderTest_PlayerContract extends DataObject implements TestOnly {
private static $db = array(
'Amount' => 'Currency',

View File

@ -294,4 +294,48 @@ class FolderTest extends SapphireTest {
parent::tearDown();
}
public function testSyncedChildren() {
mkdir(ASSETS_PATH ."/FolderTest");
mkdir(ASSETS_PATH ."/FolderTest/sync");
$files = array(
'.htaccess',
'.git',
'web.config',
'.DS_Store',
'_my_synced_file.txt'
);
$folders = array(
'_combinedfiles',
'_resampled',
'_testsync'
);
foreach($files as $file) {
$fh = fopen(ASSETS_PATH."/FolderTest/sync/$file", "w");
fwrite($fh, 'test');
fclose($fh);
}
foreach($folders as $folder) {
mkdir(ASSETS_PATH ."/FolderTest/sync/". $folder);
}
$folder = Folder::find_or_make('/FolderTest/sync');
$result = $folder->syncChildren();
$this->assertEquals(10, $result['skipped']);
$this->assertEquals(2, $result['added']);
// folder with a path of _test should exist
$this->assertEquals(1, Folder::get()->filter(array(
'Name' => '_testsync'
))->count());
$this->assertEquals(1, File::get()->filter(array(
'Name' => '_my_synced_file.txt'
))->count());
}
}

View File

@ -87,7 +87,7 @@ class FormTest extends FunctionalTest {
public function testLoadDataFromObject() {
$form = new Form(
new Controller(),
new Controller(),
'Form',
new FieldList(
new HeaderField('MyPlayerHeader','My Player'),
@ -339,6 +339,24 @@ class FormTest extends FunctionalTest {
$this->assertEquals(200, $response->getStatusCode(), 'Submission suceeds with security token');
}
public function testStrictFormMethodChecking() {
$response = $this->get('FormTest_ControllerWithStrictPostCheck');
$response = $this->get(
'FormTest_ControllerWithStrictPostCheck/Form/?Email=test@test.com&action_doSubmit=1'
);
$this->assertEquals(405, $response->getStatusCode(), 'Submission fails with wrong method');
$response = $this->get('FormTest_ControllerWithStrictPostCheck');
$response = $this->post(
'FormTest_ControllerWithStrictPostCheck/Form',
array(
'Email' => 'test@test.com',
'action_doSubmit' => 1
)
);
$this->assertEquals(200, $response->getStatusCode(), 'Submission succeeds with correct method');
}
public function testEnableSecurityToken() {
SecurityToken::disable();
$form = $this->getStubForm();
@ -476,24 +494,7 @@ class FormTest_Controller extends Controller implements TestOnly {
'SomeRequiredField'
)
);
// Disable CSRF protection for easier form submission handling
$form->disableSecurityToken();
return $form;
}
public function FormWithSecurityToken() {
$form = new Form(
$this,
'FormWithSecurityToken',
new FieldList(
new EmailField('Email')
),
new FieldList(
new FormAction('doSubmit')
)
);
$form->disableSecurityToken(); // Disable CSRF protection for easier form submission handling
return $form;
}
@ -541,7 +542,40 @@ class FormTest_ControllerWithSecurityToken extends Controller implements TestOnl
return $this->redirectBack();
}
public function getViewer($action = null) {
return new SSViewer('BlankPage');
}
class FormTest_ControllerWithStrictPostCheck extends Controller implements TestOnly {
protected $template = 'BlankPage';
public function Link($action = null) {
return Controller::join_links(
'FormTest_ControllerWithStrictPostCheck',
$this->request->latestParam('Action'),
$this->request->latestParam('ID'),
$action
);
}
public function Form() {
$form = new Form(
$this,
'Form',
new FieldList(
new EmailField('Email')
),
new FieldList(
new FormAction('doSubmit')
)
);
$form->setFormMethod('POST');
$form->setStrictFormMethodCheck(true);
$form->disableSecurityToken(); // Disable CSRF protection for easier form submission handling
return $form;
}
public function doSubmit($data, $form, $request) {
$form->sessionMessage('Test save was successful', 'good');
return $this->redirectBack();
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* @package framework
* @subpackage tests
@ -80,4 +81,34 @@ class LookupFieldTest extends SapphireTest {
);
}
public function testWithMultiDimensionalSource() {
$choices = array(
"Non-vegetarian" => array(
0 => 'Carnivore',
),
"Vegetarian" => array(
3 => 'Carrots',
),
"Other" => array(
9 => 'Vegan'
)
);
$f = new LookupField('test', 'test', $choices);
$f->setValue(3);
$this->assertEquals(
'<span class="readonly" id="test">Carrots</span><input type="hidden" name="test" value="3" />',
$f->Field()
);
$f->setValue(array(
3, 9
));
$this->assertEquals(
'<span class="readonly" id="test">Carrots, Vegan</span><input type="hidden" name="test" value="3, 9" />',
$f->Field()
);
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class NumericFieldTest extends SapphireTest {
protected $usesDatabase = false;
public function testValidator() {
i18n::set_locale('en_US');
$field = new NumericField('Number');
$field->setValue('12.00');
$validator = new RequiredFields('Number');
$this->assertTrue($field->validate($validator));
$field->setValue('12,00');
$this->assertFalse($field->validate($validator));
$field->setValue('0');
$this->assertTrue($field->validate($validator));
$field->setValue(false);
$this->assertFalse($field->validate($validator));
i18n::set_locale('de_DE');
$field->setValue('12,00');
$validator = new RequiredFields();
$this->assertTrue($field->validate($validator));
$field->setValue('12.00');
$this->assertFalse($field->validate($validator));
}
}

View File

@ -362,18 +362,18 @@ class RequirementsTest extends SapphireTest {
$backend->set_suffix_requirements(true);
$html = $backend->includeInHTML(false, $template);
$this->assertRegexp('/RequirementsTest_a\.js\?m=[\d]*/', $html);
$this->assertRegexp('/RequirementsTest_b\.js\?m=[\d]*&amp;foo=bar&amp;bla=blubb/', $html);
$this->assertRegexp('/RequirementsTest_a\.css\?m=[\d]*/', $html);
$this->assertRegexp('/RequirementsTest_b\.css\?m=[\d]*&amp;foo=bar&amp;bla=blubb/', $html);
$this->assertRegexp('/RequirementsTest_a\.js\?m=[\d]*"/', $html);
$this->assertRegexp('/RequirementsTest_b\.js\?m=[\d]*&amp;foo=bar&amp;bla=blubb"/', $html);
$this->assertRegexp('/RequirementsTest_a\.css\?m=[\d]*"/', $html);
$this->assertRegexp('/RequirementsTest_b\.css\?m=[\d]*&amp;foo=bar&amp;bla=blubb"/', $html);
$backend->set_suffix_requirements(false);
$html = $backend->includeInHTML(false, $template);
$this->assertNotContains('RequirementsTest_a.js=', $html);
$this->assertNotRegexp('/RequirementsTest_a\.js\?m=[\d]*/', $html);
$this->assertNotRegexp('/RequirementsTest_b\.js\?m=[\d]*&amp;foo=bar&amp;bla=blubb/', $html);
$this->assertNotRegexp('/RequirementsTest_a\.css\?m=[\d]*/', $html);
$this->assertNotRegexp('/RequirementsTest_b\.css\?m=[\d]*&amp;foo=bar&amp;bla=blubb/', $html);
$this->assertNotRegexp('/RequirementsTest_a\.js\?m=[\d]*"/', $html);
$this->assertNotRegexp('/RequirementsTest_b\.js\?m=[\d]*&amp;foo=bar&amp;bla=blubb"/', $html);
$this->assertNotRegexp('/RequirementsTest_a\.css\?m=[\d]*"/', $html);
$this->assertNotRegexp('/RequirementsTest_b\.css\?m=[\d]*&amp;foo=bar&amp;bla=blubb"/', $html);
}
public function assertFileIncluded($backend, $type, $files) {

View File

@ -327,6 +327,14 @@ _t("i18nTestModule.INJECTIONS3", "Hello {name} {greeting}. But it is late, {good
"New context (this should be ignored)",
array("name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen"));
_t('i18nTestModule.INJECTIONS4', array("name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"));
_t('i18nTestModule.INJECTIONS5','_DOES_NOT_EXIST', "Hello {name} {greeting}. But it is late, {goodbye}",
["name"=>"Mark", "greeting"=>"welcome", "goodbye"=>"bye"]);
_t('i18nTestModule.INJECTIONS6', "Hello {name} {greeting}. But it is late, {goodbye}",
["name"=>"Paul", "greeting"=>"good you are here", "goodbye"=>"see you"]);
_t("i18nTestModule.INJECTIONS7", "Hello {name} {greeting}. But it is late, {goodbye}",
"New context (this should be ignored)",
["name"=>"Steffen", "greeting"=>"willkommen", "goodbye"=>"wiedersehen"]);
_t('i18nTestModule.INJECTIONS8', ["name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow"]);
PHP;
$collectedTranslatables = $c->collectFromCode($php, 'mymodule');
@ -338,6 +346,11 @@ PHP;
'i18nTestModule.INJECTIONS2' => array("Hello {name} {greeting}. But it is late, {goodbye}"),
'i18nTestModule.INJECTIONS3' => array("Hello {name} {greeting}. But it is late, {goodbye}",
"New context (this should be ignored)"),
'i18nTestModule.INJECTIONS5' => array("_DOES_NOT_EXIST",
"Hello {name} {greeting}. But it is late, {goodbye}"),
'i18nTestModule.INJECTIONS6' => array("Hello {name} {greeting}. But it is late, {goodbye}"),
'i18nTestModule.INJECTIONS7' => array("Hello {name} {greeting}. But it is late, {goodbye}",
"New context (this should be ignored)"),
));
ksort($expectedArray);

View File

@ -671,78 +671,9 @@ class SSObjectCreator extends InjectionCreator {
if (strpos($class, '(') === false) {
return parent::create($class, $params);
} else {
list($class, $params) = self::parse_class_spec($class);
list($class, $params) = Object::parse_class_spec($class);
$params = $this->injector->convertServiceProperty($params);
return parent::create($class, $params);
}
}
/**
* Parses a class-spec, such as "Versioned('Stage','Live')", as passed to create_from_string().
* Returns a 2-elemnent array, with classname and arguments
*/
public static function parse_class_spec($classSpec) {
$tokens = token_get_all("<?php $classSpec");
$class = null;
$args = array();
$passedBracket = false;
// Keep track of the current bucket that we're putting data into
$bucket = &$args;
$bucketStack = array();
foreach($tokens as $token) {
$tName = is_array($token) ? $token[0] : $token;
// Get the class naem
if($class == null && is_array($token) && $token[0] == T_STRING) {
$class = $token[1];
// Get arguments
} else if(is_array($token)) {
switch($token[0]) {
case T_CONSTANT_ENCAPSED_STRING:
$argString = $token[1];
switch($argString[0]) {
case '"': $argString = stripcslashes(substr($argString,1,-1)); break;
case "'":
$argString = str_replace(array("\\\\", "\\'"),array("\\", "'"), substr($argString,1,-1));
break;
default: throw new Exception("Bad T_CONSTANT_ENCAPSED_STRING arg $argString");
}
$bucket[] = $argString;
break;
case T_DNUMBER:
$bucket[] = (double)$token[1];
break;
case T_LNUMBER:
$bucket[] = (int)$token[1];
break;
case T_STRING:
switch($token[1]) {
case 'true': $args[] = true; break;
case 'false': $args[] = false; break;
default: throw new Exception("Bad T_STRING arg '{$token[1]}'");
}
case T_ARRAY:
// Add an empty array to the bucket
$bucket[] = array();
$bucketStack[] = &$bucket;
$bucket = &$bucket[sizeof($bucket)-1];
}
} else {
if($tName == ')') {
// Pop-by-reference
$bucket = &$bucketStack[sizeof($bucketStack)-1];
array_pop($bucketStack);
}
}
}
return array($class, $args);
}
}

View File

@ -8,6 +8,9 @@ class DataExtensionTest extends SapphireTest {
'DataExtensionTest_Player',
'DataExtensionTest_RelatedObject',
'DataExtensionTest_MyObject',
'DataExtensionTest_CMSFieldsBase',
'DataExtensionTest_CMSFieldsChild',
'DataExtensionTest_CMSFieldsGrandchild'
);
protected $requiredExtensions = array(
@ -156,6 +159,61 @@ class DataExtensionTest extends SapphireTest {
$this->assertEquals("hello world", $mo->testMethodApplied());
$this->assertEquals("hello world", $do->testMethodApplied());
}
public function testPageFieldGeneration() {
$page = new DataExtensionTest_CMSFieldsBase();
$fields = $page->getCMSFields();
$this->assertNotEmpty($fields);
// Check basic field exists
$this->assertNotEmpty($fields->dataFieldByName('PageField'));
}
public function testPageExtensionsFieldGeneration() {
$page = new DataExtensionTest_CMSFieldsBase();
$fields = $page->getCMSFields();
$this->assertNotEmpty($fields);
// Check extending fields exist
$this->assertNotEmpty($fields->dataFieldByName('ExtendedFieldRemove')); // Not removed yet!
$this->assertNotEmpty($fields->dataFieldByName('ExtendedFieldKeep'));
}
public function testSubpageFieldGeneration() {
$page = new DataExtensionTest_CMSFieldsChild();
$fields = $page->getCMSFields();
$this->assertNotEmpty($fields);
// Check extending fields exist
$this->assertEmpty($fields->dataFieldByName('ExtendedFieldRemove')); // Removed by child class
$this->assertNotEmpty($fields->dataFieldByName('ExtendedFieldKeep'));
$this->assertNotEmpty($preExtendedField = $fields->dataFieldByName('ChildFieldBeforeExtension'));
$this->assertEquals($preExtendedField->Title(), 'ChildFieldBeforeExtension: Modified Title');
// Post-extension fields
$this->assertNotEmpty($fields->dataFieldByName('ChildField'));
}
public function testSubSubpageFieldGeneration() {
$page = new DataExtensionTest_CMSFieldsGrandchild();
$fields = $page->getCMSFields();
$this->assertNotEmpty($fields);
// Check extending fields exist
$this->assertEmpty($fields->dataFieldByName('ExtendedFieldRemove')); // Removed by child class
$this->assertNotEmpty($fields->dataFieldByName('ExtendedFieldKeep'));
// Check child fields removed by grandchild in beforeUpdateCMSFields
$this->assertEmpty($fields->dataFieldByName('ChildFieldBeforeExtension')); // Removed by grandchild class
// Check grandchild field modified by extension
$this->assertNotEmpty($preExtendedField = $fields->dataFieldByName('GrandchildFieldBeforeExtension'));
$this->assertEquals($preExtendedField->Title(), 'GrandchildFieldBeforeExtension: Modified Title');
// Post-extension fields
$this->assertNotEmpty($fields->dataFieldByName('ChildField'));
$this->assertNotEmpty($fields->dataFieldByName('GrandchildField'));
}
}
class DataExtensionTest_Member extends DataObject implements TestOnly {
@ -313,3 +371,93 @@ DataExtensionTest_MyObject::add_extension('DataExtensionTest_Ext1');
DataExtensionTest_MyObject::add_extension('DataExtensionTest_Ext2');
DataExtensionTest_MyObject::add_extension('DataExtensionTest_Faves');
/**
* Base class for CMS fields
*/
class DataExtensionTest_CMSFieldsBase extends DataObject implements TestOnly {
private static $db = array(
'PageField' => 'Varchar(255)'
);
private static $extensions = array(
'DataExtensionTest_CMSFieldsBaseExtension'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Test', new TextField('PageField'));
return $fields;
}
}
/**
* Extension to top level test class, tests that updateCMSFields work
*/
class DataExtensionTest_CMSFieldsBaseExtension extends DataExtension implements TestOnly {
private static $db = array(
'ExtendedFieldKeep' => 'Varchar(255)',
'ExtendedFieldRemove' => 'Varchar(255)'
);
public function updateCMSFields(FieldList $fields) {
$fields->addFieldToTab('Root.Test', new TextField('ExtendedFieldRemove'));
$fields->addFieldToTab('Root.Test', new TextField('ExtendedFieldKeep'));
if($childField = $fields->dataFieldByName('ChildFieldBeforeExtension')) {
$childField->setTitle('ChildFieldBeforeExtension: Modified Title');
}
if($grandchildField = $fields->dataFieldByName('GrandchildFieldBeforeExtension')) {
$grandchildField->setTitle('GrandchildFieldBeforeExtension: Modified Title');
}
}
}
/**
* Second level test class.
* Tests usage of beforeExtendingCMSFields
*/
class DataExtensionTest_CMSFieldsChild extends DataExtensionTest_CMSFieldsBase implements TestOnly {
private static $db = array(
'ChildField' => 'Varchar(255)',
'ChildFieldBeforeExtension' => 'Varchar(255)'
);
public function getCMSFields() {
$this->beforeExtending('updateCMSFields', function(FieldList $fields) {
$fields->addFieldToTab('Root.Test', new TextField('ChildFieldBeforeExtension'));
});
$this->afterExtending('updateCMSFields', function(FieldList $fields){
$fields->removeByName('ExtendedFieldRemove', true);
});
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Test', new TextField('ChildField'));
return $fields;
}
}
/**
* Third level test class, testing that beforeExtendingCMSFields can be nested
*/
class DataExtensionTest_CMSFieldsGrandchild extends DataExtensionTest_CMSFieldsChild implements TestOnly {
private static $db = array(
'GrandchildField' => 'Varchar(255)'
);
public function getCMSFields() {
$this->beforeUpdateCMSFields(function(FieldList $fields) {
// Remove field from parent's beforeExtendingCMSFields
$fields->removeByName('ChildFieldBeforeExtension', true);
// Adds own pre-extension field
$fields->addFieldToTab('Root.Test', new TextField('GrandchildFieldBeforeExtension'));
});
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Test', new TextField('GrandchildField'));
return $fields;
}
}

View File

@ -8,6 +8,26 @@ class DataObjectDuplicationTest extends SapphireTest {
'DataObjectDuplicateTestClass3'
);
public function testDuplicate() {
$orig = new DataObjectDuplicateTestClass1();
$orig->text = 'foo';
$orig->write();
$duplicate = $orig->duplicate();
$this->assertInstanceOf('DataObjectDuplicateTestClass1', $duplicate,
'Creates the correct type'
);
$this->assertNotEquals($duplicate->ID, $orig->ID,
'Creates a unique record'
);
$this->assertEquals('foo', $duplicate->text,
'Copies fields'
);
$this->assertEquals(2, DataObjectDuplicateTestClass1::get()->Count(),
'Only creates a single duplicate'
);
}
public function testDuplicateManyManyClasses() {
//create new test classes below
$one = new DataObjectDuplicateTestClass1();

View File

@ -103,6 +103,8 @@ class HTMLTextTest extends SapphireTest {
'<h1>should ignore</h1><p>First Mr. sentence. Second sentence.</p>' => 'First Mr. sentence.',
"<h1>should ignore</h1><p>Sentence with {$many}words. Second sentence.</p>"
=> "Sentence with {$many}words.",
'<p>This classic picture book features a repetitive format that lends itself to audience interaction.&nbsp; Illustrator Eric Carle submitted new, bolder artwork for the 25th anniversary edition.</p>'
=> 'This classic picture book features a repetitive format that lends itself to audience interaction.'
);
foreach($cases as $orig => $match) {

View File

@ -17,6 +17,7 @@ class MoneyTest extends SapphireTest {
protected $extraDataObjects = array(
'MoneyTest_DataObject',
'MoneyTest_SubClass',
);
public function testMoneyFieldsReturnedAsObjects() {
@ -268,6 +269,15 @@ class MoneyTest extends SapphireTest {
))->value()
);
}
public function testMoneyLazyLoading() {
// Get the object, ensuring that MyOtherMoney will be lazy loaded
$id = $this->idFromFixture('MoneyTest_SubClass', 'test2');
$obj = MoneyTest_DataObject::get()->byID($id);
$this->assertEquals('£2.46', $obj->obj('MyOtherMoney')->Nice());
}
}
class MoneyTest_DataObject extends DataObject implements TestOnly {
@ -277,3 +287,9 @@ class MoneyTest_DataObject extends DataObject implements TestOnly {
);
}
class MoneyTest_SubClass extends MoneyTest_DataObject implements TestOnly {
static $db = array(
'MyOtherMoney' => 'Money',
);
}

View File

@ -2,3 +2,7 @@ MoneyTest_DataObject:
test1:
MyMoneyCurrency: EUR
MyMoneyAmount: 1.23
MoneyTest_SubClass:
test2:
MyOtherMoneyCurrency: GBP
MyOtherMoneyAmount: 2.46

View File

@ -5,6 +5,8 @@
*/
class URLSegmentFilterTest extends SapphireTest {
protected $usesDatabase = false;
public function testReplacesCommonEnglishSymbols() {
$f = new URLSegmentFilter();
$f->setAllowMultibyte(false);
@ -14,6 +16,19 @@ class URLSegmentFilterTest extends SapphireTest {
);
}
public function testReplacesWhitespace() {
$f = new URLSegmentFilter();
$f->setAllowMultibyte(false);
$this->assertEquals(
'john-and-spencer',
$f->filter('John and Spencer')
);
$this->assertEquals(
'john-and-spencer',
$f->filter('John+and+Spencer')
);
}
public function testTransliteratesNonAsciiUrls() {
$f = new URLSegmentFilter();
$f->setAllowMultibyte(false);

View File

@ -1,11 +1,17 @@
<?php
/**
* @package framework
* @subpackage tests
*/
class VersionedTest extends SapphireTest {
protected static $fixture_file = 'VersionedTest.yml';
protected $extraDataObjects = array(
'VersionedTest_DataObject',
'VersionedTest_Subclass'
'VersionedTest_Subclass',
'VersionedTest_RelatedWithoutVersion'
);
protected $requiredExtensions = array(
@ -123,6 +129,13 @@ class VersionedTest extends SapphireTest {
$this->assertInstanceOf('Int', $obj2->dbObject('Version'));
}
public function testVersionedFieldsNotInCMS() {
$obj = new VersionedTest_DataObject();
// the version field in cms causes issues with Versioned::augmentWrite()
$this->assertNull($obj->getCMSFields()->dataFieldByName('Version'));
}
public function testPublishCreateNewVersion() {
$page1 = $this->objFromFixture('VersionedTest_DataObject', 'page1');
$page1->Content = 'orig';
@ -407,8 +420,45 @@ class VersionedTest extends SapphireTest {
'Additional version fields returned');
$this->assertEquals($extraFields, array('2005', '2007', '2009'), 'Additional version fields returned');
}
public function testArchiveRelatedDataWithoutVersioned() {
SS_Datetime::set_mock_now('2009-01-01 00:00:00');
$relatedData = new VersionedTest_RelatedWithoutVersion();
$relatedData->Name = 'Related Data';
$relatedDataId = $relatedData->write();
$testData = new VersionedTest_DataObject();
$testData->Title = 'Test';
$testData->Content = 'Before Content';
$testData->Related()->add($relatedData);
$id = $testData->write();
SS_Datetime::set_mock_now('2010-01-01 00:00:00');
$testData->Content = 'After Content';
$testData->write();
$_GET['archiveDate'] = '2009-01-01 19:00:00';
Versioned::reading_archived_date('2009-01-01 19:00:00');
$fetchedData = VersionedTest_DataObject::get()->byId($id);
$this->assertEquals('Before Content', $fetchedData->Content, 'We see the correct content of the older version');
$relatedData = VersionedTest_RelatedWithoutVersion::get()->byId($relatedDataId);
$this->assertEquals(
1,
$relatedData->Related()->count(),
'We have a relation, with no version table, querying it still works'
);
}
}
/**
* @package framework
* @subpackage tests
*/
class VersionedTest_DataObject extends DataObject implements TestOnly {
private static $db = array(
"Name" => "Varchar",
@ -423,8 +473,33 @@ class VersionedTest_DataObject extends DataObject implements TestOnly {
private static $has_one = array(
'Parent' => 'VersionedTest_DataObject'
);
private static $many_many = array(
'Related' => 'VersionedTest_RelatedWithoutVersion'
);
}
/**
* @package framework
* @subpackage tests
*/
class VersionedTest_RelatedWithoutVersion extends DataObject implements TestOnly {
private static $db = array(
'Name' => 'Varchar'
);
private static $belongs_many_many = array(
'Related' => 'VersionedTest_DataObject'
);
}
/**
* @package framework
* @subpackage tests
*/
class VersionedTest_Subclass extends VersionedTest_DataObject implements TestOnly {
private static $db = array(
"ExtraField" => "Varchar",
@ -436,7 +511,8 @@ class VersionedTest_Subclass extends VersionedTest_DataObject implements TestOnl
}
/**
* @ignore
* @package framework
* @subpackage tests
*/
class VersionedTest_UnversionedWithField extends DataObject implements TestOnly {
private static $db = array('Version' => 'Varchar(255)');

View File

@ -14,14 +14,6 @@
<!-- PHP-PEG generated file not intended for human consumption -->
<exclude-pattern>*/SSTemplateParser.php$</exclude-pattern>
<rule ref="Generic.Files.LineEndings.InvalidEOLChar">
<severity>8</severity>
</rule>
<rule ref="Generic.Files.LineEndings">
<properties>
<property name="eolChar" value="\n" />
</properties>
</rule>
<rule ref="Generic.Files.LineLength.TooLong">
<severity>7</severity>
</rule>

View File

@ -14,5 +14,13 @@
<!-- PHP-PEG generated file not intended for human consumption -->
<exclude-pattern>*/SSTemplateParser.php$</exclude-pattern>
<rule ref="Generic.WhiteSpace.DisallowSpaceIndent"/>
<rule ref="Generic.Files.LineEndings.InvalidEOLChar">
<severity>8</severity>
</rule>
<rule ref="Generic.Files.LineEndings">
<properties>
<property name="eolChar" value="\n" />
</properties>
</rule>
</ruleset>

48
tests/phpcs_runner.php Normal file
View File

@ -0,0 +1,48 @@
<?php
if(php_sapi_name() != 'cli') {
die("This script must be called from the command line\n");
}
if(!empty($_SERVER['argv'][1])) {
$path = $_SERVER['argv'][1];
} else {
die("Usage: php {$_SERVER['argv'][0]} <file>\n");
}
$result = array('comments' => array());
// Run each sniff
// phpcs --encoding=utf-8 --standard=framework/tests/phpcs/tabs.xml
run_sniff('tabs.xml', $path, $result);
// phpcs --encoding=utf-8 --tab-width=4 --standard=framework/tests/phpcs/ruleset.xml
run_sniff('ruleset.xml', $path, $result, '--tab-width=4');
echo json_encode($result);
function run_sniff($standard, $path, array &$result, $extraFlags = '') {
$sniffPath = escapeshellarg(__DIR__ . '/phpcs/' . $standard);
$checkPath = escapeshellarg($path);
exec("phpcs --encoding=utf-8 $extraFlags --standard=$sniffPath --report=xml $checkPath", $output);
// We can't check the return code as it's non-zero if the sniff finds an error
if($output) {
$xml = implode("\n", $output);
$xml = simplexml_load_string($xml);
$errors = $xml->xpath('/phpcs/file/error');
if($errors) {
$sanePath = str_replace('/', '_', $path);
foreach($errors as $error) {
$attributes = $error->attributes();
$result['comments'][] = array(
'line' => (int)strval($attributes->line),
'id' => $standard . '-' . $sanePath . '-' . $attributes->line . '-' . $attributes->column,
'message' => strval($error)
);
}
}
}
}

4
thirdparty/Zend/Locale.php vendored Normal file → Executable file
View File

@ -120,6 +120,7 @@ class Zend_Locale
// Remove once updating Zend_Locale to 2.0 (which uses CLDR 1.9), see
// https://github.com/zendframework/zf2/tree/master/resources/cldr/main
'mi' => true, 'mi_NZ' => true,
'lc' => true, 'lc_XX' => true,
);
/**
@ -180,7 +181,8 @@ class Zend_Locale
// CUSTOM SilverStripe Add Maori locales.
// Remove once updating Zend_Locale to 2.0 (which uses CLDR 1.9), see
// https://github.com/zendframework/zf2/tree/master/resources/cldr/main
'MI' => 'mi_NZ'
'MI' => 'mi_NZ',
'LC' => 'lc_XX',
);
/**

4107
thirdparty/Zend/Locale/Data/lc.xml vendored Executable file
View File

@ -0,0 +1,4107 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ldml>
<identity>
<version number="$Revision: 1.234 $"/>
<generation date="$Date: 2009/06/17 16:15:14 $"/>
<language type="lc"/>
</identity>
<localeDisplayNames>
<localeDisplayPattern>
<localePattern>{0} ({1})</localePattern>
<localeSeparator>, </localeSeparator>
</localeDisplayPattern>
<languages>
<language type="aa">Afar</language>
<language type="ab">Abkhazian</language>
<language type="ace">Achinese</language>
<language type="ach">Acoli</language>
<language type="ada">Adangme</language>
<language type="ady">Adyghe</language>
<language type="ae">Avestan</language>
<language type="af">Afrikaans</language>
<language type="afa">Afro-Asiatic Language</language>
<language type="afh">Afrihili</language>
<language type="ain">Ainu</language>
<language type="ak">Akan</language>
<language type="akk">Akkadian</language>
<language type="ale">Aleut</language>
<language type="alg">Algonquian Language</language>
<language type="alt">Southern Altai</language>
<language type="am">Amharic</language>
<language type="an">Aragonese</language>
<language type="ang">Old English</language>
<language type="anp">Angika</language>
<language type="apa">Apache Language</language>
<language type="ar">Arabic</language>
<language type="arc">Aramaic</language>
<language type="arn">Araucanian</language>
<language type="arp">Arapaho</language>
<language type="art">Artificial Language</language>
<language type="arw">Arawak</language>
<language type="as">Assamese</language>
<language type="ast">Asturian</language>
<language type="ath">Athapascan Language</language>
<language type="aus">Australian Language</language>
<language type="av">Avaric</language>
<language type="awa">Awadhi</language>
<language type="ay">Aymara</language>
<language type="az">Azerbaijani</language>
<language type="ba">Bashkir</language>
<language type="bad">Banda</language>
<language type="bai">Bamileke Language</language>
<language type="bal">Baluchi</language>
<language type="ban">Balinese</language>
<language type="bas">Basa</language>
<language type="bat">Baltic Language</language>
<language type="be">Belarusian</language>
<language type="bej">Beja</language>
<language type="bem">Bemba</language>
<language type="ber">Berber</language>
<language type="bg">Bulgarian</language>
<language type="bh">Bihari</language>
<language type="bho">Bhojpuri</language>
<language type="bi">Bislama</language>
<language type="bik">Bikol</language>
<language type="bin">Bini</language>
<language type="bla">Siksika</language>
<language type="bm">Bambara</language>
<language type="bn">Bengali</language>
<language type="bnt">Bantu</language>
<language type="bo">Tibetan</language>
<language type="br">Breton</language>
<language type="bra">Braj</language>
<language type="bs">Bosnian</language>
<language type="btk">Batak</language>
<language type="bua">Buriat</language>
<language type="bug">Buginese</language>
<language type="byn">Blin</language>
<language type="ca">Catalan</language>
<language type="cad">Caddo</language>
<language type="cai">Central American Indian Language</language>
<language type="car">Carib</language>
<language type="cau">Caucasian Language</language>
<language type="cch">Atsam</language>
<language type="ce">Chechen</language>
<language type="ceb">Cebuano</language>
<language type="cel">Celtic Language</language>
<language type="ch">Chamorro</language>
<language type="chb">Chibcha</language>
<language type="chg">Chagatai</language>
<language type="chk">Chuukese</language>
<language type="chm">Mari</language>
<language type="chn">Chinook Jargon</language>
<language type="cho">Choctaw</language>
<language type="chp">Chipewyan</language>
<language type="chr">Cherokee</language>
<language type="chy">Cheyenne</language>
<language type="cmc">Chamic Language</language>
<language type="co">Corsican</language>
<language type="cop">Coptic</language>
<language type="cpe">English-based Creole or Pidgin</language>
<language type="cpf">French-based Creole or Pidgin</language>
<language type="cpp">Portuguese-based Creole or Pidgin</language>
<language type="cr">Cree</language>
<language type="crh">Crimean Turkish</language>
<language type="crp">Creole or Pidgin</language>
<language type="cs">Czech</language>
<language type="csb">Kashubian</language>
<language type="cu">Church Slavic</language>
<language type="cus">Cushitic Language</language>
<language type="cv">Chuvash</language>
<language type="cy">Welsh</language>
<language type="da">Danish</language>
<language type="dak">Dakota</language>
<language type="dar">Dargwa</language>
<language type="day">Dayak</language>
<language type="de">German</language>
<language type="de_AT">Austrian German</language>
<language type="de_CH">Swiss High German</language>
<language type="del">Delaware</language>
<language type="den">Slave</language>
<language type="dgr">Dogrib</language>
<language type="din">Dinka</language>
<language type="doi">Dogri</language>
<language type="dra">Dravidian Language</language>
<language type="dsb">Lower Sorbian</language>
<language type="dua">Duala</language>
<language type="dum">Middle Dutch</language>
<language type="dv">Divehi</language>
<language type="dyu">Dyula</language>
<language type="dz">Dzongkha</language>
<language type="ee">Ewe</language>
<language type="efi">Efik</language>
<language type="egy">Ancient Egyptian</language>
<language type="eka">Ekajuk</language>
<language type="el">Greek</language>
<language type="elx">Elamite</language>
<language type="en">English</language>
<language type="en_AU">Australian English</language>
<language type="en_CA">Canadian English</language>
<language type="en_GB">British English</language>
<language type="en_US">U.S. English</language>
<language type="enm">Middle English</language>
<language type="eo">Esperanto</language>
<language type="es">Spanish</language>
<language type="es_419">Latin American Spanish</language>
<language type="es_ES">Iberian Spanish</language>
<language type="et">Estonian</language>
<language type="eu">Basque</language>
<language type="ewo">Ewondo</language>
<language type="fa">Persian</language>
<language type="fan">Fang</language>
<language type="fat">Fanti</language>
<language type="ff">Fulah</language>
<language type="fi">Finnish</language>
<language type="fil">Filipino</language>
<language type="fiu">Finno-Ugrian Language</language>
<language type="fj">Fijian</language>
<language type="fo">Faroese</language>
<language type="fon">Fon</language>
<language type="fr">French</language>
<language type="fr_CA">Canadian French</language>
<language type="fr_CH">Swiss French</language>
<language type="frm">Middle French</language>
<language type="fro">Old French</language>
<language type="frr">Northern Frisian</language>
<language type="frs">Eastern Frisian</language>
<language type="fur">Friulian</language>
<language type="fy">Western Frisian</language>
<language type="ga">Irish</language>
<language type="gaa">Ga</language>
<language type="gay">Gayo</language>
<language type="gba">Gbaya</language>
<language type="gd">Scottish Gaelic</language>
<language type="gem">Germanic Language</language>
<language type="gez">Geez</language>
<language type="gil">Gilbertese</language>
<language type="gl">Galician</language>
<language type="gmh">Middle High German</language>
<language type="gn">Guarani</language>
<language type="goh">Old High German</language>
<language type="gon">Gondi</language>
<language type="gor">Gorontalo</language>
<language type="got">Gothic</language>
<language type="grb">Grebo</language>
<language type="grc">Ancient Greek</language>
<language type="gsw">Swiss German</language>
<language type="gu">Gujarati</language>
<language type="gv">Manx</language>
<language type="gwi">Gwichʼin</language>
<language type="ha">Hausa</language>
<language type="hai">Haida</language>
<language type="haw">Hawaiian</language>
<language type="he">Hebrew</language>
<language type="hi">Hindi</language>
<language type="hil">Hiligaynon</language>
<language type="him">Himachali</language>
<language type="hit">Hittite</language>
<language type="hmn">Hmong</language>
<language type="ho">Hiri Motu</language>
<language type="hr">Croatian</language>
<language type="hsb">Upper Sorbian</language>
<language type="ht">Haitian</language>
<language type="hu">Hungarian</language>
<language type="hup">Hupa</language>
<language type="hy">Armenian</language>
<language type="hz">Herero</language>
<language type="ia">Interlingua</language>
<language type="iba">Iban</language>
<language type="id">Indonesian</language>
<language type="ie">Interlingue</language>
<language type="ig">Igbo</language>
<language type="ii">Sichuan Yi</language>
<language type="ijo">Ijo</language>
<language type="ik">Inupiaq</language>
<language type="ilo">Iloko</language>
<language type="inc">Indic Language</language>
<language type="ine">Indo-European Language</language>
<language type="inh">Ingush</language>
<language type="io">Ido</language>
<language type="ira">Iranian Language</language>
<language type="iro">Iroquoian Language</language>
<language type="is">Icelandic</language>
<language type="it">Italian</language>
<language type="iu">Inuktitut</language>
<language type="ja">Japanese</language>
<language type="jbo">Lojban</language>
<language type="jpr">Judeo-Persian</language>
<language type="jrb">Judeo-Arabic</language>
<language type="jv">Javanese</language>
<language type="ka">Georgian</language>
<language type="kaa">Kara-Kalpak</language>
<language type="kab">Kabyle</language>
<language type="kac">Kachin</language>
<language type="kaj">Jju</language>
<language type="kam">Kamba</language>
<language type="kar">Karen</language>
<language type="kaw">Kawi</language>
<language type="kbd">Kabardian</language>
<language type="kcg">Tyap</language>
<language type="kfo">Koro</language>
<language type="kg">Kongo</language>
<language type="kha">Khasi</language>
<language type="khi">Khoisan Language</language>
<language type="kho">Khotanese</language>
<language type="ki">Kikuyu</language>
<language type="kj">Kuanyama</language>
<language type="kk">Kazakh</language>
<language type="kl">Kalaallisut</language>
<language type="km">Khmer</language>
<language type="kmb">Kimbundu</language>
<language type="kn">Kannada</language>
<language type="ko">Korean</language>
<language type="kok">Konkani</language>
<language type="kos">Kosraean</language>
<language type="kpe">Kpelle</language>
<language type="kr">Kanuri</language>
<language type="krc">Karachay-Balkar</language>
<language type="krl">Karelian</language>
<language type="kro">Kru</language>
<language type="kru">Kurukh</language>
<language type="ks">Kashmiri</language>
<language type="ku">Kurdish</language>
<language type="kum">Kumyk</language>
<language type="kut">Kutenai</language>
<language type="kv">Komi</language>
<language type="kw">Cornish</language>
<language type="ky">Kirghiz</language>
<language type="la">Latin</language>
<language type="lad">Ladino</language>
<language type="lah">Lahnda</language>
<language type="lam">Lamba</language>
<language type="lb">Luxembourgish</language>
<language type="lez">Lezghian</language>
<language type="lg">Ganda</language>
<language type="li">Limburgish</language>
<language type="ln">Lingala</language>
<language type="lo">Lao</language>
<language type="lol">Mongo</language>
<language type="loz">Lozi</language>
<language type="lt">Lithuanian</language>
<language type="lu">Luba-Katanga</language>
<language type="lua">Luba-Lulua</language>
<language type="lui">Luiseno</language>
<language type="lun">Lunda</language>
<language type="luo">Luo</language>
<language type="lus">Lushai</language>
<language type="lv">Latvian</language>
<language type="mad">Madurese</language>
<language type="mag">Magahi</language>
<language type="mai">Maithili</language>
<language type="mak">Makasar</language>
<language type="man">Mandingo</language>
<language type="map">Austronesian Language</language>
<language type="mas">Masai</language>
<language type="mdf">Moksha</language>
<language type="mdr">Mandar</language>
<language type="men">Mende</language>
<language type="mfe">Morisyen</language>
<language type="mg">Malagasy</language>
<language type="mga">Middle Irish</language>
<language type="mh">Marshallese</language>
<language type="mi">Maori</language>
<language type="mic">Micmac</language>
<language type="min">Minangkabau</language>
<language type="mis">Miscellaneous Language</language>
<language type="mk">Macedonian</language>
<language type="mkh">Mon-Khmer Language</language>
<language type="ml">Malayalam</language>
<language type="mn">Mongolian</language>
<language type="mnc">Manchu</language>
<language type="mni">Manipuri</language>
<language type="mno">Manobo Language</language>
<language type="mo">Moldavian</language>
<language type="moh">Mohawk</language>
<language type="mos">Mossi</language>
<language type="mr">Marathi</language>
<language type="ms">Malay</language>
<language type="mt">Maltese</language>
<language type="mul">Multiple Languages</language>
<language type="mun">Munda Language</language>
<language type="mus">Creek</language>
<language type="mwl">Mirandese</language>
<language type="mwr">Marwari</language>
<language type="my">Burmese</language>
<language type="myn">Mayan Language</language>
<language type="myv">Erzya</language>
<language type="na">Nauru</language>
<language type="nah">Nahuatl</language>
<language type="nai">North American Indian Language</language>
<language type="nap">Neapolitan</language>
<language type="nb">Norwegian Bokmål</language>
<language type="nd">North Ndebele</language>
<language type="nds">Low German</language>
<language type="ne">Nepali</language>
<language type="new">Newari</language>
<language type="ng">Ndonga</language>
<language type="nia">Nias</language>
<language type="nic">Niger-Kordofanian Language</language>
<language type="niu">Niuean</language>
<language type="nl">Dutch</language>
<language type="nl_BE">Flemish</language>
<language type="nn">Norwegian Nynorsk</language>
<language type="no">Norwegian</language>
<language type="nog">Nogai</language>
<language type="non">Old Norse</language>
<language type="nqo">NKo</language>
<language type="nr">South Ndebele</language>
<language type="nso">Northern Sotho</language>
<language type="nub">Nubian Language</language>
<language type="nv">Navajo</language>
<language type="nwc">Classical Newari</language>
<language type="ny">Nyanja</language>
<language type="nym">Nyamwezi</language>
<language type="nyn">Nyankole</language>
<language type="nyo">Nyoro</language>
<language type="nzi">Nzima</language>
<language type="oc">Occitan</language>
<language type="oj">Ojibwa</language>
<language type="om">Oromo</language>
<language type="or">Oriya</language>
<language type="os">Ossetic</language>
<language type="osa">Osage</language>
<language type="ota">Ottoman Turkish</language>
<language type="oto">Otomian Language</language>
<language type="pa">Punjabi</language>
<language type="paa">Papuan Language</language>
<language type="pag">Pangasinan</language>
<language type="pal">Pahlavi</language>
<language type="pam">Pampanga</language>
<language type="pap">Papiamento</language>
<language type="pau">Palauan</language>
<language type="peo">Old Persian</language>
<language type="phi">Philippine Language</language>
<language type="phn">Phoenician</language>
<language type="pi">Pali</language>
<language type="pl">Polish</language>
<language type="pon">Pohnpeian</language>
<language type="pra">Prakrit Language</language>
<language type="pro">Old Provençal</language>
<language type="ps">Pashto</language>
<language type="pt">Portuguese</language>
<language type="pt_BR">Brazilian Portuguese</language>
<language type="pt_PT">Iberian Portuguese</language>
<language type="qu">Quechua</language>
<language type="raj">Rajasthani</language>
<language type="rap">Rapanui</language>
<language type="rar">Rarotongan</language>
<language type="rm">Rhaeto-Romance</language>
<language type="rn">Rundi</language>
<language type="ro">Romanian</language>
<language type="roa">Romance Language</language>
<language type="rom">Romany</language>
<language type="root">Root</language>
<language type="ru">Russian</language>
<language type="rup">Aromanian</language>
<language type="rw">Kinyarwanda</language>
<language type="sa">Sanskrit</language>
<language type="sad">Sandawe</language>
<language type="sah">Yakut</language>
<language type="sai">South American Indian Language</language>
<language type="sal">Salishan Language</language>
<language type="sam">Samaritan Aramaic</language>
<language type="sas">Sasak</language>
<language type="sat">Santali</language>
<language type="sc">Sardinian</language>
<language type="scn">Sicilian</language>
<language type="sco">Scots</language>
<language type="sd">Sindhi</language>
<language type="se">Northern Sami</language>
<language type="sel">Selkup</language>
<language type="sem">Semitic Language</language>
<language type="sg">Sango</language>
<language type="sga">Old Irish</language>
<language type="sgn">Sign Language</language>
<language type="sh">Serbo-Croatian</language>
<language type="shn">Shan</language>
<language type="si">Sinhala</language>
<language type="sid">Sidamo</language>
<language type="sio">Siouan Language</language>
<language type="sit">Sino-Tibetan Language</language>
<language type="sk">Slovak</language>
<language type="sl">Slovenian</language>
<language type="sla">Slavic Language</language>
<language type="sm">Samoan</language>
<language type="sma">Southern Sami</language>
<language type="smi">Sami Language</language>
<language type="smj">Lule Sami</language>
<language type="smn">Inari Sami</language>
<language type="sms">Skolt Sami</language>
<language type="sn">Shona</language>
<language type="snk">Soninke</language>
<language type="so">Somali</language>
<language type="sog">Sogdien</language>
<language type="son">Songhai</language>
<language type="sq">Albanian</language>
<language type="sr">Serbian</language>
<language type="srn">Sranan Tongo</language>
<language type="srr">Serer</language>
<language type="ss">Swati</language>
<language type="ssa">Nilo-Saharan Language</language>
<language type="st">Southern Sotho</language>
<language type="su">Sundanese</language>
<language type="suk">Sukuma</language>
<language type="sus">Susu</language>
<language type="sux">Sumerian</language>
<language type="sv">Swedish</language>
<language type="sw">Swahili</language>
<language type="syc">Classical Syriac</language>
<language type="syr">Syriac</language>
<language type="ta">Tamil</language>
<language type="tai">Tai Language</language>
<language type="te">Telugu</language>
<language type="tem">Timne</language>
<language type="ter">Tereno</language>
<language type="tet">Tetum</language>
<language type="tg">Tajik</language>
<language type="th">Thai</language>
<language type="ti">Tigrinya</language>
<language type="tig">Tigre</language>
<language type="tiv">Tiv</language>
<language type="tk">Turkmen</language>
<language type="tkl">Tokelau</language>
<language type="tl">Tagalog</language>
<language type="tlh">Klingon</language>
<language type="tli">Tlingit</language>
<language type="tmh">Tamashek</language>
<language type="tn">Tswana</language>
<language type="to">Tonga</language>
<language type="tog">Nyasa Tonga</language>
<language type="tpi">Tok Pisin</language>
<language type="tr">Turkish</language>
<language type="trv">Taroko</language>
<language type="ts">Tsonga</language>
<language type="tsi">Tsimshian</language>
<language type="tt">Tatar</language>
<language type="tum">Tumbuka</language>
<language type="tup">Tupi Language</language>
<language type="tut">Altaic Language</language>
<language type="tvl">Tuvalu</language>
<language type="tw">Twi</language>
<language type="ty">Tahitian</language>
<language type="tyv">Tuvinian</language>
<language type="udm">Udmurt</language>
<language type="ug">Uighur</language>
<language type="uga">Ugaritic</language>
<language type="uk">Ukrainian</language>
<language type="umb">Umbundu</language>
<language type="und">Unknown or Invalid Language</language>
<language type="ur">Urdu</language>
<language type="uz">Uzbek</language>
<language type="vai">Vai</language>
<language type="ve">Venda</language>
<language type="vi">Vietnamese</language>
<language type="vo">Volapük</language>
<language type="vot">Votic</language>
<language type="wa">Walloon</language>
<language type="wak">Wakashan Language</language>
<language type="wal">Walamo</language>
<language type="war">Waray</language>
<language type="was">Washo</language>
<language type="wen">Sorbian Language</language>
<language type="wo">Wolof</language>
<language type="xal">Kalmyk</language>
<language type="xh">Xhosa</language>
<language type="yao">Yao</language>
<language type="yap">Yapese</language>
<language type="yi">Yiddish</language>
<language type="yo">Yoruba</language>
<language type="ypk">Yupik Language</language>
<language type="za">Zhuang</language>
<language type="zap">Zapotec</language>
<language type="zbl">Blissymbols</language>
<language type="zen">Zenaga</language>
<language type="zh">Chinese</language>
<language type="zh_Hans">Simplified Chinese</language>
<language type="zh_Hant">Traditional Chinese</language>
<language type="znd">Zande</language>
<language type="zu">Zulu</language>
<language type="zun">Zuni</language>
<language type="zxx">No linguistic content</language>
<language type="zza">Zaza</language>
</languages>
<scripts>
<script type="Arab">Arabic</script>
<script type="Armi">Imperial Aramaic</script>
<script type="Armn">Armenian</script>
<script type="Avst">Avestan</script>
<script type="Bali">Balinese</script>
<script type="Batk">Batak</script>
<script type="Beng">Bengali</script>
<script type="Blis">Blissymbols</script>
<script type="Bopo">Bopomofo</script>
<script type="Brah">Brahmi</script>
<script type="Brai">Braille</script>
<script type="Bugi">Buginese</script>
<script type="Buhd">Buhid</script>
<script type="Cakm">Chakma</script>
<script type="Cans">Unified Canadian Aboriginal Syllabics</script>
<script type="Cari">Carian</script>
<script type="Cham">Cham</script>
<script type="Cher">Cherokee</script>
<script type="Cirt">Cirth</script>
<script type="Copt">Coptic</script>
<script type="Cprt">Cypriot</script>
<script type="Cyrl">Cyrillic</script>
<script type="Cyrs">Old Church Slavonic Cyrillic</script>
<script type="Deva">Devanagari</script>
<script type="Dsrt">Deseret</script>
<script type="Egyd">Egyptian demotic</script>
<script type="Egyh">Egyptian hieratic</script>
<script type="Egyp">Egyptian hieroglyphs</script>
<script type="Ethi">Ethiopic</script>
<script type="Geok">Georgian Khutsuri</script>
<script type="Geor">Georgian</script>
<script type="Glag">Glagolitic</script>
<script type="Goth">Gothic</script>
<script type="Grek">Greek</script>
<script type="Gujr">Gujarati</script>
<script type="Guru">Gurmukhi</script>
<script type="Hang">Hangul</script>
<script type="Hani">Han</script>
<script type="Hano">Hanunoo</script>
<script type="Hans">Simplified Han</script>
<script type="Hant">Traditional Han</script>
<script type="Hebr">Hebrew</script>
<script type="Hira">Hiragana</script>
<script type="Hmng">Pahawh Hmong</script>
<script type="Hrkt">Katakana or Hiragana</script>
<script type="Hung">Old Hungarian</script>
<script type="Inds">Indus</script>
<script type="Ital">Old Italic</script>
<script type="Java">Javanese</script>
<script type="Jpan">Japanese</script>
<script type="Kali">Kayah Li</script>
<script type="Kana">Katakana</script>
<script type="Khar">Kharoshthi</script>
<script type="Khmr">Khmer</script>
<script type="Knda">Kannada</script>
<script type="Kore">Korean</script>
<script type="Kthi">Kaithi</script>
<script type="Lana">Lanna</script>
<script type="Laoo">Lao</script>
<script type="Latf">Fraktur Latin</script>
<script type="Latg">Gaelic Latin</script>
<script type="Latn">Latin</script>
<script type="Lepc">Lepcha</script>
<script type="Limb">Limbu</script>
<script type="Lina">Linear A</script>
<script type="Linb">Linear B</script>
<script type="Lyci">Lycian</script>
<script type="Lydi">Lydian</script>
<script type="Mand">Mandaean</script>
<script type="Mani">Manichaean</script>
<script type="Maya">Mayan hieroglyphs</script>
<script type="Mero">Meroitic</script>
<script type="Mlym">Malayalam</script>
<script type="Mong">Mongolian</script>
<script type="Moon">Moon</script>
<script type="Mtei">Meitei Mayek</script>
<script type="Mymr">Myanmar</script>
<script type="Nkoo">NKo</script>
<script type="Ogam">Ogham</script>
<script type="Olck">Ol Chiki</script>
<script type="Orkh">Orkhon</script>
<script type="Orya">Oriya</script>
<script type="Osma">Osmanya</script>
<script type="Perm">Old Permic</script>
<script type="Phag">Phags-pa</script>
<script type="Phli">Inscriptional Pahlavi</script>
<script type="Phlp">Psalter Pahlavi</script>
<script type="Phlv">Book Pahlavi</script>
<script type="Phnx">Phoenician</script>
<script type="Plrd">Pollard Phonetic</script>
<script type="Prti">Inscriptional Parthian</script>
<script type="Qaai">Inherited</script>
<script type="Rjng">Rejang</script>
<script type="Roro">Rongorongo</script>
<script type="Runr">Runic</script>
<script type="Samr">Samaritan</script>
<script type="Sara">Sarati</script>
<script type="Saur">Saurashtra</script>
<script type="Sgnw">SignWriting</script>
<script type="Shaw">Shavian</script>
<script type="Sinh">Sinhala</script>
<script type="Sund">Sundanese</script>
<script type="Sylo">Syloti Nagri</script>
<script type="Syrc">Syriac</script>
<script type="Syre">Estrangelo Syriac</script>
<script type="Syrj">Western Syriac</script>
<script type="Syrn">Eastern Syriac</script>
<script type="Tagb">Tagbanwa</script>
<script type="Tale">Tai Le</script>
<script type="Talu">New Tai Lue</script>
<script type="Taml">Tamil</script>
<script type="Tavt">Tai Viet</script>
<script type="Telu">Telugu</script>
<script type="Teng">Tengwar</script>
<script type="Tfng">Tifinagh</script>
<script type="Tglg">Tagalog</script>
<script type="Thaa">Thaana</script>
<script type="Thai">Thai</script>
<script type="Tibt">Tibetan</script>
<script type="Ugar">Ugaritic</script>
<script type="Vaii">Vai</script>
<script type="Visp">Visible Speech</script>
<script type="Xpeo">Old Persian</script>
<script type="Xsux">Sumero-Akkadian Cuneiform</script>
<script type="Yiii">Yi</script>
<script type="Zmth">Mathematical Notation</script>
<script type="Zsym">Symbols</script>
<script type="Zxxx">Unwritten</script>
<script type="Zyyy">Common</script>
<script type="Zzzz">Unknown or Invalid Script</script>
</scripts>
<territories>
<territory type="001">World</territory>
<territory type="002">Africa</territory>
<territory type="003">North America</territory>
<territory type="005">South America</territory>
<territory type="009">Oceania</territory>
<territory type="011">Western Africa</territory>
<territory type="013">Central America</territory>
<territory type="014">Eastern Africa</territory>
<territory type="015">Northern Africa</territory>
<territory type="017">Middle Africa</territory>
<territory type="018">Southern Africa</territory>
<territory type="019">Americas</territory>
<territory type="021">Northern America</territory>
<territory type="029">Caribbean</territory>
<territory type="030">Eastern Asia</territory>
<territory type="034">Southern Asia</territory>
<territory type="035">South-Eastern Asia</territory>
<territory type="039">Southern Europe</territory>
<territory type="053">Australia and New Zealand</territory>
<territory type="054">Melanesia</territory>
<territory type="057">Micronesian Region</territory>
<territory type="061">Polynesia</territory>
<territory type="062">South-Central Asia</territory>
<territory type="142">Asia</territory>
<territory type="143">Central Asia</territory>
<territory type="145">Western Asia</territory>
<territory type="150">Europe</territory>
<territory type="151">Eastern Europe</territory>
<territory type="154">Northern Europe</territory>
<territory type="155">Western Europe</territory>
<territory type="172">Commonwealth of Independent States</territory>
<territory type="200">Czechoslovakia</territory>
<territory type="419">Latin America and the Caribbean</territory>
<territory type="830">Channel Islands</territory>
<territory type="AD">Andorra</territory>
<territory type="AE">United Arab Emirates</territory>
<territory type="AF">Afghanistan</territory>
<territory type="AG">Antigua and Barbuda</territory>
<territory type="AI">Anguilla</territory>
<territory type="AL">Albania</territory>
<territory type="AM">Armenia</territory>
<territory type="AN">Netherlands Antilles</territory>
<territory type="AO">Angola</territory>
<territory type="AQ">Antarctica</territory>
<territory type="AR">Argentina</territory>
<territory type="AS">American Samoa</territory>
<territory type="AT">Austria</territory>
<territory type="AU">Australia</territory>
<territory type="AW">Aruba</territory>
<territory type="AX">Åland Islands</territory>
<territory type="AZ">Azerbaijan</territory>
<territory type="BA">Bosnia and Herzegovina</territory>
<territory type="BB">Barbados</territory>
<territory type="BD">Bangladesh</territory>
<territory type="BE">Belgium</territory>
<territory type="BF">Burkina Faso</territory>
<territory type="BG">Bulgaria</territory>
<territory type="BH">Bahrain</territory>
<territory type="BI">Burundi</territory>
<territory type="BJ">Benin</territory>
<territory type="BL">Saint Barthélemy</territory>
<territory type="BM">Bermuda</territory>
<territory type="BN">Brunei</territory>
<territory type="BO">Bolivia</territory>
<territory type="BQ">British Antarctic Territory</territory>
<territory type="BR">Brazil</territory>
<territory type="BS">Bahamas</territory>
<territory type="BT">Bhutan</territory>
<territory type="BV">Bouvet Island</territory>
<territory type="BW">Botswana</territory>
<territory type="BY">Belarus</territory>
<territory type="BZ">Belize</territory>
<territory type="CA">Canada</territory>
<territory type="CC">Cocos [Keeling] Islands</territory>
<territory type="CD">Congo - Kinshasa</territory>
<territory type="CF">Central African Republic</territory>
<territory type="CG">Congo - Brazzaville</territory>
<territory type="CH">Switzerland</territory>
<territory type="CI">Côte dIvoire</territory>
<territory type="CK">Cook Islands</territory>
<territory type="CL">Chile</territory>
<territory type="CM">Cameroon</territory>
<territory type="CN">China</territory>
<territory type="CO">Colombia</territory>
<territory type="CR">Costa Rica</territory>
<territory type="CS">Serbia and Montenegro</territory>
<territory type="CT">Canton and Enderbury Islands</territory>
<territory type="CU">Cuba</territory>
<territory type="CV">Cape Verde</territory>
<territory type="CX">Christmas Island</territory>
<territory type="CY">Cyprus</territory>
<territory type="CZ">Czech Republic</territory>
<territory type="DD">East Germany</territory>
<territory type="DE">Germany</territory>
<territory type="DJ">Djibouti</territory>
<territory type="DK">Denmark</territory>
<territory type="DM">Dominica</territory>
<territory type="DO">Dominican Republic</territory>
<territory type="DZ">Algeria</territory>
<territory type="EC">Ecuador</territory>
<territory type="EE">Estonia</territory>
<territory type="EG">Egypt</territory>
<territory type="EH">Western Sahara</territory>
<territory type="ER">Eritrea</territory>
<territory type="ES">Spain</territory>
<territory type="ET">Ethiopia</territory>
<territory type="FI">Finland</territory>
<territory type="FJ">Fiji</territory>
<territory type="FK">Falkland Islands</territory>
<territory type="FM">Micronesia</territory>
<territory type="FO">Faroe Islands</territory>
<territory type="FQ">French Southern and Antarctic Territories</territory>
<territory type="FR">France</territory>
<territory type="FX">Metropolitan France</territory>
<territory type="GA">Gabon</territory>
<territory type="GB">United Kingdom</territory>
<territory type="GD">Grenada</territory>
<territory type="GE">Georgia</territory>
<territory type="GF">French Guiana</territory>
<territory type="GG">Guernsey</territory>
<territory type="GH">Ghana</territory>
<territory type="GI">Gibraltar</territory>
<territory type="GL">Greenland</territory>
<territory type="GM">Gambia</territory>
<territory type="GN">Guinea</territory>
<territory type="GP">Guadeloupe</territory>
<territory type="GQ">Equatorial Guinea</territory>
<territory type="GR">Greece</territory>
<territory type="GS">South Georgia and the South Sandwich Islands</territory>
<territory type="GT">Guatemala</territory>
<territory type="GU">Guam</territory>
<territory type="GW">Guinea-Bissau</territory>
<territory type="GY">Guyana</territory>
<territory type="HK">Hong Kong SAR China</territory>
<territory type="HM">Heard Island and McDonald Islands</territory>
<territory type="HN">Honduras</territory>
<territory type="HR">Croatia</territory>
<territory type="HT">Haiti</territory>
<territory type="HU">Hungary</territory>
<territory type="ID">Indonesia</territory>
<territory type="IE">Ireland</territory>
<territory type="IL">Israel</territory>
<territory type="IM">Isle of Man</territory>
<territory type="IN">India</territory>
<territory type="IO">British Indian Ocean Territory</territory>
<territory type="IQ">Iraq</territory>
<territory type="IR">Iran</territory>
<territory type="IS">Iceland</territory>
<territory type="IT">Italy</territory>
<territory type="JE">Jersey</territory>
<territory type="JM">Jamaica</territory>
<territory type="JO">Jordan</territory>
<territory type="JP">Japan</territory>
<territory type="JT">Johnston Island</territory>
<territory type="KE">Kenya</territory>
<territory type="KG">Kyrgyzstan</territory>
<territory type="KH">Cambodia</territory>
<territory type="KI">Kiribati</territory>
<territory type="KM">Comoros</territory>
<territory type="KN">Saint Kitts and Nevis</territory>
<territory type="KP">North Korea</territory>
<territory type="KR">South Korea</territory>
<territory type="KW">Kuwait</territory>
<territory type="KY">Cayman Islands</territory>
<territory type="KZ">Kazakhstan</territory>
<territory type="LA">Laos</territory>
<territory type="LB">Lebanon</territory>
<territory type="LC">Saint Lucia</territory>
<territory type="LI">Liechtenstein</territory>
<territory type="LK">Sri Lanka</territory>
<territory type="LR">Liberia</territory>
<territory type="LS">Lesotho</territory>
<territory type="LT">Lithuania</territory>
<territory type="LU">Luxembourg</territory>
<territory type="LV">Latvia</territory>
<territory type="LY">Libya</territory>
<territory type="MA">Morocco</territory>
<territory type="MC">Monaco</territory>
<territory type="MD">Moldova</territory>
<territory type="ME">Montenegro</territory>
<territory type="MF">Saint Martin</territory>
<territory type="MG">Madagascar</territory>
<territory type="MH">Marshall Islands</territory>
<territory type="MI">Midway Islands</territory>
<territory type="MK">Macedonia</territory>
<territory type="ML">Mali</territory>
<territory type="MM">Myanmar [Burma]</territory>
<territory type="MN">Mongolia</territory>
<territory type="MO">Macau SAR China</territory>
<territory type="MP">Northern Mariana Islands</territory>
<territory type="MQ">Martinique</territory>
<territory type="MR">Mauritania</territory>
<territory type="MS">Montserrat</territory>
<territory type="MT">Malta</territory>
<territory type="MU">Mauritius</territory>
<territory type="MV">Maldives</territory>
<territory type="MW">Malawi</territory>
<territory type="MX">Mexico</territory>
<territory type="MY">Malaysia</territory>
<territory type="MZ">Mozambique</territory>
<territory type="NA">Namibia</territory>
<territory type="NC">New Caledonia</territory>
<territory type="NE">Niger</territory>
<territory type="NF">Norfolk Island</territory>
<territory type="NG">Nigeria</territory>
<territory type="NI">Nicaragua</territory>
<territory type="NL">Netherlands</territory>
<territory type="NO">Norway</territory>
<territory type="NP">Nepal</territory>
<territory type="NQ">Dronning Maud Land</territory>
<territory type="NR">Nauru</territory>
<territory type="NT">Neutral Zone</territory>
<territory type="NU">Niue</territory>
<territory type="NZ">New Zealand</territory>
<territory type="OM">Oman</territory>
<territory type="PA">Panama</territory>
<territory type="PC">Pacific Islands Trust Territory</territory>
<territory type="PE">Peru</territory>
<territory type="PF">French Polynesia</territory>
<territory type="PG">Papua New Guinea</territory>
<territory type="PH">Philippines</territory>
<territory type="PK">Pakistan</territory>
<territory type="PL">Poland</territory>
<territory type="PM">Saint Pierre and Miquelon</territory>
<territory type="PN">Pitcairn Islands</territory>
<territory type="PR">Puerto Rico</territory>
<territory type="PS">Palestinian Territories</territory>
<territory type="PT">Portugal</territory>
<territory type="PU">U.S. Miscellaneous Pacific Islands</territory>
<territory type="PW">Palau</territory>
<territory type="PY">Paraguay</territory>
<territory type="PZ">Panama Canal Zone</territory>
<territory type="QA">Qatar</territory>
<territory type="QO">Outlying Oceania</territory>
<territory type="QU">European Union</territory>
<territory type="RE">Réunion</territory>
<territory type="RO">Romania</territory>
<territory type="RS">Serbia</territory>
<territory type="RU">Russia</territory>
<territory type="RW">Rwanda</territory>
<territory type="SA">Saudi Arabia</territory>
<territory type="SB">Solomon Islands</territory>
<territory type="SC">Seychelles</territory>
<territory type="SD">Sudan</territory>
<territory type="SE">Sweden</territory>
<territory type="SG">Singapore</territory>
<territory type="SH">Saint Helena</territory>
<territory type="SI">Slovenia</territory>
<territory type="SJ">Svalbard and Jan Mayen</territory>
<territory type="SK">Slovakia</territory>
<territory type="SL">Sierra Leone</territory>
<territory type="SM">San Marino</territory>
<territory type="SN">Senegal</territory>
<territory type="SO">Somalia</territory>
<territory type="SR">Suriname</territory>
<territory type="ST">São Tomé and Príncipe</territory>
<territory type="SU">Union of Soviet Socialist Republics</territory>
<territory type="SV">El Salvador</territory>
<territory type="SY">Syria</territory>
<territory type="SZ">Swaziland</territory>
<territory type="TC">Turks and Caicos Islands</territory>
<territory type="TD">Chad</territory>
<territory type="TF">French Southern Territories</territory>
<territory type="TG">Togo</territory>
<territory type="TH">Thailand</territory>
<territory type="TJ">Tajikistan</territory>
<territory type="TK">Tokelau</territory>
<territory type="TL">Timor-Leste</territory>
<territory type="TM">Turkmenistan</territory>
<territory type="TN">Tunisia</territory>
<territory type="TO">Tonga</territory>
<territory type="TR">Turkey</territory>
<territory type="TT">Trinidad and Tobago</territory>
<territory type="TV">Tuvalu</territory>
<territory type="TW">Taiwan</territory>
<territory type="TZ">Tanzania</territory>
<territory type="UA">Ukraine</territory>
<territory type="UG">Uganda</territory>
<territory type="UM">U.S. Minor Outlying Islands</territory>
<territory type="US">United States</territory>
<territory type="UY">Uruguay</territory>
<territory type="UZ">Uzbekistan</territory>
<territory type="VA">Vatican City</territory>
<territory type="VC">Saint Vincent and the Grenadines</territory>
<territory type="VD">North Vietnam</territory>
<territory type="VE">Venezuela</territory>
<territory type="VG">British Virgin Islands</territory>
<territory type="VI">U.S. Virgin Islands</territory>
<territory type="VN">Vietnam</territory>
<territory type="VU">Vanuatu</territory>
<territory type="WF">Wallis and Futuna</territory>
<territory type="WK">Wake Island</territory>
<territory type="WS">Samoa</territory>
<territory type="YD">People's Democratic Republic of Yemen</territory>
<territory type="YE">Yemen</territory>
<territory type="YT">Mayotte</territory>
<territory type="ZA">South Africa</territory>
<territory type="ZM">Zambia</territory>
<territory type="ZW">Zimbabwe</territory>
<territory type="ZZ">Unknown or Invalid Region</territory>
</territories>
<variants>
<variant type="1901">Traditional German orthography</variant>
<variant type="1994">Standardized Resian orthography</variant>
<variant type="1996">German orthography of 1996</variant>
<variant type="1606NICT">Late Middle French to 1606</variant>
<variant type="1694ACAD">Early Modern French</variant>
<variant type="1959ACAD">Academic</variant>
<variant type="AREVELA">Eastern Armenian</variant>
<variant type="AREVMDA">Western Armenian</variant>
<variant type="BAKU1926">Unified Turkic Latin Alphabet</variant>
<variant type="BISKE">San Giorgio/Bila dialect</variant>
<variant type="BOONT">Boontling</variant>
<variant type="FONIPA">IPA Phonetics</variant>
<variant type="FONUPA">UPA Phonetics</variant>
<variant type="KKCOR">Common Orthography</variant>
<variant type="LIPAW">The Lipovaz dialect of Resian</variant>
<variant type="MONOTON">Monotonic</variant>
<variant type="NEDIS">Natisone dialect</variant>
<variant type="NJIVA">Gniva/Njiva dialect</variant>
<variant type="OSOJS">Oseacco/Osojane dialect</variant>
<variant type="PINYIN">Pinyin Romanization</variant>
<variant type="POLYTON">Polytonic</variant>
<variant type="POSIX">Computer</variant>
<variant type="REVISED">Revised Orthography</variant>
<variant type="ROZAJ">Resian</variant>
<variant type="SAAHO">Saho</variant>
<variant type="SCOTLAND">Scottish Standard English</variant>
<variant type="SCOUSE">Scouse</variant>
<variant type="SOLBA">Stolvizza/Solbica dialect</variant>
<variant type="TARASK">Taraskievica orthography</variant>
<variant type="UCCOR">Unified Orthography</variant>
<variant type="UCRCOR">Unified Revised Orthography</variant>
<variant type="VALENCIA">Valencian</variant>
<variant type="WADEGILE">Wade-Giles Romanization</variant>
</variants>
<types>
<type type="arab" key="numbers">Arabic-Indic Digits</type>
<type type="arabext" key="numbers">Extended Arabic-Indic Digits</type>
<type type="armn" key="numbers">Armenian Numerals</type>
<type type="armnlow" key="numbers">Armenian Lowercase Numerals</type>
<type type="beng" key="numbers">Bengali Digits</type>
<type type="big5han" key="collation">Traditional Chinese Sort Order - Big5</type>
<type type="buddhist" key="calendar">Buddhist Calendar</type>
<type type="chinese" key="calendar">Chinese Calendar</type>
<type type="coptic" key="calendar">Coptic Calendar</type>
<type type="deva" key="numbers">Devanagari Digits</type>
<type type="direct" key="collation">Direct Sort Order</type>
<type type="ethi" key="numbers">Ethiopic Numerals</type>
<type type="ethiopic" key="calendar">Ethiopic Calendar</type>
<type type="ethiopic-amete-alem" key="calendar">Ethiopic Amete Alem Calendar</type>
<type type="fullwide" key="numbers">Full Width Digits</type>
<type type="gb2312han" key="collation">Simplified Chinese Sort Order - GB2312</type>
<type type="geor" key="numbers">Georgian Numerals</type>
<type type="gregorian" key="calendar">Gregorian Calendar</type>
<type type="grek" key="numbers">Greek Numerals</type>
<type type="greklow" key="numbers">Greek Lowercase Numerals</type>
<type type="gujr" key="numbers">Gujarati Digits</type>
<type type="guru" key="numbers">Gurmukhi Digits</type>
<type type="hans" key="numbers">Simplified Chinese Numerals</type>
<type type="hansfin" key="numbers">Simplified Chinese Financial Numerals</type>
<type type="hant" key="numbers">Traditional Chinese Numerals</type>
<type type="hantfin" key="numbers">Traditional Chinese Financial Numerals</type>
<type type="hebr" key="numbers">Hebrew Numerals</type>
<type type="hebrew" key="calendar">Hebrew Calendar</type>
<type type="indian" key="calendar">Indian National Calendar</type>
<type type="islamic" key="calendar">Islamic Calendar</type>
<type type="islamic-civil" key="calendar">Islamic-Civil Calendar</type>
<type type="japanese" key="calendar">Japanese Calendar</type>
<type type="jpan" key="numbers">Japanese Numerals</type>
<type type="jpanfin" key="numbers">Japanese Financial Numerals</type>
<type type="khmr" key="numbers">Khmer Digits</type>
<type type="knda" key="numbers">Kannada Digits</type>
<type type="laoo" key="numbers">Lao Digits</type>
<type type="latn" key="numbers">Western Digits</type>
<type type="mlym" key="numbers">Malayalam Digits</type>
<type type="mong" key="numbers">Mongolian Digits</type>
<type type="mymr" key="numbers">Myanmar Digits</type>
<type type="orya" key="numbers">Oriya Digits</type>
<type type="persian" key="calendar">Persian Calendar</type>
<type type="phonebook" key="collation">Phonebook Sort Order</type>
<type type="pinyin" key="collation">Simplified Chinese Pinyin Sort Order</type>
<type type="roc" key="calendar">Republic of China Calendar</type>
<type type="roman" key="numbers">Roman Numerals</type>
<type type="romanlow" key="numbers">Roman Lowercase Numerals</type>
<type type="stroke" key="collation">Traditional Chinese Stroke Sort Order</type>
<type type="taml" key="numbers">Tamil Numerals</type>
<type type="telu" key="numbers">Telugu Digits</type>
<type type="thai" key="numbers">Thai Digits</type>
<type type="tibt" key="numbers">Tibetan Digits</type>
<type type="traditional" key="collation">Traditional Sort Order</type>
</types>
<measurementSystemNames>
<measurementSystemName type="metric">Metric</measurementSystemName>
<measurementSystemName type="US">US</measurementSystemName>
</measurementSystemNames>
<codePatterns>
<codePattern type="language">Language: {0}</codePattern>
<codePattern type="script">Script: {0}</codePattern>
<codePattern type="territory">Region: {0}</codePattern>
</codePatterns>
</localeDisplayNames>
<characters>
<exemplarCharacters>[a-z]</exemplarCharacters>
<exemplarCharacters type="auxiliary">[á à ă â å ä ã ā æ ç é è ĕ ê ë ē í ì ĭ î ï ī ñ ó ò ŏ ô ö ø ō œ ß ú ù ŭ û ü ū ÿ]</exemplarCharacters>
<exemplarCharacters type="currencySymbol">[a-c č d-l ł m-z]</exemplarCharacters>
</characters>
<delimiters>
<quotationStart></quotationStart>
<quotationEnd></quotationEnd>
<alternateQuotationStart></alternateQuotationStart>
<alternateQuotationEnd></alternateQuotationEnd>
</delimiters>
<dates>
<calendars>
<calendar type="gregorian">
<months>
<monthContext type="format">
<monthWidth type="abbreviated">
<month type="1">Jan</month>
<month type="2">Feb</month>
<month type="3">Mar</month>
<month type="4">Apr</month>
<month type="5">May</month>
<month type="6">Jun</month>
<month type="7">Jul</month>
<month type="8">Aug</month>
<month type="9">Sep</month>
<month type="10">Oct</month>
<month type="11">Nov</month>
<month type="12">Dec</month>
</monthWidth>
<monthWidth type="wide">
<month type="1">January</month>
<month type="2">February</month>
<month type="3">March</month>
<month type="4">April</month>
<month type="5">May</month>
<month type="6">June</month>
<month type="7">July</month>
<month type="8">August</month>
<month type="9">September</month>
<month type="10">October</month>
<month type="11">November</month>
<month type="12">December</month>
</monthWidth>
</monthContext>
<monthContext type="stand-alone">
<monthWidth type="narrow">
<month type="1">J</month>
<month type="2">F</month>
<month type="3">M</month>
<month type="4">A</month>
<month type="5">M</month>
<month type="6">J</month>
<month type="7">J</month>
<month type="8">A</month>
<month type="9">S</month>
<month type="10">O</month>
<month type="11">N</month>
<month type="12">D</month>
</monthWidth>
</monthContext>
</months>
<days>
<dayContext type="format">
<dayWidth type="abbreviated">
<day type="sun">Sun</day>
<day type="mon">Mon</day>
<day type="tue">Tue</day>
<day type="wed">Wed</day>
<day type="thu">Thu</day>
<day type="fri">Fri</day>
<day type="sat">Sat</day>
</dayWidth>
<dayWidth type="wide">
<day type="sun">Sunday</day>
<day type="mon">Monday</day>
<day type="tue">Tuesday</day>
<day type="wed">Wednesday</day>
<day type="thu">Thursday</day>
<day type="fri">Friday</day>
<day type="sat">Saturday</day>
</dayWidth>
</dayContext>
<dayContext type="stand-alone">
<dayWidth type="narrow">
<day type="sun">S</day>
<day type="mon">M</day>
<day type="tue">T</day>
<day type="wed">W</day>
<day type="thu">T</day>
<day type="fri">F</day>
<day type="sat">S</day>
</dayWidth>
</dayContext>
</days>
<quarters>
<quarterContext type="format">
<quarterWidth type="abbreviated">
<quarter type="1">Q1</quarter>
<quarter type="2">Q2</quarter>
<quarter type="3">Q3</quarter>
<quarter type="4">Q4</quarter>
</quarterWidth>
<quarterWidth type="wide">
<quarter type="1">1st quarter</quarter>
<quarter type="2">2nd quarter</quarter>
<quarter type="3">3rd quarter</quarter>
<quarter type="4">4th quarter</quarter>
</quarterWidth>
</quarterContext>
<quarterContext type="stand-alone">
<quarterWidth type="narrow">
<quarter type="1">1</quarter>
<quarter type="2">2</quarter>
<quarter type="3">3</quarter>
<quarter type="4">4</quarter>
</quarterWidth>
</quarterContext>
</quarters>
<am>AM</am>
<pm>PM</pm>
<eras>
<eraNames>
<era type="0">Before Christ</era>
<era type="1">Anno Domini</era>
</eraNames>
<eraAbbr>
<era type="0">BC</era>
<era type="1">AD</era>
</eraAbbr>
<eraNarrow>
<era type="0">B</era>
<era type="1">A</era>
</eraNarrow>
</eras>
<dateFormats>
<dateFormatLength type="full">
<dateFormat>
<pattern>EEEE, MMMM d, y</pattern>
</dateFormat>
</dateFormatLength>
<dateFormatLength type="long">
<dateFormat>
<pattern>MMMM d, y</pattern>
</dateFormat>
</dateFormatLength>
<dateFormatLength type="medium">
<dateFormat>
<pattern>MMM d, y</pattern>
</dateFormat>
</dateFormatLength>
<dateFormatLength type="short">
<dateFormat>
<pattern>M/d/yy</pattern>
</dateFormat>
</dateFormatLength>
</dateFormats>
<timeFormats>
<timeFormatLength type="full">
<timeFormat>
<pattern>h:mm:ss a zzzz</pattern>
</timeFormat>
</timeFormatLength>
<timeFormatLength type="long">
<timeFormat>
<pattern>h:mm:ss a z</pattern>
</timeFormat>
</timeFormatLength>
<timeFormatLength type="medium">
<timeFormat>
<pattern>h:mm:ss a</pattern>
</timeFormat>
</timeFormatLength>
<timeFormatLength type="short">
<timeFormat>
<pattern>h:mm a</pattern>
</timeFormat>
</timeFormatLength>
</timeFormats>
<dateTimeFormats>
<dateTimeFormatLength type="full">
<dateTimeFormat>
<pattern>{1} {0}</pattern>
</dateTimeFormat>
</dateTimeFormatLength>
<dateTimeFormatLength type="long">
<dateTimeFormat>
<pattern>{1} {0}</pattern>
</dateTimeFormat>
</dateTimeFormatLength>
<dateTimeFormatLength type="medium">
<dateTimeFormat>
<pattern>{1} {0}</pattern>
</dateTimeFormat>
</dateTimeFormatLength>
<dateTimeFormatLength type="short">
<dateTimeFormat>
<pattern>{1} {0}</pattern>
</dateTimeFormat>
</dateTimeFormatLength>
<availableFormats>
<dateFormatItem id="d">d</dateFormatItem>
<dateFormatItem id="EEEd">d EEE</dateFormatItem>
<dateFormatItem id="hm">h:mm a</dateFormatItem>
<dateFormatItem id="Hm">H:mm</dateFormatItem>
<dateFormatItem id="Hms">H:mm:ss</dateFormatItem>
<dateFormatItem id="M">L</dateFormatItem>
<dateFormatItem id="Md">M/d</dateFormatItem>
<dateFormatItem id="MEd">E, M/d</dateFormatItem>
<dateFormatItem id="MMM">LLL</dateFormatItem>
<dateFormatItem id="MMMd">MMM d</dateFormatItem>
<dateFormatItem id="MMMEd">E, MMM d</dateFormatItem>
<dateFormatItem id="MMMMd">MMMM d</dateFormatItem>
<dateFormatItem id="MMMMEd">E, MMMM d</dateFormatItem>
<dateFormatItem id="ms">mm:ss</dateFormatItem>
<dateFormatItem id="y">y</dateFormatItem>
<dateFormatItem id="yM">M/yyyy</dateFormatItem>
<dateFormatItem id="yMEd">EEE, M/d/yyyy</dateFormatItem>
<dateFormatItem id="yMMM">MMM y</dateFormatItem>
<dateFormatItem id="yMMMEd">EEE, MMM d, y</dateFormatItem>
<dateFormatItem id="yMMMM">MMMM y</dateFormatItem>
<dateFormatItem id="yQ">Q yyyy</dateFormatItem>
<dateFormatItem id="yQQQ">QQQ y</dateFormatItem>
</availableFormats>
<intervalFormats>
<intervalFormatFallback>{0} {1}</intervalFormatFallback>
<intervalFormatItem id="d">
<greatestDifference id="d">dd</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="h">
<greatestDifference id="a">h a h a</greatestDifference>
<greatestDifference id="h">hh a</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="hm">
<greatestDifference id="a">h:mm a h:mm a</greatestDifference>
<greatestDifference id="h">h:mmh:mm a</greatestDifference>
<greatestDifference id="m">h:mmh:mm a</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="hmv">
<greatestDifference id="a">h:mm a h:mm a v</greatestDifference>
<greatestDifference id="h">h:mmh:mm a v</greatestDifference>
<greatestDifference id="m">h:mmh:mm a v</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="hv">
<greatestDifference id="a">h a h a v</greatestDifference>
<greatestDifference id="h">hh a v</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="M">
<greatestDifference id="M">MM</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="Md">
<greatestDifference id="d">M/d M/d</greatestDifference>
<greatestDifference id="M">M/d M/d</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="MEd">
<greatestDifference id="d">E, M/d E, M/d</greatestDifference>
<greatestDifference id="M">E, M/d E, M/d</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="MMM">
<greatestDifference id="M">MMMMMM</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="MMMd">
<greatestDifference id="d">MMM dd</greatestDifference>
<greatestDifference id="M">MMM d MMM d</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="MMMEd">
<greatestDifference id="d">E, MMM d E, MMM d</greatestDifference>
<greatestDifference id="M">E, MMM d E, MMM d</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="MMMM">
<greatestDifference id="M">LLLL-LLLL</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="y">
<greatestDifference id="y">yy</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="yM">
<greatestDifference id="M">M/yy M/yy</greatestDifference>
<greatestDifference id="y">M/yy M/yy</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="yMd">
<greatestDifference id="d">M/d/yy M/d/yy</greatestDifference>
<greatestDifference id="M">M/d/yy M/d/yy</greatestDifference>
<greatestDifference id="y">M/d/yy M/d/yy</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="yMEd">
<greatestDifference id="d">E, M/d/yy E, M/d/yy</greatestDifference>
<greatestDifference id="M">E, M/d/yy E, M/d/yy</greatestDifference>
<greatestDifference id="y">E, M/d/yy E, M/d/yy</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="yMMM">
<greatestDifference id="M">MMMMMM y</greatestDifference>
<greatestDifference id="y">MMM y MMM y</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="yMMMd">
<greatestDifference id="d">MMM dd, y</greatestDifference>
<greatestDifference id="M">MMM d MMM d, y</greatestDifference>
<greatestDifference id="y">MMM d, y MMM d, y</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="yMMMEd">
<greatestDifference id="d">E, MMM d E, MMM d, y</greatestDifference>
<greatestDifference id="M">E, MMM d E, MMM d, y</greatestDifference>
<greatestDifference id="y">E, MMM d, y E, MMM d, y</greatestDifference>
</intervalFormatItem>
<intervalFormatItem id="yMMMM">
<greatestDifference id="M">MMMMMMMM y</greatestDifference>
<greatestDifference id="y">MMMM y MMMM y</greatestDifference>
</intervalFormatItem>
</intervalFormats>
</dateTimeFormats>
<fields>
<field type="era">
<displayName>Era</displayName>
</field>
<field type="year">
<displayName>Year</displayName>
</field>
<field type="month">
<displayName>Month</displayName>
</field>
<field type="week">
<displayName>Week</displayName>
</field>
<field type="day">
<displayName>Day</displayName>
<relative type="-1">Yesterday</relative>
<relative type="0">Today</relative>
<relative type="1">Tomorrow</relative>
</field>
<field type="weekday">
<displayName>Day of the Week</displayName>
</field>
<field type="dayperiod">
<displayName>AM/PM</displayName>
</field>
<field type="hour">
<displayName>Hour</displayName>
</field>
<field type="minute">
<displayName>Minute</displayName>
</field>
<field type="second">
<displayName>Second</displayName>
</field>
<field type="zone">
<displayName>Zone</displayName>
</field>
</fields>
</calendar>
</calendars>
<timeZoneNames>
<hourFormat>+HH:mm;-HH:mm</hourFormat>
<gmtFormat>GMT{0}</gmtFormat>
<regionFormat>{0} Time</regionFormat>
<fallbackFormat>{1} ({0})</fallbackFormat>
<zone type="Etc/Unknown">
<exemplarCity>Unknown</exemplarCity>
</zone>
<zone type="Antarctica/DumontDUrville">
<exemplarCity>Dumont dUrville</exemplarCity>
</zone>
<zone type="Europe/London">
<long>
<daylight>British Summer Time</daylight>
</long>
<short>
<daylight>BST</daylight>
</short>
</zone>
<zone type="Europe/Dublin">
<long>
<daylight>Irish Summer Time</daylight>
</long>
<short>
<daylight>IST (Irish)</daylight>
</short>
</zone>
<metazone type="Acre">
<long>
<standard>Acre Time</standard>
<daylight>Acre Summer Time</daylight>
</long>
</metazone>
<metazone type="Afghanistan">
<long>
<standard>Afghanistan Time</standard>
</long>
</metazone>
<metazone type="Africa_Central">
<long>
<standard>Central Africa Time</standard>
</long>
</metazone>
<metazone type="Africa_Eastern">
<long>
<standard>East Africa Time</standard>
</long>
</metazone>
<metazone type="Africa_Southern">
<long>
<generic>South Africa Time</generic>
<standard>South Africa Standard Time</standard>
</long>
<short>
<generic>SAT</generic>
</short>
</metazone>
<metazone type="Africa_Western">
<long>
<standard>West Africa Time</standard>
<daylight>West Africa Summer Time</daylight>
</long>
</metazone>
<metazone type="Aktyubinsk">
<long>
<standard>Aktyubinsk Time</standard>
<daylight>Aktyubinsk Summer Time</daylight>
</long>
</metazone>
<metazone type="Alaska">
<long>
<generic>Alaska Time</generic>
<standard>Alaska Standard Time</standard>
<daylight>Alaska Daylight Time</daylight>
</long>
<short>
<generic>AKT</generic>
</short>
<commonlyUsed>true</commonlyUsed>
</metazone>
<metazone type="Alaska_Hawaii">
<long>
<generic>Alaska-Hawaii Time</generic>
<standard>Alaska-Hawaii Standard Time</standard>
<daylight>Alaska-Hawaii Daylight Time</daylight>
</long>
<short>
<generic>AHT</generic>
</short>
</metazone>
<metazone type="Almaty">
<long>
<standard>Almaty Time</standard>
<daylight>Almaty Summer Time</daylight>
</long>
</metazone>
<metazone type="Amazon">
<long>
<standard>Amazon Time</standard>
<daylight>Amazon Summer Time</daylight>
</long>
</metazone>
<metazone type="America_Central">
<long>
<generic>Central Time</generic>
<standard>Central Standard Time</standard>
<daylight>Central Daylight Time</daylight>
</long>
<short>
<generic>CT</generic>
</short>
<commonlyUsed>true</commonlyUsed>
</metazone>
<metazone type="America_Eastern">
<long>
<generic>Eastern Time</generic>
<standard>Eastern Standard Time</standard>
<daylight>Eastern Daylight Time</daylight>
</long>
<short>
<generic>ET</generic>
</short>
<commonlyUsed>true</commonlyUsed>
</metazone>
<metazone type="America_Mountain">
<long>
<generic>Mountain Time</generic>
<standard>Mountain Standard Time</standard>
<daylight>Mountain Daylight Time</daylight>
</long>
<short>
<generic>MT</generic>
</short>
<commonlyUsed>true</commonlyUsed>
</metazone>
<metazone type="America_Pacific">
<long>
<generic>Pacific Time</generic>
<standard>Pacific Standard Time</standard>
<daylight>Pacific Daylight Time</daylight>
</long>
<short>
<generic>PT</generic>
</short>
<commonlyUsed>true</commonlyUsed>
</metazone>
<metazone type="Anadyr">
<long>
<standard>Anadyr Time</standard>
<daylight>Anadyr Summer Time</daylight>
</long>
</metazone>
<metazone type="Aqtau">
<long>
<standard>Aqtau Time</standard>
<daylight>Aqtau Summer Time</daylight>
</long>
</metazone>
<metazone type="Aqtobe">
<long>
<standard>Aqtobe Time</standard>
<daylight>Aqtobe Summer Time</daylight>
</long>
</metazone>
<metazone type="Arabian">
<long>
<generic>Arabian Time</generic>
<standard>Arabian Standard Time</standard>
<daylight>Arabian Daylight Time</daylight>
</long>
<short>
<generic>AT (Arabian)</generic>
<standard>AST (Arabian)</standard>
<daylight>ADT (Arabian)</daylight>
</short>
</metazone>
<metazone type="Argentina">
<long>
<standard>Argentina Time</standard>
<daylight>Argentina Summer Time</daylight>
</long>
</metazone>
<metazone type="Argentina_Western">
<long>
<standard>Western Argentina Time</standard>
</long>
</metazone>
<metazone type="Armenia">
<long>
<standard>Armenia Time</standard>
<daylight>Armenia Summer Time</daylight>
</long>
<short>
<standard>AMT (Armenia)</standard>
<daylight>AMST (Armenia)</daylight>
</short>
</metazone>
<metazone type="Ashkhabad">
<long>
<standard>Ashkhabad Time</standard>
<daylight>Ashkhabad Summer Time</daylight>
</long>
</metazone>
<metazone type="Atlantic">
<long>
<generic>Atlantic Time</generic>
<standard>Atlantic Standard Time</standard>
<daylight>Atlantic Daylight Time</daylight>
</long>
<short>
<generic>AT</generic>
</short>
<commonlyUsed>true</commonlyUsed>
</metazone>
<metazone type="Australia_Central">
<long>
<generic>Central Australia Time</generic>
<standard>Australian Central Standard Time</standard>
<daylight>Australian Central Daylight Time</daylight>
</long>
<short>
<generic>ACT</generic>
</short>
</metazone>
<metazone type="Australia_CentralWestern">
<long>
<generic>Australian Central Western Time</generic>
<standard>Australian Central Western Standard Time</standard>
<daylight>Australian Central Western Daylight Time</daylight>
</long>
<short>
<generic>ACWT</generic>
</short>
</metazone>
<metazone type="Australia_Eastern">
<long>
<generic>Eastern Australia Time</generic>
<standard>Australian Eastern Standard Time</standard>
<daylight>Australian Eastern Daylight Time</daylight>
</long>
<short>
<generic>AET</generic>
</short>
</metazone>
<metazone type="Australia_Western">
<long>
<generic>Western Australia Time</generic>
<standard>Australian Western Standard Time</standard>
<daylight>Australian Western Daylight Time</daylight>
</long>
<short>
<generic>AWT</generic>
</short>
</metazone>
<metazone type="Azerbaijan">
<long>
<standard>Azerbaijan Time</standard>
<daylight>Azerbaijan Summer Time</daylight>
</long>
</metazone>
<metazone type="Azores">
<long>
<standard>Azores Time</standard>
<daylight>Azores Summer Time</daylight>
</long>
</metazone>
<metazone type="Baku">
<long>
<standard>Baku Time</standard>
<daylight>Baku Summer Time</daylight>
</long>
</metazone>
<metazone type="Bangladesh">
<long>
<standard>Bangladesh Time</standard>
<daylight>Bangladesh Summer Time</daylight>
</long>
</metazone>
<metazone type="Bering">
<long>
<generic>Bering Time</generic>
<standard>Bering Standard Time</standard>
<daylight>Bering Daylight Time</daylight>
</long>
<short>
<generic>BT (Bering)</generic>
</short>
</metazone>
<metazone type="Bhutan">
<long>
<standard>Bhutan Time</standard>
</long>
</metazone>
<metazone type="Bolivia">
<long>
<standard>Bolivia Time</standard>
</long>
</metazone>
<metazone type="Borneo">
<long>
<standard>Borneo Time</standard>
<daylight>Borneo Summer Time</daylight>
</long>
</metazone>
<metazone type="Brasilia">
<long>
<standard>Brasilia Time</standard>
<daylight>Brasilia Summer Time</daylight>
</long>
</metazone>
<metazone type="Brunei">
<long>
<standard>Brunei Darussalam Time</standard>
</long>
</metazone>
<metazone type="Cape_Verde">
<long>
<standard>Cape Verde Time</standard>
<daylight>Cape Verde Summer Time</daylight>
</long>
</metazone>
<metazone type="Chamorro">
<long>
<generic>Chamorro Time</generic>
<standard>Chamorro Standard Time</standard>
</long>
<short>
<generic>ChT</generic>
</short>
</metazone>
<metazone type="Changbai">
<long>
<standard>Changbai Time</standard>
</long>
</metazone>
<metazone type="Chatham">
<long>
<standard>Chatham Standard Time</standard>
<daylight>Chatham Daylight Time</daylight>
</long>
</metazone>
<metazone type="Chile">
<long>
<standard>Chile Time</standard>
<daylight>Chile Summer Time</daylight>
</long>
</metazone>
<metazone type="China">
<long>
<generic>China Time</generic>
<standard>China Standard Time</standard>
<daylight>China Daylight Time</daylight>
</long>
<short>
<generic>CT (China)</generic>
<standard>CST (China)</standard>
<daylight>CDT (China)</daylight>
</short>
</metazone>
<metazone type="Choibalsan">
<long>
<standard>Choibalsan Time</standard>
<daylight>Choibalsan Summer Time</daylight>
</long>
</metazone>
<metazone type="Christmas">
<long>
<standard>Christmas Island Time</standard>
</long>
</metazone>
<metazone type="Cocos">
<long>
<standard>Cocos Islands Time</standard>
</long>
</metazone>
<metazone type="Colombia">
<long>
<standard>Colombia Time</standard>
<daylight>Colombia Summer Time</daylight>
</long>
</metazone>
<metazone type="Cook">
<long>
<standard>Cook Islands Time</standard>
<daylight>Cook Islands Half Summer Time</daylight>
</long>
</metazone>
<metazone type="Cuba">
<long>
<generic>Cuba Time</generic>
<standard>Cuba Standard Time</standard>
<daylight>Cuba Daylight Time</daylight>
</long>
<short>
<standard>CST (Cuba)</standard>
<daylight>CDT (Cuba)</daylight>
</short>
</metazone>
<metazone type="Dacca">
<long>
<standard>Dacca Time</standard>
</long>
</metazone>
<metazone type="Davis">
<long>
<standard>Davis Time</standard>
</long>
</metazone>
<metazone type="DumontDUrville">
<long>
<standard>Dumont-d'Urville Time</standard>
</long>
</metazone>
<metazone type="Dushanbe">
<long>
<standard>Dushanbe Time</standard>
<daylight>Dushanbe Summer Time</daylight>
</long>
</metazone>
<metazone type="Dutch_Guiana">
<long>
<standard>Dutch Guiana Time</standard>
</long>
</metazone>
<metazone type="East_Timor">
<long>
<standard>East Timor Time</standard>
</long>
</metazone>
<metazone type="Easter">
<long>
<standard>Easter Island Time</standard>
<daylight>Easter Island Summer Time</daylight>
</long>
</metazone>
<metazone type="Ecuador">
<long>
<standard>Ecuador Time</standard>
</long>
</metazone>
<metazone type="Europe_Central">
<long>
<standard>Central European Time</standard>
<daylight>Central European Summer Time</daylight>
</long>
</metazone>
<metazone type="Europe_Eastern">
<long>
<standard>Eastern European Time</standard>
<daylight>Eastern European Summer Time</daylight>
</long>
</metazone>
<metazone type="Europe_Western">
<long>
<standard>Western European Time</standard>
<daylight>Western European Summer Time</daylight>
</long>
</metazone>
<metazone type="Falkland">
<long>
<standard>Falkland Islands Time</standard>
<daylight>Falkland Islands Summer Time</daylight>
</long>
</metazone>
<metazone type="Fiji">
<long>
<standard>Fiji Time</standard>
<daylight>Fiji Summer Time</daylight>
</long>
</metazone>
<metazone type="French_Guiana">
<long>
<standard>French Guiana Time</standard>
</long>
</metazone>
<metazone type="French_Southern">
<long>
<standard>French Southern and Antarctic Time</standard>
</long>
</metazone>
<metazone type="Frunze">
<long>
<standard>Frunze Time</standard>
<daylight>Frunze Summer Time</daylight>
</long>
</metazone>
<metazone type="Galapagos">
<long>
<standard>Galapagos Time</standard>
</long>
</metazone>
<metazone type="Gambier">
<long>
<standard>Gambier Time</standard>
</long>
</metazone>
<metazone type="Georgia">
<long>
<standard>Georgia Time</standard>
<daylight>Georgia Summer Time</daylight>
</long>
</metazone>
<metazone type="Gilbert_Islands">
<long>
<standard>Gilbert Islands Time</standard>
</long>
</metazone>
<metazone type="GMT">
<long>
<standard>Greenwich Mean Time</standard>
</long>
<commonlyUsed>true</commonlyUsed>
</metazone>
<metazone type="Greenland_Central">
<long>
<standard>Central Greenland Time</standard>
<daylight>Central Greenland Summer Time</daylight>
</long>
</metazone>
<metazone type="Greenland_Eastern">
<long>
<standard>East Greenland Time</standard>
<daylight>East Greenland Summer Time</daylight>
</long>
</metazone>
<metazone type="Greenland_Western">
<long>
<standard>West Greenland Time</standard>
<daylight>West Greenland Summer Time</daylight>
</long>
</metazone>
<metazone type="Guam">
<long>
<standard>Guam Standard Time</standard>
</long>
<short>
<standard>GST (Guam)</standard>
</short>
</metazone>
<metazone type="Gulf">
<long>
<generic>Gulf Time</generic>
<standard>Gulf Standard Time</standard>
</long>
<short>
<generic>GT</generic>
</short>
</metazone>
<metazone type="Guyana">
<long>
<standard>Guyana Time</standard>
</long>
</metazone>
<metazone type="Hawaii_Aleutian">
<long>
<standard>Hawaii-Aleutian Standard Time</standard>
</long>
<commonlyUsed>true</commonlyUsed>
</metazone>
<metazone type="Hong_Kong">
<long>
<standard>Hong Kong Time</standard>
<daylight>Hong Kong Summer Time</daylight>
</long>
</metazone>
<metazone type="Hovd">
<long>
<standard>Hovd Time</standard>
<daylight>Hovd Summer Time</daylight>
</long>
</metazone>
<metazone type="India">
<long>
<standard>India Standard Time</standard>
</long>
</metazone>
<metazone type="Indian_Ocean">
<long>
<standard>Indian Ocean Time</standard>
</long>
</metazone>
<metazone type="Indochina">
<long>
<standard>Indochina Time</standard>
</long>
</metazone>
<metazone type="Indonesia_Central">
<long>
<standard>Central Indonesia Time</standard>
</long>
</metazone>
<metazone type="Indonesia_Eastern">
<long>
<standard>Eastern Indonesia Time</standard>
</long>
</metazone>
<metazone type="Indonesia_Western">
<long>
<standard>Western Indonesia Time</standard>
</long>
</metazone>
<metazone type="Iran">
<long>
<standard>Iran Standard Time</standard>
<daylight>Iran Daylight Time</daylight>
</long>
</metazone>
<metazone type="Irkutsk">
<long>
<standard>Irkutsk Time</standard>
<daylight>Irkutsk Summer Time</daylight>
</long>
</metazone>
<metazone type="Israel">
<long>
<generic>Israel Time</generic>
<standard>Israel Standard Time</standard>
<daylight>Israel Daylight Time</daylight>
</long>
<short>
<standard>IST (Israel)</standard>
</short>
</metazone>
<metazone type="Japan">
<long>
<generic>Japan Time</generic>
<standard>Japan Standard Time</standard>
<daylight>Japan Daylight Time</daylight>
</long>
<short>
<generic>JT</generic>
</short>
</metazone>
<metazone type="Kamchatka">
<long>
<standard>Petropavlovsk-Kamchatski Time</standard>
<daylight>Petropavlovsk-Kamchatski Summer Time</daylight>
</long>
</metazone>
<metazone type="Karachi">
<long>
<standard>Karachi Time</standard>
</long>
</metazone>
<metazone type="Kashgar">
<long>
<standard>Kashgar Time</standard>
</long>
</metazone>
<metazone type="Kazakhstan_Eastern">
<long>
<generic>East Kazakhstan Time</generic>
<standard>East Kazakhstan Standard Time</standard>
</long>
</metazone>
<metazone type="Kazakhstan_Western">
<long>
<generic>West Kazakhstan Time</generic>
<standard>West Kazakhstan Standard Time</standard>
</long>
</metazone>
<metazone type="Kizilorda">
<long>
<standard>Kizilorda Time</standard>
<daylight>Kizilorda Summer Time</daylight>
</long>
</metazone>
<metazone type="Korea">
<long>
<generic>Korean Time</generic>
<standard>Korean Standard Time</standard>
<daylight>Korean Daylight Time</daylight>
</long>
<short>
<generic>KT</generic>
</short>
</metazone>
<metazone type="Kosrae">
<long>
<standard>Kosrae Time</standard>
</long>
</metazone>
<metazone type="Krasnoyarsk">
<long>
<standard>Krasnoyarsk Time</standard>
<daylight>Krasnoyarsk Summer Time</daylight>
</long>
</metazone>
<metazone type="Kuybyshev">
<long>
<standard>Kuybyshev Time</standard>
<daylight>Kuybyshev Summer Time</daylight>
</long>
</metazone>
<metazone type="Kwajalein">
<long>
<standard>Kwajalein Time</standard>
</long>
</metazone>
<metazone type="Kyrgystan">
<long>
<standard>Kyrgystan Time</standard>
</long>
</metazone>
<metazone type="Lanka">
<long>
<standard>Lanka Time</standard>
</long>
</metazone>
<metazone type="Line_Islands">
<long>
<standard>Line Islands Time</standard>
</long>
</metazone>
<metazone type="Long_Shu">
<long>
<standard>Long-Shu Time</standard>
</long>
</metazone>
<metazone type="Lord_Howe">
<long>
<generic>Lord Howe Time</generic>
<standard>Lord Howe Standard Time</standard>
<daylight>Lord Howe Daylight Time</daylight>
</long>
<short>
<generic>LHT</generic>
</short>
</metazone>
<metazone type="Macau">
<long>
<standard>Macau Time</standard>
<daylight>Macau Summer Time</daylight>
</long>
</metazone>
<metazone type="Magadan">
<long>
<standard>Magadan Time</standard>
<daylight>Magadan Summer Time</daylight>
</long>
</metazone>
<metazone type="Malaya">
<long>
<standard>Malaya Time</standard>
</long>
</metazone>
<metazone type="Malaysia">
<long>
<standard>Malaysia Time</standard>
</long>
</metazone>
<metazone type="Maldives">
<long>
<standard>Maldives Time</standard>
</long>
</metazone>
<metazone type="Marquesas">
<long>
<standard>Marquesas Time</standard>
</long>
</metazone>
<metazone type="Marshall_Islands">
<long>
<standard>Marshall Islands Time</standard>
</long>
</metazone>
<metazone type="Mauritius">
<long>
<standard>Mauritius Time</standard>
<daylight>Mauritius Summer Time</daylight>
</long>
</metazone>
<metazone type="Mawson">
<long>
<standard>Mawson Time</standard>
</long>
</metazone>
<metazone type="Mongolia">
<long>
<standard>Ulan Bator Time</standard>
<daylight>Ulan Bator Summer Time</daylight>
</long>
</metazone>
<metazone type="Moscow">
<long>
<generic>Moscow Time</generic>
<standard>Moscow Standard Time</standard>
<daylight>Moscow Summer Time</daylight>
</long>
</metazone>
<metazone type="Myanmar">
<long>
<standard>Myanmar Time</standard>
</long>
</metazone>
<metazone type="Nauru">
<long>
<standard>Nauru Time</standard>
</long>
</metazone>
<metazone type="Nepal">
<long>
<standard>Nepal Time</standard>
</long>
</metazone>
<metazone type="New_Caledonia">
<long>
<standard>New Caledonia Time</standard>
<daylight>New Caledonia Summer Time</daylight>
</long>
</metazone>
<metazone type="New_Zealand">
<long>
<generic>New Zealand Time</generic>
<standard>New Zealand Standard Time</standard>
<daylight>New Zealand Daylight Time</daylight>
</long>
<short>
<generic>NZT</generic>
</short>
</metazone>
<metazone type="Newfoundland">
<long>
<generic>Newfoundland Time</generic>
<standard>Newfoundland Standard Time</standard>
<daylight>Newfoundland Daylight Time</daylight>
</long>
<short>
<generic>NT</generic>
</short>
</metazone>
<metazone type="Niue">
<long>
<standard>Niue Time</standard>
</long>
</metazone>
<metazone type="Norfolk">
<long>
<standard>Norfolk Islands Time</standard>
</long>
</metazone>
<metazone type="Noronha">
<long>
<standard>Fernando de Noronha Time</standard>
<daylight>Fernando de Noronha Summer Time</daylight>
</long>
</metazone>
<metazone type="North_Mariana">
<long>
<standard>North Mariana Islands Time</standard>
</long>
</metazone>
<metazone type="Novosibirsk">
<long>
<standard>Novosibirsk Time</standard>
<daylight>Novosibirsk Summer Time</daylight>
</long>
</metazone>
<metazone type="Omsk">
<long>
<standard>Omsk Time</standard>
<daylight>Omsk Summer Time</daylight>
</long>
</metazone>
<metazone type="Pakistan">
<long>
<standard>Pakistan Time</standard>
<daylight>Pakistan Summer Time</daylight>
</long>
</metazone>
<metazone type="Palau">
<long>
<standard>Palau Time</standard>
</long>
</metazone>
<metazone type="Papua_New_Guinea">
<long>
<standard>Papua New Guinea Time</standard>
</long>
</metazone>
<metazone type="Paraguay">
<long>
<standard>Paraguay Time</standard>
<daylight>Paraguay Summer Time</daylight>
</long>
</metazone>
<metazone type="Peru">
<long>
<standard>Peru Time</standard>
<daylight>Peru Summer Time</daylight>
</long>
</metazone>
<metazone type="Philippines">
<long>
<standard>Philippine Time</standard>
<daylight>Philippine Summer Time</daylight>
</long>
</metazone>
<metazone type="Phoenix_Islands">
<long>
<standard>Phoenix Islands Time</standard>
</long>
</metazone>
<metazone type="Pierre_Miquelon">
<long>
<generic>Pierre and Miquelon Time</generic>
<standard>Pierre and Miquelon Standard Time</standard>
<daylight>Pierre and Miquelon Daylight Time</daylight>
</long>
<short>
<generic>PMT</generic>
</short>
</metazone>
<metazone type="Pitcairn">
<long>
<standard>Pitcairn Time</standard>
</long>
</metazone>
<metazone type="Ponape">
<long>
<standard>Ponape Time</standard>
</long>
</metazone>
<metazone type="Qyzylorda">
<long>
<standard>Qyzylorda Time</standard>
<daylight>Qyzylorda Summer Time</daylight>
</long>
</metazone>
<metazone type="Reunion">
<long>
<standard>Reunion Time</standard>
</long>
</metazone>
<metazone type="Rothera">
<long>
<standard>Rothera Time</standard>
</long>
</metazone>
<metazone type="Sakhalin">
<long>
<standard>Sakhalin Time</standard>
<daylight>Sakhalin Summer Time</daylight>
</long>
</metazone>
<metazone type="Samara">
<long>
<standard>Samara Time</standard>
<daylight>Samara Summer Time</daylight>
</long>
</metazone>
<metazone type="Samarkand">
<long>
<standard>Samarkand Time</standard>
<daylight>Samarkand Summer Time</daylight>
</long>
</metazone>
<metazone type="Samoa">
<long>
<standard>Samoa Standard Time</standard>
</long>
</metazone>
<metazone type="Seychelles">
<long>
<standard>Seychelles Time</standard>
</long>
</metazone>
<metazone type="Shevchenko">
<long>
<standard>Shevchenko Time</standard>
<daylight>Shevchenko Summer Time</daylight>
</long>
</metazone>
<metazone type="Singapore">
<long>
<standard>Singapore Standard Time</standard>
</long>
</metazone>
<metazone type="Solomon">
<long>
<standard>Solomon Islands Time</standard>
</long>
</metazone>
<metazone type="South_Georgia">
<long>
<standard>South Georgia Time</standard>
</long>
<short>
<standard>GST (S. Georgia)</standard>
</short>
</metazone>
<metazone type="Suriname">
<long>
<standard>Suriname Time</standard>
</long>
</metazone>
<metazone type="Sverdlovsk">
<long>
<standard>Sverdlovsk Time</standard>
<daylight>Sverdlovsk Summer Time</daylight>
</long>
</metazone>
<metazone type="Syowa">
<long>
<standard>Syowa Time</standard>
</long>
</metazone>
<metazone type="Tahiti">
<long>
<standard>Tahiti Time</standard>
</long>
</metazone>
<metazone type="Taipei">
<long>
<generic>Taipei Time</generic>
<standard>Taipei Standard Time</standard>
<daylight>Taipei Daylight Time</daylight>
</long>
<short>
<generic>CT (Taipei)</generic>
<standard>CST (Taipei)</standard>
<daylight>CDT (Taipei)</daylight>
</short>
</metazone>
<metazone type="Tajikistan">
<long>
<standard>Tajikistan Time</standard>
</long>
</metazone>
<metazone type="Tashkent">
<long>
<standard>Tashkent Time</standard>
<daylight>Tashkent Summer Time</daylight>
</long>
</metazone>
<metazone type="Tbilisi">
<long>
<standard>Tbilisi Time</standard>
<daylight>Tbilisi Summer Time</daylight>
</long>
</metazone>
<metazone type="Tokelau">
<long>
<standard>Tokelau Time</standard>
</long>
</metazone>
<metazone type="Tonga">
<long>
<standard>Tonga Time</standard>
<daylight>Tonga Summer Time</daylight>
</long>
</metazone>
<metazone type="Truk">
<long>
<standard>Truk Time</standard>
</long>
</metazone>
<metazone type="Turkey">
<long>
<standard>Turkey Time</standard>
<daylight>Turkey Summer Time</daylight>
</long>
</metazone>
<metazone type="Turkmenistan">
<long>
<standard>Turkmenistan Time</standard>
<daylight>Turkmenistan Summer Time</daylight>
</long>
</metazone>
<metazone type="Tuvalu">
<long>
<standard>Tuvalu Time</standard>
</long>
</metazone>
<metazone type="Uralsk">
<long>
<standard>Ural'sk Time</standard>
<daylight>Ural'sk Summer Time</daylight>
</long>
</metazone>
<metazone type="Uruguay">
<long>
<standard>Uruguay Time</standard>
<daylight>Uruguay Summer Time</daylight>
</long>
</metazone>
<metazone type="Urumqi">
<long>
<standard>Urumqi Time</standard>
</long>
</metazone>
<metazone type="Uzbekistan">
<long>
<standard>Uzbekistan Time</standard>
<daylight>Uzbekistan Summer Time</daylight>
</long>
</metazone>
<metazone type="Vanuatu">
<long>
<standard>Vanuatu Time</standard>
<daylight>Vanuatu Summer Time</daylight>
</long>
</metazone>
<metazone type="Venezuela">
<long>
<standard>Venezuela Time</standard>
</long>
</metazone>
<metazone type="Vladivostok">
<long>
<standard>Vladivostok Time</standard>
<daylight>Vladivostok Summer Time</daylight>
</long>
</metazone>
<metazone type="Volgograd">
<long>
<standard>Volgograd Time</standard>
<daylight>Volgograd Summer Time</daylight>
</long>
</metazone>
<metazone type="Vostok">
<long>
<standard>Vostok Time</standard>
</long>
</metazone>
<metazone type="Wake">
<long>
<standard>Wake Island Time</standard>
</long>
</metazone>
<metazone type="Wallis">
<long>
<standard>Wallis and Futuna Time</standard>
</long>
</metazone>
<metazone type="Yakutsk">
<long>
<standard>Yakutsk Time</standard>
<daylight>Yakutsk Summer Time</daylight>
</long>
</metazone>
<metazone type="Yekaterinburg">
<long>
<standard>Yekaterinburg Time</standard>
<daylight>Yekaterinburg Summer Time</daylight>
</long>
</metazone>
<metazone type="Yerevan">
<long>
<standard>Yerevan Time</standard>
<daylight>Yerevan Summer Time</daylight>
</long>
</metazone>
<metazone type="Yukon">
<long>
<generic>Yukon Time</generic>
<standard>Yukon Standard Time</standard>
<daylight>Yukon Daylight Time</daylight>
</long>
<short>
<generic>YT</generic>
</short>
</metazone>
</timeZoneNames>
</dates>
<numbers>
<symbols>
<decimal>.</decimal>
<group>,</group>
<list>;</list>
<percentSign>%</percentSign>
<nativeZeroDigit>0</nativeZeroDigit>
<patternDigit>#</patternDigit>
<plusSign>+</plusSign>
<minusSign>-</minusSign>
<exponential>E</exponential>
<perMille></perMille>
<infinity></infinity>
<nan>NaN</nan>
</symbols>
<decimalFormats>
<decimalFormatLength>
<decimalFormat>
<pattern>#,##0.###</pattern>
</decimalFormat>
</decimalFormatLength>
</decimalFormats>
<scientificFormats>
<scientificFormatLength>
<scientificFormat>
<pattern>#E0</pattern>
</scientificFormat>
</scientificFormatLength>
</scientificFormats>
<percentFormats>
<percentFormatLength>
<percentFormat>
<pattern>#,##0%</pattern>
</percentFormat>
</percentFormatLength>
</percentFormats>
<currencyFormats>
<currencyFormatLength>
<currencyFormat>
<pattern>¤#,##0.00;(¤#,##0.00)</pattern>
</currencyFormat>
</currencyFormatLength>
<unitPattern count="one">{0} {1}</unitPattern>
<unitPattern count="other">{0} {1}</unitPattern>
</currencyFormats>
<currencies>
<currency type="ADP">
<displayName>Andorran Peseta</displayName>
<displayName count="one">Andorran peseta</displayName>
<displayName count="other">Andorran pesetas</displayName>
</currency>
<currency type="AED">
<displayName>United Arab Emirates Dirham</displayName>
<displayName count="one">UAE dirham</displayName>
<displayName count="other">UAE dirhams</displayName>
</currency>
<currency type="AFA">
<displayName>Afghan Afghani (1927-2002)</displayName>
<displayName count="one">Afghan Afghani (AFA)</displayName>
<displayName count="other">Afghan Afghanis (AFA)</displayName>
</currency>
<currency type="AFN">
<displayName>Afghan Afghani</displayName>
<displayName count="one">Afghan Afghani</displayName>
<displayName count="other">Afghan Afghanis</displayName>
</currency>
<currency type="ALL">
<displayName>Albanian Lek</displayName>
<displayName count="one">Albanian lek</displayName>
<displayName count="other">Albanian lekë</displayName>
</currency>
<currency type="AMD">
<displayName>Armenian Dram</displayName>
<displayName count="one">Armenian dram</displayName>
<displayName count="other">Armenian drams</displayName>
</currency>
<currency type="ANG">
<displayName>Netherlands Antillean Guilder</displayName>
<displayName count="one">Netherlands Antillean guilder</displayName>
<displayName count="other">Netherlands Antillean guilders</displayName>
</currency>
<currency type="AOA">
<displayName>Angolan Kwanza</displayName>
<displayName count="one">Angolan kwanza</displayName>
<displayName count="other">Angolan kwanzas</displayName>
</currency>
<currency type="AOK">
<displayName>Angolan Kwanza (1977-1990)</displayName>
<displayName count="one">Angolan kwanza (AOK)</displayName>
<displayName count="other">Angolan kwanzas (AOK)</displayName>
</currency>
<currency type="AON">
<displayName>Angolan New Kwanza (1990-2000)</displayName>
<displayName count="one">Angolan new kwanza (AON)</displayName>
<displayName count="other">Angolan new kwanzas (AON)</displayName>
</currency>
<currency type="AOR">
<displayName>Angolan Kwanza Reajustado (1995-1999)</displayName>
<displayName count="one">Angolan kwanza reajustado (AOR)</displayName>
<displayName count="other">Angolan kwanzas reajustado (AOR)</displayName>
</currency>
<currency type="ARA">
<displayName>Argentine Austral</displayName>
<displayName count="one">Argentine austral</displayName>
<displayName count="other">Argentine australs</displayName>
</currency>
<currency type="ARL">
<displayName>Argentine Peso Ley</displayName>
<displayName count="one">Argentine peso ley</displayName>
<displayName count="other">Argentine pesos ley</displayName>
</currency>
<currency type="ARM">
<displayName>Argentine Peso Moneda Nacional</displayName>
<displayName count="one">Argentine peso moneda nacional</displayName>
<displayName count="other">Argentine pesos moneda nacional</displayName>
</currency>
<currency type="ARP">
<displayName>Argentine Peso (1983-1985)</displayName>
<displayName count="one">Argentine peso (ARP)</displayName>
<displayName count="other">Argentine pesos (ARP)</displayName>
</currency>
<currency type="ARS">
<displayName>Argentine Peso</displayName>
<displayName count="one">Argentine peso</displayName>
<displayName count="other">Argentine pesos</displayName>
</currency>
<currency type="ATS">
<displayName>Austrian Schilling</displayName>
<displayName count="one">Austrian schilling</displayName>
<displayName count="other">Austrian schillings</displayName>
</currency>
<currency type="AUD">
<displayName>Australian Dollar</displayName>
<displayName count="one">Australian dollar</displayName>
<displayName count="other">Australian dollars</displayName>
</currency>
<currency type="AWG">
<displayName>Aruban Florin</displayName>
<displayName count="one">Aruban florin</displayName>
<displayName count="other">Aruban florin</displayName>
</currency>
<currency type="AZM">
<displayName>Azerbaijani Manat (1993-2006)</displayName>
<displayName count="one">Azerbaijani manat (AZM)</displayName>
<displayName count="other">Azerbaijani manats (AZM)</displayName>
</currency>
<currency type="AZN">
<displayName>Azerbaijani Manat</displayName>
<displayName count="one">Azerbaijani manat</displayName>
<displayName count="other">Azerbaijani manats</displayName>
</currency>
<currency type="BAD">
<displayName>Bosnia-Herzegovina Dinar</displayName>
<displayName count="one">Bosnia-Herzegovina dinar</displayName>
<displayName count="other">Bosnia-Herzegovina dinars</displayName>
</currency>
<currency type="BAM">
<displayName>Bosnia-Herzegovina Convertible Mark</displayName>
<displayName count="one">Bosnia-Herzegovina convertible mark</displayName>
<displayName count="other">Bosnia-Herzegovina convertible marks</displayName>
</currency>
<currency type="BAN">
<displayName>Bosnia-Herzegovina New Dinar</displayName>
<displayName count="one">Bosnia-Herzegovina new dinar</displayName>
<displayName count="other">Bosnia-Herzegovina new dinars</displayName>
</currency>
<currency type="BBD">
<displayName>Barbadian Dollar</displayName>
<displayName count="one">Barbadian dollar</displayName>
<displayName count="other">Barbadian dollars</displayName>
</currency>
<currency type="BDT">
<displayName>Bangladeshi Taka</displayName>
<displayName count="one">Bangladeshi taka</displayName>
<displayName count="other">Bangladeshi takas</displayName>
</currency>
<currency type="BEC">
<displayName>Belgian Franc (convertible)</displayName>
<displayName count="one">Belgian franc (convertible)</displayName>
<displayName count="other">Belgian francs (convertible)</displayName>
</currency>
<currency type="BEF">
<displayName>Belgian Franc</displayName>
<displayName count="one">Belgian franc</displayName>
<displayName count="other">Belgian francs</displayName>
</currency>
<currency type="BEL">
<displayName>Belgian Franc (financial)</displayName>
<displayName count="one">Belgian franc (financial)</displayName>
<displayName count="other">Belgian francs (financial)</displayName>
</currency>
<currency type="BGL">
<displayName>Bulgarian Hard Lev</displayName>
<displayName count="one">Bulgarian hard lev</displayName>
<displayName count="other">Bulgarian hard leva</displayName>
</currency>
<currency type="BGM">
<displayName>Bulgarian Socialist Lev</displayName>
<displayName count="one">Bulgarian socialist lev</displayName>
<displayName count="other">Bulgarian socialist leva</displayName>
</currency>
<currency type="BGN">
<displayName>Bulgarian Lev</displayName>
<displayName count="one">Bulgarian lev</displayName>
<displayName count="other">Bulgarian leva</displayName>
</currency>
<currency type="BGO">
<displayName>Old Bulgarian Lev</displayName>
<displayName count="one">Old Bulgarian lev</displayName>
<displayName count="other">Old Bulgarian leva</displayName>
</currency>
<currency type="BHD">
<displayName>Bahraini Dinar</displayName>
<displayName count="one">Bahraini dinar</displayName>
<displayName count="other">Bahraini dinars</displayName>
</currency>
<currency type="BIF">
<displayName>Burundian Franc</displayName>
<displayName count="one">Burundian franc</displayName>
<displayName count="other">Burundian francs</displayName>
</currency>
<currency type="BMD">
<displayName>Bermudan Dollar</displayName>
<displayName count="one">Bermudan dollar</displayName>
<displayName count="other">Bermudan dollars</displayName>
</currency>
<currency type="BND">
<displayName>Brunei Dollar</displayName>
<displayName count="one">Brunei dollar</displayName>
<displayName count="other">Brunei dollars</displayName>
</currency>
<currency type="BOB">
<displayName>Bolivian Boliviano</displayName>
<displayName count="one">Bolivian boliviano</displayName>
<displayName count="other">Bolivian bolivianos</displayName>
</currency>
<currency type="BOL">
<displayName>Old Bolivian Boliviano</displayName>
<displayName count="one">Old Bolivian boliviano</displayName>
<displayName count="other">Old Bolivian bolivianos</displayName>
</currency>
<currency type="BOP">
<displayName>Bolivian Peso</displayName>
<displayName count="one">Bolivian peso</displayName>
<displayName count="other">Bolivian pesos</displayName>
</currency>
<currency type="BOV">
<displayName>Bolivian Mvdol</displayName>
<displayName count="one">Bolivian mvdol</displayName>
<displayName count="other">Bolivian mvdols</displayName>
</currency>
<currency type="BRB">
<displayName>Brazilian Cruzeiro Novo (1967-1986)</displayName>
<displayName count="one">Brazilian cruzeiro novo (BRB)</displayName>
<displayName count="other">Brazilian cruzeiros novo (BRB)</displayName>
</currency>
<currency type="BRC">
<displayName>Brazilian Cruzado</displayName>
<displayName count="one">Brazilian cruzado</displayName>
<displayName count="other">Brazilian cruzados</displayName>
</currency>
<currency type="BRE">
<displayName>Brazilian Cruzeiro (1990-1993)</displayName>
<displayName count="one">Brazilian cruzeiro (BRE)</displayName>
<displayName count="other">Brazilian cruzeiros (BRE)</displayName>
</currency>
<currency type="BRL">
<displayName>Brazilian Real</displayName>
<displayName count="one">Brazilian real</displayName>
<displayName count="other">Brazilian reals</displayName>
</currency>
<currency type="BRN">
<displayName>Brazilian Cruzado Novo</displayName>
<displayName count="one">Brazilian cruzado novo</displayName>
<displayName count="other">Brazilian cruzado novos</displayName>
</currency>
<currency type="BRR">
<displayName>Brazilian Cruzeiro</displayName>
<displayName count="one">Brazilian cruzeiro</displayName>
<displayName count="other">Brazilian cruzeiros</displayName>
</currency>
<currency type="BRZ">
<displayName>Old Brazilian Cruzeiro</displayName>
<displayName count="one">Old Brazilian cruzeiro</displayName>
<displayName count="other">Old Brazilian cruzeiros</displayName>
</currency>
<currency type="BSD">
<displayName>Bahamian Dollar</displayName>
<displayName count="one">Bahamian dollar</displayName>
<displayName count="other">Bahamian dollars</displayName>
</currency>
<currency type="BTN">
<displayName>Bhutanese Ngultrum</displayName>
<displayName count="one">Bhutanese ngultrum</displayName>
<displayName count="other">Bhutanese ngultrums</displayName>
</currency>
<currency type="BUK">
<displayName>Burmese Kyat</displayName>
<displayName count="one">Burmese kyat</displayName>
<displayName count="other">Burmese kyats</displayName>
</currency>
<currency type="BWP">
<displayName>Botswanan Pula</displayName>
<displayName count="one">Botswanan pula</displayName>
<displayName count="other">Botswanan pulas</displayName>
</currency>
<currency type="BYB">
<displayName>Belarusian New Ruble (1994-1999)</displayName>
<displayName count="one">Belarusian new ruble (BYB)</displayName>
<displayName count="other">Belarusian new rubles (BYB)</displayName>
</currency>
<currency type="BYR">
<displayName>Belarusian Ruble</displayName>
<displayName count="one">Belarusian ruble</displayName>
<displayName count="other">Belarusian rubles</displayName>
</currency>
<currency type="BZD">
<displayName>Belize Dollar</displayName>
<displayName count="one">Belize dollar</displayName>
<displayName count="other">Belize dollars</displayName>
</currency>
<currency type="CAD">
<displayName>Canadian Dollar</displayName>
<displayName count="one">Canadian dollar</displayName>
<displayName count="other">Canadian dollars</displayName>
</currency>
<currency type="CDF">
<displayName>Congolese Franc</displayName>
<displayName count="one">Congolese franc</displayName>
<displayName count="other">Congolese francs</displayName>
</currency>
<currency type="CHE">
<displayName>WIR Euro</displayName>
<displayName count="one">WIR euro</displayName>
<displayName count="other">WIR euros</displayName>
</currency>
<currency type="CHF">
<displayName>Swiss Franc</displayName>
<displayName count="one">Swiss franc</displayName>
<displayName count="other">Swiss francs</displayName>
</currency>
<currency type="CHW">
<displayName>WIR Franc</displayName>
<displayName count="one">WIR franc</displayName>
<displayName count="other">WIR francs</displayName>
</currency>
<currency type="CLE">
<displayName>Chilean Escudo</displayName>
<displayName count="one">Chilean escudo</displayName>
<displayName count="other">Chilean escudos</displayName>
</currency>
<currency type="CLF">
<displayName>Chilean Unidades de Fomento</displayName>
<displayName count="one">Chilean unidades de fomento</displayName>
<displayName count="other">Chilean unidades de fomentos</displayName>
</currency>
<currency type="CLP">
<displayName>Chilean Peso</displayName>
<displayName count="one">Chilean peso</displayName>
<displayName count="other">Chilean pesos</displayName>
</currency>
<currency type="CNY">
<displayName>Chinese Yuan Renminbi</displayName>
<displayName count="one">Chinese yuan</displayName>
<displayName count="other">Chinese yuan</displayName>
</currency>
<currency type="COP">
<displayName>Colombian Peso</displayName>
<displayName count="one">Colombian peso</displayName>
<displayName count="other">Colombian pesos</displayName>
</currency>
<currency type="COU">
<displayName>Unidad de Valor Real</displayName>
<displayName count="one">unidad de valor real</displayName>
<displayName count="other">unidad de valor reals</displayName>
</currency>
<currency type="CRC">
<displayName>Costa Rican Colón</displayName>
<displayName count="one">Costa Rican colón</displayName>
<displayName count="other">Costa Rican colóns</displayName>
</currency>
<currency type="CSD">
<displayName>Old Serbian Dinar</displayName>
<displayName count="one">Old Serbian dinar</displayName>
<displayName count="other">Old Serbian dinars</displayName>
</currency>
<currency type="CSK">
<displayName>Czechoslovak Hard Koruna</displayName>
<displayName count="one">Czechoslovak hard koruna</displayName>
<displayName count="other">Czechoslovak hard korunas</displayName>
</currency>
<currency type="CUC">
<displayName>Cuban Convertible Peso</displayName>
<displayName count="one">Cuban convertible peso</displayName>
<displayName count="other">Cuban convertible pesos</displayName>
</currency>
<currency type="CUP">
<displayName>Cuban Peso</displayName>
<displayName count="one">Cuban peso</displayName>
<displayName count="other">Cuban pesos</displayName>
</currency>
<currency type="CVE">
<displayName>Cape Verdean Escudo</displayName>
<displayName count="one">Cape Verdean escudo</displayName>
<displayName count="other">Cape Verdean escudos</displayName>
</currency>
<currency type="CYP">
<displayName>Cypriot Pound</displayName>
<displayName count="one">Cypriot pound</displayName>
<displayName count="other">Cypriot pounds</displayName>
</currency>
<currency type="CZK">
<displayName>Czech Republic Koruna</displayName>
<displayName count="one">Czech Republic koruna</displayName>
<displayName count="other">Czech Republic korunas</displayName>
</currency>
<currency type="DDM">
<displayName>East German Mark</displayName>
<displayName count="one">East German mark</displayName>
<displayName count="other">East German marks</displayName>
</currency>
<currency type="DEM">
<displayName>German Mark</displayName>
<displayName count="one">German mark</displayName>
<displayName count="other">German marks</displayName>
</currency>
<currency type="DJF">
<displayName>Djiboutian Franc</displayName>
<displayName count="one">Djiboutian franc</displayName>
<displayName count="other">Djiboutian francs</displayName>
</currency>
<currency type="DKK">
<displayName>Danish Krone</displayName>
<displayName count="one">Danish krone</displayName>
<displayName count="other">Danish kroner</displayName>
</currency>
<currency type="DOP">
<displayName>Dominican Peso</displayName>
<displayName count="one">Dominican peso</displayName>
<displayName count="other">Dominican pesos</displayName>
</currency>
<currency type="DZD">
<displayName>Algerian Dinar</displayName>
<displayName count="one">Algerian dinar</displayName>
<displayName count="other">Algerian dinars</displayName>
</currency>
<currency type="ECS">
<displayName>Ecuadorian Sucre</displayName>
<displayName count="one">Ecuadorian sucre</displayName>
<displayName count="other">Ecuadorian sucres</displayName>
</currency>
<currency type="ECV">
<displayName>Ecuadorian Unidad de Valor Constante (UVC)</displayName>
<displayName count="one">Ecuadorian unidad de valor Constante (UVC)</displayName>
<displayName count="other">Ecuadorian unidads de valor Constante (UVC)</displayName>
</currency>
<currency type="EEK">
<displayName>Estonian Kroon</displayName>
<displayName count="one">Estonian kroon</displayName>
<displayName count="other">Estonian kroons</displayName>
</currency>
<currency type="EGP">
<displayName>Egyptian Pound</displayName>
<displayName count="one">Egyptian pound</displayName>
<displayName count="other">Egyptian pounds</displayName>
</currency>
<currency type="ERN">
<displayName>Eritrean Nakfa</displayName>
<displayName count="one">Eritrean nakfa</displayName>
<displayName count="other">Eritrean nakfas</displayName>
</currency>
<currency type="ESA">
<displayName>Spanish Peseta (A account)</displayName>
<displayName count="one">Spanish peseta (A account)</displayName>
<displayName count="other">Spanish pesetas (A account)</displayName>
</currency>
<currency type="ESB">
<displayName>Spanish Peseta (convertible account)</displayName>
<displayName count="one">Spanish peseta (convertible account)</displayName>
<displayName count="other">Spanish pesetas (convertible account)</displayName>
</currency>
<currency type="ESP">
<displayName>Spanish Peseta</displayName>
<displayName count="one">Spanish peseta</displayName>
<displayName count="other">Spanish pesetas</displayName>
</currency>
<currency type="ETB">
<displayName>Ethiopian Birr</displayName>
<displayName count="one">Ethiopian birr</displayName>
<displayName count="other">Ethiopian birrs</displayName>
</currency>
<currency type="EUR">
<displayName>Euro</displayName>
<displayName count="one">euro</displayName>
<displayName count="other">euros</displayName>
</currency>
<currency type="FIM">
<displayName>Finnish Markka</displayName>
<displayName count="one">Finnish markka</displayName>
<displayName count="other">Finnish markkas</displayName>
</currency>
<currency type="FJD">
<displayName>Fijian Dollar</displayName>
<displayName count="one">Fijian dollar</displayName>
<displayName count="other">Fijian dollars</displayName>
</currency>
<currency type="FKP">
<displayName>Falkland Islands Pound</displayName>
<displayName count="one">Falkland Islands pound</displayName>
<displayName count="other">Falkland Islands pounds</displayName>
</currency>
<currency type="FRF">
<displayName>French Franc</displayName>
<displayName count="one">French franc</displayName>
<displayName count="other">French francs</displayName>
</currency>
<currency type="GBP">
<displayName>British Pound Sterling</displayName>
<displayName count="one">British pound sterling</displayName>
<displayName count="other">British pound sterlings</displayName>
</currency>
<currency type="GEK">
<displayName>Georgian Kupon Larit</displayName>
<displayName count="one">Georgian kupon larit</displayName>
<displayName count="other">Georgian kupon larits</displayName>
</currency>
<currency type="GEL">
<displayName>Georgian Lari</displayName>
<displayName count="one">Georgian lari</displayName>
<displayName count="other">Georgian laris</displayName>
</currency>
<currency type="GHC">
<displayName>Ghanaian Cedi (1979-2007)</displayName>
<displayName count="one">Ghanaian cedi (GHC)</displayName>
<displayName count="other">Ghanaian cedis (GHC)</displayName>
</currency>
<currency type="GHS">
<displayName>Ghanaian Cedi</displayName>
<displayName count="one">Ghanaian cedi</displayName>
<displayName count="other">Ghanaian cedis</displayName>
</currency>
<currency type="GIP">
<displayName>Gibraltar Pound</displayName>
<displayName count="one">Gibraltar pound</displayName>
<displayName count="other">Gibraltar pounds</displayName>
</currency>
<currency type="GMD">
<displayName>Gambian Dalasi</displayName>
<displayName count="one">Gambian dalasi</displayName>
<displayName count="other">Gambian dalasis</displayName>
</currency>
<currency type="GNF">
<displayName>Guinean Franc</displayName>
<displayName count="one">Guinean franc</displayName>
<displayName count="other">Guinean francs</displayName>
</currency>
<currency type="GNS">
<displayName>Guinean Syli</displayName>
<displayName count="one">Guinean syli</displayName>
<displayName count="other">Guinean sylis</displayName>
</currency>
<currency type="GQE">
<displayName>Equatorial Guinean Ekwele</displayName>
<displayName count="one">Equatorial Guinean ekwele</displayName>
<displayName count="other">Equatorial Guinean ekwele</displayName>
</currency>
<currency type="GRD">
<displayName>Greek Drachma</displayName>
<displayName count="one">Greek drachma</displayName>
<displayName count="other">Greek drachmas</displayName>
</currency>
<currency type="GTQ">
<displayName>Guatemalan Quetzal</displayName>
<displayName count="one">Guatemalan quetzal</displayName>
<displayName count="other">Guatemalan quetzals</displayName>
</currency>
<currency type="GWE">
<displayName>Portuguese Guinea Escudo</displayName>
<displayName count="one">Portuguese Guinea escudo</displayName>
<displayName count="other">Portuguese Guinea escudos</displayName>
</currency>
<currency type="GWP">
<displayName>Guinea-Bissau Peso</displayName>
<displayName count="one">Guinea-Bissau peso</displayName>
<displayName count="other">Guinea-Bissau pesos</displayName>
</currency>
<currency type="GYD">
<displayName>Guyanaese Dollar</displayName>
<displayName count="one">Guyanaese dollar</displayName>
<displayName count="other">Guyanaese dollars</displayName>
</currency>
<currency type="HKD">
<displayName>Hong Kong Dollar</displayName>
<displayName count="one">Hong Kong dollar</displayName>
<displayName count="other">Hong Kong dollars</displayName>
</currency>
<currency type="HNL">
<displayName>Honduran Lempira</displayName>
<displayName count="one">Honduran lempira</displayName>
<displayName count="other">Honduran lempiras</displayName>
</currency>
<currency type="HRD">
<displayName>Croatian Dinar</displayName>
<displayName count="one">Croatian dinar</displayName>
<displayName count="other">Croatian dinars</displayName>
</currency>
<currency type="HRK">
<displayName>Croatian Kuna</displayName>
<displayName count="one">Croatian kuna</displayName>
<displayName count="other">Croatian kunas</displayName>
</currency>
<currency type="HTG">
<displayName>Haitian Gourde</displayName>
<displayName count="one">Haitian gourde</displayName>
<displayName count="other">Haitian gourdes</displayName>
</currency>
<currency type="HUF">
<displayName>Hungarian Forint</displayName>
<displayName count="one">Hungarian forint</displayName>
<displayName count="other">Hungarian forints</displayName>
</currency>
<currency type="IDR">
<displayName>Indonesian Rupiah</displayName>
<displayName count="one">Indonesian rupiah</displayName>
<displayName count="other">Indonesian rupiahs</displayName>
</currency>
<currency type="IEP">
<displayName>Irish Pound</displayName>
<displayName count="one">Irish pound</displayName>
<displayName count="other">Irish pounds</displayName>
</currency>
<currency type="ILP">
<displayName>Israeli Pound</displayName>
<displayName count="one">Israeli pound</displayName>
<displayName count="other">Israeli pounds</displayName>
</currency>
<currency type="ILR">
<displayName>Old Israeli Sheqel</displayName>
<displayName count="one">Old Israeli sheqel</displayName>
<displayName count="other">Old Israeli sheqels</displayName>
</currency>
<currency type="ILS">
<displayName>Israeli New Sheqel</displayName>
<displayName count="one">Israeli new sheqel</displayName>
<displayName count="other">Israeli new sheqels</displayName>
</currency>
<currency type="INR">
<displayName>Indian Rupee</displayName>
<displayName count="one">Indian rupee</displayName>
<displayName count="other">Indian rupees</displayName>
</currency>
<currency type="IQD">
<displayName>Iraqi Dinar</displayName>
<displayName count="one">Iraqi dinar</displayName>
<displayName count="other">Iraqi dinars</displayName>
</currency>
<currency type="IRR">
<displayName>Iranian Rial</displayName>
<displayName count="one">Iranian rial</displayName>
<displayName count="other">Iranian rials</displayName>
</currency>
<currency type="ISJ">
<displayName>Old Icelandic Króna</displayName>
<displayName count="one">Old Icelandic króna</displayName>
<displayName count="other">Old Icelandic krónur</displayName>
</currency>
<currency type="ISK">
<displayName>Icelandic Króna</displayName>
<displayName count="one">Icelandic króna</displayName>
<displayName count="other">Icelandic krónur</displayName>
</currency>
<currency type="ITL">
<displayName>Italian Lira</displayName>
<displayName count="one">Italian lira</displayName>
<displayName count="other">Italian liras</displayName>
</currency>
<currency type="JMD">
<displayName>Jamaican Dollar</displayName>
<displayName count="one">Jamaican dollar</displayName>
<displayName count="other">Jamaican dollars</displayName>
</currency>
<currency type="JOD">
<displayName>Jordanian Dinar</displayName>
<displayName count="one">Jordanian dinar</displayName>
<displayName count="other">Jordanian dinars</displayName>
</currency>
<currency type="JPY">
<displayName>Japanese Yen</displayName>
<displayName count="one">Japanese yen</displayName>
<displayName count="other">Japanese yen</displayName>
<symbol>¥</symbol>
</currency>
<currency type="KES">
<displayName>Kenyan Shilling</displayName>
<displayName count="one">Kenyan shilling</displayName>
<displayName count="other">Kenyan shillings</displayName>
</currency>
<currency type="KGS">
<displayName>Kyrgystani Som</displayName>
<displayName count="one">Kyrgystani som</displayName>
<displayName count="other">Kyrgystani soms</displayName>
</currency>
<currency type="KHR">
<displayName>Cambodian Riel</displayName>
<displayName count="one">Cambodian riel</displayName>
<displayName count="other">Cambodian riels</displayName>
</currency>
<currency type="KMF">
<displayName>Comorian Franc</displayName>
<displayName count="one">Comorian franc</displayName>
<displayName count="other">Comorian francs</displayName>
</currency>
<currency type="KPW">
<displayName>North Korean Won</displayName>
<displayName count="one">North Korean won</displayName>
<displayName count="other">North Korean won</displayName>
</currency>
<currency type="KRH">
<displayName>South Korean Hwan</displayName>
<displayName count="one">South Korean hwan</displayName>
<displayName count="other">South Korean hwan</displayName>
</currency>
<currency type="KRO">
<displayName>Old South Korean Won</displayName>
<displayName count="one">Old South Korean won</displayName>
<displayName count="other">Old South Korean won</displayName>
</currency>
<currency type="KRW">
<displayName>South Korean Won</displayName>
<displayName count="one">South Korean won</displayName>
<displayName count="other">South Korean won</displayName>
</currency>
<currency type="KWD">
<displayName>Kuwaiti Dinar</displayName>
<displayName count="one">Kuwaiti dinar</displayName>
<displayName count="other">Kuwaiti dinars</displayName>
</currency>
<currency type="KYD">
<displayName>Cayman Islands Dollar</displayName>
<displayName count="one">Cayman Islands dollar</displayName>
<displayName count="other">Cayman Islands dollars</displayName>
</currency>
<currency type="KZT">
<displayName>Kazakhstan Tenge</displayName>
<displayName count="one">Kazakhstan tenge</displayName>
<displayName count="other">Kazakhstan tenges</displayName>
</currency>
<currency type="LAK">
<displayName>Laotian Kip</displayName>
<displayName count="one">Laotian kip</displayName>
<displayName count="other">Laotian kips</displayName>
</currency>
<currency type="LBP">
<displayName>Lebanese Pound</displayName>
<displayName count="one">Lebanese pound</displayName>
<displayName count="other">Lebanese pounds</displayName>
</currency>
<currency type="LKR">
<displayName>Sri Lanka Rupee</displayName>
<displayName count="one">Sri Lanka rupee</displayName>
<displayName count="other">Sri Lanka rupees</displayName>
</currency>
<currency type="LRD">
<displayName>Liberian Dollar</displayName>
<displayName count="one">Liberian dollar</displayName>
<displayName count="other">Liberian dollars</displayName>
</currency>
<currency type="LSL">
<displayName>Lesotho Loti</displayName>
<displayName count="one">Lesotho loti</displayName>
<displayName count="other">Lesotho lotis</displayName>
</currency>
<currency type="LTL">
<displayName>Lithuanian Litas</displayName>
<displayName count="one">Lithuanian litas</displayName>
<displayName count="other">Lithuanian litai</displayName>
</currency>
<currency type="LTT">
<displayName>Lithuanian Talonas</displayName>
<displayName count="one">Lithuanian talonas</displayName>
<displayName count="other">Lithuanian talonases</displayName>
</currency>
<currency type="LUC">
<displayName>Luxembourgian Convertible Franc</displayName>
<displayName count="one">Luxembourgian convertible franc</displayName>
<displayName count="other">Luxembourgian convertible francs</displayName>
</currency>
<currency type="LUF">
<displayName>Luxembourgian Franc</displayName>
<displayName count="one">Luxembourgian franc</displayName>
<displayName count="other">Luxembourgian francs</displayName>
</currency>
<currency type="LUL">
<displayName>Luxembourg Financial Franc</displayName>
<displayName count="one">Luxembourg financial franc</displayName>
<displayName count="other">Luxembourg financial francs</displayName>
</currency>
<currency type="LVL">
<displayName>Latvian Lats</displayName>
<displayName count="one">Latvian lats</displayName>
<displayName count="other">Latvian lati</displayName>
</currency>
<currency type="LVR">
<displayName>Latvian Ruble</displayName>
<displayName count="one">Latvian ruble</displayName>
<displayName count="other">Latvian rubles</displayName>
</currency>
<currency type="LYD">
<displayName>Libyan Dinar</displayName>
<displayName count="one">Libyan dinar</displayName>
<displayName count="other">Libyan dinars</displayName>
</currency>
<currency type="MAD">
<displayName>Moroccan Dirham</displayName>
<displayName count="one">Moroccan dirham</displayName>
<displayName count="other">Moroccan dirhams</displayName>
</currency>
<currency type="MAF">
<displayName>Moroccan Franc</displayName>
<displayName count="one">Moroccan franc</displayName>
<displayName count="other">Moroccan francs</displayName>
</currency>
<currency type="MCF">
<displayName>Monegasque Franc</displayName>
<displayName count="one">Monegasque franc</displayName>
<displayName count="other">Monegasque francs</displayName>
</currency>
<currency type="MDC">
<displayName>Moldovan Cupon</displayName>
<displayName count="one">Moldovan cupon</displayName>
<displayName count="other">Moldovan cupon</displayName>
</currency>
<currency type="MDL">
<displayName>Moldovan Leu</displayName>
<displayName count="one">Moldovan leu</displayName>
<displayName count="other">Moldovan lei</displayName>
</currency>
<currency type="MGA">
<displayName>Malagasy Ariary</displayName>
<displayName count="one">Malagasy Ariary</displayName>
<displayName count="other">Malagasy Ariaries</displayName>
</currency>
<currency type="MGF">
<displayName>Malagasy Franc</displayName>
<displayName count="one">Malagasy franc</displayName>
<displayName count="other">Malagasy francs</displayName>
</currency>
<currency type="MKD">
<displayName>Macedonian Denar</displayName>
<displayName count="one">Macedonian denar</displayName>
<displayName count="other">Macedonian denari</displayName>
</currency>
<currency type="MKN">
<displayName>Old Macedonian Denar</displayName>
<displayName count="one">Old Macedonian denar</displayName>
<displayName count="other">Old Macedonian denari</displayName>
</currency>
<currency type="MLF">
<displayName>Malian Franc</displayName>
<displayName count="one">Malian franc</displayName>
<displayName count="other">Malian francs</displayName>
</currency>
<currency type="MMK">
<displayName>Myanma Kyat</displayName>
<displayName count="one">Myanma kyat</displayName>
<displayName count="other">Myanma kyats</displayName>
</currency>
<currency type="MNT">
<displayName>Mongolian Tugrik</displayName>
<displayName count="one">Mongolian tugrik</displayName>
<displayName count="other">Mongolian tugriks</displayName>
</currency>
<currency type="MOP">
<displayName>Macanese Pataca</displayName>
<displayName count="one">Macanese pataca</displayName>
<displayName count="other">Macanese patacas</displayName>
</currency>
<currency type="MRO">
<displayName>Mauritanian Ouguiya</displayName>
<displayName count="one">Mauritanian ouguiya</displayName>
<displayName count="other">Mauritanian ouguiyas</displayName>
</currency>
<currency type="MTL">
<displayName>Maltese Lira</displayName>
<displayName count="one">Maltese lira</displayName>
<displayName count="other">Maltese lira</displayName>
</currency>
<currency type="MTP">
<displayName>Maltese Pound</displayName>
<displayName count="one">Maltese pound</displayName>
<displayName count="other">Maltese pounds</displayName>
</currency>
<currency type="MUR">
<displayName>Mauritian Rupee</displayName>
<displayName count="one">Mauritian rupee</displayName>
<displayName count="other">Mauritian rupees</displayName>
</currency>
<currency type="MVP">
<displayName>Maldivian Rupee</displayName>
<displayName count="one">Maldivian rupee</displayName>
<displayName count="other">Maldivian rupees</displayName>
</currency>
<currency type="MVR">
<displayName>Maldivian Rufiyaa</displayName>
<displayName count="one">Maldivian rufiyaa</displayName>
<displayName count="other">Maldivian rufiyaas</displayName>
</currency>
<currency type="MWK">
<displayName>Malawian Kwacha</displayName>
<displayName count="one">Malawian Kwacha</displayName>
<displayName count="other">Malawian Kwachas</displayName>
</currency>
<currency type="MXN">
<displayName>Mexican Peso</displayName>
<displayName count="one">Mexican peso</displayName>
<displayName count="other">Mexican pesos</displayName>
</currency>
<currency type="MXP">
<displayName>Mexican Silver Peso (1861-1992)</displayName>
<displayName count="one">Mexican silver peso (MXP)</displayName>
<displayName count="other">Mexican silver pesos (MXP)</displayName>
</currency>
<currency type="MXV">
<displayName>Mexican Unidad de Inversion (UDI)</displayName>
<displayName count="one">Mexican unidad de inversion (UDI)</displayName>
<displayName count="other">Mexican unidads de inversion (UDI)</displayName>
</currency>
<currency type="MYR">
<displayName>Malaysian Ringgit</displayName>
<displayName count="one">Malaysian ringgit</displayName>
<displayName count="other">Malaysian ringgits</displayName>
</currency>
<currency type="MZE">
<displayName>Mozambican Escudo</displayName>
<displayName count="one">Mozambican escudo</displayName>
<displayName count="other">Mozambican escudos</displayName>
</currency>
<currency type="MZM">
<displayName>Old Mozambican Metical</displayName>
<displayName count="one">Old Mozambican metical</displayName>
<displayName count="other">Old Mozambican meticals</displayName>
</currency>
<currency type="MZN">
<displayName>Mozambican Metical</displayName>
<displayName count="one">Mozambican metical</displayName>
<displayName count="other">Mozambican meticals</displayName>
</currency>
<currency type="NAD">
<displayName>Namibian Dollar</displayName>
<displayName count="one">Namibian dollar</displayName>
<displayName count="other">Namibian dollars</displayName>
</currency>
<currency type="NGN">
<displayName>Nigerian Naira</displayName>
<displayName count="one">Nigerian naira</displayName>
<displayName count="other">Nigerian nairas</displayName>
</currency>
<currency type="NIC">
<displayName>Nicaraguan Cordoba</displayName>
<displayName count="one">Nicaraguan cordoba</displayName>
<displayName count="other">Nicaraguan cordobas</displayName>
</currency>
<currency type="NIO">
<displayName>Nicaraguan Cordoba Oro</displayName>
<displayName count="one">Nicaraguan cordoba oro</displayName>
<displayName count="other">Nicaraguan cordobas oro</displayName>
</currency>
<currency type="NLG">
<displayName>Dutch Guilder</displayName>
<displayName count="one">Dutch guilder</displayName>
<displayName count="other">Dutch guilders</displayName>
</currency>
<currency type="NOK">
<displayName>Norwegian Krone</displayName>
<displayName count="one">Norwegian krone</displayName>
<displayName count="other">Norwegian kroner</displayName>
</currency>
<currency type="NPR">
<displayName>Nepalese Rupee</displayName>
<displayName count="one">Nepalese rupee</displayName>
<displayName count="other">Nepalese rupees</displayName>
</currency>
<currency type="NZD">
<displayName>New Zealand Dollar</displayName>
<displayName count="one">New Zealand dollar</displayName>
<displayName count="other">New Zealand dollars</displayName>
</currency>
<currency type="OMR">
<displayName>Omani Rial</displayName>
<displayName count="one">Omani rial</displayName>
<displayName count="other">Omani rials</displayName>
</currency>
<currency type="PAB">
<displayName>Panamanian Balboa</displayName>
<displayName count="one">Panamanian balboa</displayName>
<displayName count="other">Panamanian balboas</displayName>
</currency>
<currency type="PEI">
<displayName>Peruvian Inti</displayName>
<displayName count="one">Peruvian inti</displayName>
<displayName count="other">Peruvian intis</displayName>
</currency>
<currency type="PEN">
<displayName>Peruvian Nuevo Sol</displayName>
<displayName count="one">Peruvian nuevo sol</displayName>
<displayName count="other">Peruvian nuevos soles</displayName>
</currency>
<currency type="PES">
<displayName>Peruvian Sol</displayName>
<displayName count="one">Peruvian sol</displayName>
<displayName count="other">Peruvian soles</displayName>
</currency>
<currency type="PGK">
<displayName>Papua New Guinean Kina</displayName>
<displayName count="one">Papua New Guinean kina</displayName>
<displayName count="other">Papua New Guinean kina</displayName>
</currency>
<currency type="PHP">
<displayName>Philippine Peso</displayName>
<displayName count="one">Philippine peso</displayName>
<displayName count="other">Philippine pesos</displayName>
</currency>
<currency type="PKR">
<displayName>Pakistani Rupee</displayName>
<displayName count="one">Pakistani rupee</displayName>
<displayName count="other">Pakistani rupees</displayName>
</currency>
<currency type="PLN">
<displayName>Polish Zloty</displayName>
<displayName count="one">Polish zloty</displayName>
<displayName count="other">Polish zlotys</displayName>
</currency>
<currency type="PLZ">
<displayName>Polish Zloty (1950-1995)</displayName>
<displayName count="one">Polish zloty (PLZ)</displayName>
<displayName count="other">Polish zlotys (PLZ)</displayName>
</currency>
<currency type="PTE">
<displayName>Portuguese Escudo</displayName>
<displayName count="one">Portuguese escudo</displayName>
<displayName count="other">Portuguese escudos</displayName>
</currency>
<currency type="PYG">
<displayName>Paraguayan Guarani</displayName>
<displayName count="one">Paraguayan guarani</displayName>
<displayName count="other">Paraguayan guaranis</displayName>
</currency>
<currency type="QAR">
<displayName>Qatari Rial</displayName>
<displayName count="one">Qatari rial</displayName>
<displayName count="other">Qatari rials</displayName>
</currency>
<currency type="RHD">
<displayName>Rhodesian Dollar</displayName>
<displayName count="one">Rhodesian dollar</displayName>
<displayName count="other">Rhodesian dollars</displayName>
</currency>
<currency type="ROL">
<displayName>Old Romanian Leu</displayName>
<displayName count="one">Old Romanian leu</displayName>
<displayName count="other">Old Romanian Lei</displayName>
</currency>
<currency type="RON">
<displayName>Romanian Leu</displayName>
<displayName count="one">Romanian leu</displayName>
<displayName count="other">Romanian lei</displayName>
</currency>
<currency type="RSD">
<displayName>Serbian Dinar</displayName>
<displayName count="one">Serbian dinar</displayName>
<displayName count="other">Serbian dinars</displayName>
</currency>
<currency type="RUB">
<displayName>Russian Ruble</displayName>
<displayName count="one">Russian ruble</displayName>
<displayName count="other">Russian rubles</displayName>
</currency>
<currency type="RUR">
<displayName>Russian Ruble (1991-1998)</displayName>
<displayName count="one">Russian ruble (RUR)</displayName>
<displayName count="other">Russian rubles (RUR)</displayName>
</currency>
<currency type="RWF">
<displayName>Rwandan Franc</displayName>
<displayName count="one">Rwandan franc</displayName>
<displayName count="other">Rwandan francs</displayName>
</currency>
<currency type="SAR">
<displayName>Saudi Riyal</displayName>
<displayName count="one">Saudi riyal</displayName>
<displayName count="other">Saudi riyals</displayName>
</currency>
<currency type="SBD">
<displayName>Solomon Islands Dollar</displayName>
<displayName count="one">Solomon Islands dollar</displayName>
<displayName count="other">Solomon Islands dollars</displayName>
</currency>
<currency type="SCR">
<displayName>Seychellois Rupee</displayName>
<displayName count="one">Seychellois rupee</displayName>
<displayName count="other">Seychellois rupees</displayName>
</currency>
<currency type="SDD">
<displayName>Old Sudanese Dinar</displayName>
<displayName count="one">Old Sudanese dinar</displayName>
<displayName count="other">Old Sudanese dinars</displayName>
</currency>
<currency type="SDG">
<displayName>Sudanese Pound</displayName>
<displayName count="one">Sudanese pound</displayName>
<displayName count="other">Sudanese pounds</displayName>
</currency>
<currency type="SDP">
<displayName>Old Sudanese Pound</displayName>
<displayName count="one">Old Sudanese pound</displayName>
<displayName count="other">Old Sudanese pounds</displayName>
</currency>
<currency type="SEK">
<displayName>Swedish Krona</displayName>
<displayName count="one">Swedish krona</displayName>
<displayName count="other">Swedish kronor</displayName>
</currency>
<currency type="SGD">
<displayName>Singapore Dollar</displayName>
<displayName count="one">Singapore dollar</displayName>
<displayName count="other">Singapore dollars</displayName>
</currency>
<currency type="SHP">
<displayName>Saint Helena Pound</displayName>
<displayName count="one">Saint Helena pound</displayName>
<displayName count="other">Saint Helena pounds</displayName>
</currency>
<currency type="SIT">
<displayName>Slovenian Tolar</displayName>
<displayName count="one">Slovenian tolar</displayName>
<displayName count="other">Slovenian tolars</displayName>
</currency>
<currency type="SKK">
<displayName>Slovak Koruna</displayName>
<displayName count="one">Slovak koruna</displayName>
<displayName count="other">Slovak korunas</displayName>
</currency>
<currency type="SLL">
<displayName>Sierra Leonean Leone</displayName>
<displayName count="one">Sierra Leonean leone</displayName>
<displayName count="other">Sierra Leonean leones</displayName>
</currency>
<currency type="SOS">
<displayName>Somali Shilling</displayName>
<displayName count="one">Somali shilling</displayName>
<displayName count="other">Somali shillings</displayName>
</currency>
<currency type="SRD">
<displayName>Surinamese Dollar</displayName>
<displayName count="one">Surinamese dollar</displayName>
<displayName count="other">Surinamese dollars</displayName>
</currency>
<currency type="SRG">
<displayName>Suriname Guilder</displayName>
<displayName count="one">Suriname guilder</displayName>
<displayName count="other">Suriname guilders</displayName>
</currency>
<currency type="STD">
<displayName>São Tomé and Príncipe Dobra</displayName>
<displayName count="one">São Tomé and Príncipe dobra</displayName>
<displayName count="other">São Tomé and Príncipe dobras</displayName>
</currency>
<currency type="SUR">
<displayName>Soviet Rouble</displayName>
<displayName count="one">Soviet rouble</displayName>
<displayName count="other">Soviet roubles</displayName>
</currency>
<currency type="SVC">
<displayName>Salvadoran Colón</displayName>
<displayName count="one">Salvadoran colón</displayName>
<displayName count="other">Salvadoran colones</displayName>
</currency>
<currency type="SYP">
<displayName>Syrian Pound</displayName>
<displayName count="one">Syrian pound</displayName>
<displayName count="other">Syrian pounds</displayName>
</currency>
<currency type="SZL">
<displayName>Swazi Lilangeni</displayName>
<displayName count="one">Swazi lilangeni</displayName>
<displayName count="other">Swazi emalangeni</displayName>
</currency>
<currency type="THB">
<displayName>Thai Baht</displayName>
<displayName count="one">Thai baht</displayName>
<displayName count="other">Thai baht</displayName>
</currency>
<currency type="TJR">
<displayName>Tajikistani Ruble</displayName>
<displayName count="one">Tajikistani ruble</displayName>
<displayName count="other">Tajikistani rubles</displayName>
</currency>
<currency type="TJS">
<displayName>Tajikistani Somoni</displayName>
<displayName count="one">Tajikistani somoni</displayName>
<displayName count="other">Tajikistani somonis</displayName>
</currency>
<currency type="TMM">
<displayName>Turkmenistani Manat</displayName>
<displayName count="one">Turkmenistani manat</displayName>
<displayName count="other">Turkmenistani manat</displayName>
</currency>
<currency type="TMT">
<displayName>Turkmenistani New Manat</displayName>
<displayName count="one">Turkmenistani new manat</displayName>
<displayName count="other">Turkmenistani new manat</displayName>
</currency>
<currency type="TND">
<displayName>Tunisian Dinar</displayName>
<displayName count="one">Tunisian dinar</displayName>
<displayName count="other">Tunisian dinars</displayName>
</currency>
<currency type="TOP">
<displayName>Tongan Paʻanga</displayName>
<displayName count="one">Tongan paʻanga</displayName>
<displayName count="other">Tongan paʻanga</displayName>
</currency>
<currency type="TPE">
<displayName>Timorese Escudo</displayName>
<displayName count="one">Timorese escudo</displayName>
<displayName count="other">Timorese escudos</displayName>
</currency>
<currency type="TRL">
<displayName>Old Turkish Lira</displayName>
<displayName count="one">old Turkish lira</displayName>
<displayName count="other">Old Turkish Lira</displayName>
</currency>
<currency type="TRY">
<displayName>Turkish Lira</displayName>
<displayName count="one">Turkish lira</displayName>
<displayName count="other">Turkish Lira</displayName>
</currency>
<currency type="TTD">
<displayName>Trinidad and Tobago Dollar</displayName>
<displayName count="one">Trinidad and Tobago dollar</displayName>
<displayName count="other">Trinidad and Tobago dollars</displayName>
</currency>
<currency type="TWD">
<displayName>New Taiwan Dollar</displayName>
<displayName count="one">New Taiwan dollar</displayName>
<displayName count="other">New Taiwan dollars</displayName>
</currency>
<currency type="TZS">
<displayName>Tanzanian Shilling</displayName>
<displayName count="one">Tanzanian shilling</displayName>
<displayName count="other">Tanzanian shillings</displayName>
</currency>
<currency type="UAH">
<displayName>Ukrainian Hryvnia</displayName>
<displayName count="one">Ukrainian hryvnia</displayName>
<displayName count="other">Ukrainian hryvnias</displayName>
</currency>
<currency type="UAK">
<displayName>Ukrainian Karbovanets</displayName>
<displayName count="one">Ukrainian karbovanets</displayName>
<displayName count="other">Ukrainian karbovantsiv</displayName>
</currency>
<currency type="UGS">
<displayName>Ugandan Shilling (1966-1987)</displayName>
<displayName count="one">Ugandan shilling (UGS)</displayName>
<displayName count="other">Ugandan shillings (UGS)</displayName>
</currency>
<currency type="UGX">
<displayName>Ugandan Shilling</displayName>
<displayName count="one">Ugandan shilling</displayName>
<displayName count="other">Ugandan shillings</displayName>
</currency>
<currency type="USD">
<displayName>US Dollar</displayName>
<displayName count="one">US dollar</displayName>
<displayName count="other">US dollars</displayName>
<symbol>$</symbol>
</currency>
<currency type="USN">
<displayName>US Dollar (Next day)</displayName>
<displayName count="one">US dollar (next day)</displayName>
<displayName count="other">US dollars (next day)</displayName>
</currency>
<currency type="USS">
<displayName>US Dollar (Same day)</displayName>
<displayName count="one">US dollar (same day)</displayName>
<displayName count="other">US dollars (same day)</displayName>
</currency>
<currency type="UYI">
<displayName>Uruguayan Peso en Unidades Indexadas</displayName>
<displayName count="one">Uruguayan peso en unidades indexadas</displayName>
<displayName count="other">Uruguayan pesos en unidades indexadas</displayName>
</currency>
<currency type="UYP">
<displayName>Uruguayan Peso (1975-1993)</displayName>
<displayName count="one">Uruguayan peso (UYP)</displayName>
<displayName count="other">Uruguayan pesos (UYP)</displayName>
</currency>
<currency type="UYU">
<displayName>Uruguayan Peso</displayName>
<displayName count="one">Uruguayan peso</displayName>
<displayName count="other">Uruguayan pesos</displayName>
</currency>
<currency type="UZS">
<displayName>Uzbekistan Som</displayName>
<displayName count="one">Uzbekistan som</displayName>
<displayName count="other">Uzbekistan som</displayName>
</currency>
<currency type="VEB">
<displayName>Venezuelan Bolívar</displayName>
<displayName count="one">Venezuelan bolívar</displayName>
<displayName count="other">Venezuelan bolívars</displayName>
</currency>
<currency type="VEF">
<displayName>Venezuelan Bolívar Fuerte</displayName>
<displayName count="one">Venezuelan bolívar fuerte</displayName>
<displayName count="other">Venezuelan bolívars fuertes</displayName>
</currency>
<currency type="VND">
<displayName>Vietnamese Dong</displayName>
<displayName count="one">Vietnamese dong</displayName>
<displayName count="other">Vietnamese dong</displayName>
</currency>
<currency type="VNN">
<displayName>Old Vietnamese Dong</displayName>
<displayName count="one">Old Vietnamese dong</displayName>
<displayName count="other">Old Vietnamese dong</displayName>
</currency>
<currency type="VUV">
<displayName>Vanuatu Vatu</displayName>
<displayName count="one">Vanuatu vatu</displayName>
<displayName count="other">Vanuatu vatus</displayName>
</currency>
<currency type="WST">
<displayName>Samoan Tala</displayName>
<displayName count="one">Samoan tala</displayName>
<displayName count="other">Samoan tala</displayName>
</currency>
<currency type="XAF">
<displayName>CFA Franc BEAC</displayName>
<displayName count="one">CFA franc BEAC</displayName>
<displayName count="other">CFA francs BEAC</displayName>
</currency>
<currency type="XAG">
<displayName>Silver</displayName>
<displayName count="one">Silver</displayName>
</currency>
<currency type="XAU">
<displayName>Gold</displayName>
<displayName count="one">Gold</displayName>
</currency>
<currency type="XBA">
<displayName>European Composite Unit</displayName>
<displayName count="one">European composite unit</displayName>
<displayName count="other">European composite units</displayName>
</currency>
<currency type="XBB">
<displayName>European Monetary Unit</displayName>
<displayName count="one">European monetary unit</displayName>
<displayName count="other">European monetary units</displayName>
</currency>
<currency type="XBC">
<displayName>European Unit of Account (XBC)</displayName>
<displayName count="one">European unit of account (XBC)</displayName>
<displayName count="other">European units of account (XBC)</displayName>
</currency>
<currency type="XBD">
<displayName>European Unit of Account (XBD)</displayName>
<displayName count="one">European unit of account (XBD)</displayName>
<displayName count="other">European units of account (XBD)</displayName>
</currency>
<currency type="XCD">
<displayName>East Caribbean Dollar</displayName>
<displayName count="one">East Caribbean dollar</displayName>
<displayName count="other">East Caribbean dollars</displayName>
</currency>
<currency type="XDR">
<displayName>Special Drawing Rights</displayName>
<displayName count="one">special drawing rights</displayName>
<displayName count="other">special drawing rights</displayName>
</currency>
<currency type="XEU">
<displayName>European Currency Unit</displayName>
<displayName count="one">European currency unit</displayName>
<displayName count="other">European currency units</displayName>
</currency>
<currency type="XFO">
<displayName>French Gold Franc</displayName>
<displayName count="one">French gold franc</displayName>
<displayName count="other">French gold francs</displayName>
</currency>
<currency type="XFU">
<displayName>French UIC-Franc</displayName>
<displayName count="one">French UIC-franc</displayName>
<displayName count="other">French UIC-francs</displayName>
</currency>
<currency type="XOF">
<displayName>CFA Franc BCEAO</displayName>
<displayName count="one">CFA franc BCEAO</displayName>
<displayName count="other">CFA francs BCEAO</displayName>
</currency>
<currency type="XPD">
<displayName>Palladium</displayName>
<displayName count="one">Palladium</displayName>
</currency>
<currency type="XPF">
<displayName>CFP Franc</displayName>
<displayName count="one">CFP franc</displayName>
<displayName count="other">CFP francs</displayName>
</currency>
<currency type="XPT">
<displayName>Platinum</displayName>
<displayName count="one">Platinum</displayName>
</currency>
<currency type="XRE">
<displayName>RINET Funds</displayName>
<displayName count="one">RINET Funds</displayName>
</currency>
<currency type="XTS">
<displayName>Testing Currency Code</displayName>
<displayName count="one">Testing Currency Code</displayName>
</currency>
<currency type="XXX">
<displayName>Unknown or Invalid Currency</displayName>
<displayName count="one">unknown/invalid currency</displayName>
</currency>
<currency type="YDD">
<displayName>Yemeni Dinar</displayName>
<displayName count="one">Yemeni dinar</displayName>
<displayName count="other">Yemeni dinars</displayName>
</currency>
<currency type="YER">
<displayName>Yemeni Rial</displayName>
<displayName count="one">Yemeni rial</displayName>
<displayName count="other">Yemeni rials</displayName>
</currency>
<currency type="YUD">
<displayName>Yugoslavian Hard Dinar</displayName>
<displayName count="one">Yugoslavian hard dinar</displayName>
<displayName count="other">Yugoslavian hard dinars</displayName>
</currency>
<currency type="YUM">
<displayName>Yugoslavian Noviy Dinar</displayName>
<displayName count="one">Yugoslavian noviy dinar</displayName>
<displayName count="other">Yugoslavian Noviy dinars</displayName>
</currency>
<currency type="YUN">
<displayName>Yugoslavian Convertible Dinar</displayName>
<displayName count="one">Yugoslavian convertible dinar</displayName>
<displayName count="other">Yugoslavian convertible dinars</displayName>
</currency>
<currency type="YUR">
<displayName>Yugoslavian Reformed Dinar</displayName>
<displayName count="one">Yugoslavian reformed dinar</displayName>
<displayName count="other">Yugoslavian reformed dinars</displayName>
</currency>
<currency type="ZAL">
<displayName>South African Rand (financial)</displayName>
<displayName count="one">South African rand (financial)</displayName>
<displayName count="other">South African rands (financial)</displayName>
</currency>
<currency type="ZAR">
<displayName>South African Rand</displayName>
<displayName count="one">South African rand</displayName>
<displayName count="other">South African rand</displayName>
</currency>
<currency type="ZMK">
<displayName>Zambian Kwacha</displayName>
<displayName count="one">Zambian kwacha</displayName>
<displayName count="other">Zambian kwachas</displayName>
</currency>
<currency type="ZRN">
<displayName>Zairean New Zaire</displayName>
<displayName count="one">Zairean new zaire</displayName>
<displayName count="other">Zairean new zaires</displayName>
</currency>
<currency type="ZRZ">
<displayName>Zairean Zaire</displayName>
<displayName count="one">Zairean zaire</displayName>
<displayName count="other">Zairean zaires</displayName>
</currency>
<currency type="ZWD">
<displayName>Zimbabwean Dollar</displayName>
<displayName count="one">Zimbabwean dollar</displayName>
<displayName count="other">Zimbabwean dollars</displayName>
</currency>
<currency type="ZWL">
<displayName>Zimbabwean Dollar (2009)</displayName>
<displayName count="one">Zimbabwean dollar (2009)</displayName>
<displayName count="other">Zimbabwean dollars (2009)</displayName>
</currency>
<currency type="ZWR">
<displayName>Zimbabwean Dollar (2008)</displayName>
<displayName count="one">Zimbabwean dollar (2008)</displayName>
<displayName count="other">Zimbabwean dollars (2008)</displayName>
</currency>
</currencies>
</numbers>
<units>
<unit type="day">
<unitPattern count="one">{0} day</unitPattern>
<unitPattern count="other">{0} days</unitPattern>
</unit>
<unit type="hour">
<unitPattern count="one">{0} hour</unitPattern>
<unitPattern count="other">{0} hours</unitPattern>
</unit>
<unit type="minute">
<unitPattern count="one">{0} minute</unitPattern>
<unitPattern count="other">{0} minutes</unitPattern>
</unit>
<unit type="month">
<unitPattern count="one">{0} month</unitPattern>
<unitPattern count="other">{0} months</unitPattern>
</unit>
<unit type="second">
<unitPattern count="one">{0} second</unitPattern>
<unitPattern count="other">{0} seconds</unitPattern>
</unit>
<unit type="week">
<unitPattern count="one">{0} week</unitPattern>
<unitPattern count="other">{0} weeks</unitPattern>
</unit>
<unit type="year">
<unitPattern count="one">{0} year</unitPattern>
<unitPattern count="other">{0} years</unitPattern>
</unit>
</units>
<posix>
<messages>
<yesstr>yes:y</yesstr>
<nostr>no:n</nostr>
</messages>
</posix>
</ldml>

9
thirdparty/Zend/Locale/Data/lc_XX.xml vendored Executable file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ldml>
<identity>
<version number="$Revision: 1.53 $"/>
<generation date="$Date: 2009/05/05 23:06:35 $"/>
<language type="lc"/>
<territory type="XX"/>
</identity>
</ldml>

View File

@ -810,6 +810,8 @@ class Requirements_Backend {
}
$suffix .= substr($fileOrUrl, strpos($fileOrUrl, '?')+1);
$fileOrUrl = substr($fileOrUrl, 0, strpos($fileOrUrl, '?'));
} else {
$suffix = '';
}
return "{$prefix}{$fileOrUrl}{$mtimesuffix}{$suffix}";
} else {

View File

@ -371,7 +371,7 @@ class ViewableData extends Object implements IteratorAggregate {
}
$valueObject = Object::create_from_string($castConstructor, $fieldName);
$valueObject->setValue($value, ($this->hasMethod('toMap') ? $this->toMap() : null));
$valueObject->setValue($value, $this);
$value = $valueObject;
}