Merge remote-tracking branch 'origin/3.1' into 3

Conflicts:
	.scrutinizer.yml
	admin/javascript/LeftAndMain.Panel.js
	core/startup/ParameterConfirmationToken.php
	dev/Debug.php
	dev/FixtureBlueprint.php
	docs/en/00_Getting_Started/05_Coding_Conventions.md
	docs/en/00_Getting_Started/index.md
	docs/en/02_Developer_Guides/01_Templates/01_Syntax.md
	filesystem/File.php
	filesystem/Folder.php
	forms/FieldList.php
	forms/LabelField.php
	forms/MoneyField.php
	forms/TextField.php
	forms/TreeDropdownField.php
	forms/Validator.php
	forms/gridfield/GridField.php
	forms/gridfield/GridFieldExportButton.php
	lang/de.yml
	lang/fi.yml
	model/DataObject.php
	model/SQLQuery.php
	parsers/ShortcodeParser.php
	security/ChangePasswordForm.php
	security/Security.php
	tests/control/DirectorTest.php
	tests/core/startup/ParameterConfirmationTokenTest.php
	tests/dev/FixtureBlueprintTest.php
	tests/forms/FieldListTest.php
	tests/forms/MoneyFieldTest.php
	tests/model/SQLQueryTest.php
	tests/security/SecurityTest.php
This commit is contained in:
Damian Mooyman 2015-06-02 19:13:38 +12:00
commit 8331171f2c
104 changed files with 3738 additions and 1105 deletions

View File

@ -1,15 +1,7 @@
tools: inherit: true
php_pdepend: filter:
enabled: true excluded_paths:
excluded_dirs: - thirdparty/*
- vendor - parsers/*
- thirdparty - docs/*
- tests - images/*
- parsers/HTML/BBCodeParser
- docs
custom_commands:
-
scope: file
command: php tests/phpcs_runner.php %pathname%
filter:
excluded_paths: ["*/css/*", "css/*", "thirdparty/*", "*/jquery-changetracker/*", "parsers/HTML/BBCodeParser/*", "*/SSTemplateParser.php$", "docs/*", "*/images/*"]

View File

@ -17,7 +17,10 @@ env:
matrix: matrix:
allow_failures: allow_failures:
- php: hhvm-nightly - php: hhvm
- php: 7.0
- php: nightly
include: include:
- php: 5.4 - php: 5.4
env: DB=MYSQL PDO=1 env: DB=MYSQL PDO=1
@ -29,7 +32,11 @@ matrix:
env: DB=MYSQL BEHAT_TEST=1 env: DB=MYSQL BEHAT_TEST=1
- php: 5.3 - php: 5.3
env: DB=MYSQL env: DB=MYSQL
- php: hhvm-nightly - php: 7.0
env: DB=MYSQL
- php: nightly
env: DB=MYSQL
- php: hhvm
env: DB=MYSQL env: DB=MYSQL
before_install: before_install:
- sudo apt-get update -qq - sudo apt-get update -qq

View File

@ -8,6 +8,12 @@ Oembed:
'https://*.youtube.com/watch*': 'https://*.youtube.com/watch*':
http: 'http://www.youtube.com/oembed/', http: 'http://www.youtube.com/oembed/',
https: 'https://www.youtube.com/oembed/?scheme=https' https: 'https://www.youtube.com/oembed/?scheme=https'
'http://*.youtu.be/*':
http: 'http://www.youtube.com/oembed/',
https: 'https://www.youtube.com/oembed/?scheme=https'
'https://youtu.be/*':
http: 'http://www.youtube.com/oembed/',
https: 'https://www.youtube.com/oembed/?scheme=https'
'http://*.flickr.com/*': 'http://*.flickr.com/*':
'http://www.flickr.com/services/oembed/' 'http://www.flickr.com/services/oembed/'
'http://*.viddler.com/*': 'http://*.viddler.com/*':

View File

@ -120,7 +120,7 @@ abstract class ModelAdmin extends LeftAndMain {
public function getEditForm($id = null, $fields = null) { public function getEditForm($id = null, $fields = null) {
$list = $this->getList(); $list = $this->getList();
$exportButton = new GridFieldExportButton('before'); $exportButton = new GridFieldExportButton('buttons-before-left');
$exportButton->setExportColumns($this->getExportFields()); $exportButton->setExportColumns($this->getExportFields());
$listField = GridField::create( $listField = GridField::create(
$this->sanitiseClassName($this->modelClass), $this->sanitiseClassName($this->modelClass),
@ -129,7 +129,7 @@ abstract class ModelAdmin extends LeftAndMain {
$fieldConfig = GridFieldConfig_RecordEditor::create($this->stat('page_length')) $fieldConfig = GridFieldConfig_RecordEditor::create($this->stat('page_length'))
->addComponent($exportButton) ->addComponent($exportButton)
->removeComponentsByType('GridFieldFilterHeader') ->removeComponentsByType('GridFieldFilterHeader')
->addComponents(new GridFieldPrintButton('before')) ->addComponents(new GridFieldPrintButton('buttons-before-left'))
); );
// Validation // Validation

View File

@ -141,6 +141,7 @@ body, html { font-size: 12px; line-height: 16px; font-family: Arial, sans-serif;
.ui-accordion .ui-accordion-content { border: 1px solid #c0c0c2; border-top: none; } .ui-accordion .ui-accordion-content { border: 1px solid #c0c0c2; border-top: none; }
.ui-autocomplete { max-height: 240px; overflow-x: hidden; overflow-y: auto; } .ui-autocomplete { max-height: 240px; overflow-x: hidden; overflow-y: auto; }
.ui-autocomplete-loading { background-image: url(../images/throbber.gif) !important; background-position: 97% center !important; background-repeat: no-repeat !important; background-size: auto !important; }
/** This file defines common styles for form elements used throughout the CMS interface. It is an addition to the base styles defined in framework/css/Form.css. @package framework @subpackage admin */ /** This file defines common styles for form elements used throughout the CMS interface. It is an addition to the base styles defined in framework/css/Form.css. @package framework @subpackage admin */
/** ---------------------------------------------------- Basic form fields ---------------------------------------------------- */ /** ---------------------------------------------------- Basic form fields ---------------------------------------------------- */
@ -455,7 +456,7 @@ body.cms { overflow: hidden; }
.ss-loading-screen .loading-animation { display: none; position: absolute; left: 50%; margin-left: -21.5px; top: 80%; } .ss-loading-screen .loading-animation { display: none; position: absolute; left: 50%; margin-left: -21.5px; top: 80%; }
/** -------------------------------------------- Actions -------------------------------------------- */ /** -------------------------------------------- Actions -------------------------------------------- */
.cms-content-actions, .cms-preview-controls { margin: 0; padding: 12px 12px; z-index: 0; border-top: 1px solid #cacacc; -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; -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; 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; } .cms-content-actions, .cms-preview-controls { margin: 0; padding: 12px 12px; z-index: 999; border-top: 1px solid #cacacc; -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; -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; 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 -------------------------------------------- */ /** -------------------------------------------- Messages -------------------------------------------- */
.message { display: block; clear: both; margin: 0 0 8px; padding: 10px 12px; font-weight: normal; border: 1px #ccc solid; background: #fff; background: rgba(255, 255, 255, 0.5); text-shadow: none; -moz-border-radius: 3px 3px 3px 3px; -webkit-border-radius: 3px; border-radius: 3px 3px 3px 3px; } .message { display: block; clear: both; margin: 0 0 8px; padding: 10px 12px; font-weight: normal; border: 1px #ccc solid; background: #fff; background: rgba(255, 255, 255, 0.5); text-shadow: none; -moz-border-radius: 3px 3px 3px 3px; -webkit-border-radius: 3px; border-radius: 3px 3px 3px 3px; }

View File

@ -46,9 +46,6 @@
// when JSTree auto-selects elements on first load. // when JSTree auto-selects elements on first load.
if(!origEvent) { if(!origEvent) {
return false; return false;
}else if($(origEvent.target).hasClass('jstree-icon') || $(origEvent.target).hasClass('jstree-pageicon')){
// in case the click is not on the node title, ie on pageicon or dragicon,
return false;
} }
// Don't allow checking disabled nodes // Don't allow checking disabled nodes

View File

@ -670,8 +670,11 @@ jQuery.noConflict();
// Support a full reload // Support a full reload
if(xhr.getResponseHeader('X-Reload') && xhr.getResponseHeader('X-ControllerURL')) { if(xhr.getResponseHeader('X-Reload') && xhr.getResponseHeader('X-ControllerURL')) {
document.location.href = $('base').attr('href').replace(/\/*$/, '') var baseUrl = $('base').attr('href'),
+ '/' + xhr.getResponseHeader('X-ControllerURL'); rawURL = xhr.getResponseHeader('X-ControllerURL'),
url = $.path.isAbsoluteUrl(rawURL) ? rawURL : $.path.makeUrlAbsolute(rawURL, baseUrl);
document.location.href = url;
return; return;
} }

View File

@ -0,0 +1,16 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/id.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('id', {
"LeftAndMain.CONFIRMUNSAVED": "Anda ingin tinggalkan laman ini?\n\nPERINGATAN: Perubahan tidak akan disimpan.\n\nTekan OK untuk lanjut, atau Batal untuk tetap di laman ini.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "PERINGATAN: Perubahan tidak akan disimpan.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Anda ingin menghapus kelompok %s?",
"ModelAdmin.SAVED": "Tersimpan",
"ModelAdmin.REALLYDELETE": "Anda yakin ingin menghapus?",
"ModelAdmin.DELETED": "Terhapus",
"ModelAdmin.VALIDATIONERROR": "Kesalahan Validasi",
"LeftAndMain.PAGEWASDELETED": "Laman sudah terhapus. Untuk mengedit, pilih pada sisi kiri."
});
}

View File

@ -0,0 +1,16 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/id_ID.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('id_ID', {
"LeftAndMain.CONFIRMUNSAVED": "Anda ingin tinggalkan laman ini?\n\nPERINGATAN: Perubahan tidak akan disimpan.\n\nTekan OK untuk lanjut, atau Batal untuk tetap di laman ini.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "PERINGATAN: Perubahan tidak akan disimpan.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Anda ingin menghapus kelompok %s?",
"ModelAdmin.SAVED": "Tersimpan",
"ModelAdmin.REALLYDELETE": "Anda yakin ingin menghapus?",
"ModelAdmin.DELETED": "Terhapus",
"ModelAdmin.VALIDATIONERROR": "Kesalahan Validasi",
"LeftAndMain.PAGEWASDELETED": "Laman sudah terhapus. Untuk mengedit, pilih pada sisi kiri."
});
}

View File

@ -0,0 +1,10 @@
{
"LeftAndMain.CONFIRMUNSAVED": "Anda ingin tinggalkan laman ini?\n\nPERINGATAN: Perubahan tidak akan disimpan.\n\nTekan OK untuk lanjut, atau Batal untuk tetap di laman ini.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "PERINGATAN: Perubahan tidak akan disimpan.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Anda ingin menghapus kelompok %s?",
"ModelAdmin.SAVED": "Tersimpan",
"ModelAdmin.REALLYDELETE": "Anda yakin ingin menghapus?",
"ModelAdmin.DELETED": "Terhapus",
"ModelAdmin.VALIDATIONERROR": "Kesalahan Validasi",
"LeftAndMain.PAGEWASDELETED": "Laman sudah terhapus. Untuk mengedit, pilih pada sisi kiri."
}

View File

@ -0,0 +1,10 @@
{
"LeftAndMain.CONFIRMUNSAVED": "Anda ingin tinggalkan laman ini?\n\nPERINGATAN: Perubahan tidak akan disimpan.\n\nTekan OK untuk lanjut, atau Batal untuk tetap di laman ini.",
"LeftAndMain.CONFIRMUNSAVEDSHORT": "PERINGATAN: Perubahan tidak akan disimpan.",
"SecurityAdmin.BATCHACTIONSDELETECONFIRM": "Anda ingin menghapus kelompok %s?",
"ModelAdmin.SAVED": "Tersimpan",
"ModelAdmin.REALLYDELETE": "Anda yakin ingin menghapus?",
"ModelAdmin.DELETED": "Terhapus",
"ModelAdmin.VALIDATIONERROR": "Kesalahan Validasi",
"LeftAndMain.PAGEWASDELETED": "Laman sudah terhapus. Untuk mengedit, pilih pada sisi kiri."
}

View File

@ -6,5 +6,5 @@
"ModelAdmin.REALLYDELETE": "Vill du verkligen radera?", "ModelAdmin.REALLYDELETE": "Vill du verkligen radera?",
"ModelAdmin.DELETED": "Raderad", "ModelAdmin.DELETED": "Raderad",
"ModelAdmin.VALIDATIONERROR": "Valideringsfel", "ModelAdmin.VALIDATIONERROR": "Valideringsfel",
"LeftAndMain.PAGEWASDELETED": "Sidan raderades. För att redigera en sida, välj den från menyn till vänster." "LeftAndMain.PAGEWASDELETED": "Sidan raderades. För att redigera en sida, välj den i menyn till vänster."
} }

View File

@ -11,6 +11,6 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
"ModelAdmin.REALLYDELETE": "Vill du verkligen radera?", "ModelAdmin.REALLYDELETE": "Vill du verkligen radera?",
"ModelAdmin.DELETED": "Raderad", "ModelAdmin.DELETED": "Raderad",
"ModelAdmin.VALIDATIONERROR": "Valideringsfel", "ModelAdmin.VALIDATIONERROR": "Valideringsfel",
"LeftAndMain.PAGEWASDELETED": "Sidan raderades. För att redigera en sida, välj den från menyn till vänster." "LeftAndMain.PAGEWASDELETED": "Sidan raderades. För att redigera en sida, välj den i menyn till vänster."
}); });
} }

View File

@ -429,7 +429,7 @@ body.cms {
.cms-content-actions, .cms-preview-controls { .cms-content-actions, .cms-preview-controls {
margin: 0; margin: 0;
padding: $grid-y*1.5 $grid-y*1.5; padding: $grid-y*1.5 $grid-y*1.5;
z-index: 0; z-index: 999;
border-top: 1px solid lighten($color-separator, 4%); border-top: 1px solid lighten($color-separator, 4%);
@include box-shadow( @include box-shadow(
1px 0 0 $tab-panel-texture-color, 1px 0 0 $tab-panel-texture-color,

View File

@ -85,8 +85,16 @@
} }
} }
.ui-autocomplete{ .ui-autocomplete {
max-height: 240px; max-height: 240px;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
/** sorry about the !important but the specificity of other selectors mandates it over writing out very specific selectors **/
&-loading {
background-image: url(../images/throbber.gif) !important;
background-position: 97% center !important;
background-repeat: no-repeat !important;
background-size: auto !important;
}
} }

View File

@ -501,6 +501,7 @@ class Controller extends RequestHandler implements TemplateGlobalProvider {
// absolute redirection URLs not located on this site may cause phishing // absolute redirection URLs not located on this site may cause phishing
if(Director::is_site_url($url)) { if(Director::is_site_url($url)) {
$url = Director::absoluteURL($url, true);
return $this->redirect($url); return $this->redirect($url);
} else { } else {
return false; return false;

View File

@ -510,14 +510,16 @@ class Director implements TemplateGlobalProvider {
if ($protocol = Config::inst()->get('Director', 'alternate_protocol')) { if ($protocol = Config::inst()->get('Director', 'alternate_protocol')) {
$return = ($protocol == 'https'); $return = ($protocol == 'https');
} else if( } else if(
isset($_SERVER['HTTP_X_FORWARDED_PROTO']) TRUSTED_PROXY
&& isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https' && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'
) { ) {
// Convention for (non-standard) proxy signaling a HTTPS forward, // Convention for (non-standard) proxy signaling a HTTPS forward,
// see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields // see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
$return = true; $return = true;
} else if( } else if(
isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) TRUSTED_PROXY
&& isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https' && strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https'
) { ) {
// Less conventional proxy header // Less conventional proxy header

View File

@ -655,10 +655,10 @@ class SS_HTTPRequest implements ArrayAccess {
* @return string * @return string
*/ */
public function getIP() { public function getIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) { if (TRUSTED_PROXY && !empty($_SERVER['HTTP_CLIENT_IP'])) {
//check ip from share internet //check ip from share internet
return $_SERVER['HTTP_CLIENT_IP']; return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { } elseif (TRUSTED_PROXY && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
//to check ip is pass from proxy //to check ip is pass from proxy
return $_SERVER['HTTP_X_FORWARDED_FOR']; return $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif(isset($_SERVER['REMOTE_ADDR'])) { } elseif(isset($_SERVER['REMOTE_ADDR'])) {

View File

@ -286,7 +286,7 @@ EOT
* @return bool * @return bool
*/ */
public function isFinished() { public function isFinished() {
return in_array($this->statusCode, array(301, 302, 401, 403)); return in_array($this->statusCode, array(301, 302, 303, 304, 305, 307, 401, 403));
} }
} }

View File

@ -23,6 +23,8 @@
* - FRAMEWORK_ADMIN_PATH: Absolute filepath, e.g. "/var/www/my-webroot/framework/admin" * - FRAMEWORK_ADMIN_PATH: Absolute filepath, e.g. "/var/www/my-webroot/framework/admin"
* - THIRDPARTY_DIR: Path relative to webroot, e.g. "framework/thirdparty" * - THIRDPARTY_DIR: Path relative to webroot, e.g. "framework/thirdparty"
* - THIRDPARTY_PATH: Absolute filepath, e.g. "/var/www/my-webroot/framework/thirdparty" * - THIRDPARTY_PATH: Absolute filepath, e.g. "/var/www/my-webroot/framework/thirdparty"
* - TRUSTED_PROXY: true or false, depending on whether the X-Forwarded-* HTTP
* headers from the given client are trustworthy (e.g. from a reverse proxy).
* *
* @package framework * @package framework
* @subpackage core * @subpackage core
@ -85,6 +87,35 @@ function stripslashes_recursively(&$array) {
} }
} }
/**
* Validate whether the request comes directly from a trusted server or not
* This is necessary to validate whether or not the values of X-Forwarded-
* or Client-IP HTTP headers can be trusted
*/
if(!defined('TRUSTED_PROXY')) {
$trusted = true; // will be false by default in a future release
if(getenv('BlockUntrustedProxyHeaders') // Legacy setting (reverted from documentation)
|| getenv('BlockUntrustedIPs') // Documented setting
|| defined('SS_TRUSTED_PROXY_IPS')
) {
$trusted = false;
if(defined('SS_TRUSTED_PROXY_IPS') && SS_TRUSTED_PROXY_IPS !== 'none') {
if(SS_TRUSTED_PROXY_IPS === '*') {
$trusted = true;
} elseif(isset($_SERVER['REMOTE_ADDR'])) {
$trusted = in_array($_SERVER['REMOTE_ADDR'], explode(',', SS_TRUSTED_PROXY_IPS));
}
}
}
/**
* Declare whether or not the connecting server is a trusted proxy
*/
define('TRUSTED_PROXY', $trusted);
}
/** /**
* A blank HTTP_HOST value is used to detect command-line execution. * A blank HTTP_HOST value is used to detect command-line execution.
* We update the $_SERVER variable to contain data consistent with the rest of the application. * We update the $_SERVER variable to contain data consistent with the rest of the application.
@ -147,12 +178,21 @@ if(!isset($_SERVER['HTTP_HOST'])) {
/** /**
* Fix HTTP_HOST from reverse proxies * Fix HTTP_HOST from reverse proxies
*/ */
if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { if (TRUSTED_PROXY && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
// Get the first host, in case there's multiple separated through commas // Get the first host, in case there's multiple separated through commas
$_SERVER['HTTP_HOST'] = strtok($_SERVER['HTTP_X_FORWARDED_HOST'], ','); $_SERVER['HTTP_HOST'] = strtok($_SERVER['HTTP_X_FORWARDED_HOST'], ',');
} }
} }
if (defined('SS_ALLOWED_HOSTS')) {
$all_allowed_hosts = explode(',', SS_ALLOWED_HOSTS);
if (!in_array($_SERVER['HTTP_HOST'], $all_allowed_hosts)) {
header('HTTP/1.1 400 Invalid Host', true, 400);
die();
}
}
/** /**
* Define system paths * Define system paths
*/ */

View File

@ -11,14 +11,37 @@
* It will likely be heavily refactored before the release of 3.2 * It will likely be heavily refactored before the release of 3.2
*/ */
class ParameterConfirmationToken { class ParameterConfirmationToken {
/**
* The name of the parameter
*
* @var string
*/
protected $parameterName = null; protected $parameterName = null;
/**
* The parameter given
*
* @var string|null The string value, or null if not provided
*/
protected $parameter = null; protected $parameter = null;
/**
* The validated and checked token for this parameter
*
* @var string|null A string value, or null if either not provided or invalid
*/
protected $token = null; protected $token = null;
protected function pathForToken($token) { protected function pathForToken($token) {
return TEMP_FOLDER.'/token_'.preg_replace('/[^a-z0-9]+/', '', $token); return TEMP_FOLDER.'/token_'.preg_replace('/[^a-z0-9]+/', '', $token);
} }
/**
* Generate a new random token and store it
*
* @return string Token name
*/
protected function genToken() { protected function genToken() {
// Generate a new random token (as random as possible) // Generate a new random token (as random as possible)
require_once(dirname(dirname(dirname(__FILE__))).'/security/RandomGenerator.php'); require_once(dirname(dirname(dirname(__FILE__))).'/security/RandomGenerator.php');
@ -31,7 +54,17 @@ class ParameterConfirmationToken {
return $token; return $token;
} }
/**
* Validate a token
*
* @param string $token
* @return boolean True if the token is valid
*/
protected function checkToken($token) { protected function checkToken($token) {
if(!$token) {
return false;
}
$file = $this->pathForToken($token); $file = $this->pathForToken($token);
$content = null; $content = null;
@ -43,16 +76,23 @@ class ParameterConfirmationToken {
return $content == $token; return $content == $token;
} }
/**
* Create a new ParameterConfirmationToken
*
* @param string $parameterName Name of the querystring parameter to check
*/
public function __construct($parameterName) { public function __construct($parameterName) {
// Store the parameter name // Store the parameter name
$this->parameterName = $parameterName; $this->parameterName = $parameterName;
// Store the parameter value // Store the parameter value
$this->parameter = isset($_GET[$parameterName]) ? $_GET[$parameterName] : null; $this->parameter = isset($_GET[$parameterName]) ? $_GET[$parameterName] : null;
// Store the token
$this->token = isset($_GET[$parameterName.'token']) ? $_GET[$parameterName.'token'] : null;
// If a token was provided, but isn't valid, ignore it // If the token provided is valid, mark it as such
if ($this->token && (!$this->checkToken($this->token))) $this->token = null; $token = isset($_GET[$parameterName.'token']) ? $_GET[$parameterName.'token'] : null;
if ($this->checkToken($token)) {
$this->token = $token;
}
} }
/** /**
@ -66,6 +106,7 @@ class ParameterConfirmationToken {
/** /**
* Is the parameter requested? * Is the parameter requested?
* ?parameter and ?parameter=1 are both considered requested
* *
* @return bool * @return bool
*/ */
@ -75,11 +116,12 @@ class ParameterConfirmationToken {
/** /**
* Is the necessary token provided for this parameter? * Is the necessary token provided for this parameter?
* A value must be provided for the token
* *
* @return bool * @return bool
*/ */
public function tokenProvided() { public function tokenProvided() {
return $this->token !== null; return !empty($this->token);
} }
/** /**
@ -98,6 +140,11 @@ class ParameterConfirmationToken {
unset($_GET[$this->parameterName]); unset($_GET[$this->parameterName]);
} }
/**
* Determine the querystring parameters to include
*
* @return array List of querystring parameters with name and token parameters
*/
public function params() { public function params() {
return array( return array(
$this->parameterName => $this->parameter, $this->parameterName => $this->parameter,
@ -114,14 +161,16 @@ class ParameterConfirmationToken {
// Are we http or https? Replicates Director::is_https() without its dependencies/ // Are we http or https? Replicates Director::is_https() without its dependencies/
$proto = 'http'; $proto = 'http';
if( if(
isset($_SERVER['HTTP_X_FORWARDED_PROTO']) TRUSTED_PROXY
&& isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https' && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'
) { ) {
// Convention for (non-standard) proxy signaling a HTTPS forward, // Convention for (non-standard) proxy signaling a HTTPS forward,
// see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields // see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
$proto = 'https'; $proto = 'https';
} else if( } else if(
isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) TRUSTED_PROXY
&& isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https' && strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https'
) { ) {
// Less conventional proxy header // Less conventional proxy header
@ -154,6 +203,10 @@ class ParameterConfirmationToken {
return "$proto://" . preg_replace('#/{2,}#', '/', implode('/', $parts)); return "$proto://" . preg_replace('#/{2,}#', '/', implode('/', $parts));
} }
/**
* Forces a reload of the request with the token included
* This method will terminate the script with `die`
*/
public function reloadWithToken() { public function reloadWithToken() {
$location = $this->currentAbsoluteURL(); $location = $this->currentAbsoluteURL();
@ -179,7 +232,7 @@ You are being redirected. If you are not redirected soon, <a href='$location'>cl
* Given a list of token names, suppress all tokens that have not been validated, and * Given a list of token names, suppress all tokens that have not been validated, and
* return the non-validated token with the highest priority * return the non-validated token with the highest priority
* *
* @param type $keys List of token keys in ascending priority (low to high) * @param array $keys List of token keys in ascending priority (low to high)
* @return ParameterConfirmationToken The token container for the unvalidated $key given with the highest priority * @return ParameterConfirmationToken The token container for the unvalidated $key given with the highest priority
*/ */
public static function prepare_tokens($keys) { public static function prepare_tokens($keys) {

View File

@ -68,11 +68,14 @@ Used in side panels and action tabs
.cms table.ss-gridfield-table tr.sortable-header { background: #dbe3e8; } .cms table.ss-gridfield-table tr.sortable-header { background: #dbe3e8; }
.cms table.ss-gridfield-table tr.sortable-header th { padding: 0; font-weight: normal; } .cms table.ss-gridfield-table tr.sortable-header th { padding: 0; font-weight: normal; }
.cms table.ss-gridfield-table tr.sortable-header th .ss-ui-button { font-weight: normal; } .cms table.ss-gridfield-table tr.sortable-header th .ss-ui-button { font-weight: normal; }
.cms table.ss-gridfield-table tr:hover { background: #FFFAD6 !important; } .cms table.ss-gridfield-table tr:hover { background: #FFFAD6; }
.cms table.ss-gridfield-table tr:first-child { background: transparent; } .cms table.ss-gridfield-table tr:first-child { background: transparent; }
.cms table.ss-gridfield-table tr:first-child:hover { background: #FFFAD6; }
.cms table.ss-gridfield-table tr.ss-gridfield-even { background: #F0F4F7; } .cms table.ss-gridfield-table tr.ss-gridfield-even { background: #F0F4F7; }
.cms table.ss-gridfield-table tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; } .cms table.ss-gridfield-table tr.ss-gridfield-even.ss-gridfield-last { border-bottom: none; }
.cms table.ss-gridfield-table tr.ss-gridfield-even:hover { background: #FFFAD6; }
.cms table.ss-gridfield-table tr.even { background: #F0F4F7; } .cms table.ss-gridfield-table tr.even { background: #F0F4F7; }
.cms table.ss-gridfield-table tr.even:hover { background: #FFFAD6; }
.cms table.ss-gridfield-table tr th { font-weight: bold; font-size: 12px; color: #FFF; padding: 5px; border-right: 1px solid rgba(0, 0, 0, 0.1); } .cms table.ss-gridfield-table tr th { font-weight: bold; font-size: 12px; color: #FFF; padding: 5px; border-right: 1px solid rgba(0, 0, 0, 0.1); }
.cms table.ss-gridfield-table tr th div.fieldgroup, .cms table.ss-gridfield-table tr th div.fieldgroup-field { width: 100%; position: relative; } .cms table.ss-gridfield-table tr th div.fieldgroup, .cms table.ss-gridfield-table tr th div.fieldgroup-field { width: 100%; position: relative; }
.cms table.ss-gridfield-table tr th div.fieldgroup { min-width: 200px; padding-right: 0; } .cms table.ss-gridfield-table tr th div.fieldgroup { min-width: 200px; padding-right: 0; }

View File

@ -190,13 +190,7 @@ class FixtureBlueprint {
// If LastEdited was set in the fixture, set it here // If LastEdited was set in the fixture, set it here
if($data && array_key_exists('LastEdited', $data)) { if($data && array_key_exists('LastEdited', $data)) {
$edited = $this->parseValue($data['LastEdited'], $fixtures); $this->overrideField($obj, 'LastEdited', $data['LastEdited'], $fixtures);
$update = new SQLUpdate(
$class,
array('"LastEdited"' => $edited),
array('"ID"' => $obj->id)
);
$update->execute();
} }
} catch(Exception $e) { } catch(Exception $e) {
Config::inst()->update('DataObject', 'validation_enabled', $validationenabled); Config::inst()->update('DataObject', 'validation_enabled', $validationenabled);
@ -297,4 +291,17 @@ class FixtureBlueprint {
$obj->$name = $this->parseValue($value, $fixtures); $obj->$name = $this->parseValue($value, $fixtures);
} }
protected function overrideField($obj, $fieldName, $value, $fixtures = null) {
$table = ClassInfo::table_for_object_field(get_class($obj), $fieldName);
$value = $this->parseValue($value, $fixtures);
DB::manipulate(array(
$table => array(
"command" => "update", "id" => $obj->ID,
"fields" => array($fieldName => is_string($value) ? "'$value'" : $value)
)
));
$obj->$fieldName = $value;
}
} }

View File

@ -1,75 +1,49 @@
# Mac OSX # Mac OSX with MAMP
This topic covers setting up your Mac as a Web Server and installing SilverStripe. This topic covers setting up your Mac as a web server and installing SilverStripe.
While OSX Comes bundled with PHP and Apache (Thanks Apple!) Its not quite ideal for SilverStripe so for setting up a OSX comes bundled with PHP and Apache, but you're stuck with the versions it ships with.
webserver on OSX we suggest using [MAMP](http://www.mamp.info/en/index.php) or using [MacPorts](http://www.macports.org/) It is also a bit harder to install additional PHP modules required by SilverStripe.
to manage your packages. [MAMP](http://www.mamp.info/en/) is a simple way to get a complete webserver
environment going on your OSX machine, without removing or altering any system-level configuration.
If you want to use the default OSX PHP version then you will need to recompile your own versions of PHP with GD. Providing instructions Check out the [MAC OSX with Homebrew](other_installation_options/Mac_OSX_Homebrew)
for how to recompile PHP is beyond the scope of our documentation but try an online search. for an alternative, more configurable installation process.
## Installing MAMP ## Requirements
If you have decided to install using MacPorts you can skip this section. Please check the [system requirements](http://www.mamp.info/en/documentation/) for MAMP,
you'll need a fairly new version of OSX to run it.
Once you have downloaded and Installed MAMP start the Application and Make sure everything is running by clicking the ## MAMP Installation
MAMP icon. Under `Preferences -> PHP` make sure Version 5 is Selected.
Open up `/Applications/MAMP/conf/PHP5/php.ini` and make the following configuration changes: * [Download MAMP](http://www.mamp.info/en/)
* Install and start MAMP
* Check out your new web server environment on `http://localhost:8888`
memory_limit = 64M ## SilverStripe Installation
Once you make that change open the MAMP App Again by clicking on the MAMP Icon and click Stop Servers then Start [Composer](http://getcomposer.org) is a dependancy manager for PHP, and the preferred way to
Servers - this is so our changes to the php.ini take effect. install SilverStripe. It ensures that you get the correct set of files for your project.
Composer uses your MAMP PHP executable to run and also requires [git](http://git-scm.com)
to automatically download the required files from GitHub and other repositories.
## Installing SilverStripe In order to install Composer, we need to let the system know where to find the PHP executable.
Open or create the `~/.bash_profile` file in your home folder, then add the following line:
`export PATH=/Applications/MAMP/bin/php/php5.5.22/bin:$PATH`
You'll need to adjust the PHP version number (`php5.5.22`). The currently running PHP version is shown on `http://localhost:8888/MAMP/index.php?page=phpinfo`.
Run `source ~/.bash_profile` for the changes to take effect. You can verify that the correct executable
is used by running `which php`. It should show the path to MAMP from above.
### Composer Now you're ready to install Composer: Run `curl -sS https://getcomposer.org/installer | php`.
[Composer (a dependancy manager for PHP)](http://getcomposer.org) is the preferred way to install SilverStripe and ensure you get the correct set of files for your project. We recommend that you make the `composer` executable available globally,
which requires moving the file to a different folder. Run `mv composer.phar /usr/local/bin/composer`.
More detailed installation instructions are available on [getcomposer.org](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx).
You can verify the installation by typing the `composer` command, which should show you a command overview.
Composer uses your MAMP PHP executable to run and also requires [git](http://git-scm.com) (so it can automatically download the required files from GitHub). Finally, we're ready to install SilverStripe through composer:
`composer create-project silverstripe/installer /Applications/MAMP/htdocs/silverstripe/`.
After finishing, the installation wizard should be available at `http://localhost:8888/silverstripe`.
The MAMP default database credentials are user `root` and password `root`.
#### Install composer using MAMP We have a separate in-depth tutorial for [Composer Installation and Usage](composer).
1. First create an alias for our bash profile, using your preferred terminal text editor (nano, vim, etc) open `~/.bash_profile`.
2. Add the following line (adjusting the version number of PHP to your installation of MAMP): `alias phpmamp='/Applications/MAMP/bin/php/php5.4.10/bin/php'`.
3. The run `. ~/.bash_profile` to reload the bash profile and make it accessible.
4. This will create an alias, `phpmamp`, allowing you to use the MAMP installation of PHP. Please take note of the PHP version, in this case 5.4.10, as with different versions of MAMP this may be different. Check your installation and see what version you have, and replace the number accordingly (this was written with MAMP version 2.1.2).
5. With that setup, we are ready to install `composer`. This is a two step process if we would like this to be installed globally (only do the first step if you would like `composer` installed to the local working directory only).
- First, run the following command in the terminal: `curl -sS https://getcomposer.org/installer | phpmamp`
We are using `phpmamp` so that we correctly use the MAMP installation of PHP from above.
- Second, if you want to make composer available globally, we need to move the file to '/usr/local/bin/composer'. To do this, run the following command:
`sudo mv composer.phar /usr/local/bin/composer`
Terminal will ask you for your root password, after entering it and pressing the 'return' (or enter) key, you'll have a working global installation of composer on your mac that uses MAMP.
6. You can verify your installation worked by typing the following command:
`composer`
It'll show you the current version and a list of commands you can use.
7. Run the following command to get a fresh copy of SilverStripe via composer:
`composer create-project silverstripe/installer /Applications/MAMP/htdocs/silverstripe/`
8. You can now [use composer](http://doc.silverstripe.org/framework/en/getting_started/composer/) to manage future SilverStripe updates and adding modules with a few easy commands.
### Package Download
[Download](http://silverstripe.org/software/download/) the latest SilverStripe installer package. Copy the tar.gz or zip file to the 'Document Root' for MAMP - By Default its `/Applications/MAMP/htdocs`.
Don't know what your Document Root is? Open MAMP Click `Preferences -> Apache`.
Extract the tar.gz file to a folder, e.g. `silverstripe/` (you always move the tar.gz file first and not the other way
around as SilverStripe uses a '.htaccess' file which is hidden from OSX so if you move SilverStripe the .htaccess file
won't come along.
### Run the installation wizard
Once you have a copy of the required code (by either of the above methods), open your web browser and go to `http://localhost:8888/silverstripe/`. Enter your database details - by default with MAMP its user `root` and password `root` and select your account details. Click "Check Details".
Once everything is sorted hit "Install!" and Voila, you have SilverStripe installed

View File

@ -0,0 +1,118 @@
# Mac OSX with Homebrew
This topic covers setting up your Mac as a web server and installing SilverStripe.
OSX comes bundled with PHP, but you're stuck with the version and modules it ships with.
If you run projects on different PHP versions, or care about additional PHP module support
and other dependencies such as MariaDB, we recommend an installation through [Homebrew](http://brew.sh/).
Check out the [MAC OSX with MAMP](../Mac_OSX) for an alternative installation process
which packages up the whole environment into a convenient application.
## Requirements
Since we're compiling PHP, some build tooling is required.
Run the following command to install Xcode Command Line Tools.
xcode-select --install
Now you can install Homebrew itself:
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
## Install PHP
First we're telling Homebrew about some new repositories to get the PHP installation from:
brew tap homebrew/dupes
brew tap homebrew/php
We're installing PHP 5.5 here, with the required `mcrypt` module:
brew install php55 php55-mcrypt
There's a [Homebrew Troubleshooting](https://github.com/Homebrew/homebrew/blob/master/share/doc/homebrew/Troubleshooting.md) guide if Homebrew doesn't work out as expected (run `brew update` and `brew doctor`).
Have a look at the [brew-php-switcher](https://github.com/philcook/brew-php-switcher)
project to install multiple PHP versions in parallel and switch between them easily.
## Install the Database (MariaDB/MySQL)
brew install mariadb
unset TMPDIR
mysql_install_db --user=`whoami` --basedir="$(brew --prefix mariadb)" --datadir=/usr/local/var/mysql --tmpdir=/tmp
mysql.server start
'/usr/local/opt/mariadb/bin/mysql_secure_installation'
To start the database server on boot, run the following:
ln -sfv /usr/local/opt/mariadb/*.plist ~/Library/LaunchAgents
You can also use `mysql.server start` and `mysql.server stop` on demand.
## Configure PHP and Apache
We're not installing Apache, since OSX already ships with a perfectly fine installation of it.
Edit the existing configuration at `/etc/apache2/httpd.conf`,
and uncomment/add the following lines to activate the required modules:
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
LoadModule php5_module /usr/local/opt/php55/libexec/apache2/libphp5.so
Change the `DocumentRoot` setting to your user folder (replacing `<user>` with your OSX user name):
DocumentRoot "/Users/<user>/Sites"
Now find the section starting with `<Directory "/Library/WebServer/Documents">` and change it as follows,
again replacing `<user>` with your OSX user name:
<Directory "/Users/<user>/Sites">
Options FollowSymLinks Multiviews
MultiviewsMatch Any
AllowOverride All
Require all granted
</Directory>
We also recommend running the web server process with your own user on a development environment,
since it makes permissions easier to handle when running commands both
from the command line and through the web server. Find and adjust the following options,
replacing the `<user>` placeholder:
User <user>
Group staff
Now start the web server:
sudo apachectl start
Every configuration change requires a restart:
sudo apachectl restart
You can also load this webserver on boot:
sudo launchctl load -w /System/Library/LaunchDaemons/org.apache.httpd.plist
After starting the webserver, you should see a simple "Forbidden" page generated by Apache
when accessing `http://localhost`.
## SilverStripe Installation
[Composer](http://getcomposer.org) is a dependancy manager for PHP, and the preferred way to
install SilverStripe. It ensures that you get the correct set of files for your project.
Composer uses the PHP executable we've just installed. It also needs [git](http://git-scm.com)
to automatically download the required files from GitHub and other repositories.
Run `curl -sS https://getcomposer.org/installer | php` to install the `composer` executable.
We recommend that you make the executable available globally,
which requires moving the file to a different folder. Run `mv composer.phar /usr/local/bin/composer`.
More detailed installation instructions are available on [getcomposer.org](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx).
You can verify the installation by typing the `composer` command, which should show you a command overview.
Finally, we're ready to install SilverStripe through composer:
`composer create-project silverstripe/installer /Users/<user>/Sites/silverstripe/`.
After finishing, the installation wizard should be available at `http://localhost/silverstripe`.
The Homebrew MariaDB default database credentials are user `root` and password `root`.
We have a separate in-depth tutorial for [Composer Installation and Usage](composer).

View File

@ -1,152 +0,0 @@
# Install SilverStripe manually on Windows using IIS 6
<div class="warning" markdown="1">Note: These instructions may not work, as they're no longer maintained.</div>
How to prepare Windows Server 2003 for SilverStripe using IIS 6 and FastCGI.
This guide will work for the following operating systems:
* Windows Server 2003
* Windows Server 2003 R2
Database install and configuration is not covered here, it is assumed you will do this yourself.
PHP comes with MySQL support out of the box, but you will need to install the [SQL Server Driver for PHP](http://www.microsoft.com/downloads/en/details.aspx?displaylang=en&FamilyID=80e44913-24b4-4113-8807-caae6cf2ca05)
from Microsoft if you want to use SQL Server.
## Preparation
Open **Windows Update** and make sure everything is updated, including optional updates. It is important that all .NET Framework updates including service packs are installed.
## Install IIS
- Open **Control Panel** > **Add/Remove Programs**
- Click **Add/Remove Windows Components** on the left hand bar
- Check **Application Server** and then click **Next** to install it
## Install FastCGI for IIS
- Download and install this package: http://www.iis.net/download/fastcgi
- Open **inetmgr.exe**
- Right click **Web Sites** and go to **Properties**
- Click the **Home Directory** tab
- Click **Configuration...** then **Add**
- In the **Add/Edit Extension Mapping** dialog, click **Browse...** and navigate to fcgiext.dll which is located in %windir%\system32\inetsrv
- In the **Extension** text box, enter **.php**
- Under **Verbs** in the **Limit to** text box, enter **GET,HEAD,POST**
- Ensure that the **Script engine** and **Verify that file exists** boxes are checked then click **OK**
- Open fcgiext.ini located in %windir%\system32\inetsrv. In the [Types] section of the file, add **php=PHP**
- Create a new section called **[PHP]** at the bottom of the file, like this:
[PHP]
ExePath=c:\php5\php-cgi.exe
Finally, run these commands in **Command Prompt**
cd %windir%\system32\inetsrv
cscript fcgiconfig.js -set -section:"PHP" -InstanceMaxRequests:10000
cscript fcgiconfig.js -set -section:"PHP" -EnvironmentVars:PHP_FCGI_MAX_REQUESTS:10000
cscript fcgiconfig.js -set -section:"PHP" -ActivityTimeout:300
## Install PHP
- [Download PHP](http://windows.php.net/download) (**Zip** link underneath the **VC9 x86 Non Thread Safe** section)
- [Download WinCache](http://www.iis.net/download/WinCacheForPHP) (**WinCache 1.1 for PHP 5.3**)
- Extract the PHP zip contents to **c:\php5**
- Run the WinCache self-extractor and extract to **c:\php5\ext**. A file called **php_wincache.dll** should now reside in **c:\php5\ext**
- Rename **php.ini-development** to **php.ini** in **c:\php5**
- Open **php.ini**, located in **c:\php5** with **Notepad** or another editor like **Notepad++**
- Search for **date.timezone**, uncomment it by removing the semicolon and set a timezone from here: http://php.net/manual/en/timezones.php
- Search for **fastcgi.impersonate**, uncomment it by removing the semicolon and set it like this: **fastcgi.impersonate = 1**
- Search for **cgi.fix_pathinfo**, uncomment it by removing the semicolon and set it like this: **cgi.fix_pathinfo = 1**
- Search for **cgi.force_redirect**, uncomment it by removing the semicolon and set it like this: **cgi.force_redirect = 0**
- Search for **fastcgi.logging**, uncomment it by removing the semicolon and set it like this: **fastcgi.logging = 0**
- Search for **extension_dir** and make sure it looks like this: **extension_dir = "ext"** (use proper double quotation characters here)
- Find the "Dynamic Extensions" part of the file, and replace all extension entries with the following:
;extension=php_bz2.dll
extension=php_curl.dll
;extension=php_enchant.dll
;extension=php_exif.dll
;extension=php_fileinfo.dll
extension=php_gd2.dll
;extension=php_gettext.dll
;extension=php_gmp.dll
;extension=php_imap.dll
;extension=php_intl.dll
;extension=php_ldap.dll
extension=php_mbstring.dll
extension=php_mysql.dll
extension=php_mysqli.dll
;extension=php_oci8.dll
;extension=php_oci8_11g.dll
;extension=php_openssl.dll
;extension=php_pdo_mysql.dll
;extension=php_pdo_oci.dll
;extension=php_pdo_odbc.dll
;extension=php_pdo_pgsql.dll
;extension=php_pdo_sqlite.dll
;extension=php_pgsql.dll
;extension=php_shmop.dll
;extension=php_snmp.dll
;extension=php_soap.dll
;extension=php_sockets.dll
;extension=php_sqlite3.dll
;extension=php_sqlite.dll
extension=php_tidy.dll
extension=php_wincache.dll
;extension=php_xmlrpc.dll
;extension=php_xsl.dll
This is a minimal set of loaded extensions which will get you started.
If want to use **SQL Server** as a database, you will need to install the [SQL Server Driver for PHP](http://www.microsoft.com/downloads/en/details.aspx?displaylang=en&FamilyID=80e44913-24b4-4113-8807-caae6cf2ca05) and add an extension entry for it to the list above.
## Test PHP
- Open **Command Prompt** and type the following:
c:\php5\php.exe -v
You should see some output showing the PHP version. If you get something else, or nothing at all, then there are missing updates for your copy of Windows Server 2003. Open **Windows Update** and make sure you've updated everything including optional updates.
## Install SilverStripe
- [Download SilverStripe](http://silverstripe.org/downloads)
- Extract the download contents to **C:\Inetpub\wwwroot\silverstripe**
- Open **inetmgr.exe**
- Right click **Web Sites** and go to **New** > **Web Site**
- Fill in all appropriate details. If you enter **(All Unassigned)** for the IP address field, make sure the port is something other than **80**, as this will conflict with "Default Web Site" in IIS. When asked for path, enter **C:\Inetpub\wwwroot\silverstripe**
- Browse to **http://localhost:8888** or to the IP address you just assigned in your browser.
An installation screen should appear. There may be some permission problems, which you should be able to correct by assigning the **Users** group write permissions by right clicking files / folders in Windows Explorer and going to **Properties** then the **Security** tab.
When ready, hit **Install SilverStripe**.
SilverStripe should now be installed and you should have a basic site with three pages.
However, URLs will not look "nice", like this: http://localhost/index.php/about-us. In order to fix this problem, we need to install a third-party URL rewriting tool, as IIS 6 does not support this natively.
Proceed to **Install IIRF** below to enable nice URLs.
## Install IIRF
At the moment, all URLs will have index.php in them. This is because IIS does not support URL rewriting. To make this work, we need to install IIRF which is a third-party plugin for IIS.
- [Download IIRF](http://iirf.codeplex.com/releases/view/36814) and install it
- Create a new file called iirf.ini in C:\inetpub\wwwroot\silverstripe with this content
RewriteEngine On
MaxMatchCount 10
IterationLimit 5
# URLs with query strings
# Don't catch successful file references
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)\?(.+)$ /framework/main.php?url=$1&$2
# URLs without query strings
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /framework/main.php?url=$1
Friendly URLs should now be working when you browse to your site.
Remember that IIRF works on a per-virtual host basis. This means for each site you want IIRF to work for, you need to add a new entry to **Web Sites** in **inetmgr.exe**.
Thanks to **kcd** for the rules: [http://www.silverstripe.org/installing-silverstripe/show/10488#post294415](http://www.silverstripe.org/installing-silverstripe/show/10488#post294415)

View File

@ -8,12 +8,14 @@ able to run PHP files via the FastCGI-wrapper from Nginx.
Now you need to set up a virtual host in Nginx with configuration settings Now you need to set up a virtual host in Nginx with configuration settings
that are similar to those shown below. that are similar to those shown below.
<div class="notice" markdown='1'> <div class="notice" markdown='1'>
If you don't fully understand the configuration presented here, consult the If you don't fully understand the configuration presented here, consult the
[nginx documentation](http://nginx.org/en/docs/). [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. 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> </div>
But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`: But enough of the disclaimer, on to the actual configuration — typically in `nginx.conf`:
server { server {
@ -22,6 +24,11 @@ But enough of the disclaimer, on to the actual configuration — typically in `n
server_name site.com www.site.com; server_name site.com www.site.com;
# Defend against SS-2015-013 -- http://www.silverstripe.org/software/download/security-releases/ss-2015-013
if ($http_x_forwarded_host) {
return 400;
}
location / { location / {
try_files $uri /framework/main.php?url=$uri&$query_string; try_files $uri /framework/main.php?url=$uri&$query_string;
} }

View File

@ -1,19 +1,16 @@
# Installation # Installation
These instructions show you how to install SilverStripe on any web server. These instructions show you how to install SilverStripe on any web server.
The best way to install from the source code is to use [Composer](../composer).
Check out our operating system specific guides for [Linux](linux_unix), Check out our operating system specific guides for [Linux](linux_unix),
[Windows Server](windows) and [Mac OSX](mac_osx). [Windows Server](windows) and [Mac OSX](mac_osx).
## Installation Steps ## Installation Steps
* [Download](http://silverstripe.org/download) the installer package * Make sure the webserver has MySQL and PHP support (check our [server requirements](../server_requirements)).
* Make sure the webserver has MySQL and PHP support. See [Server Requirements](../server_requirements) for more information. * Either [download the installer package](http://silverstripe.org/download), or [install through Composer](../composer).
* Unpack the installer somewhere into your web-root. Usually the www folder or similar. Most downloads from SilverStripe * If using with the installer download, extract it into your webroot.
are compressed tarballs. To extract these files you can either do them natively (Unix) or with 7-Zip (Windows) * Visit your domain or IP address in your web browser.
* Visit your sites domain or IP address in your web browser. * You will be presented with an installation wizard asking for database and login credentials.
* You will be presented with a form where you enter your MySQL login details and are asked to give your site a 'project
name' and the default login details. Follow the questions and select the *install* button at the bottom of the page.
* After a couple of minutes, your site will be set up. Visit your site and enjoy! * After a couple of minutes, your site will be set up. Visit your site and enjoy!
## Issues? ## Issues?

View File

@ -9,7 +9,9 @@ We also have separate instructions for [installing modules with Composer](/devel
## Installing composer ## Installing composer
To install Composer, run the following commands from your command-line. Before installing Composer you should ensure your system has the version control system, [Git installed](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git). Composer uses Git to check out the code dependancies you need to run your SilverStripe CMS website from the code repositories maintained on GitHub.
Next, to install Composer, run the following commands from your command-line.
# Download composer.phar # Download composer.phar
curl -s https://getcomposer.org/installer | php curl -s https://getcomposer.org/installer | php
@ -77,7 +79,7 @@ You can find other packages with the following command:
composer search silverstripe composer search silverstripe
This will return a list of package names of the forum `vendor/package`. If you prefer, you can search for pacakges on [packagist.org](https://packagist.org/search/?q=silverstripe). This will return a list of package names of the forum `vendor/package`. If you prefer, you can search for packages on [packagist.org](https://packagist.org/search/?q=silverstripe).
The second part after the colon, `*`, is a version string. `*` is a good default: it will give you the latest version that works with the other modules you have installed. Alternatively, you can specificy a specific version, or a constraint such as `>=3.0`. For more information, read the [Composer documentation](http://getcomposer.org/doc/01-basic-usage.md#the-require-key). The second part after the colon, `*`, is a version string. `*` is a good default: it will give you the latest version that works with the other modules you have installed. Alternatively, you can specificy a specific version, or a constraint such as `>=3.0`. For more information, read the [Composer documentation](http://getcomposer.org/doc/01-basic-usage.md#the-require-key).

View File

@ -431,7 +431,7 @@ Put code into the classes in the following order (where applicable).
### SQL Format ### SQL Format
If you have to use raw SQL, make sure your code works across databases make sure you escape your queries like below, If you have to use raw SQL, make sure your code works across databases. Make sure you escape your queries like below,
with the column or table name escaped with double quotes as below. with the column or table name escaped with double quotes as below.
:::php :::php

View File

@ -37,8 +37,27 @@ For more flexibility, you can set up either of the following web servers, and us
Mac OS X comes with a built-in webserver, but there are a number of other options: Mac OS X comes with a built-in webserver, but there are a number of other options:
* [Install using MAMP](mac-osx) * [Install using MAMP](mac-osx)
* Install using the built-in webserver (no docs yet) * [Install using Homebrew](installation/other_installation_options/mac_osx_homebrew)
* Install using MacPorts (no docs yet)
### Virtual Machines through Vagrant
[Vagrant](https://www.vagrantup.com/) creates portable development environments
which can be hosted on Linux, Windows and Mac OS X. The virtual machine
usually runs a flavour of Linux. As a self-contained pre-configured environment,
getting up an running with Vagrant tends to be easier than creating a complete
development environment from scratch on your own machine.
* [silverstripe-australia/vagrant-environment](https://github.com/silverstripe-australia/vagrant-environment)
* [BetterBrief/vagrant-skeleton](https://github.com/BetterBrief/vagrant-skeleton)
Note: These instructions are supported by the community.
## Virtual Machines through Bitnami
[Bitnami](https://bitnami.com) is an online service that makes it easy to get
apps running on cloud providers like Amazon Web Services as well as local
virtualised environments. Bitnami has a [SilverStripe Virtual Machine](https://bitnami.com/stack/silverstripe/virtual-machine)
ready for download or installation on a cloud platform.
## Troubleshooting ## Troubleshooting

View File

@ -10,16 +10,16 @@ These tutorials are deprecated, and have been replaced by the new [Lessons](http
These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites. These include video screencasts, written tutorials and code examples to get you started working with SilverStripe websites.
* [How to set up a local development environment in SilverStripe](https://vimeo.com/108861537) * [How to set up a local development environment in SilverStripe](https://vimeo.com/108861537)
* [Lesson 1: Creating your first theme](http://www.silverstripe.org/learn/lessons/lesson-1-creating-your-first-theme/) * [Lesson 1: Creating your first theme](http://www.silverstripe.org/learn/lessons/creating-your-first-theme)
* [Lesson 2: Migrating static templates into your theme](http://www.silverstripe.org/learn/lessons/lesson-2-migrating-static-templates-into-your-theme/) * [Lesson 2: Migrating static templates into your theme]http://www.silverstripe.org/learn/lessons/migrating-static-templates-into-your-theme)
* [Lesson 3: Adding dynamic content](http://www.silverstripe.org/learn/lessons/lesson-3-adding-dynamic-content/) * [Lesson 3: Adding dynamic content](http://www.silverstripe.org/learn/lessons/adding-dynamic-content)
* [Lesson 4: Working with multiple templates](http://www.silverstripe.org/learn/lessons/lesson-4-working-with-multiple-templates/) * [Lesson 4: Working with multiple templates](http://www.silverstripe.org/learn/lessons/working-with-multiple-templates)
* [Lesson 5: The holder/page pattern](http://www.silverstripe.org/learn/lessons/lesson-5-the-holderpage-pattern/) * [Lesson 5: The holder/page pattern](http://www.silverstripe.org/learn/lessons/the-holderpage-pattern)
* [Lesson 6: Adding Custom Fields to a Page](http://www.silverstripe.org/learn/lessons/lesson-6-adding-custom-fields-to-a-page/) * [Lesson 6: Adding Custom Fields to a Page](http://www.silverstripe.org/learn/lessons/adding-custom-fields-to-a-page)
* [Lesson 7: Working with Files and Images](http://www.silverstripe.org/learn/lessons/lesson-7-working-with-files-and-images/) * [Lesson 7: Working with Files and Images](http://www.silverstripe.org/learn/lessons/working-with-files-and-images)
* [Lesson 8: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/lesson-8-introduction-to-the-orm) * [Lesson 8: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/introduction-to-the-orm)
* [Lesson 9: Data Relationships - $has_many](http://www.silverstripe.org/learn/lessons/lesson-9-working-with-data-relationships-has-many) * [Lesson 9: Data Relationships - $has_many](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-has-many)
* [Lesson 10: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/lesson-10-working-with-data-relationships-many-many) * [Lesson 10: Introduction to the ORM](http://www.silverstripe.org/learn/lessons/working-with-data-relationships-many-many)
## Help: If you get stuck ## Help: If you get stuck

View File

@ -70,7 +70,7 @@ These variables will call a method / field on the object and insert the returned
* `$Foo.Bar` will call `$obj->Foo()->Bar()` * `$Foo.Bar` will call `$obj->Foo()->Bar()`
If a variable returns a string, that string will be inserted into the template. If the variable returns an object, then If a variable returns a string, that string will be inserted into the template. If the variable returns an object, then
the system will attempt to render the object through its' `forTemplate()` method. If the `forTemplate()` method has not the system will attempt to render the object through its `forTemplate()` method. If the `forTemplate()` method has not
been defined, the system will return an error. been defined, the system will return an error.
<div class="note" markdown="1"> <div class="note" markdown="1">
@ -96,7 +96,7 @@ Variables can come from your database fields, or custom methods you define on yo
Method names that begin with `get` will automatically be resolved when their prefix is excluded. For example, the above method call `$UsersIpAddress` would also invoke a method named `getUsersIpAddress()`. Method names that begin with `get` will automatically be resolved when their prefix is excluded. For example, the above method call `$UsersIpAddress` would also invoke a method named `getUsersIpAddress()`.
</div> </div>
The variable's that can be used in a template vary based on the object currently in [scope](#scope). Scope defines what The variables that can be used in a template vary based on the object currently in [scope](#scope). Scope defines what
object the methods get called on. For the standard `Page.ss` template the scope is the current [api:Page_Controller] object the methods get called on. For the standard `Page.ss` template the scope is the current [api:Page_Controller]
class. This object gives you access to all the database fields on [api:Page_Controller], its corresponding [api:Page] class. This object gives you access to all the database fields on [api:Page_Controller], its corresponding [api:Page]
record and any subclasses of those two. record and any subclasses of those two.
@ -243,7 +243,7 @@ object that is being looped over.
`<% loop %>` statements iterate over a [api:DataList] instance. As the template has access to the list object, `<% loop %>` statements iterate over a [api:DataList] instance. As the template has access to the list object,
templates can call [api:DataList] methods. templates can call [api:DataList] methods.
Sort the list by a given field. Sorting the list by a given field.
:::ss :::ss
<ul> <ul>
@ -270,7 +270,7 @@ Reversing the loop.
<% end_loop %> <% end_loop %>
</ul> </ul>
Filtering the loop Filtering the loop.
:::ss :::ss
<ul> <ul>
@ -279,7 +279,7 @@ Filtering the loop
<% end_loop %> <% end_loop %>
</ul> </ul>
Methods can also be chained Methods can also be chained.
:::ss :::ss
<ul> <ul>
@ -293,15 +293,15 @@ Methods can also be chained
Inside the loop scope, there are many variables at your disposal to determine the current position in the list and Inside the loop scope, there are many variables at your disposal to determine the current position in the list and
iteration. iteration.
* `$Even`, `$Odd`: Returns boolean, handy for zebra striping * `$Even`, `$Odd`: Returns boolean, handy for zebra striping.
* `$EvenOdd`: Returns a string, either 'even' or 'odd'. Useful for CSS classes. * `$EvenOdd`: Returns a string, either 'even' or 'odd'. Useful for CSS classes.
* `$First`, `$Last`, `$Middle`: Booleans about the position in the list * `$First`, `$Last`, `$Middle`: Booleans about the position in the list.
* `$FirstLast`: Returns a string, "first", "last", "first last" (if both), or "". Useful for CSS classes. * `$FirstLast`: Returns a string, "first", "last", "first last" (if both), or "". Useful for CSS classes.
* `$Pos`: The current position in the list (integer). * `$Pos`: The current position in the list (integer).
Will start at 1, but can take a starting index as a parameter. Will start at 1, but can take a starting index as a parameter.
* `$FromEnd`: The position of the item from the end (integer). * `$FromEnd`: The position of the item from the end (integer).
Last item defaults to 1, but can be passed as a parameter. Last item defaults to 1, but can be passed as a parameter.
* `$TotalItems`: Number of items in the list (integer) * `$TotalItems`: Number of items in the list (integer).
:::ss :::ss
<ul> <ul>
@ -344,7 +344,7 @@ $Modulus and $MultipleOf can help to build column and grid layouts.
</div> </div>
$MultipleOf(value, offset) can also be utilized to build column and grid layouts. In this case we want to add a `<br>` $MultipleOf(value, offset) can also be utilized to build column and grid layouts. In this case we want to add a `<br>`
after every 3th item. after every 3rd item.
:::ss :::ss
<% loop $Children %> <% loop $Children %>
@ -389,7 +389,7 @@ layout template is the [api:Page_Controller] that is currently being rendered.
When the scope is a `Page_Controller` it will automatically also look up any methods in the corresponding `Page` data When the scope is a `Page_Controller` it will automatically also look up any methods in the corresponding `Page` data
record. In the case of `$Title` the flow looks like record. In the case of `$Title` the flow looks like
$Title --> [Looks up: Current Page_Controller and parent classes] --> [Looks up: Current Page and parent classes]. $Title --> [Looks up: Current Page_Controller and parent classes] --> [Looks up: Current Page and parent classes]
The list of variables you could use in your template is the total of all the methods in the current scope object, parent The list of variables you could use in your template is the total of all the methods in the current scope object, parent
classes of the current scope object, and any [api:Extension] instances you have. classes of the current scope object, and any [api:Extension] instances you have.

View File

@ -186,7 +186,7 @@ If the Javascript files are preferred to be placed in the `<head>` tag rather th
`Requirements.write_js_to_body` should be set to false. `Requirements.write_js_to_body` should be set to false.
:::php :::php
Requirements::set_force_js_to_bottom(true); Requirements::set_write_js_to_body(false);
## API Documentation ## API Documentation

View File

@ -76,7 +76,7 @@ does, such as `ArrayData` or `ArrayList`.
'Title' => 'First Job' 'Title' => 'First Job'
))); )));
return $this->customize(new ArrayData(array( return $this->customise(new ArrayData(array(
'Name' => 'John', 'Name' => 'John',
'Role' => 'Head Coach', 'Role' => 'Head Coach',
'Experience' => $experience 'Experience' => $experience

View File

@ -100,7 +100,7 @@ Some common examples are [api:TextField] or [api:DropdownField].
TextField::create($name, $title, $value); TextField::create($name, $title, $value);
<div class="info" markdown='1'> <div class="info" markdown='1'>
A list of the common FormField subclasses is available on the [Common Subclasses](fields/common_subclasses) page. A list of the common FormField subclasses is available on the [Common Subclasses](field_types/common_subclasses/) page.
</div> </div>
The fields are added to the [api:FieldList] `fields` property on the `Form` and can be modified at up to the point the The fields are added to the [api:FieldList] `fields` property on the `Form` and can be modified at up to the point the

View File

@ -137,7 +137,7 @@ reusable and would not be possible within the `CMS` or other automated `UI` but
$form = new Form($controller, 'MyForm', $fields, $actions); $form = new Form($controller, 'MyForm', $fields, $actions);
return $form return $form;
} }
public function doSubmitForm($data, $form) { public function doSubmitForm($data, $form) {

View File

@ -226,7 +226,7 @@ Example: Remove field for "image captions"
// File: mysite/code/MyToolbarExtension.php // File: mysite/code/MyToolbarExtension.php
class MyToolbarExtension extends Extension { class MyToolbarExtension extends Extension {
public function updateFieldsForImage(&$fields, $url, $file) { public function updateFieldsForImage(&$fields, $url, $file) {
$fields->removeByName('Caption'); $fields->removeByName('CaptionText');
} }
} }

View File

@ -71,7 +71,7 @@ the `getConfig()` method on `GridField`.
); );
// GridField configuration // GridField configuration
$config = $gridField->getConfig(); $config = $grid->getConfig();
// //
// Modification of existing components can be done by fetching that component. // Modification of existing components can be done by fetching that component.
@ -373,7 +373,7 @@ Your new area can also be used by existing components, e.g. the [api:GridFieldPr
## Creating a Custom GridFieldComponent ## Creating a Custom GridFieldComponent
Customizing a `GridField` is easy, applications and modules can provide their own `GridFieldComponent` instances to add Customizing a `GridField` is easy, applications and modules can provide their own `GridFieldComponent` instances to add
functionality. See [How to Create a GridFieldComponent](../how_tos/create_a_gridfield_component). functionality. See [How to Create a GridFieldComponent](../how_tos/create_a_gridfieldcomponent).
## Creating a Custom GridField_ActionProvider ## Creating a Custom GridField_ActionProvider

View File

@ -112,7 +112,7 @@ we added a `SayHi` method which is unique to our extension.
## Modifying Existing Methods ## Modifying Existing Methods
If the `Extension` needs to modify an existing method it's a little tricker. It requires that the method you want to If the `Extension` needs to modify an existing method it's a little trickier. It requires that the method you want to
customize has provided an *Extension Hook* in the place where you want to modify the data. An *Extension Hook* is done customize has provided an *Extension Hook* in the place where you want to modify the data. An *Extension Hook* is done
through the `[api:Object->extend]` method. through the `[api:Object->extend]` method.

View File

@ -15,10 +15,10 @@ email was sent using this method.
$e->send(); $e->send();
} }
To test that `MyMethod` sends the correct email, use the [api:Email::assertEmailSent] method. To test that `MyMethod` sends the correct email, use the [api:SapphireTest::assertEmailSent] method.
:::php :::php
$this->assertEmailSend($to, $from, $subject, $body); $this->assertEmailSent($to, $from, $subject, $body);
// to assert that the email is sent to the correct person // to assert that the email is sent to the correct person
$this->assertEmailSent("someone@example.com", null, "/th.*e$/"); $this->assertEmailSent("someone@example.com", null, "/th.*e$/");

View File

@ -19,22 +19,16 @@ If you are familiar with PHP coding but new to unit testing then check out Mark'
You should also read over [the PHPUnit manual](http://www.phpunit.de/manual/current/en/). It provides a lot of You should also read over [the PHPUnit manual](http://www.phpunit.de/manual/current/en/). It provides a lot of
fundamental concepts that we build on in this documentation. fundamental concepts that we build on in this documentation.
Unit tests are not included in the normal SilverStripe downloads so you need to install them through git repositories Unit tests are not included in the zip/tar.gz SilverStripe [downloads](http://www.silverstripe.org/software/download/) so to get them, install SilverStripe [with composer](/getting_started/composer).
([installation instructions](/getting_started/composer)).
## Install with Composer ## Invoking phpunit
Once you've got your project up and running, open a terminal and cd to your project root. Once you have used composer to create your project, `cd` to your project root. Composer will have installed PHPUnit alongside the required PHP classes into the `vendor/bin/` directory.
composer require --dev "phpunit/phpunit:*@stable" If you don't want to invoke PHPUnit through its full path (`vendor/bin/phpunit`), add `./vendor/bin` to your $PATH, or symlink phpunit into the root directory of your website:
This will install [PHPUnit](http://www.phpunit.de/) dependency, which is the framework we're - `PATH=./vendor/bin:$PATH` in your shell's profile script; **or**
building our unit tests on. Composer installs it alongside the required PHP classes into the `vendor/bin/` directory. - `ln -s vendor/bin/phpunit phpunit` at the command prompt in your project root
### Symlinking the PHPUnit Binary
You can either use PHPUnit through its full path (`vendor/bin/phpunit`), or symlink it into the root directory of your website:
ln -s vendor/bin/phpunit phpunit
## Configuration ## Configuration
@ -128,6 +122,6 @@ particularly around formatting test output.
### Via Web Browser ### Via Web Browser
Executing tests from the command line is recommended, since it most closely reflects Executing tests from the command line is recommended, since it most closely reflects
test runs in any automated testing environments. However, you can also run tests through the browser (requires PHPUnit version 3.7.*@stable): test runs in any automated testing environments. However, you can also run tests through the browser:
http://localhost/dev/tests http://localhost/dev/tests

View File

@ -8,41 +8,28 @@ authentication system.
The main login system uses these controllers to handle the various security requests: The main login system uses these controllers to handle the various security requests:
`[api:Security]` Which is the controller which handles most front-end security requests, including `[api:Security]` - Which is the controller which handles most front-end security requests, including logging in, logging out, resetting password, or changing password. This class also provides an interface to allow configured `[api:Authenticator]` classes to each display a custom login form.
Logging in, logging out, resetting password, or changing password. This class also provides an interface
to allow configured `[api:Authenticator]` classes to each display a custom login form.
`[api:CMSSecurity]` Which is the controller which handles security requests within the CMS, and allows `[api:CMSSecurity]` - Which is the controller which handles security requests within the CMS, and allows users to re-login without leaving the CMS.
users to re-login without leaving the CMS.
## Member Authentication ## Member Authentication
The default member authentication system is implemented in the following classes: The default member authentication system is implemented in the following classes:
`[api:MemberAuthenticator]` Which is the default member authentication implementation. This uses the email `[api:MemberAuthenticator]` - Which is the default member authentication implementation. This uses the email and password stored internally for each member to authenticate them.
and password stored internally for each member to authenticate them.
`[api:MemberLoginForm]` Is the default form used by `MemberAuthenticator`, and is displayed on the public site `[api:MemberLoginForm]` - Is the default form used by `MemberAuthenticator`, and is displayed on the public site at the url `Security/login` by default.
at the url `Security/login` by default.
`[api:CMSMemberLoginForm]` Is the secondary form used by `MemberAuthenticator`, and will be displayed to the `[api:CMSMemberLoginForm]` - Is the secondary form used by `MemberAuthenticator`, and will be displayed to the user within the CMS any time their session expires or they are logged out via an action. This form is presented via a popup dialog, and can be used to re-authenticate that user automatically without them having to lose their workspace. E.g. if editing a form, the user can login and continue to publish their content.
user within the CMS any time their session expires or they are logged out via an action. This form is
presented via a popup dialog, and can be used to re-authenticate that user automatically without them having
to lose their workspace. E.g. if editing a form, the user can login and continue to publish their content.
## Custom Authentication ## Custom Authentication
Additional authentication methods (oauth, etc) can be implemented by creating custom implementations of each of the Additional authentication methods (oauth, etc) can be implemented by creating custom implementations of each of the
following base classes: following base classes:
`[api:Authenticator]` The base class for authentication systems. This class also acts as the factory `[api:Authenticator]` - The base class for authentication systems. This class also acts as the factory to generate various login forms for parts of the system. If an authenticator supports in-cms reauthentication then it will be necessary to override the `supports_cms` and `get_cms_login_form` methods.
to generate various login forms for parts of the system. If an authenticator supports in-cms
reauthentication then it will be necessary to override the `supports_cms` and `get_cms_login_form` methods.
`[api:LoginForm]` which is the base class for a login form which links to a specific authenticator. At the very `[api:LoginForm]` - which is the base class for a login form which links to a specific authenticator. At the very least, it will be necessary to implement a form class which provides a default login interface. If in-cms re-authentication is desired, then a specialised subclass of this method may be necessary. For example, this form could be extended to require confirmation of username as well as password.
least, it will be necessary to implement a form class which provides a default login interface. If in-cms
re-authentication is desired, then a specialised subclass of this method may be necessary. For example, this form
could be extended to require confirmation of username as well as password.
## Default Admin ## Default Admin

View File

@ -552,6 +552,58 @@ This is a recommended option to secure any controller which displays
or submits sensitive user input, and is enabled by default in all CMS controllers, or submits sensitive user input, and is enabled by default in all CMS controllers,
as well as the login form. as well as the login form.
## Request hostname forgery
To prevent a forged hostname appearing being used by the application, SilverStripe
allows the configure of a whitelist of hosts that are allowed to access the system. By defining
this whitelist in your _ss_environment.php file, any request presenting a `Host` header that is
_not_ in this list will be blocked with a HTTP 400 error:
:::php
define('SS_ALLOWED_HOSTS', 'www.mysite.com,mysite.com,subdomain.mysite.com');
Please note that if this configuration is defined, you _must_ include _all_ subdomains (eg www.)
that will be accessing the site.
When SilverStripe is run behind a reverse proxy, it's normally necessary for this proxy to
use the `X-Forwarded-Host` request header to tell the webserver which hostname was originally
requested. However, when SilverStripe is not run behind a proxy, this header can still be
used by attackers to fool the server into mistaking its own identity.
The risk of this kind of attack causing damage is especially high on sites which utilise caching
mechanisms, as rewritten urls could persist between requests in order to misdirect other users
into visiting external sites.
In order to prevent this kind of attack, it's necessary to whitelist trusted proxy
server IPs using the SS_TRUSTED_PROXY_IPS define in your _ss_environment.php.
:::php
define('SS_TRUSTED_PROXY_IPS', '127.0.0.1,192.168.0.1');
If there is no proxy server, 'none' can be used to distrust all clients.
If only trusted servers will make requests then you can use '*' to trust all clients.
Otherwise a comma separated list of individual IP addresses should be declared.
This behaviour is enabled whenever SS_TRUSTED_PROXY_IPS is defined, or if the
`BlockUntrustedIPs` environment variable is declared. It is advisable to include the
following in your .htaccess to ensure this behaviour is activated.
<IfModule mod_env.c>
# Ensure that X-Forwarded-Host is only allowed to determine the request
# hostname for servers ips defined by SS_TRUSTED_PROXY_IPS in your _ss_environment.php
# Note that in a future release this setting will be always on.
SetEnv BlockUntrustedIPs true
</IfModule>
In a future release this behaviour will be changed to be on by default, and this environment
variable will be no longer necessary, thus it will be necessary to always set
SS_TRUSTED_PROXY_IPS if using a proxy.
## Related ## Related
* [http://silverstripe.org/security-releases/](http://silverstripe.org/security-releases/) * [http://silverstripe.org/security-releases/](http://silverstripe.org/security-releases/)

View File

@ -13,7 +13,7 @@ feel familiar to you. This is just a quick run down to get you started
with some special conventions. with some special conventions.
For a more practical-oriented approach to CMS customizations, refer to the For a more practical-oriented approach to CMS customizations, refer to the
[Howto: Extend the CMS Interface](../how_tos/extend_cms_interface) which builds [Howto: Extend the CMS Interface](how_tos/extend_cms_interface) which builds
## Markup and Style Conventions ## Markup and Style Conventions

View File

@ -1,6 +1,6 @@
# How to implement an alternating button # # How to implement an alternating button
## Introduction ## ## Introduction
*Save* and *Save & publish* buttons alternate their appearance to reflect the state of the underlying `SiteTree` object. *Save* and *Save & publish* buttons alternate their appearance to reflect the state of the underlying `SiteTree` object.
This is based on a `ssui.button` extension available in `ssui.core.js`. This is based on a `ssui.button` extension available in `ssui.core.js`.
@ -16,7 +16,7 @@ This how-to will walk you through creation of a "Clean-up" button with two appea
The controller code that goes with this example is listed in [Extend CMS Interface](extend_cms_interface). The controller code that goes with this example is listed in [Extend CMS Interface](extend_cms_interface).
## Backend support ## ## Backend support
First create and configure the action button with alternate state on a page type. The button comes with the default First create and configure the action button with alternate state on a page type. The button comes with the default
state already, so you just need to add the alternate state using two data additional attributes: state already, so you just need to add the alternate state using two data additional attributes:
@ -60,7 +60,7 @@ Here we initialise the button based on the backend check, and assume that the bu
// ... // ...
} }
## Frontend support ## ## Frontend support
As with the *Save* and *Save & publish* buttons, you might want to add some scripted reactions to user actions on the As with the *Save* and *Save & publish* buttons, you might want to add some scripted reactions to user actions on the
frontend. You can affect the state of the button through the jQuery UI calls. frontend. You can affect the state of the button through the jQuery UI calls.
@ -103,7 +103,7 @@ CMS core that tracks the changes to the input fields and reacts by enabling the
} }
}); });
## Frontend hooks ## ## Frontend hooks
`ssui.button` defines several additional events so that you can extend the code with your own behaviours. For example `ssui.button` defines several additional events so that you can extend the code with your own behaviours. For example
this is used in the CMS to style the buttons. Three events are available: this is used in the CMS to style the buttons. Three events are available:
@ -153,7 +153,7 @@ cases.
}(jQuery)); }(jQuery));
## Summary ## ## Summary
The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS. The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS.
These alternating buttons can be used to give user the advantage of visual feedback upon their actions. These alternating buttons can be used to give user the advantage of visual feedback upon their actions.

View File

@ -0,0 +1,39 @@
# 3.1.13
# Overview
This release includes several security fixes to prevent HTTP Hostname injection,
as well as a fix for flush or isDev querystring parameters
to be set via unauthenticated requests.
Users upgrading from 3.1.12 or below should read the [security documentation](/security/secure_coding)
on securing their site.
### Security
* 2015-05-22 [a978b89](https://github.com/silverstripe/sapphire/commit/a978b89) Fix handling of empty parameter token (Damian Mooyman) - See [ss-2015-014](http://www.silverstripe.org/software/download/security-releases/ss-2015-014)
* 2015-05-25 [75137db](https://github.com/silverstripe/sapphire/commit/75137db) Ensure only trusted proxy servers have control over certain HTTP headers (Damian Mooyman) - See [ss-2015-013](http://www.silverstripe.org/software/download/security-releases/ss-2015-013)
* 2015-05-25 [22a35e4](https://github.com/silverstripe/sapphire/commit/22a35e4) Fix malformed urls redirecting to external sites (Damian Mooyman) - See [ss-2015-012](http://www.silverstripe.org/software/download/security-releases/ss-2015-012)
* 2015-05-22 [79cfa2b](https://github.com/silverstripe/sapphire/commit/79cfa2b) Bug fix sqlquery select (Damian Mooyman) - See [ss-2015-011](http://www.silverstripe.org/software/download/security-releases/ss-2015-011)
### Bugfixes
* 2015-04-24 [242de4e](https://github.com/silverstripe/sapphire/commit/242de4e) Added Youtube's short URL. (Michael Strong)
* 2015-05-28 [9c8fa51](https://github.com/silverstripe/sapphire/commit/9c8fa51) Allow users to specify allowed hosts (Marcus Nyeholt)
* 2015-05-07 [828ad6e](https://github.com/silverstripe/sapphire/commit/828ad6e) Modifications to GridFieldExportButton to allow ArrayList use in SS_Report (Will Rossiter)
* 2015-04-30 [be10d90](https://github.com/silverstripe/sapphire/commit/be10d90) count breaks when having clause defined (Aram Balakjian)
* 2015-04-27 [120b983](https://github.com/silverstripe/sapphire/commit/120b983) X-Reload & X-ControllerURL didn't support absolute URLs (fixes #4119) (Loz Calver)
* 2015-04-25 [bfd8b66](https://github.com/silverstripe/sapphire/commit/bfd8b66) for #4104, minor revision of error messages in ListboxField (more intuitive). (Patrick Nelson)
* 2015-04-23 [5ae0ca1](https://github.com/silverstripe/sapphire/commit/5ae0ca1) #4100 Setup the ability to overload the ShortcodeParser class and ensuring its methods/properties are extensible via the "static" keyword. (Patrick Nelson)
* 2015-04-23 [c2fd18e](https://github.com/silverstripe/sapphire/commit/c2fd18e) use config for Security::$login_url (Daniel Hensby)
* 2015-04-23 [19423e9](https://github.com/silverstripe/sapphire/commit/19423e9) Fix tinymce errors crashing CMS When removing a tinymce field, internal third party errors should be caught and ignored gracefully rather than breaking the whole CMS. (Damian Mooyman)
* 2015-04-20 [8e24511](https://github.com/silverstripe/sapphire/commit/8e24511) Fix users with all cms section access not able to edit files Fixes #4078 (Damian Mooyman)
* 2015-04-14 [8caaae6](https://github.com/silverstripe/sapphire/commit/8caaae6) Fix accordion sometimes displaying scrollbars (Damian Mooyman)
* 2015-03-31 [a71f5f9](https://github.com/silverstripe/silverstripe-cms/commit/a71f5f9) Use SearchForm::create to instantiate SearchForm (Daniel Hensby)
* 2015-03-26 [636cddb](https://github.com/silverstripe/sapphire/commit/636cddb) export and print buttons outside button row (Naomi Guyer)
* 2015-03-26 [a7d3f89](https://github.com/silverstripe/sapphire/commit/a7d3f89) Check for existence of HTTP_USER_AGENT to avoid E_NOTICE error. (Sean Harvey)
* 2015-03-25 [8d6cd15](https://github.com/silverstripe/sapphire/commit/8d6cd15) Fix some database errors during dev/build where an auth token exists for the current user Fixes #3660 (Damian Mooyman)
* 2015-03-23 [aba0b70](https://github.com/silverstripe/sapphire/commit/aba0b70) GridFieldDetailForm::setItemEditFormCalback broke chaining (Daniel Hensby)
* 2015-03-23 [72bb9a2](https://github.com/silverstripe/sapphire/commit/72bb9a2) Debug::text no longer incorrecty returns "ViewableData_debugger" (Daniel Hensby)
* 2015-03-16 [f2b1fa9](https://github.com/silverstripe/sapphire/commit/f2b1fa9) broken link in docs to how_tos/extend_cms_interface (Jeremy Shipman)
* 2015-02-24 [6c92a86](https://github.com/silverstripe/silverstripe-cms/commit/6c92a86) Fix CMSMainTest attempting to render page on Security permission error (Damian Mooyman)

View File

@ -0,0 +1,22 @@
# 3.1.13-rc1
### Bugfixes
* 2015-04-24 [242de4e](https://github.com/silverstripe/sapphire/commit/242de4e) Added Youtube's short URL. (Michael Strong)
* 2015-05-07 [828ad6e](https://github.com/silverstripe/sapphire/commit/828ad6e) Modifications to GridFieldExportButton to allow ArrayList use in SS_Report (Will Rossiter)
* 2015-04-30 [be10d90](https://github.com/silverstripe/sapphire/commit/be10d90) count breaks when having clause defined (Aram Balakjian)
* 2015-04-27 [120b983](https://github.com/silverstripe/sapphire/commit/120b983) X-Reload & X-ControllerURL didn't support absolute URLs (fixes #4119) (Loz Calver)
* 2015-04-25 [bfd8b66](https://github.com/silverstripe/sapphire/commit/bfd8b66) for #4104, minor revision of error messages in ListboxField (more intuitive). (Patrick Nelson)
* 2015-04-23 [5ae0ca1](https://github.com/silverstripe/sapphire/commit/5ae0ca1) #4100 Setup the ability to overload the ShortcodeParser class and ensuring its methods/properties are extensible via the "static" keyword. (Patrick Nelson)
* 2015-04-23 [c2fd18e](https://github.com/silverstripe/sapphire/commit/c2fd18e) use config for Security::$login_url (Daniel Hensby)
* 2015-04-23 [19423e9](https://github.com/silverstripe/sapphire/commit/19423e9) Fix tinymce errors crashing CMS When removing a tinymce field, internal third party errors should be caught and ignored gracefully rather than breaking the whole CMS. (Damian Mooyman)
* 2015-04-20 [8e24511](https://github.com/silverstripe/sapphire/commit/8e24511) Fix users with all cms section access not able to edit files Fixes #4078 (Damian Mooyman)
* 2015-04-14 [8caaae6](https://github.com/silverstripe/sapphire/commit/8caaae6) Fix accordion sometimes displaying scrollbars (Damian Mooyman)
* 2015-03-31 [a71f5f9](https://github.com/silverstripe/silverstripe-cms/commit/a71f5f9) Use SearchForm::create to instantiate SearchForm (Daniel Hensby)
* 2015-03-26 [636cddb](https://github.com/silverstripe/sapphire/commit/636cddb) export and print buttons outside button row (Naomi Guyer)
* 2015-03-26 [a7d3f89](https://github.com/silverstripe/sapphire/commit/a7d3f89) Check for existence of HTTP_USER_AGENT to avoid E_NOTICE error. (Sean Harvey)
* 2015-03-25 [8d6cd15](https://github.com/silverstripe/sapphire/commit/8d6cd15) Fix some database errors during dev/build where an auth token exists for the current user Fixes #3660 (Damian Mooyman)
* 2015-03-23 [aba0b70](https://github.com/silverstripe/sapphire/commit/aba0b70) GridFieldDetailForm::setItemEditFormCalback broke chaining (Daniel Hensby)
* 2015-03-23 [72bb9a2](https://github.com/silverstripe/sapphire/commit/72bb9a2) Debug::text no longer incorrecty returns "ViewableData_debugger" (Daniel Hensby)
* 2015-03-16 [f2b1fa9](https://github.com/silverstripe/sapphire/commit/f2b1fa9) broken link in docs to how_tos/extend_cms_interface (Jeremy Shipman)
* 2015-02-24 [6c92a86](https://github.com/silverstripe/silverstripe-cms/commit/6c92a86) Fix CMSMainTest attempting to render page on Security permission error (Damian Mooyman)

View File

@ -326,7 +326,7 @@ class File extends DataObject {
$result = $this->extendedCan('canEdit', $member); $result = $this->extendedCan('canEdit', $member);
if($result !== null) return $result; if($result !== null) return $result;
return Permission::checkMember($member, 'CMS_ACCESS_AssetAdmin'); return Permission::checkMember($member, array('CMS_ACCESS_AssetAdmin', 'CMS_ACCESS_LeftAndMain'));
} }
/** /**
@ -411,7 +411,6 @@ class File extends DataObject {
//get a tree listing with only folder, no files //get a tree listing with only folder, no files
$folderTree = new TreeDropdownField("ParentID", _t('AssetTableField.FOLDER','Folder'), 'Folder'); $folderTree = new TreeDropdownField("ParentID", _t('AssetTableField.FOLDER','Folder'), 'Folder');
$folderTree->setChildrenMethod('ChildFolders');
$fields = new FieldList( $fields = new FieldList(
new TabSet('Root', new TabSet('Root',

View File

@ -474,6 +474,12 @@ class Folder extends File {
return Folder::get()->filter('ParentID', $this->ID); return Folder::get()->filter('ParentID', $this->ID);
} }
/**
* Get the number of children of this folder that are also folders.
*/
public function numChildFolders() {
return $this->ChildFolders()->count();
}
/** /**
* @return String * @return String
*/ */
@ -486,7 +492,7 @@ class Folder extends File {
if(!$this->canEdit()) if(!$this->canEdit())
$classes .= " disabled"; $classes .= " disabled";
$classes .= $this->markingClasses(); $classes .= $this->markingClasses('numChildFolders');
return $classes; return $classes;
} }

View File

@ -569,7 +569,7 @@ class FieldList extends ArrayList {
} }
// Add the leftover fields to the end of the list. // Add the leftover fields to the end of the list.
$fields = $fields + array_values($fieldMap); $fields = array_values($fields + $fieldMap);
// Update our internal $this->items parameter. // Update our internal $this->items parameter.
$this->items = $fields; $this->items = $fields;

View File

@ -468,6 +468,7 @@ class Form extends RequestHandler {
if(Director::is_site_url($pageURL)) { if(Director::is_site_url($pageURL)) {
// Remove existing pragmas // Remove existing pragmas
$pageURL = preg_replace('/(#.*)/', '', $pageURL); $pageURL = preg_replace('/(#.*)/', '', $pageURL);
$pageURL = Director::absoluteURL($pageURL, true);
return $this->controller->redirect($pageURL . '#' . $this->FormName()); return $this->controller->redirect($pageURL . '#' . $this->FormName());
} }
} }

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Hidden field. * Hidden field.
* *
@ -6,25 +7,42 @@
* @subpackage fields-dataless * @subpackage fields-dataless
*/ */
class HiddenField extends FormField { class HiddenField extends FormField {
/**
* @param array $properties
*
* @return string
*/
public function FieldHolder($properties = array()) { public function FieldHolder($properties = array()) {
return $this->Field($properties); return $this->Field($properties);
} }
/**
* @return static
*/
public function performReadonlyTransformation() { public function performReadonlyTransformation() {
$clone = clone $this; $clone = clone $this;
$clone->setReadonly(true); $clone->setReadonly(true);
return $clone; return $clone;
} }
/**
* @return bool
*/
public function IsHidden() { public function IsHidden() {
return true; return true;
} }
/**
* {@inheritdoc}
*/
public function getAttributes() { public function getAttributes() {
return array_merge( return array_merge(
parent::getAttributes(), parent::getAttributes(),
array('type' => 'hidden') array(
'type' => 'hidden',
)
); );
} }

View File

@ -1,30 +1,34 @@
<?php <?php
/** /**
* Simple label tag. This can be used to add extra text in your forms. * Simple label, to add extra text in your forms.
* Consider using a {@link ReadonlyField} if you need to display a label *
* AND a value. * Use a {@link ReadonlyField} if you need to display a label and value.
* *
* @package forms * @package forms
* @subpackage fields-dataless * @subpackage fields-dataless
*/ */
class LabelField extends DatalessField { class LabelField extends DatalessField {
/** /**
* @param string $name * @param string $name
* @param string $title * @param null|string $title
* @param Form $form
*/ */
public function __construct($name, $title) { public function __construct($name, $title = null) {
// legacy handling for old parameters: $title, $heading, ... // legacy handling:
// instead of new handling: $name, $title, $heading, ... // $title, $headingLevel...
$args = func_get_args(); $args = func_get_args();
if(!isset($args[1])) { if(!isset($args[1])) {
$title = (isset($args[0])) ? $args[0] : null; $title = (isset($args[0])) ? $args[0] : null;
$name = $title;
$form = (isset($args[3])) ? $args[3] : null; if(isset($args[0])) {
$title = $args[0];
}
// Prefix name to avoid collisions.
$name = 'LabelField' . $title;
} }
parent::__construct($name, $title); parent::__construct($name, $title);
} }
} }

View File

@ -215,13 +215,14 @@ class ListboxField extends DropdownField {
if($val) { if($val) {
if(!$this->multiple && is_array($val)) { if(!$this->multiple && is_array($val)) {
throw new InvalidArgumentException('No array values allowed with multiple=false'); throw new InvalidArgumentException('Array values are not allowed (when multiple=false).');
} }
if($this->multiple) { if($this->multiple) {
$parts = (is_array($val)) ? $val : preg_split("/ *, */", trim($val)); $parts = (is_array($val)) ? $val : preg_split("/ *, */", trim($val));
if(ArrayLib::is_associative($parts)) { if(ArrayLib::is_associative($parts)) {
throw new InvalidArgumentException('No associative arrays allowed multiple=true'); // This is due to the possibility of accidentally passing an array of values (as keys) and titles (as values) when only the keys were intended to be saved.
throw new InvalidArgumentException('Associative arrays are not allowed as values (when multiple=true), only indexed arrays.');
} }
// Doesn't check against unknown values in order to allow for less rigid data handling. // Doesn't check against unknown values in order to allow for less rigid data handling.

View File

@ -106,12 +106,12 @@ class MoneyField extends FormField {
$fieldName = $this->name; $fieldName = $this->name;
if($dataObject->hasMethod("set$fieldName")) { if($dataObject->hasMethod("set$fieldName")) {
$dataObject->$fieldName = DBField::create_field('Money', array( $dataObject->$fieldName = DBField::create_field('Money', array(
"Currency" => $this->fieldCurrency->Value(), "Currency" => $this->fieldCurrency->dataValue(),
"Amount" => $this->fieldAmount->Value() "Amount" => $this->fieldAmount->dataValue()
)); ));
} else { } else {
$dataObject->$fieldName->setCurrency($this->fieldCurrency->Value()); $dataObject->$fieldName->setCurrency($this->fieldCurrency->dataValue());
$dataObject->$fieldName->setAmount($this->fieldAmount->Value()); $dataObject->$fieldName->setAmount($this->fieldAmount->dataValue());
} }
} }
@ -155,7 +155,7 @@ class MoneyField extends FormField {
$this->allowedCurrencies = $arr; $this->allowedCurrencies = $arr;
// @todo Has to be done twice in case allowed currencies changed since construction // @todo Has to be done twice in case allowed currencies changed since construction
$oldVal = $this->fieldCurrency->Value(); $oldVal = $this->fieldCurrency->dataValue();
$this->fieldCurrency = $this->FieldCurrency($this->name); $this->fieldCurrency = $this->FieldCurrency($this->name);
$this->fieldCurrency->setValue($oldVal); $this->fieldCurrency->setValue($oldVal);

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Text input field. * Text input field.
* *
@ -6,49 +7,77 @@
* @subpackage fields-basic * @subpackage fields-basic
*/ */
class TextField extends FormField { class TextField extends FormField {
/** /**
* @var int * @var int
*/ */
protected $maxLength; protected $maxLength;
/** /**
* Returns an input field, class="text" and type="text" with an optional maxlength * Returns an input field.
*
* @param string $name
* @param null|string $title
* @param string $value
* @param null|int $maxLength
* @param null|Form $form
*/ */
public function __construct($name, $title = null, $value = '', $maxLength = null, $form = null) { public function __construct($name, $title = null, $value = '', $maxLength = null, $form = null) {
$this->maxLength = $maxLength; if($maxLength) {
$this->setMaxLength($maxLength);
}
parent::__construct($name, $title, $value, $form); if($form) {
$this->setForm($form);
}
parent::__construct($name, $title, $value);
} }
/** /**
* @param int $length * @param int $maxLength
*
* @return static
*/ */
public function setMaxLength($length) { public function setMaxLength($maxLength) {
$this->maxLength = $length; $this->maxLength = $maxLength;
return $this; return $this;
} }
/** /**
* @return int * @return null|int
*/ */
public function getMaxLength() { public function getMaxLength() {
return $this->maxLength; return $this->maxLength;
} }
/**
* @return array
*/
public function getAttributes() { public function getAttributes() {
$maxLength = $this->getMaxLength();
$attributes = array();
if($maxLength) {
$attributes['maxLength'] = $maxLength;
$attributes['size'] = min($maxLength, 30);
}
return array_merge( return array_merge(
parent::getAttributes(), parent::getAttributes(),
array( $attributes
'maxlength' => $this->getMaxLength(),
'size' => ($this->getMaxLength()) ? min($this->getMaxLength(), 30) : null
)
); );
} }
/**
* @return string
*/
public function InternallyLabelledField() { public function InternallyLabelledField() {
if(!$this->value) $this->value = $this->Title(); if(!$this->value) {
$this->value = $this->Title();
}
return $this->Field(); return $this->Field();
} }

View File

@ -56,10 +56,15 @@ class TreeDropdownField extends FormField {
protected $sourceObject, $keyField, $labelField, $filterCallback, protected $sourceObject, $keyField, $labelField, $filterCallback,
$disableCallback, $searchCallback, $baseID = 0; $disableCallback, $searchCallback, $baseID = 0;
/** /**
* @var string default child method in Hierarcy->getChildrenAsUL * @var string default child method in Hierarchy->getChildrenAsUL
*/ */
protected $childrenMethod = 'AllChildrenIncludingDeleted'; protected $childrenMethod = 'AllChildrenIncludingDeleted';
/**
* @var string default child counting method in Hierarchy->getChildrenAsUL
*/
protected $numChildrenMethod = 'numChildren';
/** /**
* Used by field search to leave only the relevant entries * Used by field search to leave only the relevant entries
*/ */
@ -97,6 +102,12 @@ class TreeDropdownField extends FormField {
$this->labelField = $labelField; $this->labelField = $labelField;
$this->showSearch = $showSearch; $this->showSearch = $showSearch;
//Extra settings for Folders
if ($sourceObject == 'Folder') {
$this->childrenMethod = 'ChildFolders';
$this->numChildrenMethod = 'numChildFolders';
}
$this->addExtraClass('single'); $this->addExtraClass('single');
parent::__construct($name, $title); parent::__construct($name, $title);
@ -171,9 +182,9 @@ class TreeDropdownField extends FormField {
/** /**
* @param $method The parameter to ChildrenMethod to use when calling Hierarchy->getChildrenAsUL in * @param $method The parameter to ChildrenMethod to use when calling Hierarchy->getChildrenAsUL in
* {@link Hierarchy}. The method specified determined the structure of the returned list. Use "ChildFolders" * {@link Hierarchy}. The method specified determines the structure of the returned list. Use "ChildFolders"
* in place of the default to get a drop-down listing with only folders, i.e. not including the child elements in * in place of the default to get a drop-down listing with only folders, i.e. not including the child elements in
* the currently selected folder. * the currently selected folder. setNumChildrenMethod() should be used as well for proper functioning.
* *
* See {@link Hierarchy} for a complete list of possible methods. * See {@link Hierarchy} for a complete list of possible methods.
*/ */
@ -182,6 +193,16 @@ class TreeDropdownField extends FormField {
return $this; return $this;
} }
/**
* @param $method The parameter to numChildrenMethod to use when calling Hierarchy->getChildrenAsUL in
* {@link Hierarchy}. Should be used in conjunction with setChildrenMethod().
*
*/
public function setNumChildrenMethod($method) {
$this->numChildrenMethod = $method;
return $this;
}
/** /**
* @return string * @return string
*/ */
@ -273,10 +294,11 @@ class TreeDropdownField extends FormField {
if ( $this->search != "" ) if ( $this->search != "" )
$this->populateIDs(); $this->populateIDs();
if ($this->filterCallback || $this->sourceObject == 'Folder' || $this->search != "" ) if ($this->filterCallback || $this->search != "" )
$obj->setMarkingFilterFunction(array($this, "filterMarking")); $obj->setMarkingFilterFunction(array($this, "filterMarking"));
$obj->markPartialTree(); $obj->markPartialTree($nodeCountThreshold = 30, $context = null,
$this->childrenMethod, $this->numChildrenMethod);
// allow to pass values to be selected within the ajax request // allow to pass values to be selected within the ajax request
if( isset($_REQUEST['forceValue']) || $this->value ) { if( isset($_REQUEST['forceValue']) || $this->value ) {
@ -298,7 +320,7 @@ class TreeDropdownField extends FormField {
Convert::raw2xml($child->$keyField), Convert::raw2xml($child->$keyField),
Convert::raw2xml($child->$keyField), Convert::raw2xml($child->$keyField),
Convert::raw2xml($child->class), Convert::raw2xml($child->class),
Convert::raw2xml($child->markingClasses()), Convert::raw2xml($child->markingClasses($self->numChildrenMethod)),
($self->nodeIsDisabled($child)) ? 'disabled' : '', ($self->nodeIsDisabled($child)) ? 'disabled' : '',
(int)$child->ID, (int)$child->ID,
$child->obj($labelField)->forTemplate() $child->obj($labelField)->forTemplate()
@ -330,7 +352,7 @@ class TreeDropdownField extends FormField {
null, null,
true, true,
$this->childrenMethod, $this->childrenMethod,
'numChildren', $this->numChildrenMethod,
true, // root call true, // root call
null, null,
$nodeCountCallback $nodeCountCallback
@ -343,7 +365,7 @@ class TreeDropdownField extends FormField {
null, null,
true, true,
$this->childrenMethod, $this->childrenMethod,
'numChildren', $this->numChildrenMethod,
true, // root call true, // root call
null, null,
$nodeCountCallback $nodeCountCallback
@ -353,15 +375,14 @@ class TreeDropdownField extends FormField {
} }
/** /**
* Marking public function for the tree, which combines different filters sensibly. If a filter function has been * Marking public function for the tree, which combines different filters sensibly.
* set, that will be called. If the source is a folder, automatically filter folder. And if search text is set, * If a filter function has been set, that will be called. And if search text is set,
* filter on that too. Return true if all applicable conditions are true, false otherwise. * filter on that too. Return true if all applicable conditions are true, false otherwise.
* @param $node * @param $node
* @return unknown_type * @return unknown_type
*/ */
public function filterMarking($node) { public function filterMarking($node) {
if ($this->filterCallback && !call_user_func($this->filterCallback, $node)) return false; if ($this->filterCallback && !call_user_func($this->filterCallback, $node)) return false;
if ($this->sourceObject == "Folder" && $node->ClassName != 'Folder') return false;
if ($this->search != "") { if ($this->search != "") {
return isset($this->searchIds[$node->ID]) && $this->searchIds[$node->ID] ? true : false; return isset($this->searchIds[$node->ID]) && $this->searchIds[$node->ID] ? true : false;
} }

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* This validation class handles all form and custom form validation through * This validation class handles all form and custom form validation through
* the use of Required fields. * the use of Required fields.
@ -24,18 +25,25 @@ abstract class Validator extends Object {
/** /**
* @param Form $form * @param Form $form
*
* @return static
*/ */
public function setForm($form) { public function setForm($form) {
$this->form = $form; $this->form = $form;
return $this; return $this;
} }
/** /**
* @return array Errors (if any) * Returns any errors there may be.
*
* @return null|array
*/ */
public function validate(){ public function validate() {
$this->errors = null; $this->errors = null;
$this->php($this->form->getData()); $this->php($this->form->getData());
return $this->errors; return $this->errors;
} }
@ -45,9 +53,10 @@ abstract class Validator extends Object {
* @param string $fieldName name of the field * @param string $fieldName name of the field
* @param string $message error message to display * @param string $message error message to display
* @param string $messageType optional parameter, gets loaded into the HTML class attribute in the rendered output. * @param string $messageType optional parameter, gets loaded into the HTML class attribute in the rendered output.
*
* See {@link getErrors()} for details. * See {@link getErrors()} for details.
*/ */
public function validationError($fieldName, $message, $messageType='') { public function validationError($fieldName, $message, $messageType = '') {
$this->errors[] = array( $this->errors[] = array(
'fieldName' => $fieldName, 'fieldName' => $fieldName,
'message' => $message, 'message' => $message,
@ -57,6 +66,7 @@ abstract class Validator extends Object {
/** /**
* Returns all errors found by a previous call to {@link validate()}. * Returns all errors found by a previous call to {@link validate()}.
*
* The array contains the following keys for each error: * The array contains the following keys for each error:
* - 'fieldName': the name of the FormField instance * - 'fieldName': the name of the FormField instance
* - 'message': Validation message (optionally localized) * - 'message': Validation message (optionally localized)
@ -70,28 +80,45 @@ abstract class Validator extends Object {
return $this->errors; return $this->errors;
} }
/**
* @param string $fieldName
* @param array $data
*/
public function requireField($fieldName, $data) { public function requireField($fieldName, $data) {
if(is_array($data[$fieldName]) && count($data[$fieldName])) { if(is_array($data[$fieldName]) && count($data[$fieldName])) {
foreach($data[$fieldName] as $componentkey => $componentVal){ foreach($data[$fieldName] as $componentKey => $componentValue) {
if(!strlen($componentVal)) { if(!strlen($componentValue)) {
$this->validationError($fieldName, "$fieldName $componentkey is required", "required"); $this->validationError(
$fieldName,
sprintf('%s %s is required', $fieldName, $componentKey),
'required'
);
} }
} }
} else if(!strlen($data[$fieldName])) { } else if(!strlen($data[$fieldName])) {
$this->validationError($fieldName, "$fieldName is required", "required"); $this->validationError(
$fieldName,
sprintf('%s is required', $fieldName),
'required'
);
} }
} }
/** /**
* Returns true if the named field is "required". * Returns true if the named field is "required".
*
* Used by FormField to return a value for FormField::Required(), to do things like show *s on the form template. * Used by FormField to return a value for FormField::Required(), to do things like show *s on the form template.
*
* By default, it always returns false. * By default, it always returns false.
*/ */
public function fieldIsRequired($fieldName) { public function fieldIsRequired($fieldName) {
return false; return false;
} }
/**
* @param array $data
*
* @return mixed
*/
abstract public function php($data); abstract public function php($data);
} }

View File

@ -1,4 +1,5 @@
<?php <?php
/** /**
* Displays a {@link SS_List} in a grid format. * Displays a {@link SS_List} in a grid format.
* *
@ -32,18 +33,21 @@ class GridField extends FormField {
/** /**
* The datasource * The datasource
*
* @var SS_List * @var SS_List
*/ */
protected $list = null; protected $list = null;
/** /**
* The classname of the DataObject that the GridField will display. Defaults to the value of $this->list->dataClass * The classname of the DataObject that the GridField will display. Defaults to the value of $this->list->dataClass
*
* @var string * @var string
*/ */
protected $modelClassName = ''; protected $modelClassName = '';
/** /**
* the current state of the GridField * the current state of the GridField
*
* @var GridState * @var GridState
*/ */
protected $state = null; protected $state = null;
@ -115,6 +119,7 @@ class GridField extends FormField {
* this modelclass $summary_fields * this modelclass $summary_fields
* *
* @param string $modelClassName * @param string $modelClassName
*
* @see GridFieldDataColumns::getDisplayFields() * @see GridFieldDataColumns::getDisplayFields()
*/ */
public function setModelClass($modelClassName) { public function setModelClass($modelClassName) {
@ -129,8 +134,8 @@ class GridField extends FormField {
* @return string * @return string
*/ */
public function getModelClass() { public function getModelClass() {
if ($this->modelClassName) return $this->modelClassName; if($this->modelClassName) return $this->modelClassName;
if ($this->list && method_exists($this->list, 'dataClass')) { if($this->list && method_exists($this->list, 'dataClass')) {
$class = $this->list->dataClass(); $class = $this->list->dataClass();
if($class) return $class; if($class) return $class;
} }
@ -150,6 +155,7 @@ class GridField extends FormField {
/** /**
* @param GridFieldConfig $config * @param GridFieldConfig $config
*
* @return GridField * @return GridField
*/ */
public function setConfig(GridFieldConfig $config) { public function setConfig(GridFieldConfig $config) {
@ -166,6 +172,7 @@ class GridField extends FormField {
* *
* @param $value * @param $value
* @param $castingDefinition * @param $castingDefinition
*
* @todo refactor this into GridFieldComponent * @todo refactor this into GridFieldComponent
*/ */
public function getCastedValue($value, $castingDefinition) { public function getCastedValue($value, $castingDefinition) {
@ -177,16 +184,16 @@ class GridField extends FormField {
$castingParams = array(); $castingParams = array();
} }
if(strpos($castingDefinition,'->') === false) { if(strpos($castingDefinition, '->') === false) {
$castingFieldType = $castingDefinition; $castingFieldType = $castingDefinition;
$castingField = DBField::create_field($castingFieldType, $value); $castingField = DBField::create_field($castingFieldType, $value);
$value = call_user_func_array(array($castingField,'XML'),$castingParams); $value = call_user_func_array(array($castingField, 'XML'), $castingParams);
} else { } else {
$fieldTypeParts = explode('->', $castingDefinition); $fieldTypeParts = explode('->', $castingDefinition);
$castingFieldType = $fieldTypeParts[0]; $castingFieldType = $fieldTypeParts[0];
$castingMethod = $fieldTypeParts[1]; $castingMethod = $fieldTypeParts[1];
$castingField = DBField::create_field($castingFieldType, $value); $castingField = DBField::create_field($castingFieldType, $value);
$value = call_user_func_array(array($castingField,$castingMethod),$castingParams); $value = call_user_func_array(array($castingField, $castingMethod), $castingParams);
} }
return $value; return $value;
@ -294,7 +301,7 @@ class GridField extends FormField {
$fragmentDefined = array('header' => true, 'footer' => true, 'before' => true, 'after' => true); $fragmentDefined = array('header' => true, 'footer' => true, 'before' => true, 'after' => true);
reset($content); reset($content);
while(list($k,$v) = each($content)) { while(list($k, $v) = each($content)) {
if(preg_match_all('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $v, $matches)) { if(preg_match_all('/\$DefineFragment\(([a-z0-9\-_]+)\)/i', $v, $matches)) {
foreach($matches[1] as $match) { foreach($matches[1] as $match) {
$fragmentName = strtolower($match); $fragmentName = strtolower($match);
@ -332,7 +339,7 @@ class GridField extends FormField {
foreach($content as $k => $v) { foreach($content as $k => $v) {
if(empty($fragmentDefined[$k])) { if(empty($fragmentDefined[$k])) {
throw new LogicException("GridField HTML fragment '$k' was given content," throw new LogicException("GridField HTML fragment '$k' was given content,"
. " but not defined. Perhaps there is a supporting GridField component you need to add?"); . " but not defined. Perhaps there is a supporting GridField component you need to add?");
} }
} }
@ -343,29 +350,25 @@ class GridField extends FormField {
if($record->hasMethod('canView') && !$record->canView()) { if($record->hasMethod('canView') && !$record->canView()) {
continue; continue;
} }
$rowContent = ''; $rowContent = '';
foreach($this->getColumns() as $column) { foreach($this->getColumns() as $column) {
$colContent = $this->getColumnContent($record, $column); $colContent = $this->getColumnContent($record, $column);
// A return value of null means this columns should be skipped altogether. // A return value of null means this columns should be skipped altogether.
if($colContent === null) continue; if($colContent === null) {
continue;
}
$colAttributes = $this->getColumnAttributes($record, $column); $colAttributes = $this->getColumnAttributes($record, $column);
$rowContent .= FormField::create_tag('td', $colAttributes, $colContent);
$rowContent .= $this->newCell($total, $idx, $record, $colAttributes, $colContent);
} }
$classes = array('ss-gridfield-item');
if ($idx == 0) $classes[] = 'first'; $rowAttributes = $this->getRowAttributes($total, $idx, $record);
if ($idx == $total-1) $classes[] = 'last';
$classes[] = ($idx % 2) ? 'even' : 'odd'; $rows[] = $this->newRow($total, $idx, $record, $rowAttributes, $rowContent);
$row = FormField::create_tag(
'tr',
array(
"class" => implode(' ', $classes),
'data-id' => $record->ID,
// TODO Allow per-row customization similar to GridFieldDataColumns
'data-class' => $record->ClassName,
),
$rowContent
);
$rows[] = $row;
} }
$content['body'] = implode("\n", $rows); $content['body'] = implode("\n", $rows);
} }
@ -418,11 +421,85 @@ class GridField extends FormField {
return return
FormField::create_tag('fieldset', $attrs, FormField::create_tag('fieldset', $attrs,
$content['before'] . $content['before'] .
FormField::create_tag('table', $tableAttrs, $head."\n".$foot."\n".$body) . FormField::create_tag('table', $tableAttrs, $head . "\n" . $foot . "\n" . $body) .
$content['after'] $content['after']
); );
} }
/**
* @param int $total
* @param int $index
* @param DataObject $record
* @param array $attributes
* @param string $content
*
* @return string
*/
protected function newCell($total, $index, $record, $attributes, $content) {
return FormField::create_tag(
'td',
$attributes,
$content
);
}
/**
* @param int $total
* @param int $index
* @param DataObject $record
* @param array $attributes
* @param string $content
*
* @return string
*/
protected function newRow($total, $index, $record, $attributes, $content) {
return FormField::create_tag(
'tr',
$attributes,
$content
);
}
/**
* @param int $total
* @param int $index
* @param DataObject $record
*
* @return array
*/
protected function getRowAttributes($total, $index, $record) {
$rowClasses = $this->newRowClasses($total, $index, $record);
return array(
'class' => implode(' ', $rowClasses),
'data-id' => $record->ID,
'data-class' => $record->ClassName,
);
}
/**
* @param int $total
* @param int $index
* @param DataObject $record
*
* @return array
*/
protected function newRowClasses($total, $index, $record) {
$classes = array('ss-gridfield-item');
if($index == 0) {
$classes[] = 'first';
}
if($index == $total - 1) {
$classes[] = 'last';
}
$classes[] = ($index % 2) ? 'even' : 'odd';
return $classes;
}
public function Field($properties = array()) { public function Field($properties = array()) {
return $this->FieldHolder($properties); return $this->FieldHolder($properties);
} }
@ -453,6 +530,7 @@ class GridField extends FormField {
* *
* @param DataObject $record * @param DataObject $record
* @param string $column * @param string $column
*
* @return string * @return string
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
@ -477,7 +555,7 @@ class GridField extends FormField {
* Add additional calculated data fields to be used on this GridField * Add additional calculated data fields to be used on this GridField
* *
* @param array $fields a map of fieldname to callback. The callback will * @param array $fields a map of fieldname to callback. The callback will
* be passed the record as an argument. * be passed the record as an argument.
*/ */
public function addDataFields($fields) { public function addDataFields($fields) {
if($this->customDataFields) { if($this->customDataFields) {
@ -513,6 +591,7 @@ class GridField extends FormField {
* *
* @param DataObject $record * @param DataObject $record
* @param string $column * @param string $column
*
* @return array * @return array
* @throws LogicException * @throws LogicException
* @throws InvalidArgumentException * @throws InvalidArgumentException
@ -547,6 +626,7 @@ class GridField extends FormField {
* Get metadata for a column, example array('Title'=>'Email address') * Get metadata for a column, example array('Title'=>'Email address')
* *
* @param string $column * @param string $column
*
* @return array * @return array
* @throws LogicException * @throws LogicException
* @throws InvalidArgumentException * @throws InvalidArgumentException
@ -611,6 +691,7 @@ class GridField extends FormField {
* This is the action that gets executed when a GridField_AlterAction gets clicked. * This is the action that gets executed when a GridField_AlterAction gets clicked.
* *
* @param array $data * @param array $data
*
* @return string * @return string
*/ */
public function gridFieldAlterAction($data, $form, SS_HTTPRequest $request) { public function gridFieldAlterAction($data, $form, SS_HTTPRequest $request) {
@ -661,7 +742,8 @@ class GridField extends FormField {
* *
* @param string $actionName * @param string $actionName
* @param mixed $args * @param mixed $args
* @param arrray $data - send data from a form * @param array $data - send data from a form
*
* @return mixed * @return mixed
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
@ -672,7 +754,7 @@ class GridField extends FormField {
continue; continue;
} }
if(in_array($actionName, array_map('strtolower', (array)$component->getActions($this)))) { if(in_array($actionName, array_map('strtolower', (array) $component->getActions($this)))) {
return $component->handleAction($this, $actionName, $args, $data); return $component->handleAction($this, $actionName, $args, $data);
} }
} }
@ -705,7 +787,7 @@ class GridField extends FormField {
if($urlHandlers) foreach($urlHandlers as $rule => $action) { if($urlHandlers) foreach($urlHandlers as $rule => $action) {
if($params = $request->match($rule, true)) { if($params = $request->match($rule, true)) {
// Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action', // Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action',
if($action[0] == '$') $action = $params[substr($action,1)]; if($action[0] == '$') $action = $params[substr($action, 1)];
if(!method_exists($component, 'checkAccessAction') || $component->checkAccessAction($action)) { if(!method_exists($component, 'checkAccessAction') || $component->checkAccessAction($action)) {
if(!$action) { if(!$action) {
$action = "index"; $action = "index";
@ -724,7 +806,8 @@ class GridField extends FormField {
} }
if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result) if($this !== $result && !$request->isEmptyPattern($rule) && is_object($result)
&& $result instanceof RequestHandler) { && $result instanceof RequestHandler
) {
$returnValue = $result->handleRequest($request, $model); $returnValue = $result->handleRequest($request, $model);
@ -734,11 +817,11 @@ class GridField extends FormField {
return $returnValue; return $returnValue;
// If we return some other data, and all the URL is parsed, then return that // If we return some other data, and all the URL is parsed, then return that
} else if($request->allParsed()) { } else if($request->allParsed()) {
return $result; return $result;
// But if we have more content on the URL and we don't know what to do with it, return an error // But if we have more content on the URL and we don't know what to do with it, return an error
} else { } else {
return $this->httpError(404, return $this->httpError(404,
"I can't handle sub-URLs of a " . get_class($result) . " object."); "I can't handle sub-URLs of a " . get_class($result) . " object.");
@ -766,7 +849,7 @@ class GridField extends FormField {
* This class is the base class when you want to have an action that alters * 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. * the state of the {@link GridField}, rendered as a button element.
* *
* @package forms * @package forms
* @subpackage fields-gridfield * @subpackage fields-gridfield
*/ */
class GridField_FormAction extends FormAction { class GridField_FormAction extends FormAction {
@ -827,7 +910,7 @@ class GridField_FormAction extends FormAction {
* @param string $val * @param string $val
*/ */
public function _nameEncode($match) { public function _nameEncode($match) {
return '%'.dechex(ord($match[0])); return '%' . dechex(ord($match[0]));
} }
/** /**
@ -842,7 +925,7 @@ class GridField_FormAction extends FormAction {
); );
// Ensure $id doesn't contain only numeric characters // Ensure $id doesn't contain only numeric characters
$id = 'gf_'.substr(md5(serialize($state)), 0, 8); $id = 'gf_' . substr(md5(serialize($state)), 0, 8);
Session::set($id, $state); Session::set($id, $state);
$actionData['StateID'] = $id; $actionData['StateID'] = $id;
@ -851,7 +934,7 @@ class GridField_FormAction extends FormAction {
array( array(
// Note: This field needs to be less than 65 chars, otherwise Suhosin security patch // Note: This field needs to be less than 65 chars, otherwise Suhosin security patch
// will strip it from the requests // will strip it from the requests
'name' => 'action_gridFieldAlterAction'. '?' . http_build_query($actionData), 'name' => 'action_gridFieldAlterAction' . '?' . http_build_query($actionData),
'data-url' => $this->gridField->Link(), 'data-url' => $this->gridField->Link(),
) )
); );
@ -861,6 +944,7 @@ class GridField_FormAction extends FormAction {
* Calculate the name of the gridfield relative to the Form * Calculate the name of the gridfield relative to the Form
* *
* @param GridField $base * @param GridField $base
*
* @return string * @return string
*/ */
protected function getNameFromParent() { protected function getNameFromParent() {
@ -870,7 +954,7 @@ class GridField_FormAction extends FormAction {
do { do {
array_unshift($name, $base->getName()); array_unshift($name, $base->getName());
$base = $base->getForm(); $base = $base->getForm();
} while ($base && !($base instanceof Form)); } while($base && !($base instanceof Form));
return implode('.', $name); return implode('.', $name);
} }

View File

@ -132,7 +132,7 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP
} }
foreach($items->limit(null) as $item) { foreach($items->limit(null) as $item) {
if($item->hasMethod('canView') && $item->canView()) { if(!$item->hasMethod('canView') || $item->canView()) {
$columnData = array(); $columnData = array();
foreach($csvColumns as $columnSource => $columnHeader) { foreach($csvColumns as $columnSource => $columnHeader) {
@ -146,16 +146,21 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP
$value = $columnHeader($relObj); $value = $columnHeader($relObj);
} else { } else {
$value = $gridField->getDataFieldValue($item, $columnSource); $value = $gridField->getDataFieldValue($item, $columnSource);
if(!$value) {
$value = $gridField->getDataFieldValue($item, $columnHeader);
}
} }
$value = str_replace(array("\r", "\n"), "\n", $value); $value = str_replace(array("\r", "\n"), "\n", $value);
$columnData[] = '"' . str_replace('"', '""', $value) . '"'; $columnData[] = '"' . str_replace('"', '""', $value) . '"';
} }
$fileData .= implode($separator, $columnData); $fileData .= implode($separator, $columnData);
$fileData .= "\n"; $fileData .= "\n";
} }
if ($item->hasMethod('destroy')) { if($item->hasMethod('destroy')) {
$item->destroy(); $item->destroy();
} }
} }

View File

@ -204,8 +204,9 @@ class GridFieldPrintButton implements GridField_HTMLProvider, GridField_ActionPr
$itemRows->push(new ArrayData(array( $itemRows->push(new ArrayData(array(
"ItemRow" => $itemRow "ItemRow" => $itemRow
))); )));
if ($item->hasMethod('destroy')) {
$item->destroy(); $item->destroy();
}
} }
$ret = new ArrayData(array( $ret = new ArrayData(array(

View File

@ -515,7 +515,7 @@ class i18nTextCollector_Writer_Php implements i18nTextCollector_Writer {
// Create folder for lang files // Create folder for lang files
$langFolder = $path . '/lang'; $langFolder = $path . '/lang';
if(!file_exists($langFolder)) { if(!file_exists($langFolder)) {
Filesystem::makeFolder($langFolder, Config::inst()->get('Filesystem', 'folder_create_mask')); Filesystem::makeFolder($langFolder);
touch($langFolder . '/_manifest_exclude'); touch($langFolder . '/_manifest_exclude');
} }
@ -595,7 +595,7 @@ class i18nTextCollector_Writer_RailsYaml implements i18nTextCollector_Writer {
// Create folder for lang files // Create folder for lang files
$langFolder = $path . '/lang'; $langFolder = $path . '/lang';
if(!file_exists($langFolder)) { if(!file_exists($langFolder)) {
Filesystem::makeFolder($langFolder, Config::inst()->get('Filesystem', 'folder_create_mask')); Filesystem::makeFolder($langFolder);
touch($langFolder . '/_manifest_exclude'); touch($langFolder . '/_manifest_exclude');
} }

View File

@ -293,8 +293,15 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
onremove: function() { onremove: function() {
var ed = tinyMCE.get(this.attr('id')); var ed = tinyMCE.get(this.attr('id'));
if (ed) { if (ed) {
ed.remove(); try {
ed.destroy(); ed.remove();
} catch(ex) {}
try {
ed.destroy();
} catch(ex) {}
// Remove any residual tinyMCE editor element
this.next('.mceEditor').remove();
// TinyMCE leaves behind events. We should really fix TinyMCE, but lets brute force it for now // TinyMCE leaves behind events. We should really fix TinyMCE, but lets brute force it for now
$.each(jQuery.cache, function(){ $.each(jQuery.cache, function(){

View File

@ -5,6 +5,7 @@
this._super(); this._super();
this.accordion({ this.accordion({
heightStyle: "content",
collapsible: true, collapsible: true,
active: (this.hasClass("ss-toggle-start-closed")) ? false : 0 active: (this.hasClass("ss-toggle-start-closed")) ? false : 0
}); });

47
javascript/lang/id.js Normal file
View File

@ -0,0 +1,47 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/id.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('id', {
"VALIDATOR.FIELDREQUIRED": "Anda wajib mengisi \"%s\".",
"HASMANYFILEFIELD.UPLOADING": "Mengunggah... %s",
"TABLEFIELD.DELETECONFIRMMESSAGE": "Anda ingin menghapus data ini?",
"LOADING": "memuat...",
"UNIQUEFIELD.SUGGESTED": "Nilai diubah ke '%s' : %s",
"UNIQUEFIELD.ENTERNEWVALUE": "Anda perlu mengisi nilai baru pada isian ini",
"UNIQUEFIELD.CANNOTLEAVEEMPTY": "Isian ini tidak boleh kosong",
"RESTRICTEDTEXTFIELD.CHARCANTBEUSED": "Karakter '%s' tidak diijinkan pada isian ini",
"UPDATEURL.CONFIRM": "Anda ingin mengubah URL ke:\n\n%s/\n\nKlik OK untuk lanjut, atau klik Batal untuk tetap pada:\n\n%s",
"UPDATEURL.CONFIRMURLCHANGED": "URL sudah diubah ke\n'%s'",
"FILEIFRAMEFIELD.DELETEFILE": "Hapus Berkas",
"FILEIFRAMEFIELD.UNATTACHFILE": "Lepaskan Berkas",
"FILEIFRAMEFIELD.DELETEIMAGE": "Hapus Gambar",
"FILEIFRAMEFIELD.CONFIRMDELETE": "Anda ingin menghapus berkas ini?",
"LeftAndMain.IncompatBrowserWarning": "Browser Anda tidak mendukung antarmuka CMS. Mohon gunakan Internet Explorer 7+, Google Chrome 10+ atau Mozilla Firefox 3.5+.",
"GRIDFIELD.ERRORINTRANSACTION": "Terjadi kesalahan dalam menarik data dari server\n Mohon coba lagi nanti.",
"HtmlEditorField.SelectAnchor": "Pilih jangkar",
"UploadField.ConfirmDelete": "Anda ingin menghapus berkas ini dari sistem berkas server?",
"UploadField.PHP_MAXFILESIZE": "Ukuran berkas melebihi upload_max_filesize (direktif php.ini)",
"UploadField.HTML_MAXFILESIZE": "Ukuran berkas melebihi MAX_FILE_SIZE (direktif HTML form)",
"UploadField.ONLYPARTIALUPLOADED": "Berkas hanya sebagian terunggah",
"UploadField.NOFILEUPLOADED": "Tidak ada berkas terunggah",
"UploadField.NOTMPFOLDER": "Folder sementara tidak ditemukan",
"UploadField.WRITEFAILED": "Gagal menyimpan berkas",
"UploadField.STOPEDBYEXTENSION": "Ekstensi berkas yang diunggah tidak diijinkan",
"UploadField.TOOLARGE": "Ukuran berkas terlalu besar",
"UploadField.TOOSMALL": "Ukuran berkas terlalu kecil",
"UploadField.INVALIDEXTENSION": "Ekstensi berkas tidak diijinkan",
"UploadField.MAXNUMBEROFFILESSIMPLE": "Jumlah maksimal berkas sudah terlampaui",
"UploadField.UPLOADEDBYTES": "Berkas yang diunggah melebihi batas ukuran berkas",
"UploadField.EMPTYRESULT": "Pengunggahan menghasilkan berkas kosong",
"UploadField.LOADING": "Memuat...",
"UploadField.Editing": "Mengedit...",
"UploadField.Uploaded": "Terunggah",
"UploadField.OVERWRITEWARNING": "Berkas dengan nama sama sudah ada",
"TreeDropdownField.ENTERTOSEARCH": "Tekan Enter untuk pencarian",
"TreeDropdownField.OpenLink": "Buka",
"TreeDropdownField.FieldTitle": "Pilih",
"TreeDropdownField.SearchFieldTitle": "Pilih atau Cari"
});
}

47
javascript/lang/id_ID.js Normal file
View File

@ -0,0 +1,47 @@
// This file was generated by GenerateJavaScriptI18nTask from javascript/lang/src/id_ID.js.
// See https://github.com/silverstripe/silverstripe-buildtools for details
if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
if(typeof(console) != 'undefined') console.error('Class ss.i18n not defined');
} else {
ss.i18n.addDictionary('id_ID', {
"VALIDATOR.FIELDREQUIRED": "Anda wajib mengisi \"%s\".",
"HASMANYFILEFIELD.UPLOADING": "Mengunggah... %s",
"TABLEFIELD.DELETECONFIRMMESSAGE": "Anda ingin menghapus data ini?",
"LOADING": "memuat...",
"UNIQUEFIELD.SUGGESTED": "Nilai diubah ke '%s' : %s",
"UNIQUEFIELD.ENTERNEWVALUE": "Anda perlu mengisi nilai baru pada isian ini",
"UNIQUEFIELD.CANNOTLEAVEEMPTY": "Isian ini tidak boleh kosong",
"RESTRICTEDTEXTFIELD.CHARCANTBEUSED": "Karakter '%s' tidak diijinkan pada isian ini",
"UPDATEURL.CONFIRM": "Anda ingin mengubah URL ke:\n\n%s/\n\nKlik OK untuk lanjut, atau klik Batal untuk tetap pada:\n\n%s",
"UPDATEURL.CONFIRMURLCHANGED": "URL sudah diubah ke\n'%s'",
"FILEIFRAMEFIELD.DELETEFILE": "Hapus Berkas",
"FILEIFRAMEFIELD.UNATTACHFILE": "Lepaskan Berkas",
"FILEIFRAMEFIELD.DELETEIMAGE": "Hapus Gambar",
"FILEIFRAMEFIELD.CONFIRMDELETE": "Anda ingin menghapus berkas ini?",
"LeftAndMain.IncompatBrowserWarning": "Browser Anda tidak mendukung antarmuka CMS. Mohon gunakan Internet Explorer 7+, Google Chrome 10+ atau Mozilla Firefox 3.5+.",
"GRIDFIELD.ERRORINTRANSACTION": "Terjadi kesalahan dalam menarik data dari server\n Mohon coba lagi nanti.",
"HtmlEditorField.SelectAnchor": "Pilih jangkar",
"UploadField.ConfirmDelete": "Anda ingin menghapus berkas ini dari sistem berkas server?",
"UploadField.PHP_MAXFILESIZE": "Ukuran berkas melebihi upload_max_filesize (direktif php.ini)",
"UploadField.HTML_MAXFILESIZE": "Ukuran berkas melebihi MAX_FILE_SIZE (direktif HTML form)",
"UploadField.ONLYPARTIALUPLOADED": "Berkas hanya sebagian terunggah",
"UploadField.NOFILEUPLOADED": "Tidak ada berkas terunggah",
"UploadField.NOTMPFOLDER": "Folder sementara tidak ditemukan",
"UploadField.WRITEFAILED": "Gagal menyimpan berkas",
"UploadField.STOPEDBYEXTENSION": "Ekstensi berkas yang diunggah tidak diijinkan",
"UploadField.TOOLARGE": "Ukuran berkas terlalu besar",
"UploadField.TOOSMALL": "Ukuran berkas terlalu kecil",
"UploadField.INVALIDEXTENSION": "Ekstensi berkas tidak diijinkan",
"UploadField.MAXNUMBEROFFILESSIMPLE": "Jumlah maksimal berkas sudah terlampaui",
"UploadField.UPLOADEDBYTES": "Berkas yang diunggah melebihi batas ukuran berkas",
"UploadField.EMPTYRESULT": "Pengunggahan menghasilkan berkas kosong",
"UploadField.LOADING": "Memuat...",
"UploadField.Editing": "Mengedit...",
"UploadField.Uploaded": "Terunggah",
"UploadField.OVERWRITEWARNING": "Berkas dengan nama sama sudah ada",
"TreeDropdownField.ENTERTOSEARCH": "Tekan Enter untuk pencarian",
"TreeDropdownField.OpenLink": "Buka",
"TreeDropdownField.FieldTitle": "Pilih",
"TreeDropdownField.SearchFieldTitle": "Pilih atau Cari"
});
}

41
javascript/lang/src/id.js Normal file
View File

@ -0,0 +1,41 @@
{
"VALIDATOR.FIELDREQUIRED": "Anda wajib mengisi \"%s\".",
"HASMANYFILEFIELD.UPLOADING": "Mengunggah... %s",
"TABLEFIELD.DELETECONFIRMMESSAGE": "Anda ingin menghapus data ini?",
"LOADING": "memuat...",
"UNIQUEFIELD.SUGGESTED": "Nilai diubah ke '%s' : %s",
"UNIQUEFIELD.ENTERNEWVALUE": "Anda perlu mengisi nilai baru pada isian ini",
"UNIQUEFIELD.CANNOTLEAVEEMPTY": "Isian ini tidak boleh kosong",
"RESTRICTEDTEXTFIELD.CHARCANTBEUSED": "Karakter '%s' tidak diijinkan pada isian ini",
"UPDATEURL.CONFIRM": "Anda ingin mengubah URL ke:\n\n%s/\n\nKlik OK untuk lanjut, atau klik Batal untuk tetap pada:\n\n%s",
"UPDATEURL.CONFIRMURLCHANGED": "URL sudah diubah ke\n'%s'",
"FILEIFRAMEFIELD.DELETEFILE": "Hapus Berkas",
"FILEIFRAMEFIELD.UNATTACHFILE": "Lepaskan Berkas",
"FILEIFRAMEFIELD.DELETEIMAGE": "Hapus Gambar",
"FILEIFRAMEFIELD.CONFIRMDELETE": "Anda ingin menghapus berkas ini?",
"LeftAndMain.IncompatBrowserWarning": "Browser Anda tidak mendukung antarmuka CMS. Mohon gunakan Internet Explorer 7+, Google Chrome 10+ atau Mozilla Firefox 3.5+.",
"GRIDFIELD.ERRORINTRANSACTION": "Terjadi kesalahan dalam menarik data dari server\n Mohon coba lagi nanti.",
"HtmlEditorField.SelectAnchor": "Pilih jangkar",
"UploadField.ConfirmDelete": "Anda ingin menghapus berkas ini dari sistem berkas server?",
"UploadField.PHP_MAXFILESIZE": "Ukuran berkas melebihi upload_max_filesize (direktif php.ini)",
"UploadField.HTML_MAXFILESIZE": "Ukuran berkas melebihi MAX_FILE_SIZE (direktif HTML form)",
"UploadField.ONLYPARTIALUPLOADED": "Berkas hanya sebagian terunggah",
"UploadField.NOFILEUPLOADED": "Tidak ada berkas terunggah",
"UploadField.NOTMPFOLDER": "Folder sementara tidak ditemukan",
"UploadField.WRITEFAILED": "Gagal menyimpan berkas",
"UploadField.STOPEDBYEXTENSION": "Ekstensi berkas yang diunggah tidak diijinkan",
"UploadField.TOOLARGE": "Ukuran berkas terlalu besar",
"UploadField.TOOSMALL": "Ukuran berkas terlalu kecil",
"UploadField.INVALIDEXTENSION": "Ekstensi berkas tidak diijinkan",
"UploadField.MAXNUMBEROFFILESSIMPLE": "Jumlah maksimal berkas sudah terlampaui",
"UploadField.UPLOADEDBYTES": "Berkas yang diunggah melebihi batas ukuran berkas",
"UploadField.EMPTYRESULT": "Pengunggahan menghasilkan berkas kosong",
"UploadField.LOADING": "Memuat...",
"UploadField.Editing": "Mengedit...",
"UploadField.Uploaded": "Terunggah",
"UploadField.OVERWRITEWARNING": "Berkas dengan nama sama sudah ada",
"TreeDropdownField.ENTERTOSEARCH": "Tekan Enter untuk pencarian",
"TreeDropdownField.OpenLink": "Buka",
"TreeDropdownField.FieldTitle": "Pilih",
"TreeDropdownField.SearchFieldTitle": "Pilih atau Cari"
}

View File

@ -0,0 +1,41 @@
{
"VALIDATOR.FIELDREQUIRED": "Anda wajib mengisi \"%s\".",
"HASMANYFILEFIELD.UPLOADING": "Mengunggah... %s",
"TABLEFIELD.DELETECONFIRMMESSAGE": "Anda ingin menghapus data ini?",
"LOADING": "memuat...",
"UNIQUEFIELD.SUGGESTED": "Nilai diubah ke '%s' : %s",
"UNIQUEFIELD.ENTERNEWVALUE": "Anda perlu mengisi nilai baru pada isian ini",
"UNIQUEFIELD.CANNOTLEAVEEMPTY": "Isian ini tidak boleh kosong",
"RESTRICTEDTEXTFIELD.CHARCANTBEUSED": "Karakter '%s' tidak diijinkan pada isian ini",
"UPDATEURL.CONFIRM": "Anda ingin mengubah URL ke:\n\n%s/\n\nKlik OK untuk lanjut, atau klik Batal untuk tetap pada:\n\n%s",
"UPDATEURL.CONFIRMURLCHANGED": "URL sudah diubah ke\n'%s'",
"FILEIFRAMEFIELD.DELETEFILE": "Hapus Berkas",
"FILEIFRAMEFIELD.UNATTACHFILE": "Lepaskan Berkas",
"FILEIFRAMEFIELD.DELETEIMAGE": "Hapus Gambar",
"FILEIFRAMEFIELD.CONFIRMDELETE": "Anda ingin menghapus berkas ini?",
"LeftAndMain.IncompatBrowserWarning": "Browser Anda tidak mendukung antarmuka CMS. Mohon gunakan Internet Explorer 7+, Google Chrome 10+ atau Mozilla Firefox 3.5+.",
"GRIDFIELD.ERRORINTRANSACTION": "Terjadi kesalahan dalam menarik data dari server\n Mohon coba lagi nanti.",
"HtmlEditorField.SelectAnchor": "Pilih jangkar",
"UploadField.ConfirmDelete": "Anda ingin menghapus berkas ini dari sistem berkas server?",
"UploadField.PHP_MAXFILESIZE": "Ukuran berkas melebihi upload_max_filesize (direktif php.ini)",
"UploadField.HTML_MAXFILESIZE": "Ukuran berkas melebihi MAX_FILE_SIZE (direktif HTML form)",
"UploadField.ONLYPARTIALUPLOADED": "Berkas hanya sebagian terunggah",
"UploadField.NOFILEUPLOADED": "Tidak ada berkas terunggah",
"UploadField.NOTMPFOLDER": "Folder sementara tidak ditemukan",
"UploadField.WRITEFAILED": "Gagal menyimpan berkas",
"UploadField.STOPEDBYEXTENSION": "Ekstensi berkas yang diunggah tidak diijinkan",
"UploadField.TOOLARGE": "Ukuran berkas terlalu besar",
"UploadField.TOOSMALL": "Ukuran berkas terlalu kecil",
"UploadField.INVALIDEXTENSION": "Ekstensi berkas tidak diijinkan",
"UploadField.MAXNUMBEROFFILESSIMPLE": "Jumlah maksimal berkas sudah terlampaui",
"UploadField.UPLOADEDBYTES": "Berkas yang diunggah melebihi batas ukuran berkas",
"UploadField.EMPTYRESULT": "Pengunggahan menghasilkan berkas kosong",
"UploadField.LOADING": "Memuat...",
"UploadField.Editing": "Mengedit...",
"UploadField.Uploaded": "Terunggah",
"UploadField.OVERWRITEWARNING": "Berkas dengan nama sama sudah ada",
"TreeDropdownField.ENTERTOSEARCH": "Tekan Enter untuk pencarian",
"TreeDropdownField.OpenLink": "Buka",
"TreeDropdownField.FieldTitle": "Pilih",
"TreeDropdownField.SearchFieldTitle": "Pilih atau Cari"
}

View File

@ -19,7 +19,7 @@
"UploadField.ConfirmDelete": "Är du säker på att du vill radera denna fil från servern?", "UploadField.ConfirmDelete": "Är du säker på att du vill radera denna fil från servern?",
"UploadField.PHP_MAXFILESIZE": "Filen överskrider upload_max_filesize (php-ini-direktiv)", "UploadField.PHP_MAXFILESIZE": "Filen överskrider upload_max_filesize (php-ini-direktiv)",
"UploadField.HTML_MAXFILESIZE": "Filen överskrider MAX_FILE_SIZE (HTML form-direktiv)", "UploadField.HTML_MAXFILESIZE": "Filen överskrider MAX_FILE_SIZE (HTML form-direktiv)",
"UploadField.ONLYPARTIALUPLOADED": "Filen laddas bara upp delvis", "UploadField.ONLYPARTIALUPLOADED": "Filen laddades bara upp delvis",
"UploadField.NOFILEUPLOADED": "Ingen fil laddades upp", "UploadField.NOFILEUPLOADED": "Ingen fil laddades upp",
"UploadField.NOTMPFOLDER": "Tillfällig mapp saknas", "UploadField.NOTMPFOLDER": "Tillfällig mapp saknas",
"UploadField.WRITEFAILED": "Kunde inte skriva filen", "UploadField.WRITEFAILED": "Kunde inte skriva filen",

View File

@ -24,7 +24,7 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') {
"UploadField.ConfirmDelete": "Är du säker på att du vill radera denna fil från servern?", "UploadField.ConfirmDelete": "Är du säker på att du vill radera denna fil från servern?",
"UploadField.PHP_MAXFILESIZE": "Filen överskrider upload_max_filesize (php-ini-direktiv)", "UploadField.PHP_MAXFILESIZE": "Filen överskrider upload_max_filesize (php-ini-direktiv)",
"UploadField.HTML_MAXFILESIZE": "Filen överskrider MAX_FILE_SIZE (HTML form-direktiv)", "UploadField.HTML_MAXFILESIZE": "Filen överskrider MAX_FILE_SIZE (HTML form-direktiv)",
"UploadField.ONLYPARTIALUPLOADED": "Filen laddas bara upp delvis", "UploadField.ONLYPARTIALUPLOADED": "Filen laddades bara upp delvis",
"UploadField.NOFILEUPLOADED": "Ingen fil laddades upp", "UploadField.NOFILEUPLOADED": "Ingen fil laddades upp",
"UploadField.NOTMPFOLDER": "Tillfällig mapp saknas", "UploadField.NOTMPFOLDER": "Tillfällig mapp saknas",
"UploadField.WRITEFAILED": "Kunde inte skriva filen", "UploadField.WRITEFAILED": "Kunde inte skriva filen",

View File

@ -60,6 +60,8 @@ cs:
ERRORNOTREC: 'Toto uživatelské jméno / heslo nebylo rozpoznáno' ERRORNOTREC: 'Toto uživatelské jméno / heslo nebylo rozpoznáno'
Boolean: Boolean:
ANY: Jakkýkoliv ANY: Jakkýkoliv
NOANSWER: 'Ne'
YESANSWER: 'Ano'
CMSLoadingScreen_ss: CMSLoadingScreen_ss:
LOADING: Nahrávání... LOADING: Nahrávání...
REQUIREJS: 'CMS vyžaduje, aby jste měli JavaScript zapnut.' REQUIREJS: 'CMS vyžaduje, aby jste měli JavaScript zapnut.'
@ -78,6 +80,23 @@ cs:
EMAIL: E-mail EMAIL: E-mail
HELLO: Dobrý den HELLO: Dobrý den
PASSWORD: Heslo PASSWORD: Heslo
CheckboxField:
NOANSWER: 'Ne'
YESANSWER: 'Ano'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Prosím vyberte hodnotu v seznamu. {value} není platná volba'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Zapomenuté heslo?'
BUTTONLOGIN: 'Přihlásit se zpět'
BUTTONLOGOUT: 'Odhlásit se'
PASSWORDEXPIRED: '<p>Vaše heslo expirovalo. <a target="_top" href="{link}">Prosím zvolte nové heslo.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Neplatný uživatel. <a target="_top" href="{link}">Prosím oveřte se zde</a> pro pokračování.</p>'
LoginMessage: '<p>Máte-li jakékoli neuložené práce, můžete se vrátit na místo, kde jste přestali, po přihlášení se zpět níže.</p>'
SUCCESS: Úspěch
SUCCESSCONTENT: '<p>Úspěšné přihlášení. Pokud nebudete automaticky přesměrován <a target="_top" href="{link}">klikněte sem</a></p>'
TimedOutTitleAnonymous: 'Čas Vašeho sezení vypršel.'
TimedOutTitleMember: 'Ahoj {name}!<br />Čas Vašeho sezení vypršel.'
ConfirmedPasswordField: ConfirmedPasswordField:
ATLEAST: 'Hesla musí být nejméně {min} znaků dlouhé.' ATLEAST: 'Hesla musí být nejméně {min} znaků dlouhé.'
BETWEEN: 'Hesla musí být {min} až {max} znaků dlouhé.' BETWEEN: 'Hesla musí být {min} až {max} znaků dlouhé.'
@ -124,6 +143,7 @@ cs:
DropdownField: DropdownField:
CHOOSE: (Vyberte) CHOOSE: (Vyberte)
CHOOSESEARCH: '(Vybrat nebo vyhledat)' CHOOSESEARCH: '(Vybrat nebo vyhledat)'
SOURCE_VALIDATION: 'Prosím vyberte hodnotu v seznamu. {value} není platná volba'
EmailField: EmailField:
VALIDATION: 'Prosím zadejte e-mailovou adresu' VALIDATION: 'Prosím zadejte e-mailovou adresu'
Enum: Enum:
@ -171,7 +191,7 @@ cs:
TEXT2: 'odkaz na reset hesla' TEXT2: 'odkaz na reset hesla'
TEXT3: pro TEXT3: pro
Form: Form:
CSRF_FAILED_MESSAGE: "Zdá se, že nastal technický problém. Prosím, klikněte na tlačítko zpět, \n\t\t\t\t\taktualizujte prohlížeč a zkuste to znovu." CSRF_FAILED_MESSAGE: 'Vypadá to, že to musí být technický problém. Kliněte prosím na tlačítko zpět, obnovte váš prohlížeč a zkuste opět.'
FIELDISREQUIRED: '{name} je požadováno' FIELDISREQUIRED: '{name} je požadováno'
SubmitBtnLabel: Jdi SubmitBtnLabel: Jdi
VALIDATIONCREDITNUMBER: 'Prosím ujistěte se, že jste zadal/a {number} číslo kreditní karty správně' VALIDATIONCREDITNUMBER: 'Prosím ujistěte se, že jste zadal/a {number} číslo kreditní karty správně'
@ -238,7 +258,7 @@ cs:
many_many_Members: Členové many_many_Members: Členové
GroupImportForm: GroupImportForm:
Help1: '<p>Import jedné nebo více skupin v <em>CSV</em> formátu (čárkou-oddělené hodnoty). <small><a href="#" class="toggle-advanced">Zobrazit rozšířené použití</a></small></p>' Help1: '<p>Import jedné nebo více skupin v <em>CSV</em> formátu (čárkou-oddělené hodnoty). <small><a href="#" class="toggle-advanced">Zobrazit rozšířené použití</a></small></p>'
Help2: "<div class=\"advanced\">\n<h4>Pokročilé použití</h4>\n<ul>\n<li>Povolené sloupce: <em>%s</em></li>\n<li>Existující skupiny jsou porovnány jejich unikátní vlastností <em>Code</em>, a aktualizovány s novými hodnotami z\nimportovaného souboru.</li>\n<li>Hierarchie skupin může být tvořena použitím sloupce <em>ParentCode</em>.</li>\n<li>Kódy oprávnění mohou být přiřazeny sloupcem <em>PermissionCode</em>. Existující oprávnění nejsou smazána.</li>\n</ul>\n</div>" Help2: "<div class=\"advanced\">\n\t<h4>Pokročilé použití</h4>\n\t<ul>\n\t<li>Povolené sloupce: <em>%s</em></li>\n\t<li>Existující skupiny jsou porovnány jejich unikátním <em>Code</em> hodnotou, a aktualizovány s novými hodnotami z\\nimportovaného souboru</li>\n\t<li>Hierarchie skupin může být tvořena použitím <em>ParentCode</em> sloupce.</li>\n\t<li>Kódy oprávnění mohou být přiřazeny <em>PermissionCode</em> sloupcem. Existující oprávnění nejsou smazána.</li>\n\t</ul>\n</div>"
ResultCreated: 'Vytvořeno {count} skupin' ResultCreated: 'Vytvořeno {count} skupin'
ResultDeleted: 'Smazáno %d skupin' ResultDeleted: 'Smazáno %d skupin'
ResultUpdated: 'Aktualizováno %d skupin' ResultUpdated: 'Aktualizováno %d skupin'
@ -247,6 +267,8 @@ cs:
HtmlEditorField: HtmlEditorField:
ADDURL: 'Přidat URL' ADDURL: 'Přidat URL'
ADJUSTDETAILSDIMENSIONS: 'Detaily &amp; rozměry' ADJUSTDETAILSDIMENSIONS: 'Detaily &amp; rozměry'
ANCHORSCANNOTACCESSPAGE: 'Nemáte povolen přístup k obsahu cílové stránky.'
ANCHORSPAGENOTFOUND: 'Cílová stránka nenelazena.'
ANCHORVALUE: Záložka (kotva) ANCHORVALUE: Záložka (kotva)
BUTTONADDURL: 'Přidat url' BUTTONADDURL: 'Přidat url'
BUTTONINSERT: Vložit BUTTONINSERT: Vložit
@ -290,6 +312,7 @@ cs:
URL: URL URL: URL
URLNOTANOEMBEDRESOURCE: 'URL ''{url}'' nemůže být vloženo do zdroje médií.' URLNOTANOEMBEDRESOURCE: 'URL ''{url}'' nemůže být vloženo do zdroje médií.'
UpdateMEDIA: 'Aktualizovat média' UpdateMEDIA: 'Aktualizovat média'
SUBJECT: 'Předmět emailu'
Image: Image:
PLURALNAME: Soubory PLURALNAME: Soubory
SINGULARNAME: Soubor SINGULARNAME: Soubor
@ -318,6 +341,8 @@ cs:
LeftAndMain_Menu_ss: LeftAndMain_Menu_ss:
Hello: Ahoj Hello: Ahoj
LOGOUT: 'Odhlásit se' LOGOUT: 'Odhlásit se'
ListboxField:
SOURCE_VALIDATION: 'Prosím vyberte hodnotu v seznamu. {value} není platná volba'
LoginAttempt: LoginAttempt:
Email: 'Emailové adresy' Email: 'Emailové adresy'
IP: 'IP adresy' IP: 'IP adresy'
@ -350,6 +375,7 @@ cs:
NEWPASSWORD: 'Nové heslo' NEWPASSWORD: 'Nové heslo'
NoPassword: 'Neni zde heslo pro tohoto člena' NoPassword: 'Neni zde heslo pro tohoto člena'
PASSWORD: Heslo PASSWORD: Heslo
PASSWORDEXPIRED: 'Vaše heslo expirovalo. Prosím, zvolte nové heslo.'
PLURALNAME: Členové PLURALNAME: Členové
REMEMBERME: 'Zapamatovat si mě pro příště?' REMEMBERME: 'Zapamatovat si mě pro příště?'
SINGULARNAME: Člen SINGULARNAME: Člen
@ -456,8 +482,8 @@ cs:
SINGULARNAME: Role SINGULARNAME: Role
Title: Název Title: Název
PermissionRoleCode: PermissionRoleCode:
PLURALNAME: 'Kódy role oprávnění'
PermsError: 'Nelze připojit kód "%s" s privilegovanými právy (vyžaduje ADMIN přístup)' PermsError: 'Nelze připojit kód "%s" s privilegovanými právy (vyžaduje ADMIN přístup)'
PLURALNAME: 'Kódy role oprávnění'
SINGULARNAME: 'Kód role oprávnění' SINGULARNAME: 'Kód role oprávnění'
Permissions: Permissions:
PERMISSIONS_CATEGORY: 'Role a přístupová práva' PERMISSIONS_CATEGORY: 'Role a přístupová práva'
@ -557,3 +583,5 @@ cs:
UPLOADSINTO: 'uloží do /{path}' UPLOADSINTO: 'uloží do /{path}'
Versioned: Versioned:
has_many_Versions: Verze has_many_Versions: Verze
CheckboxSetField:
SOURCE_VALIDATION: 'Prosím vyberte hodnotu v seznamu. ''{value}'' není platná volba'

View File

@ -57,11 +57,11 @@ de:
BasicAuth: BasicAuth:
ENTERINFO: 'Bitte geben Sie einen Nutzernamen und ein Passwort ein' ENTERINFO: 'Bitte geben Sie einen Nutzernamen und ein Passwort ein'
ERRORNOTADMIN: 'Dieser Nutzer ist kein Administrator' ERRORNOTADMIN: 'Dieser Nutzer ist kein Administrator'
ERRORNOTREC: 'Dieser/s Nutzername/Passwort wurde nicht erkannt' ERRORNOTREC: 'Der Nutzername oder das Passwort wurden nicht erkannt'
Boolean: Boolean:
ANY: alle ANY: alle
NOANSWER: "Nein" NOANSWER: 'Nein'
YESANSWER: "Ja" YESANSWER: 'Ja'
CMSLoadingScreen_ss: CMSLoadingScreen_ss:
LOADING: Lade Daten ... LOADING: Lade Daten ...
REQUIREJS: 'Für die Benutzung des CMS wird JavaScript benötigt.' REQUIREJS: 'Für die Benutzung des CMS wird JavaScript benötigt.'
@ -76,21 +76,38 @@ de:
MENUTITLE: 'Mein Profil' MENUTITLE: 'Mein Profil'
ChangePasswordEmail_ss: ChangePasswordEmail_ss:
CHANGEPASSWORDTEXT1: 'Sie haben Ihr Passwort geändert für' CHANGEPASSWORDTEXT1: 'Sie haben Ihr Passwort geändert für'
CHANGEPASSWORDTEXT2: 'Sie können nun folgende Angaben benutzen um sich einzuloggen' CHANGEPASSWORDTEXT2: 'Sie können nun folgende Angaben benutzen um sich einzuloggen:'
EMAIL: E-Mail EMAIL: E-Mail
HELLO: Hallo HELLO: Hallo
PASSWORD: Passwort PASSWORD: Passwort
CheckboxField:
NOANSWER: 'Nein'
YESANSWER: 'Ja'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Bitte wählen Sie aus der Liste. {value} ist kein gültiger Wert'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Passwort vergessen?'
BUTTONLOGIN: 'Wieder einloggen'
BUTTONLOGOUT: 'Abmelden'
PASSWORDEXPIRED: '<p>Ihr Passwort ist abgelaufen. <a target="_top" href="{link}">Bitte wählen Sie ein neues Passwort.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Ungültiger Benutzer. <a target="_top" href="{link}">Bitte melden Sie sich hier an</a> um fortzufahren.</p>'
LoginMessage: '<p>Wenn Sie ungespeicherte Arbeiten haben, können Sie wieder weiterarbeiten indem Sie sich unterhalb einloggen.</p>'
SUCCESS: Erfolg
SUCCESSCONTENT: '<p>Login erfolgreich. Falls Sie nicht automatisch weitergeleitet werden, bitte <a target="_top" href="{link}">hier klicken</a></p>'
TimedOutTitleAnonymous: 'Ihre Sitzung ist abgelaufen.'
TimedOutTitleMember: 'Hallo {name}!<br />Ihre Sitzung ist abgelaufen.'
ConfirmedPasswordField: ConfirmedPasswordField:
ATLEAST: 'Passwörter müssen mindestens {min} Zeichen lang sein.' ATLEAST: 'Passwörter müssen mindestens {min} Zeichen lang sein.'
BETWEEN: 'Passwörter müssen mindestens {min} bis maximal {max} Zeichen lang sein.' BETWEEN: 'Passwörter müssen zwischen {min} und {max} Zeichen lang sein.'
MAXIMUM: 'Passwörter dürfen maxinal {max} Zeichen lang sein.' MAXIMUM: 'Passwörter dürfen maxinal {max} Zeichen lang sein.'
SHOWONCLICKTITLE: 'Passwort ändern' SHOWONCLICKTITLE: 'Passwort ändern'
ContentController: ContentController:
NOTLOGGEDIN: 'Nicht eingeloggt' NOTLOGGEDIN: 'Nicht eingeloggt'
CreditCardField: CreditCardField:
FIRST: Zuerst FIRST: erste
FOURTH: vierte FOURTH: vierte
SECOND: erste SECOND: zweite
THIRD: dritte THIRD: dritte
CurrencyField: CurrencyField:
CURRENCYSYMBOL: CURRENCYSYMBOL:
@ -117,8 +134,8 @@ de:
NOTSET: 'nicht gesetzt' NOTSET: 'nicht gesetzt'
TODAY: heute TODAY: heute
VALIDDATEFORMAT2: 'Bitte geben sie das Datum im korrekten Format ein ({format})' VALIDDATEFORMAT2: 'Bitte geben sie das Datum im korrekten Format ein ({format})'
VALIDDATEMAXDATE: 'Ihr Datum muss nach dem erlaubtem Datum ({date}) liegen oder gleich sein' VALIDDATEMAXDATE: 'Ihr Datum muss vor dem erlaubtem Datum ({date}) liegen oder gleich sein'
VALIDDATEMINDATE: 'Ihr Datum muss vor dem erlaubtem Datum ({date}) liegen oder gleich sein' VALIDDATEMINDATE: 'Ihr Datum muss nach dem erlaubtem Datum ({date}) liegen oder gleich sein'
DatetimeField: DatetimeField:
NOTSET: 'nicht gesetzt' NOTSET: 'nicht gesetzt'
Director: Director:
@ -126,6 +143,7 @@ de:
DropdownField: DropdownField:
CHOOSE: (Auswahl) CHOOSE: (Auswahl)
CHOOSESEARCH: '(Auswählen oder Suchen)' CHOOSESEARCH: '(Auswählen oder Suchen)'
SOURCE_VALIDATION: 'Bitte wählen Sie aus der Liste. {value} ist kein gültiger Wert'
EmailField: EmailField:
VALIDATION: 'Bitte geben Sie eine E-Mail-Adresse ein' VALIDATION: 'Bitte geben Sie eine E-Mail-Adresse ein'
Enum: Enum:
@ -148,7 +166,7 @@ de:
JsType: 'Javascript Datei' JsType: 'Javascript Datei'
Mp3Type: 'MP3 Audiodatei' Mp3Type: 'MP3 Audiodatei'
MpgType: 'MPEG Videodatei' MpgType: 'MPEG Videodatei'
NOFILESIZE: 'Dateigröße ist 0 bytes' NOFILESIZE: 'Dateigröße ist 0 Bytes'
NOVALIDUPLOAD: 'Datei ist kein gültiger Upload' NOVALIDUPLOAD: 'Datei ist kein gültiger Upload'
Name: Name Name: Name
PLURALNAME: Dateien PLURALNAME: Dateien
@ -160,7 +178,7 @@ de:
TiffType: 'TIFF Bild - ideal für hohe Auflösungen' TiffType: 'TIFF Bild - ideal für hohe Auflösungen'
Title: Titel Title: Titel
WavType: 'WAV Audiodatei' WavType: 'WAV Audiodatei'
XlsType: 'Exceltabelle' XlsType: 'Excel Arbeitsmappe'
ZipType: 'ZIP komprimierte Datei' ZipType: 'ZIP komprimierte Datei'
Filesystem: Filesystem:
SYNCRESULTS: 'Synchronisation beendet: {createdcount} Objekte erstellt, {deletedcount} Objekte gelöscht' SYNCRESULTS: 'Synchronisation beendet: {createdcount} Objekte erstellt, {deletedcount} Objekte gelöscht'
@ -173,12 +191,12 @@ de:
TEXT2: 'Link zum Zurücksetzen des Passworts' TEXT2: 'Link zum Zurücksetzen des Passworts'
TEXT3: für TEXT3: für
Form: Form:
CSRF_FAILED_MESSAGE: "Es gab ein technisches Problem. Bitte versuchen Sie es erneut, nachdem sie die vorherige Seite neu geladen haben." CSRF_FAILED_MESSAGE: 'Es gab ein technisches Problem. Bitte versuchen Sie es erneut, nachdem sie die vorherige Seite neu geladen haben.'
FIELDISREQUIRED: '{name} muss ausgefüllt werden' FIELDISREQUIRED: '{name} muss ausgefüllt werden'
SubmitBtnLabel: Los SubmitBtnLabel: Los
VALIDATIONCREDITNUMBER: 'Bitte stellen Sie sicher, dass Sie die Kreditkartennummer ({number}) korrekt eingegeben haben' VALIDATIONCREDITNUMBER: 'Bitte stellen Sie sicher, dass Sie die Kreditkartennummer ({number}) korrekt eingegeben haben'
VALIDATIONNOTUNIQUE: 'Der eingegebene Wert ist nicht einzigartig' VALIDATIONNOTUNIQUE: 'Der eingegebene Wert ist nicht einzigartig'
VALIDATIONPASSWORDSDONTMATCH: 'Passwörter stimmen nicht überein' VALIDATIONPASSWORDSDONTMATCH: 'Die Passwörter stimmen nicht überein'
VALIDATIONPASSWORDSNOTEMPTY: 'Passwortfelder dürfen nicht leer sein' VALIDATIONPASSWORDSNOTEMPTY: 'Passwortfelder dürfen nicht leer sein'
VALIDATIONSTRONGPASSWORD: 'Passwörter müssen mindestens eine Zahl und ein alphanumerisches Zeichen enthalten' VALIDATIONSTRONGPASSWORD: 'Passwörter müssen mindestens eine Zahl und ein alphanumerisches Zeichen enthalten'
VALIDATOR: Prüfer VALIDATOR: Prüfer
@ -194,7 +212,7 @@ de:
GridField: GridField:
Add: '{name} hinzufügen' Add: '{name} hinzufügen'
Filter: Filter Filter: Filter
FilterBy: 'Filter nach' FilterBy: 'Filtern nach'
Find: Suchen Find: Suchen
LEVELUP: 'Eine Ebene hoch' LEVELUP: 'Eine Ebene hoch'
LinkExisting: 'Bestehenden Datensatz verknüpfen' LinkExisting: 'Bestehenden Datensatz verknüpfen'
@ -202,7 +220,7 @@ de:
NoItemsFound: 'Keine Elemente gefunden' NoItemsFound: 'Keine Elemente gefunden'
PRINTEDAT: 'Gedruckt am' PRINTEDAT: 'Gedruckt am'
PRINTEDBY: 'Gedruckt von' PRINTEDBY: 'Gedruckt von'
PlaceHolder: '{type} Suchen' PlaceHolder: '{type} suchen'
PlaceHolderWithLabels: 'Suche {type} über {name}' PlaceHolderWithLabels: 'Suche {type} über {name}'
RelationSearch: 'Relationssuche' RelationSearch: 'Relationssuche'
ResetFilter: Zurücksetzen ResetFilter: Zurücksetzen
@ -228,7 +246,7 @@ de:
DefaultGroupTitleContentAuthors: 'Inhaltsautoren' DefaultGroupTitleContentAuthors: 'Inhaltsautoren'
Description: Beschreibung Description: Beschreibung
GroupReminder: 'Diese Gruppe übernimmt automatisch die Rollen der Elterngruppe' GroupReminder: 'Diese Gruppe übernimmt automatisch die Rollen der Elterngruppe'
HierarchyPermsError: 'Kann Berechtigungen der Eltern-Gruppe "%s" nicht hinzufügen (erfordert ADMIN Rechte)' HierarchyPermsError: 'Kann Berechtigungen der Eltern-Gruppe "%s" nicht hinzufügen (erfordert Administratorrechte)'
Locked: 'Gesperrt?' Locked: 'Gesperrt?'
NoRoles: 'Keine Rollen gefunden' NoRoles: 'Keine Rollen gefunden'
PLURALNAME: Gruppen PLURALNAME: Gruppen
@ -240,15 +258,17 @@ de:
many_many_Members: Mitglieder many_many_Members: Mitglieder
GroupImportForm: GroupImportForm:
Help1: '<p>Eine oder mehrere Gruppen im <em>CSV</em>-Format (kommaseparierte Werte) importieren. <small><a href="#" class="toggle-advanced">Erweiterte Nutzung</a></small></p>' Help1: '<p>Eine oder mehrere Gruppen im <em>CSV</em>-Format (kommaseparierte Werte) importieren. <small><a href="#" class="toggle-advanced">Erweiterte Nutzung</a></small></p>'
Help2: "<div class=\"advanced\">\n<h4>Erweiterte Benutzung</h4>\n<ul>\n<li>Gültige Spalten: <em>%s</em></li>\n<li>Bereits existierende Gruppen werden anhand ihres eindeutigen <em>Code</em> identifiziert und um neue Einträge aus der Importdatei erweitert.</li>\n<li>Berechtigungen können in der Spalte <em>PermissioinCode</em> hinzugefügt werden. Schon zugewiesene Berechtigungen werden nicht entfernt.</li>\n</ul>\n</div>" Help2: "<div class=\"advanced\">\\n<h4>Erweiterte Benutzung</h4>\\n<ul>\\n<li>Gültige Spalten: <em>%s</em></li>\\n<li>Bereits existierende Gruppen werden anhand ihres eindeutigen <em>Code</em> identifiziert und um neue Einträge aus der Importdatei erweitert.</li>\\n\n<li>Hierarchien von Gruppen können über die Spalte <em>ParentCode</em> definiert werden.</li>\n<li>Berechtigungen können in der Spalte <em>PermissionCode</em> hinzugefügt werden. Schon zugewiesene Berechtigungen werden nicht entfernt.</li>\\n</ul>\\n</div>"
ResultCreated: '{count} Gruppe(n) wurden erstellt' ResultCreated: '{count} Gruppe(n) wurden erstellt'
ResultDeleted: '%d Gruppen gelöscht' ResultDeleted: '%d Gruppe(n) gelöscht'
ResultUpdated: '%d Gruppen aktualisiert' ResultUpdated: '%d Gruppe(n) aktualisiert'
Hierarchy: Hierarchy:
InfiniteLoopNotAllowed: 'Es wurde eine Endlosschleife innerhalb der "{type}"-Hierarchie gefunden. Bitte ändern Sie die übergeordnete Seite, um den Fehler zu beheben' InfiniteLoopNotAllowed: 'Es wurde eine Endlosschleife innerhalb der "{type}"-Hierarchie gefunden. Bitte ändern Sie die übergeordnete Seite, um den Fehler zu beheben'
HtmlEditorField: HtmlEditorField:
ADDURL: 'URL hinzufügen' ADDURL: 'URL hinzufügen'
ADJUSTDETAILSDIMENSIONS: 'Details &amp; Dimensionen' ADJUSTDETAILSDIMENSIONS: 'Details &amp; Dimensionen'
ANCHORSCANNOTACCESSPAGE: 'Sie haben keine Berechtigungen, den Inhalt dieser Seite zu sehen.'
ANCHORSPAGENOTFOUND: 'Zielseite nicht gefunden'
ANCHORVALUE: Anker ANCHORVALUE: Anker
BUTTONADDURL: 'URL hinzufügen' BUTTONADDURL: 'URL hinzufügen'
BUTTONINSERT: Einfügen BUTTONINSERT: Einfügen
@ -256,7 +276,7 @@ de:
BUTTONREMOVELINK: 'Verweise entfernen' BUTTONREMOVELINK: 'Verweise entfernen'
BUTTONUpdate: Aktualisieren BUTTONUpdate: Aktualisieren
CAPTIONTEXT: 'Beschriftungstext' CAPTIONTEXT: 'Beschriftungstext'
CSSCLASS: 'Ausrichtung/Stil' CSSCLASS: 'Ausrichtung / Stil'
CSSCLASSCENTER: 'Zentriert, selbstständig' CSSCLASSCENTER: 'Zentriert, selbstständig'
CSSCLASSLEFT: 'Links, mit umfließendem Text' CSSCLASSLEFT: 'Links, mit umfließendem Text'
CSSCLASSLEFTALONE: 'Links, alleinstehend' CSSCLASSLEFTALONE: 'Links, alleinstehend'
@ -279,7 +299,7 @@ de:
IMAGETITLETEXTDESC: 'Weiterführende Informationen über das Bild' IMAGETITLETEXTDESC: 'Weiterführende Informationen über das Bild'
IMAGEWIDTHPX: Breite (px) IMAGEWIDTHPX: Breite (px)
INSERTMEDIA: 'Medienobjekt einfügen' INSERTMEDIA: 'Medienobjekt einfügen'
LINK: 'Verweis' LINK: 'Verweis einfügen'
LINKANCHOR: 'Anker auf dieser Seite' LINKANCHOR: 'Anker auf dieser Seite'
LINKDESCR: 'Beschreibung des Verweises' LINKDESCR: 'Beschreibung des Verweises'
LINKEMAIL: 'E-Mail-Adresse' LINKEMAIL: 'E-Mail-Adresse'
@ -292,6 +312,7 @@ de:
URL: URL URL: URL
URLNOTANOEMBEDRESOURCE: 'Die URL ''{url}'' konnte nicht in eine Medienquelle umgewandelt werden' URLNOTANOEMBEDRESOURCE: 'Die URL ''{url}'' konnte nicht in eine Medienquelle umgewandelt werden'
UpdateMEDIA: 'Medienobjekt aktualisieren' UpdateMEDIA: 'Medienobjekt aktualisieren'
SUBJECT: 'E-Mail-Betreff'
Image: Image:
PLURALNAME: Dateien PLURALNAME: Dateien
SINGULARNAME: Datei SINGULARNAME: Datei
@ -320,6 +341,8 @@ de:
LeftAndMain_Menu_ss: LeftAndMain_Menu_ss:
Hello: Hallo Hello: Hallo
LOGOUT: 'Abmelden' LOGOUT: 'Abmelden'
ListboxField:
SOURCE_VALIDATION: 'Bitte wählen Sie aus der Liste. {value} ist kein gültiger Wert'
LoginAttempt: LoginAttempt:
Email: 'E-Mail-Adresse' Email: 'E-Mail-Adresse'
IP: 'IP-Adresse' IP: 'IP-Adresse'
@ -330,12 +353,12 @@ de:
ADDGROUP: 'Gruppe hinzufügen' ADDGROUP: 'Gruppe hinzufügen'
BUTTONCHANGEPASSWORD: 'Passwort ändern' BUTTONCHANGEPASSWORD: 'Passwort ändern'
BUTTONLOGIN: 'Einloggen' BUTTONLOGIN: 'Einloggen'
BUTTONLOGINOTHER: 'Als jemand anders einloggen' BUTTONLOGINOTHER: 'Als jemand anderes einloggen'
BUTTONLOSTPASSWORD: 'Ich habe mein Passwort vergessen' BUTTONLOSTPASSWORD: 'Ich habe mein Passwort vergessen'
CANTEDIT: 'Sie haben keine Rechte, dies zu tun' CANTEDIT: 'Sie haben keine Rechte, dies zu tun'
CONFIRMNEWPASSWORD: 'Neues Passwort bestätigen' CONFIRMNEWPASSWORD: 'Neues Passwort bestätigen'
CONFIRMPASSWORD: 'Passwort bestätigen' CONFIRMPASSWORD: 'Passwort bestätigen'
DATEFORMAT: 'Bitte geben sie das Datum im korrekten Format ein ({format})' DATEFORMAT: 'Datumsformat'
DefaultAdminFirstname: 'Standardadmin' DefaultAdminFirstname: 'Standardadmin'
DefaultDateTime: Standard DefaultDateTime: Standard
EMAIL: E-Mail EMAIL: E-Mail
@ -352,14 +375,15 @@ de:
NEWPASSWORD: 'Neues Passwort' NEWPASSWORD: 'Neues Passwort'
NoPassword: 'Dieser Benutzer hat kein Passwort.' NoPassword: 'Dieser Benutzer hat kein Passwort.'
PASSWORD: Passwort PASSWORD: Passwort
PASSWORDEXPIRED: 'Ihr Passwort ist abgelaufen. Bitte wählen Sie ein neues Passwort.'
PLURALNAME: Benutzer PLURALNAME: Benutzer
REMEMBERME: 'Für das nächste Mal merken?' REMEMBERME: 'Für das nächste Mal merken?'
SINGULARNAME: Benutzer SINGULARNAME: Benutzer
SUBJECTPASSWORDCHANGED: 'Ihr Passwort wurde geändert' SUBJECTPASSWORDCHANGED: 'Ihr Passwort wurde geändert'
SUBJECTPASSWORDRESET: 'Ihr Link zur Passwortrücksetzung' SUBJECTPASSWORDRESET: 'Ihr Link zur Passwortrücksetzung'
SURNAME: Nachname SURNAME: Nachname
TIMEFORMAT: 'Bitte geben Sie die Uhrzeit im korrekten Format ein ({format})' TIMEFORMAT: 'Uhrzeitformat'
VALIDATIONMEMBEREXISTS: 'Es gibt bereits ein Mitglied mit dieser E-Mail-Adresse' VALIDATIONMEMBEREXISTS: 'Es gibt bereits ein Mitglied mit dem/der selben %s'
ValidationIdentifierFailed: 'Das vorhandene Mitglied #{id} mit identischer Bezeichnung kann nicht überschrieben werden ({name} = {value}))' ValidationIdentifierFailed: 'Das vorhandene Mitglied #{id} mit identischer Bezeichnung kann nicht überschrieben werden ({name} = {value}))'
WELCOMEBACK: 'Hallo {firstname}. Schön, dass du wieder da bist' WELCOMEBACK: 'Hallo {firstname}. Schön, dass du wieder da bist'
YOUROLDPASSWORD: 'Ihr altes Passwort' YOUROLDPASSWORD: 'Ihr altes Passwort'
@ -373,30 +397,30 @@ de:
MemberAuthenticator: MemberAuthenticator:
TITLE: 'E-Mail &amp; Passwort' TITLE: 'E-Mail &amp; Passwort'
MemberDatetimeOptionsetField: MemberDatetimeOptionsetField:
AMORPM: 'AM (vormittag) oder PM (nachmittag)' AMORPM: 'AM (vormittags) oder PM (nachmittags)'
Custom: Benutzerdefiniert Custom: Benutzerdefiniert
DATEFORMATBAD: 'Das Datumsformat ist ungültig' DATEFORMATBAD: 'Das Datumsformat ist ungültig'
DAYNOLEADING: 'Tag ohne führende Null' DAYNOLEADING: 'Tag, ohne führende Null'
DIGITSDECFRACTIONSECOND: 'Eine oder mehrere Ziffern, die einen Dezimalbruch einer Sekunde darstellen' DIGITSDECFRACTIONSECOND: 'Eine oder mehrere Ziffern, die einen Dezimalbruch einer Sekunde darstellen'
FOURDIGITYEAR: 'Vierstellige Jahreszahl' FOURDIGITYEAR: 'Vierstellige Jahreszahl'
FULLNAMEMONTH: 'Volle Monatsbezeichnung (z.B. Juni)' FULLNAMEMONTH: 'Volle Monatsbezeichnung (z.B. Juni)'
HOURNOLEADING: 'Stunde ohne führende Null' HOURNOLEADING: 'Stunde, ohne führende Null'
MINUTENOLEADING: 'Minute' MINUTENOLEADING: 'Minute, ohne führende Null'
MONTHNOLEADING: 'Monat ohne führende Null' MONTHNOLEADING: 'Monat, ohne führende Null'
Preview: Vorschau Preview: Vorschau
SHORTMONTH: 'Kurzname des Monats (z.B. Jun)' SHORTMONTH: 'Kurzname des Monats (z.B. Jun)'
TWODIGITDAY: 'Tag des Monats mit führender Null' TWODIGITDAY: 'Tag des Monats, mit führender Null'
TWODIGITHOUR: 'Stunde mit führenden Nullen' TWODIGITHOUR: 'Stunde, mit führender Null (00 bis 23)'
TWODIGITMINUTE: 'Minute mit führenden Nullen' TWODIGITMINUTE: 'Minute, mit führender Null (00 bis 59)'
TWODIGITMONTH: 'Monat mit führender Null (z.B. 01 = Januar, usw.)' TWODIGITMONTH: 'Monat, mit führender Null (z.B. 01 = Januar, usw.)'
TWODIGITSECOND: 'Sekunde' TWODIGITSECOND: 'Sekunde, mit führender Null (00 bis 59)'
TWODIGITYEAR: 'Zweistellige Jahreszahl' TWODIGITYEAR: 'Zweistellige Jahreszahl'
Toggle: 'Hilfe zur Formatierung anzeigen' Toggle: 'Hilfe zur Formatierung anzeigen'
MemberImportForm: MemberImportForm:
Help1: '<p>Mitglieder im <em>CSV</em>-Format (kommaseparierte Werte) importieren. <small><a href="#" class="toggle-advanced">Erweiterte Nutzung</a></small></p>' Help1: '<p>Mitglieder im <em>CSV</em>-Format (kommaseparierte Werte) importieren. <small><a href="#" class="toggle-advanced">Erweiterte Nutzung</a></small></p>'
Help2: "<div class=\"advanced\">\n<h4>Erweiterte Benutzung</h4>\n<ul>\n<li>Gültige Spalten: <em>%s</em></li>\n<li>Bereits existierende Benutzer werden anhand ihres eindeutigen <em>Code</em> identifiziert und um neue Einträge aus der Importdatei erweitert.</li>\n<li>Gruppen können in der Spalte <em>Gruppen</em> hinzugefügt werden. Gruppen werden anhand ihres <em>Code</em> erkannt. Mehrere Gruppen werden Komma-separiert eingetragen. Schon zugewiesene Gruppen werden nicht entfernt.</li>\n</ul>\n</div>" Help2: "<div class=\"advanced\">\n<h4>Erweiterte Benutzung</h4>\n<ul>\n<li>Gültige Spalten: <em>%s</em></li>\n<li>Bereits existierende Benutzer werden anhand ihres eindeutigen <em>Code</em> identifiziert und um neue Einträge aus der Importdatei erweitert.</li>\n<li>Gruppen können in der Spalte <em>Gruppen</em> hinzugefügt werden. Gruppen werden anhand ihres <em>Code</em> erkannt. Mehrere Gruppen werden Komma-separiert eingetragen. Schon zugewiesene Gruppen werden nicht entfernt.</li>\n</ul>\n</div>"
ResultCreated: '{count} Mitglied(er) wurden erstellt' ResultCreated: '{count} Mitglied(er) wurde(n) erstellt'
ResultDeleted: '%d Mitglieder gelöscht' ResultDeleted: '%d Mitglied(er) gelöscht'
ResultNone: 'Keine Änderungen' ResultNone: 'Keine Änderungen'
ResultUpdated: '{count} Mitglied(er) wurde(n) aktualisiert' ResultUpdated: '{count} Mitglied(er) wurde(n) aktualisiert'
MemberPassword: MemberPassword:
@ -411,7 +435,7 @@ de:
IMPORT: 'CSV Import' IMPORT: 'CSV Import'
IMPORTEDRECORDS: '{count} Einträge wurden importiert' IMPORTEDRECORDS: '{count} Einträge wurden importiert'
NOCSVFILE: 'Wählen Sie eine CSV-Datei zum Importieren' NOCSVFILE: 'Wählen Sie eine CSV-Datei zum Importieren'
NOIMPORT: 'Kein Import notwendig.' NOIMPORT: 'Kein Import notwendig'
RESET: Zurücksetzen RESET: Zurücksetzen
Title: 'Datenmodelle' Title: 'Datenmodelle'
UPDATEDRECORDS: '{count} Einträge wurden aktualisiert' UPDATEDRECORDS: '{count} Einträge wurden aktualisiert'
@ -458,8 +482,8 @@ de:
SINGULARNAME: Rolle SINGULARNAME: Rolle
Title: Titel Title: Titel
PermissionRoleCode: PermissionRoleCode:
PermsError: 'Kann Berechtigungen dem Code "%s" nicht hinzufügen (erfordert Administratorrechte)'
PLURALNAME: 'Berechtigungsrollencodes' PLURALNAME: 'Berechtigungsrollencodes'
PermsError: 'Kann Berechtigungen dem Code "%s" nicht hinzufügen (erfordert ADMIN Rechte)'
SINGULARNAME: 'Berechtigungsrollencode' SINGULARNAME: 'Berechtigungsrollencode'
Permissions: Permissions:
PERMISSIONS_CATEGORY: 'Rollen und Zugriffsberechtigungen' PERMISSIONS_CATEGORY: 'Rollen und Zugriffsberechtigungen'
@ -473,14 +497,14 @@ de:
CHANGEPASSWORDHEADER: 'Passwort ändern' CHANGEPASSWORDHEADER: 'Passwort ändern'
ENTERNEWPASSWORD: 'Bitte geben Sie ein neues Passwort ein' ENTERNEWPASSWORD: 'Bitte geben Sie ein neues Passwort ein'
ERRORPASSWORDPERMISSION: 'Sie müssen eingeloggt sein, um Ihr Passwort ändern zu können!' ERRORPASSWORDPERMISSION: 'Sie müssen eingeloggt sein, um Ihr Passwort ändern zu können!'
LOGGEDOUT: 'Sie wurden ausgeloggt. Wenn Sie sich wieder einloggen möchten, geben Sie bitte unten Ihre Zugangsdaten ein.' LOGGEDOUT: 'Sie wurden ausgeloggt. Wenn Sie sich wieder einloggen möchten, geben Sie bitte unterhalb Ihre Zugangsdaten ein.'
LOGIN: 'Anmelden' LOGIN: 'Anmelden'
LOSTPASSWORDHEADER: 'Passwort vergessen' LOSTPASSWORDHEADER: 'Passwort vergessen'
NOTEPAGESECURED: 'Diese Seite ist geschützt. Bitte melden Sie sich an und Sie werden sofort weitergeleitet.' NOTEPAGESECURED: 'Diese Seite ist geschützt. Bitte melden Sie sich an und Sie werden sofort weitergeleitet.'
NOTERESETLINKINVALID: '<p>Der Link zum Zurücksetzen des Passworts ist entweder nicht korrekt oder abgelaufen</p><p>Sie können <a href="{link1}">einen neuen Link anfordern</a> oder Ihr Passwort nach dem <a href="{link2}">einloggen</a> ändern.</p>' NOTERESETLINKINVALID: '<p>Der Link zum Zurücksetzen des Passworts ist entweder nicht korrekt oder abgelaufen</p><p>Sie können <a href="{link1}">einen neuen Link anfordern</a> oder Ihr Passwort nach dem <a href="{link2}">einloggen</a> ändern.</p>'
NOTERESETPASSWORD: 'Geben Sie Ihre E-Mail-Adresse ein und wir werden Ihnen einen Link zuschicken, mit dem Sie Ihr Passwort zurücksetzen können.' NOTERESETPASSWORD: 'Geben Sie Ihre E-Mail-Adresse ein und wir werden Ihnen einen Link zuschicken, mit dem Sie Ihr Passwort zurücksetzen können.'
PASSWORDSENTHEADER: 'Der Link zum Zurücksetzen des Passworts wurde an {email} gesendet' PASSWORDSENTHEADER: 'Der Link zum Zurücksetzen des Passworts wurde an ''{email}'' gesendet'
PASSWORDSENTTEXT: 'Vielen Dank! Wenn ein Account zu der E-Mail Adresse {email} existiert, wurde eine E-Mail mit dem Link zum Zurücksetzen des Passworts verschickt.' PASSWORDSENTTEXT: 'Vielen Dank! Wenn ein Account zu der E-Mail Adresse ''{email}'' existiert, wurde eine E-Mail mit dem Link zum Zurücksetzen des Passworts verschickt.'
SecurityAdmin: SecurityAdmin:
ACCESS_HELP: 'Benutzer hinzufügen, anzeigen und editieren sowie diesen Berechtigungen und Rollen zuweisen.' ACCESS_HELP: 'Benutzer hinzufügen, anzeigen und editieren sowie diesen Berechtigungen und Rollen zuweisen.'
APPLY_ROLES: 'Rollen zu Gruppen zuweisen' APPLY_ROLES: 'Rollen zu Gruppen zuweisen'
@ -500,7 +524,7 @@ de:
TABROLES: Rollen TABROLES: Rollen
Users: Benutzer Users: Benutzer
SecurityAdmin_MemberImportForm: SecurityAdmin_MemberImportForm:
BtnImport: 'Import' BtnImport: 'Importieren'
FileFieldLabel: 'CSV Datei <small>(Erlaubte Dateierweiterung: *.csv)</small>' FileFieldLabel: 'CSV Datei <small>(Erlaubte Dateierweiterung: *.csv)</small>'
SilverStripeNavigator: SilverStripeNavigator:
Auto: Automatisch Auto: Automatisch
@ -549,13 +573,15 @@ de:
FROMFILES: 'Aus Dateien' FROMFILES: 'Aus Dateien'
HOTLINKINFO: 'Info: Dieses Bild wird verknüpft. Bitte vergewissere dich die Erlaubnis des Inhabers der Ursprungsseite zu haben.' HOTLINKINFO: 'Info: Dieses Bild wird verknüpft. Bitte vergewissere dich die Erlaubnis des Inhabers der Ursprungsseite zu haben.'
MAXNUMBEROFFILES: 'Maximale Anzahl an {count} Datei(en) überschritten' MAXNUMBEROFFILES: 'Maximale Anzahl an {count} Datei(en) überschritten'
MAXNUMBEROFFILESONE: 'SIe können maximal eine Datei hochladen' MAXNUMBEROFFILESONE: 'Sie können maximal eine Datei hochladen'
MAXNUMBEROFFILESSHORT: 'SIe können maximal {count} Datei(en) hochladen' MAXNUMBEROFFILESSHORT: 'Sie können maximal {count} Datei(en) hochladen'
OVERWRITEWARNING: 'Eine Datei mit dem selben Namen existiert bereits' OVERWRITEWARNING: 'Eine Datei mit dem selben Namen existiert bereits'
REMOVE: Entfernen REMOVE: Entfernen
REMOVEINFO: 'Diese Datei entfernen aber nicht am Server löschen' REMOVEINFO: 'Diese Datei entfernen, aber nicht am Server löschen'
STARTALL: 'Alle starten' STARTALL: 'Alle starten'
Saved: Gespeichert Saved: Gespeichert
UPLOADSINTO: 'speichert nach /{path}' UPLOADSINTO: 'speichert nach /{path}'
Versioned: Versioned:
has_many_Versions: Versionen has_many_Versions: Versionen
CheckboxSetField:
SOURCE_VALIDATION: 'Bitte wählen Sie aus der Liste. {value} ist kein gültiger Wert'

View File

@ -60,6 +60,8 @@ eo:
ERRORNOTREC: 'Kiuj salutnomo / pasvorto ne estas rekonebla' ERRORNOTREC: 'Kiuj salutnomo / pasvorto ne estas rekonebla'
Boolean: Boolean:
ANY: Ajna ANY: Ajna
NOANSWER: 'Ne'
YESANSWER: 'Jes'
CMSLoadingScreen_ss: CMSLoadingScreen_ss:
LOADING: Ŝargas... LOADING: Ŝargas...
REQUIREJS: 'La CMS bezonas ke vi enŝaltis Ĝavaskripton.' REQUIREJS: 'La CMS bezonas ke vi enŝaltis Ĝavaskripton.'
@ -78,6 +80,23 @@ eo:
EMAIL: Retpoŝto EMAIL: Retpoŝto
HELLO: Saluton HELLO: Saluton
PASSWORD: Pasvorto PASSWORD: Pasvorto
CheckboxField:
NOANSWER: 'Ne'
YESANSWER: 'Jes'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Bonvolu elekti valoron el la listo donita. {value} ne estas valida agordo'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Ĉu forgesis pasvorton?'
BUTTONLOGIN: 'Ree ensaluti'
BUTTONLOGOUT: 'Adiaŭi'
PASSWORDEXPIRED: '<p>Via pasvorto finiĝis. <a target="_top" href="{link}">Bonvolu elekti novan.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Nevalida uzanto. <a target="_top" href="{link}">Bonvolu aŭtentigi ĉi tie</a> por daŭrigi.</p>'
LoginMessage: '<p>Se vi havas nekonservitan laboraĵon vi povos reveni al kie vi paŭzis reensalutante sube.</p>'
SUCCESS: Sukseso
SUCCESSCONTENT: '<p>Ensaluto suksesis. Se vi ne aŭtomate alidirektiĝos, <a target="_top" href="{link}">alklaku ĉi tie</a></p>'
TimedOutTitleAnonymous: 'Via seanco eltempiĝis.'
TimedOutTitleMember: 'He, {name}!<br />Via seanco eltempiĝis.'
ConfirmedPasswordField: ConfirmedPasswordField:
ATLEAST: 'Pasvorto devas esti almenaŭ {min} signojn longa.' ATLEAST: 'Pasvorto devas esti almenaŭ {min} signojn longa.'
BETWEEN: 'Pasvorto devas esti inter {min} kaj {max} signojn longa.' BETWEEN: 'Pasvorto devas esti inter {min} kaj {max} signojn longa.'
@ -124,6 +143,7 @@ eo:
DropdownField: DropdownField:
CHOOSE: (Elekti) CHOOSE: (Elekti)
CHOOSESEARCH: '(Elekti aŭ serĉi)' CHOOSESEARCH: '(Elekti aŭ serĉi)'
SOURCE_VALIDATION: 'Bonvolu elekti valoron el la listo donita. {value} ne estas valida agordo'
EmailField: EmailField:
VALIDATION: 'Bonvolu enigi readreson' VALIDATION: 'Bonvolu enigi readreson'
Enum: Enum:
@ -171,7 +191,7 @@ eo:
TEXT2: 'pasvorta reagorda ligilo' TEXT2: 'pasvorta reagorda ligilo'
TEXT3: por TEXT3: por
Form: Form:
CSRF_FAILED_MESSAGE: "Ŝajne okazis teknika problemo. Bonvole alklaku la retrobutonon, \n\t\t\t\t\taktualigu vian foliumilon, kaj reprovu." CSRF_FAILED_MESSAGE: 'Ŝajne okazis teknika problemo. Bonvolu alklaki la retrobutonon, refreŝigi vian foliumilon, kaj reprovi.'
FIELDISREQUIRED: '{name} estas bezonata' FIELDISREQUIRED: '{name} estas bezonata'
SubmitBtnLabel: Iri SubmitBtnLabel: Iri
VALIDATIONCREDITNUMBER: 'Bonvole certigu ke vi ĝuste enigis la kreditkarton {number}' VALIDATIONCREDITNUMBER: 'Bonvole certigu ke vi ĝuste enigis la kreditkarton {number}'
@ -238,7 +258,7 @@ eo:
many_many_Members: Membroj many_many_Members: Membroj
GroupImportForm: GroupImportForm:
Help1: '<p>Importi unu aŭ pliaj grupojn en formato <em>CSV</em> (perkome disigitaj valoroj values). <small><a href="#" class="toggle-advanced">Vidigi spertulan uzadon</a></small></p>' Help1: '<p>Importi unu aŭ pliaj grupojn en formato <em>CSV</em> (perkome disigitaj valoroj values). <small><a href="#" class="toggle-advanced">Vidigi spertulan uzadon</a></small></p>'
Help2: "<div class=\"advanced\">\n⇥<h4>Spertula uzado</h4>\n⇥<ul>\n⇥<li>Permesitaj kolumnoj: <em>%s</em></li>\n⇥<li>Ekzistantaj grupoj pariĝas per ilia unika atributo <em>Kodo</em>, kaj aktualiĝas per eventualaj valoroj el \n⇥la importita dosiero</li>\n⇥<li>Grupaj hierarkioj kreiĝas per kolumno <em>PraKodo</em>.</li>\n⇥<li>Permeskodoj estas agordeblaj per la kolumno <em>PermesKodo</em>. Ekzistantaj permeskodoj \n⇥ne nuliĝas.</li>\n⇥</ul>\n</div>" Help2: "<div class=\"advanced\">\n\t<h4>Speciala uzado</h4>\n\t<ul>\n\t<li>Permesitaj kolumnoj: <em>%s</em></li>\n\t<li>Ekzistantaj grupoj kongruiĝas laŭ ilia unika valoro <em>Kodo</em>, kaj aktualiĝas per eventualaj novaj valoroj el la importita dosiero</li>\n\t<li>Grupaj hierarkioj estas kreeblaj uzante kolumnon <em>PraKodo</em>.</li>\n\t<li>Permesaj kodoj estas agordeblaj de la kolumno <em>PermesKodo</em>. Ekzistantaj permesaj kodoj ne vakiĝos.</li>\n\t</ul>\n</div>"
ResultCreated: 'Kreiĝis {count} grupoj' ResultCreated: 'Kreiĝis {count} grupoj'
ResultDeleted: 'Forigis %d grupojn' ResultDeleted: 'Forigis %d grupojn'
ResultUpdated: 'Aktualigis %d grupojn' ResultUpdated: 'Aktualigis %d grupojn'
@ -247,6 +267,8 @@ eo:
HtmlEditorField: HtmlEditorField:
ADDURL: 'Aldoni je URL' ADDURL: 'Aldoni je URL'
ADJUSTDETAILSDIMENSIONS: 'Detaloj kaj dimensioj' ADJUSTDETAILSDIMENSIONS: 'Detaloj kaj dimensioj'
ANCHORSCANNOTACCESSPAGE: 'Vi ne rajtas aliri la enhavon de la cela paĝo.'
ANCHORSPAGENOTFOUND: 'Ne trovis la celan paĝon.'
ANCHORVALUE: Ankri ANCHORVALUE: Ankri
BUTTONADDURL: 'Aldoni je url' BUTTONADDURL: 'Aldoni je url'
BUTTONINSERT: Enmeti BUTTONINSERT: Enmeti
@ -290,6 +312,7 @@ eo:
URL: URL URL: URL
URLNOTANOEMBEDRESOURCE: 'La URL ''{url}'' ne estas konvertebla al memorilo.' URLNOTANOEMBEDRESOURCE: 'La URL ''{url}'' ne estas konvertebla al memorilo.'
UpdateMEDIA: 'Ĝisdatigi memorilon' UpdateMEDIA: 'Ĝisdatigi memorilon'
SUBJECT: 'Temo de retpoŝto'
Image: Image:
PLURALNAME: Dosieroj PLURALNAME: Dosieroj
SINGULARNAME: Dosiero SINGULARNAME: Dosiero
@ -307,7 +330,7 @@ eo:
PERMAGAIN: 'Vin adiaŭis la CMS. Se vi volas denove saluti, enigu salutnomon kaj pasvorton malsupre.' PERMAGAIN: 'Vin adiaŭis la CMS. Se vi volas denove saluti, enigu salutnomon kaj pasvorton malsupre.'
PERMALREADY: 'Bedaŭrinde vi ne povas aliri tiun parton de la CMS. Se vi volas saluti kiel iu alia, tiel faru sube' PERMALREADY: 'Bedaŭrinde vi ne povas aliri tiun parton de la CMS. Se vi volas saluti kiel iu alia, tiel faru sube'
PERMDEFAULT: 'Enigi vian retadreson kaj pasvorton por aliri al la CMS.' PERMDEFAULT: 'Enigi vian retadreson kaj pasvorton por aliri al la CMS.'
PLEASESAVE: 'Bonvolu konservi paĝon: Ne eblis ĝisdatigi ĉi tiun paĝon ĉar ĝi ankoraŭ ne estas konservita.' PLEASESAVE: 'Bonvolu konservi paĝon: ne povis ĝisdatigi ĉi tiun paĝon ĉar ĝi ankoraŭ estas nekonservita.'
PreviewButton: Antaŭvido PreviewButton: Antaŭvido
REORGANISATIONSUCCESSFUL: 'Sukcese reorganizis la retejan arbon.' REORGANISATIONSUCCESSFUL: 'Sukcese reorganizis la retejan arbon.'
SAVEDUP: Konservita. SAVEDUP: Konservita.
@ -318,6 +341,8 @@ eo:
LeftAndMain_Menu_ss: LeftAndMain_Menu_ss:
Hello: Saluton Hello: Saluton
LOGOUT: 'Elsaluti' LOGOUT: 'Elsaluti'
ListboxField:
SOURCE_VALIDATION: 'Bonvolu elekti valoron el la listo donita. {value} ne estas valida agordo'
LoginAttempt: LoginAttempt:
Email: 'Retadreso' Email: 'Retadreso'
IP: 'IP-Adreso' IP: 'IP-Adreso'
@ -350,6 +375,7 @@ eo:
NEWPASSWORD: 'Novan pasvorton' NEWPASSWORD: 'Novan pasvorton'
NoPassword: 'Mankas pasvorto por ĉi tiu membro.' NoPassword: 'Mankas pasvorto por ĉi tiu membro.'
PASSWORD: Pasvorto PASSWORD: Pasvorto
PASSWORDEXPIRED: 'Via pasvorto finiĝis. Bonvolu elekti novan.'
PLURALNAME: Membroj PLURALNAME: Membroj
REMEMBERME: 'Memoru min je la sekva fojo?' REMEMBERME: 'Memoru min je la sekva fojo?'
SINGULARNAME: Membro SINGULARNAME: Membro
@ -456,8 +482,8 @@ eo:
SINGULARNAME: Rolo SINGULARNAME: Rolo
Title: Titolo Title: Titolo
PermissionRoleCode: PermissionRoleCode:
PLURALNAME: 'Permesrolaj kodoj'
PermsError: 'Ne povas agordi kodon "%s" kun privilegiaj permesoj (bezonas ADMIN-aliron)' PermsError: 'Ne povas agordi kodon "%s" kun privilegiaj permesoj (bezonas ADMIN-aliron)'
PLURALNAME: 'Permesrolaj kodoj'
SINGULARNAME: 'Permesrola kodo' SINGULARNAME: 'Permesrola kodo'
Permissions: Permissions:
PERMISSIONS_CATEGORY: 'Roloj kaj aliraj permesoj' PERMISSIONS_CATEGORY: 'Roloj kaj aliraj permesoj'
@ -557,3 +583,5 @@ eo:
UPLOADSINTO: 'konservas en /{path}' UPLOADSINTO: 'konservas en /{path}'
Versioned: Versioned:
has_many_Versions: Versioj has_many_Versions: Versioj
CheckboxSetField:
SOURCE_VALIDATION: 'Bonvolu elekti valoron el la listo donita. {value} ne estas valida agordo'

View File

@ -60,8 +60,8 @@ fi:
ERRORNOTREC: 'Tätä käyttäjänimeä/salasanaa ei tunnistettu.' ERRORNOTREC: 'Tätä käyttäjänimeä/salasanaa ei tunnistettu.'
Boolean: Boolean:
ANY: Yhtään ANY: Yhtään
NOANSWER: "Ei" NOANSWER: 'Ei'
YESANSWER: "Kyllä" YESANSWER: 'Kyllä'
CMSLoadingScreen_ss: CMSLoadingScreen_ss:
LOADING: Ladataan... LOADING: Ladataan...
REQUIREJS: 'CMS-järjestelmä vaatii, että selaimessasi on JavaSkriptit päällä.' REQUIREJS: 'CMS-järjestelmä vaatii, että selaimessasi on JavaSkriptit päällä.'
@ -80,6 +80,23 @@ fi:
EMAIL: Sähköposti EMAIL: Sähköposti
HELLO: Hei HELLO: Hei
PASSWORD: Salasana PASSWORD: Salasana
CheckboxField:
NOANSWER: 'Ei'
YESANSWER: 'Kyllä'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Valitse arvo annetuista vaihtoehdoista. {value} ei kelpaa'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Unohditko salasanasi?'
BUTTONLOGIN: 'Kirjaudu takaisin sisään'
BUTTONLOGOUT: 'Kirjaudu ulos'
PASSWORDEXPIRED: '<p>Salasanasi on vanhentunut. <a target="_top" href="{link}">Valitse uusi.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Virheellinen käyttäjä. <a target="_top" href="{link}">Ole hyvä ja tunnistaudu uudelleen</a> jatkaaksesi.</p>'
LoginMessage: '<p>Mikäli tallennus jäi tekemättä, voit kirjautua uudelleen ja jatkaa muokkausta.</p>'
SUCCESS: Onnistui
SUCCESSCONTENT: '<p>Kirjautuminen onnistui. Mikäli automaattinen edelleenohjaus ei toimi <a target="_top" href="{link}">klikkaa tästä</a></p>'
TimedOutTitleAnonymous: 'Istuntosi on vanhentunut.'
TimedOutTitleMember: 'Hei {name}!<br />Istuntosi on vanhentunut.'
ConfirmedPasswordField: ConfirmedPasswordField:
ATLEAST: 'Salasanan on oltava vähintään {min} merkkiä pitkä.' ATLEAST: 'Salasanan on oltava vähintään {min} merkkiä pitkä.'
BETWEEN: 'Salasanan on oltava väh. {min} ja enintään {max} merkkiä pitkä.' BETWEEN: 'Salasanan on oltava väh. {min} ja enintään {max} merkkiä pitkä.'
@ -126,6 +143,7 @@ fi:
DropdownField: DropdownField:
CHOOSE: (Valitse) CHOOSE: (Valitse)
CHOOSESEARCH: '(Valitse tai Hae)' CHOOSESEARCH: '(Valitse tai Hae)'
SOURCE_VALIDATION: 'Valitse arvo pudotusvalikosta. {value} ei kelpaa'
EmailField: EmailField:
VALIDATION: 'Anna sähköpostiosoite, ole hyvä.' VALIDATION: 'Anna sähköpostiosoite, ole hyvä.'
Enum: Enum:
@ -173,7 +191,7 @@ fi:
TEXT2: 'salasanan vaihtolinkki' TEXT2: 'salasanan vaihtolinkki'
TEXT3: henkilölle TEXT3: henkilölle
Form: Form:
CSRF_FAILED_MESSAGE: "On ilmeisesti tapahtunut tekninen virhe. Klikkaa selaimesi Takaisin-nappia, päivitä sivu painamalla F5-näppäintä ja yritä uudelleen." CSRF_FAILED_MESSAGE: 'On ilmeisesti tapahtunut tekninen virhe. Klikkaa selaimesi Takaisin-nappia, päivitä sivu ja yritä uudelleen.'
FIELDISREQUIRED: '{name} on pakollinen' FIELDISREQUIRED: '{name} on pakollinen'
SubmitBtnLabel: Siirry SubmitBtnLabel: Siirry
VALIDATIONCREDITNUMBER: 'Tarkista, ovatko antamasi luottokortin numerot ({number}) oikein' VALIDATIONCREDITNUMBER: 'Tarkista, ovatko antamasi luottokortin numerot ({number}) oikein'
@ -240,7 +258,8 @@ fi:
many_many_Members: Jäsenet many_many_Members: Jäsenet
GroupImportForm: GroupImportForm:
Help1: '<p>Tuo yksi tai useampi ryhmä <em>CSV</em>-muotoisena (arvot pilkulla erotettuina). <small><a href="#" class="toggle-advanced">Näytä edistyksellinen käyttö</a></small></p>' Help1: '<p>Tuo yksi tai useampi ryhmä <em>CSV</em>-muotoisena (arvot pilkulla erotettuina). <small><a href="#" class="toggle-advanced">Näytä edistyksellinen käyttö</a></small></p>'
Help2: "<div class=\"advanced\">\n<h4>Edistynyt käyttö</h4>\n<ul>\n<li>Sallitut palstat: <em>%s</em></li>\n<li>Olemassa olevat ryhmät kohdistetaan niiden uniikin <em>Code</em>-arvolla, ja päivitetään uudet arvot tuodusta tiedostosta.</li>\n<li>Ryhmien hierarkiat voidaan luoda <em>ParentCode</em>-palstalla.</li>\n<li>Oikeustasot voidaan kohdistaa <em>PermissionCode</em>-palstalla. Olemassa olevia oikeustasoja ei poisteta.</li>\n</ul>\n</div>" Help2: "<div class=\"advanced\">\n\t<h4>Edistynyt käyttö</h4>\n\t<ul>\n\t<li>Sallitut palstat: <em>%s</em></li>\n\t<li>Olemassa olevat ryhmät kohdistetaan niiden uniikin <em>Code</em> arvolla, ja päivitetään uudet arvot tuodusta tiedostosta</li>\n\t<li>Oikeustasot voidaan luoda käyttämällä <em>ParentCode</em> palstaa.</li>\n\t<li>Oikeustasokoodit voidaan kohdistaa <em>PermissionCode</em> palstassa. Olemassaolevia oikeusia ei tyhjennetä.</li>\n\t</ul>\n\
</div>"
ResultCreated: 'Luotiin {count} ryhmä(ä)' ResultCreated: 'Luotiin {count} ryhmä(ä)'
ResultDeleted: 'Poistettu %d ryhmää' ResultDeleted: 'Poistettu %d ryhmää'
ResultUpdated: 'Päivitetty %d ryhmää' ResultUpdated: 'Päivitetty %d ryhmää'
@ -249,6 +268,8 @@ fi:
HtmlEditorField: HtmlEditorField:
ADDURL: 'Lisää URL-osoite' ADDURL: 'Lisää URL-osoite'
ADJUSTDETAILSDIMENSIONS: 'Tarkat tiedot &amp; mitat' ADJUSTDETAILSDIMENSIONS: 'Tarkat tiedot &amp; mitat'
ANCHORSCANNOTACCESSPAGE: 'Sinulla ei ole oikeuksia tarkastella tämän sivun sisältöä.'
ANCHORSPAGENOTFOUND: 'Kohdesivua ei löytynyt.'
ANCHORVALUE: Ankkuri ANCHORVALUE: Ankkuri
BUTTONADDURL: 'Lisää URL-osoite' BUTTONADDURL: 'Lisää URL-osoite'
BUTTONINSERT: Liitä BUTTONINSERT: Liitä
@ -292,6 +313,7 @@ fi:
URL: URL-osoite URL: URL-osoite
URLNOTANOEMBEDRESOURCE: 'URL-osoitetteesta ''{url}'' ei voitu liittää mediaa' URLNOTANOEMBEDRESOURCE: 'URL-osoitetteesta ''{url}'' ei voitu liittää mediaa'
UpdateMEDIA: 'Päivitä media' UpdateMEDIA: 'Päivitä media'
SUBJECT: 'Sähköpostin aihe'
Image: Image:
PLURALNAME: Tiedostot PLURALNAME: Tiedostot
SINGULARNAME: Tiedosto SINGULARNAME: Tiedosto
@ -309,7 +331,7 @@ fi:
PERMAGAIN: 'Olet kirjautunut ulos CMS:stä. Jos haluat kirjautua uudelleen sisään, syötä käyttäjätunnuksesi ja salasanasi alla.' PERMAGAIN: 'Olet kirjautunut ulos CMS:stä. Jos haluat kirjautua uudelleen sisään, syötä käyttäjätunnuksesi ja salasanasi alla.'
PERMALREADY: 'Paihoittelut, mutta et pääse tähän osaan CMS:ää. Jos haluat kirjautua jonain muuna, voit tehdä sen alla.' PERMALREADY: 'Paihoittelut, mutta et pääse tähän osaan CMS:ää. Jos haluat kirjautua jonain muuna, voit tehdä sen alla.'
PERMDEFAULT: 'Valitse tunnistustapa ja syötä tunnistetietosi CMS:ään.' PERMDEFAULT: 'Valitse tunnistustapa ja syötä tunnistetietosi CMS:ään.'
PLEASESAVE: 'Ole hyvä ja tallenna sivu: tätä sivua ei voitu päivittää, koska sitä ei ole vielä tallennettu.' PLEASESAVE: 'Tallenna sivu: tätä sivua ei voida päivittää, koska sitä ei ole vielä tallennettu.'
PreviewButton: Esikatselu PreviewButton: Esikatselu
REORGANISATIONSUCCESSFUL: 'Hakemistopuu järjestettiin uudelleen onnistuneesti.' REORGANISATIONSUCCESSFUL: 'Hakemistopuu järjestettiin uudelleen onnistuneesti.'
SAVEDUP: Tallennettu. SAVEDUP: Tallennettu.
@ -320,6 +342,8 @@ fi:
LeftAndMain_Menu_ss: LeftAndMain_Menu_ss:
Hello: Hei Hello: Hei
LOGOUT: 'Kirjaudu ulos' LOGOUT: 'Kirjaudu ulos'
ListboxField:
SOURCE_VALIDATION: 'Valitse arvo annetuista vaihtoehdoista. {value} ei kelpaa.'
LoginAttempt: LoginAttempt:
Email: 'Sähköpostiosoite' Email: 'Sähköpostiosoite'
IP: 'IP-osoite' IP: 'IP-osoite'
@ -352,6 +376,7 @@ fi:
NEWPASSWORD: 'Uusi salasana' NEWPASSWORD: 'Uusi salasana'
NoPassword: 'Tällä käyttäjällä ei ole salasanaa' NoPassword: 'Tällä käyttäjällä ei ole salasanaa'
PASSWORD: Salasana PASSWORD: Salasana
PASSWORDEXPIRED: 'Salasanasi on vanhentunut. Ole hyvä ja valitse uusi.'
PLURALNAME: Jäsenet PLURALNAME: Jäsenet
REMEMBERME: 'Muista seuraavalla kerralla?' REMEMBERME: 'Muista seuraavalla kerralla?'
SINGULARNAME: Jäsen SINGULARNAME: Jäsen
@ -458,8 +483,8 @@ fi:
SINGULARNAME: Rooli SINGULARNAME: Rooli
Title: Roolin nimi Title: Roolin nimi
PermissionRoleCode: PermissionRoleCode:
PLURALNAME: 'Käyttöoikeiden roolin koodit'
PermsError: 'Ei voida asettaa koodia "%s" annetuilla oikeuksilla (vaaditaan JÄRJESTELMÄNVALVOJAN oikeudet)' PermsError: 'Ei voida asettaa koodia "%s" annetuilla oikeuksilla (vaaditaan JÄRJESTELMÄNVALVOJAN oikeudet)'
PLURALNAME: 'Käyttöoikeuden roolin koodit'
SINGULARNAME: 'Käyttöoikeiden roolin koodi' SINGULARNAME: 'Käyttöoikeiden roolin koodi'
Permissions: Permissions:
PERMISSIONS_CATEGORY: 'Roolit ja käyttöoikeudet' PERMISSIONS_CATEGORY: 'Roolit ja käyttöoikeudet'
@ -559,3 +584,5 @@ fi:
UPLOADSINTO: 'tallentuu polkuun: /{path}' UPLOADSINTO: 'tallentuu polkuun: /{path}'
Versioned: Versioned:
has_many_Versions: Versiot has_many_Versions: Versiot
CheckboxSetField:
SOURCE_VALIDATION: 'Valitse arvo annetuista vaihtoehdoista. ''{value}'' ei kelpaa'

View File

@ -1,211 +1,450 @@
id: id:
AssetAdmin: AssetAdmin:
NEWFOLDER: Berkas baru NEWFOLDER: FolderBaru
SHOWALLOWEDEXTS: 'Tampilkan ekstensi yang dibolehkan'
AssetTableField: AssetTableField:
CREATED: 'Pertama diunggah'
DIM: Dimensi
FILENAME: Nama berkas FILENAME: Nama berkas
FOLDER: Map FOLDER: Folder
LASTEDIT: 'Terakhir diubah'
OWNER: Pemilik
SIZE: 'Ukuran berkas'
TITLE: Judul TITLE: Judul
TYPE: 'Jenis berkas'
URL: URL
AssetUploadField: AssetUploadField:
EDITALL: 'Ubah seluruhnya' ChooseFiles: 'Pilih berkas'
EDITANDORGANIZE: 'Ubah dan atur' DRAGFILESHERE: 'Tarik berkas ke sini'
EDITINFO: 'Ubah berkas' DROPAREA: 'Area Taruh'
EDITALL: 'Edit semua'
EDITANDORGANIZE: 'Edit & kelola'
EDITINFO: 'Edit berkas'
FILES: Berkas FILES: Berkas
FROMCOMPUTER: 'Pilih berkas dari komputer Anda'
FROMCOMPUTERINFO: 'Unggah dari komputer Anda'
TOTAL: Total TOTAL: Total
TOUPLOAD: 'Pilih berkas untuk diunggah...'
UPLOADINPROGRESS: 'Mohon tunggu... sedang mengunggah'
UPLOADOR: ATAU
BBCodeParser: BBCodeParser:
ALIGNEMENT: Penjajaran barisan ALIGNEMENT: Perataan
ALIGNEMENTEXAMPLE: 'jajar kanan' ALIGNEMENTEXAMPLE: 'rata kanan'
BOLD: 'Teks Tebal' BOLD: 'Teks Tebal'
BOLDEXAMPLE: Tebal BOLDEXAMPLE: Tebal
CODE: 'Blok Kode' CODE: 'Blok Kode'
CODEDESCRIPTION: 'Kode blok yang tidak diformat' CODEDESCRIPTION: 'Blok kode tanpa format'
CODEEXAMPLE: 'Blok kode' CODEEXAMPLE: 'Blok kode'
COLORED: 'Teks berwarna' COLORED: 'Teks berwarna'
COLOREDEXAMPLE: 'teks biru' COLOREDEXAMPLE: 'teks biru'
EMAILLINK: 'Link email' EMAILLINK: 'Tautan email'
EMAILLINKDESCRIPTION: 'Membuat link ke alamat email' EMAILLINKDESCRIPTION: 'Buat tautan ke alamat email'
IMAGE: Gambar IMAGE: Gambar
IMAGEDESCRIPTION: 'Tampilkan gambar di pos anda' IMAGEDESCRIPTION: 'Tampilkan gambar pada entri'
ITALIC: 'Teks Miring' ITALIC: 'Teks Miring'
ITALICEXAMPLE: Miring ITALICEXAMPLE: Miring
LINK: 'Link situs web' LINK: 'Tautan situs'
LINKDESCRIPTION: 'Link ke situs web atau URL lain' LINKDESCRIPTION: 'Tautan ke situs atau URL lain'
STRUCK: 'Text distrip' STRUCK: 'Teks Coret'
STRUCKEXAMPLE: Distrip STRUCKEXAMPLE: Coret
UNDERLINE: 'Teks Dengan Garis Bawah' UNDERLINE: 'Teks Garis Bawah'
UNDERLINEEXAMPLE: Digaris bawahi UNDERLINEEXAMPLE: Garis Bawah
UNORDERED: 'Daftar tak berurut' UNORDERED: 'daftar acak'
UNORDEREDDESCRIPTION: 'Daftar tak berurut' UNORDEREDDESCRIPTION: 'Daftar acak'
UNORDEREDEXAMPLE1: 'barang 1' UNORDEREDEXAMPLE1: 'Item acak 1'
BackLink_Button_ss:
Back: Kembali
BasicAuth: BasicAuth:
ENTERINFO: 'Harap masukkan username dan password.' ENTERINFO: 'Mohon isikan nama pengguna dan kata kunci.'
ERRORNOTADMIN: 'User tersebut bukan administrator.' ERRORNOTADMIN: 'Pengguna tersebut bukan pengelola.'
ERRORNOTREC: 'Username/password tidak dikenali' ERRORNOTREC: 'Nama pengguna dan kata kunci tidak dikenal'
Boolean:
ANY: Semua
NOANSWER: 'Tidak'
YESANSWER: 'Ya'
CMSLoadingScreen_ss:
LOADING: Memuat...
REQUIREJS: 'CMS memerlukan pengaktifan JavaScript.'
CMSMain: CMSMain:
ACCESS: 'Akses ke bagian ''{title}'''
ACCESSALLINTERFACES: 'Akses ke semua bagian CMS'
ACCESSALLINTERFACESHELP: 'Kesampingkan pengaturan akses yang spesifik.'
SAVE: Simpan SAVE: Simpan
CMSPageHistoryController_versions_ss:
PREVIEW: 'Pratinjau situs'
CMSProfileController:
MENUTITLE: 'Profil Saya'
ChangePasswordEmail_ss: ChangePasswordEmail_ss:
CHANGEPASSWORDTEXT1: 'Anda merubah password anda untuk' CHANGEPASSWORDTEXT1: 'Anda mengganti kata kunci menjadi'
CHANGEPASSWORDTEXT2: 'Anda dapat menggunakan surat kepercayaan berikut untuk masuk:' CHANGEPASSWORDTEXT2: 'Anda sekarang dapat menggunakannya untuk masuk:'
EMAIL: Email
HELLO: Hai HELLO: Hai
PASSWORD: Kata kunci
CheckboxField:
NOANSWER: 'Tidak'
YESANSWER: 'Ya'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Mohon pilih nilai dari daftar yang ada. ''{value}'' bukan pilihan valid'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Lupa kata kunci?'
BUTTONLOGIN: 'Masuk kembali'
BUTTONLOGOUT: 'Keluar'
PASSWORDEXPIRED: '<p>Kata kunci Anda telah kadaluarsa. <a target="_top" href="{link}">Mohon buat yang baru.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Pengguna tidak dikenal. <a target="_top" href="{link}">Mohon otentikasi ulang di sini</a> untuk melanjutkan.</p>'
LoginMessage: '<p>Jika ada pekerjaan yang belum tersimpan, Anda dapat kembali dengan masuk di sini.</p>'
SUCCESS: Berhasil
SUCCESSCONTENT: '<p>Berhasil masuk. Jika tidak secara otomatis diarahkan, klik <a target="_top" href="{link}">di sini</a></p>'
TimedOutTitleAnonymous: 'Sesi Anda sudah habis.'
TimedOutTitleMember: 'Hai {name}!<br />Sesi Anda sudah habis.'
ConfirmedPasswordField: ConfirmedPasswordField:
SHOWONCLICKTITLE: 'Ganti Kata Sandi' ATLEAST: 'Kata kunci harus setidaknya terdiri dari {min} karakter.'
BETWEEN: 'Kata kunci harus terdiri dari minimal {min} sampai {max} karakter.'
MAXIMUM: 'Kata kunci tidak boleh lebih dari {max} karakter.'
SHOWONCLICKTITLE: 'Ganti Kata Kunci'
ContentController:
NOTLOGGEDIN: 'Belum masuk'
CreditCardField: CreditCardField:
FIRST: pertama FIRST: pertama
FOURTH: keempat FOURTH: keempat
SECOND: kedua SECOND: kedua
THIRD: ketiga THIRD: ketiga
CurrencyField:
CURRENCYSYMBOL: $
DataObject: DataObject:
PLURALNAME: 'Objek-objek Data' PLURALNAME: 'Obyek Data'
SINGULARNAME: 'Objek Data' SINGULARNAME: 'Obyek Data'
Date: Date:
DAY: hari DAY: hari
DAYS: hari DAYS: hari
HOUR: jam HOUR: jam
HOURS: jam HOURS: jam
LessThanMinuteAgo: 'kurang dari semenit' LessThanMinuteAgo: 'kurang dari semenit'
MIN: menit MIN: mnt
MINS: menit MINS: mnt
MONTH: bulan MONTH: bulan
MONTHS: bulan MONTHS: bulan
SEC: detik SEC: dtk
SECS: detik SECS: dtk
TIMEDIFFAGO: '{difference} lalu' TIMEDIFFAGO: '{difference} yang lalu'
TIMEDIFFIN: 'pada {difference}'
YEAR: tahun YEAR: tahun
YEARS: tahun YEARS: tahun
DateField: DateField:
NOTSET: 'tidak diset' NOTSET: 'tidak diatur'
TODAY: hari ini TODAY: hari ini
VALIDDATEFORMAT2: 'Mohon masukkan format tanggal yang valid ({format})' VALIDDATEFORMAT2: 'Mohon isikan format tanggal yang valid ({format})'
VALIDDATEMAXDATE: 'Tanggal Anda harus lebih lama atau sama dengan tanggal maksimum ({date})'
VALIDDATEMINDATE: 'Tanggal Anda harus lebih baru atau sama dengan tanggal minimum ({date})'
DatetimeField: DatetimeField:
NOTSET: 'Tidak diset' NOTSET: 'Tidak diatur'
Director:
INVALID_REQUEST: 'Permintaan tidak valid'
DropdownField: DropdownField:
CHOOSE: (Pilih) CHOOSE: (Pilih)
CHOOSESEARCH: '(Pilih or Cari)' CHOOSESEARCH: '(Pilih atau Cari)'
SOURCE_VALIDATION: 'Mohon pilih nilai dari daftar yang ada. ''{value}'' bukan pilihan valid'
EmailField: EmailField:
VALIDATION: 'Mohon masukkan alamat surel' VALIDATION: 'Mohon isikan alamat email'
Enum: Enum:
ANY: Lain ANY: Semua
File: File:
Content: Isi AviType: 'Berkas video AVI'
Content: Konten
CssType: 'Berkas CSS' CssType: 'Berkas CSS'
DmgType: 'Imej cakram Apple'
DocType: 'Dokumen Word' DocType: 'Dokumen Word'
Filename: Nama File Filename: Nama berkas
GifType: 'Gambar GIF - bagus untuk diagram'
GzType: 'Berkas kompresi GZIP'
HtlType: 'Berkas HTML' HtlType: 'Berkas HTML'
HtmlType: 'Berkas HTML' HtmlType: 'Berkas HTML'
IcoType: 'Ikon gambar' INVALIDEXTENSION: 'Ekstensi tidak dibolehkan (valid: {extensions})'
NOFILESIZE: 'Ukuran file adalah nol bytes.' INVALIDEXTENSIONSHORT: 'Ekstensi tidak dibolehkan'
IcoType: 'Gambar ikon'
JpgType: 'Gambar JPEG - bagus untuk foto'
JsType: 'Berkas Javascript'
Mp3Type: 'Berkas audio MP3'
MpgType: 'Berkas video MPEG'
NOFILESIZE: 'Ukuran berkas nol byte.'
NOVALIDUPLOAD: 'Berkas tidak diunggah dengan benar'
Name: Nama Name: Nama
PLURALNAME: File-file PLURALNAME: Berkas
PdfType: 'Berkas PDF Adobe Acrobat'
PngType: 'Gambar PNG - bagus untuk format serba-bisa'
SINGULARNAME: Berkas SINGULARNAME: Berkas
TOOLARGE: 'Ukuran berkas terlalu besar, maksimal {size} dibolehkan'
TOOLARGESHORT: 'Ukuran berkas melebihi {size}'
TiffType: 'Format gambar tertanda'
Title: Judul Title: Judul
WavType: 'Berkas audio WAV'
XlsType: 'Dokumen Excel'
ZipType: 'Berkas kompresi ZIP'
Filesystem:
SYNCRESULTS: 'Penyelarasan selesai: {createdcount} item dibuat, {deletedcount} item dihapus'
Folder: Folder:
PLURALNAME: Map PLURALNAME: Folder
SINGULARNAME: Map SINGULARNAME: Folder
ForgotPasswordEmail_ss: ForgotPasswordEmail_ss:
HELLO: Hai HELLO: Hai
TEXT1: 'Inilah' TEXT1: 'Berikut ini '
TEXT2: 'link untuk mereset kata sandi' TEXT2: 'tautan ganti kata kunci'
TEXT3: untuk TEXT3: untuk
Form: Form:
VALIDATIONNOTUNIQUE: 'Harga yang dimasukkan tidak unik' CSRF_FAILED_MESSAGE: 'Kemungkinan ada masalah teknis. Mohon klik tombol kembali, muat ulang browser, dan coba lagi.'
VALIDATIONPASSWORDSDONTMATCH: 'Password tidak cocok' FIELDISREQUIRED: '{name} wajib diisi'
VALIDATIONPASSWORDSNOTEMPTY: 'Password tidak boleh kosong' SubmitBtnLabel: Lanjut
VALIDATOR: Pengesah VALIDATIONCREDITNUMBER: 'Mohon pastikan Anda sudah mengisi nomer kartu kredit {number} dengan benar'
VALIDATIONNOTUNIQUE: 'Nilai yang diisikan tidak unik'
VALIDATIONPASSWORDSDONTMATCH: 'Kata kunci tidak sesuai'
VALIDATIONPASSWORDSNOTEMPTY: 'Kata kunci tidak boleh kosong'
VALIDATIONSTRONGPASSWORD: 'Kata kunci harus setidaknya terdiri dari satu angka dan satu karakter alfanumerik'
VALIDATOR: Validasi
VALIDCURRENCY: 'Mohon isikan mata uang yang benar'
CSRF_EXPIRED_MESSAGE: 'Sesi Anda sudah habis. Mohon kirim ulang formulir.'
FormField: FormField:
Example: 'misalnya %s'
NONE: tidak ada NONE: tidak ada
GridAction: GridAction:
DELETE_DESCRIPTION: Hapus DELETE_DESCRIPTION: Hapus
Delete: Hapus Delete: Hapus
UnlinkRelation: Unlink
GridField: GridField:
Add: 'Tambah {name}' Add: 'Tambah {name}'
Filter: Saring Filter: Saring
FilterBy: 'Saring dengan' FilterBy: 'Saring menurut '
Find: Temukan Find: Cari
LinkExisting: 'Tautan tersedia' LEVELUP: 'Ke atas'
NewRecord: 'Baru %s' LinkExisting: 'Tautan yang Ada'
NewRecord: '%s baru'
NoItemsFound: 'Tidak ada data'
PRINTEDAT: 'Dicetak pada'
PRINTEDBY: 'Dicetak oleh'
PlaceHolder: 'Cari {type}'
PlaceHolderWithLabels: 'Cari {type} menurut {name}'
RelationSearch: 'Cari yang terkait'
ResetFilter: Reset
GridFieldAction_Delete:
DeletePermissionsFailure: 'Tidak ada ijin menghapus'
EditPermissionsFailure: 'Tidak ada ijin membuka tautan'
GridFieldDetailForm:
CancelBtn: Batal
Create: Buat
Delete: Hapus
DeletePermissionsFailure: 'Tidak ada ijin menghapus'
Deleted: '%s %s dihapus'
Save: Simpan
Saved: 'Simpan {name} {link}'
GridFieldEditButton_ss:
EDIT: Edit
GridFieldItemEditView:
Go_back: 'Kembali'
Group: Group:
Code: 'Kode Grup' AddRole: 'Tambahkan peran untuk kelompok ini'
DefaultGroupTitleAdministrators: Pengurus Code: 'Kode Kelompok'
DefaultGroupTitleContentAuthors: 'Pencipta isi' DefaultGroupTitleAdministrators: Pengelola
DefaultGroupTitleContentAuthors: 'Penulis Konten'
Description: Deskripsi Description: Deskripsi
GroupReminder: 'Jika Anda memilih kelompok induk, kelompok ini akan mengambil perannya'
HierarchyPermsError: 'Tidak dapat menghubungkan kelompok induk "%s" dengan perijinan khusus (memerlukan akses PENGELOLA)'
Locked: 'Terkunci?' Locked: 'Terkunci?'
Parent: 'Grup induk' NoRoles: 'Tidak ada peran'
Sort: 'Urutan Sortir' PLURALNAME: Kelompok
has_many_Permissions: Ijin Parent: 'Kelompok Induk'
many_many_Members: Anggota-anggota RolesAddEditLink: 'Kelola peran'
SINGULARNAME: Kelompok
Sort: 'Urutkan'
has_many_Permissions: Perijinan
many_many_Members: Pengguna
GroupImportForm:
Help1: '<p>Impor satu atau lebih kelompok di format <em>CSV</em> (comma-separated values). <small><a href="#" class="toggle-advanced">Tampilkan penggunaan mahir</a></small></p>'
Help2: "<div class=\"advanced\">\n\t<h4>Penggunaan mahir</h4>\n\t<ul>\n\t<li>Kolom yang dibolehkan: <em>%s</em></li>\n\t<li>Kelompok yang sudah terdata dihubungkan dengan nilai <em>Kode</em> uniknya, dan diperbarui dengan nilai apapun dari berkas yang diimpor</li>\n\t<li>Hirarki kelompok dapat dibuat dengan kolom <em>ParentCode</em>.</li>\n\t<li>Kode perijinan dapat dihubungkan dengan kolom <em>PermissionCode</em>. Perijinan yang sudah ada tidak akan terpengaruh.</li>\n\t</ul>\n</div>"
ResultCreated: '{count} kelompok dibuat'
ResultDeleted: '%d kelompok dihapus'
ResultUpdated: '%d kelompok diperbarui'
Hierarchy:
InfiniteLoopNotAllowed: 'Putaran berulang ditemukan pada hirarki "{type}". Mohon ganti induk untuk mengatasi masalah ini'
HtmlEditorField: HtmlEditorField:
BUTTONINSERTLINK: 'Beri link' ADDURL: 'Tambah URL'
BUTTONREMOVELINK: 'Pindahkan link' ADJUSTDETAILSDIMENSIONS: 'Rincian &amp; dimensi'
CSSCLASS: 'Aligmen/gaya' ANCHORSCANNOTACCESSPAGE: 'Anda tidak dijinkan mengakses konten laman yang diminta.'
CSSCLASSCENTER: 'Di tengah, sendiri' ANCHORSPAGENOTFOUND: 'Laman yang diminta tidak ditemukan.'
CSSCLASSLEFT: 'Pada sebelah kiri, dengan teks disekitarnya' ANCHORVALUE: Jangkar
CSSCLASSLEFTALONE: 'Di sebelah kiri, sendirian.' BUTTONADDURL: 'Tambah URL'
CSSCLASSRIGHT: 'Pada sebelah kanan, dengan teks disekitarnya' BUTTONINSERT: Sisip
BUTTONINSERTLINK: 'Sisip tautan'
BUTTONREMOVELINK: 'Hapus tautan'
BUTTONUpdate: Perbarui
CAPTIONTEXT: 'Keterangan'
CSSCLASS: 'Perataan / gaya teks'
CSSCLASSCENTER: 'Rata tengah, mandiri'
CSSCLASSLEFT: 'Rata kiri, dengan teks menyesuaikan.'
CSSCLASSLEFTALONE: 'Rata kiri, mandiri.'
CSSCLASSRIGHT: 'Rata kanan, degan teks menyesuaikan.'
DETAILS: Rincian
EMAIL: 'Alamat email' EMAIL: 'Alamat email'
FILE: Berkas
FOLDER: Folder
FROMCMS: 'Dari CMS'
FROMCOMPUTER: 'Dari komputer Anda'
FROMWEB: 'Dari situs lain'
FindInFolder: 'Temukan di Folder'
IMAGEALT: 'Teks alternatif (alt)'
IMAGEALTTEXT: 'Teks alternatif (alt) - pengganti jika gambar tidak tampil'
IMAGEALTTEXTDESC: 'Tampil ke pembaca layar, atau jika gambar tidak tampil'
IMAGEDIMENSIONS: Dimensi IMAGEDIMENSIONS: Dimensi
IMAGEHEIGHTPX: Tinggi IMAGEHEIGHTPX: Tinggi
IMAGETITLE: 'Teks judul (tooltip) - untuk informasi tambahan tentang gambar'
IMAGETITLETEXT: 'Teks gambar (tooltip)'
IMAGETITLETEXTDESC: 'Untuk informasi tambahan tentang gambar'
IMAGEWIDTHPX: Lebar IMAGEWIDTHPX: Lebar
LINK: 'Beri/edit link untuk teks yang di-highlight' INSERTMEDIA: 'Sisipkan Media'
LINKANCHOR: 'Anchor halaman ini' LINK: 'Sisipkan Tautan'
LINKDESCR: 'Deskripsi link' LINKANCHOR: 'Jangkar pada laman ini'
LINKDESCR: 'Deskripsi tautan'
LINKEMAIL: 'Alamat email' LINKEMAIL: 'Alamat email'
LINKEXTERNAL: 'Situs web lain' LINKEXTERNAL: 'Situs lain'
LINKFILE: 'Download file' LINKFILE: 'Unduh berkas'
LINKINTERNAL: 'Halaman pada situs' LINKINTERNAL: 'Laman pada situs'
LINKOPENNEWWIN: 'Buka link pada jendela baru?' LINKOPENNEWWIN: 'Buka tautan di jendela baru?'
LINKTO: 'Link ke' LINKTO: 'Tautan ke'
PAGE: Halaman PAGE: Laman
URL: URL
URLNOTANOEMBEDRESOURCE: 'URL ''{url}'' tidak dapat dijadikan sumber daya media.'
UpdateMEDIA: 'Perbarui Media'
SUBJECT: 'Subyek email'
Image:
PLURALNAME: Berkas
SINGULARNAME: Berkas
Image_Cached:
PLURALNAME: Berkas
SINGULARNAME: Berkas
Image_iframe_ss: Image_iframe_ss:
TITLE: 'Iframe Meng-upload Gambar' TITLE: 'Iframe Pengunggahan Gambar'
LeftAndMain:
CANT_REORGANISE: 'Anda tidak diijinkan mengubah laman Tingkat Atas. Pengubahan Anda tidak tersimpan.'
DELETED: Terhapus.
DropdownBatchActionsDefault: Tindakan
HELP: Bantuan
PAGETYPE: 'Jenis laman:'
PERMAGAIN: 'Anda telah keluar dari situs. Jika ingin kembali masuk, isikan nama pengguna dan kata kunci di bawah ini.'
PERMALREADY: 'Maaf, Anda tidak dapat mengakses laman tersebut. Jika Anda ingin menggunakan akun lain, silakan masuk di sini'
PERMDEFAULT: 'Mohon pilih metode otentikasi dan isikan informasi login Anda.'
PLEASESAVE: 'Mohon Simpan Laman: Laman ini tidak dapat diperbarui karena belum disimpan.'
PreviewButton: Pratinjau
REORGANISATIONSUCCESSFUL: 'Pengaturan ulang struktur situs berhasil.'
SAVEDUP: Tersimpan.
ShowAsList: 'tampilkan sebagai daftar'
TooManyPages: 'Terlalu banyak laman'
ValidationError: 'Kesalahan validasi'
VersionUnknown: Tidak diketahui
LeftAndMain_Menu_ss:
Hello: Hai
LOGOUT: 'Keluar'
ListboxField:
SOURCE_VALIDATION: 'Mohon pilih nilai dari daftar yang ada. ''{value}'' bukan pilihan valid'
LoginAttempt: LoginAttempt:
Email: 'Alamat Email' Email: 'Alamat Email'
IP: 'Alamat IP' IP: 'Alamat IP'
PLURALNAME: 'Upaya Masuk'
SINGULARNAME: 'Upaya Masuk'
Status: Status
Member: Member:
BUTTONCHANGEPASSWORD: 'Ubah Password' ADDGROUP: 'Tambah kelompok'
BUTTONCHANGEPASSWORD: 'Ganti Kata Kunci'
BUTTONLOGIN: 'Masuk' BUTTONLOGIN: 'Masuk'
BUTTONLOGINOTHER: 'Masuk sebagai orang lain' BUTTONLOGINOTHER: 'Masuk dengan akun lain'
BUTTONLOSTPASSWORD: 'Saya lupa password saya' BUTTONLOSTPASSWORD: 'Saya lupa kata kuncinya'
CONFIRMNEWPASSWORD: 'Membenarkan Password Baru' CANTEDIT: 'Tidak ada ijin'
CONFIRMPASSWORD: 'Membenarkan Password' CONFIRMNEWPASSWORD: 'Konfirmasi Penggantian Kata Kunci'
EMAIL: E-mail CONFIRMPASSWORD: 'Konfirmasi Kata Kunci'
ENTEREMAIL: 'Mohon masukkan alamat email anda untuk mendapatkan link reset kata sandi anda.' DATEFORMAT: 'Format tanggal'
ERRORNEWPASSWORD: 'Anda memasukkan password baru secara berbeda, coba lagi' DefaultAdminFirstname: 'Pengelola Utama'
ERRORPASSWORDNOTMATCH: 'Password Anda tidak cocok, coba lagi' DefaultDateTime: default
EMAIL: Email
EMPTYNEWPASSWORD: 'Kata kunci baru tidak boleh kosong, mohon coba lagi'
ENTEREMAIL: 'Mohon isikan alamat email untuk menerima tautan penggantian kata kunci.'
ERRORLOCKEDOUT2: 'Akun Anda untuk sementara diblok karena terlalu banyak upaya masuk yang gagal. Mohon coba lagi setelah {count} menit.'
ERRORNEWPASSWORD: 'Anda mengisikan kata kunci baru yang tidak sama, coba lagi'
ERRORPASSWORDNOTMATCH: 'Kata kunci Anda tidak sama, mohon coba lagi'
ERRORWRONGCRED: 'Rincian yang diisikan tidak benar. Mohon coba lagi.'
FIRSTNAME: 'Nama Depan' FIRSTNAME: 'Nama Depan'
INTERFACELANG: 'Bahasa Interface' INTERFACELANG: 'Bahasa Antarmuka'
NEWPASSWORD: 'Password Baru' INVALIDNEWPASSWORD: 'Kata kunci berikut tidak dapat diterima: {password}'
PLURALNAME: Anggota-anggota LOGGEDINAS: 'Anda masuk sebagai {name}.'
REMEMBERME: 'Ingat saya kali berikutnya?' NEWPASSWORD: 'Kata Kunci Baru'
SINGULARNAME: Anggota NoPassword: 'Tidak ada kata kunci untuk pengguna ini.'
SUBJECTPASSWORDCHANGED: 'Password Anda telah diubah' PASSWORD: Kata kunci
SUBJECTPASSWORDRESET: 'Link mengubah total password Anda' PASSWORDEXPIRED: 'Kata kunci Anda telah kadaluarsa. Mohon buat yang baru.'
SURNAME: Nama Panggilan PLURALNAME: Pengguna
VALIDATIONMEMBEREXISTS: 'Sudah ada member dengan email ini' REMEMBERME: 'Ingat akun saya?'
YOUROLDPASSWORD: 'Password lama Anda' SINGULARNAME: Pengguna
belongs_many_many_Groups: Grup-grup SUBJECTPASSWORDCHANGED: 'Kata kunci Anda telah diganti'
db_LastVisited: 'Tanggal Kunjungan Terakhir' SUBJECTPASSWORDRESET: 'Tautan penggantian kata kunci Anda'
db_LockedOutUntil: 'Terkunci sampai' SURNAME: Nama Belakang
TIMEFORMAT: 'Format waktu'
VALIDATIONMEMBEREXISTS: 'Pengguna dengan %s yang sama sudah ada'
ValidationIdentifierFailed: 'Tidak dapat menimpa pengguna #{id} dengan pengenal yang sama ({name} = {value}))'
WELCOMEBACK: 'Selamat Datang kembali, {firstname}'
YOUROLDPASSWORD: 'Kata kunci lama'
belongs_many_many_Groups: Kelompok
db_LastVisited: 'Kunjungan Terakhir'
db_Locale: 'Lokal Antarmuka'
db_LockedOutUntil: 'Kunci sampai'
db_NumVisit: 'Jumlah Kunjungan' db_NumVisit: 'Jumlah Kunjungan'
db_Password: Kata sandi db_Password: Kata kunci
db_PasswordExpiry: 'Tanggal Kata Sandi Berakhir' db_PasswordExpiry: 'Tanggal Kadaluarsa'
MemberAuthenticator:
TITLE: 'E-mail &amp; Kata Kunci'
MemberDatetimeOptionsetField: MemberDatetimeOptionsetField:
TWODIGITDAY: 'Dua digit bulan' AMORPM: 'AM (Ante meridiem) atau PM (Post meridiem)'
TWODIGITHOUR: 'Dua angka jam (00 ke 23)' Custom: Custom
TWODIGITYEAR: 'Dua-angka tahun' DATEFORMATBAD: 'Format tanggal tidak benar'
DAYNOLEADING: 'Angka tanggal tanpa nol di depan'
DIGITSDECFRACTIONSECOND: 'Satu atau lebih angka merepresentasikan pecahan desimal dari detik'
FOURDIGITYEAR: 'Tahun empat angka'
FULLNAMEMONTH: 'Nama lengkap bulan (misalnya Juni)'
HOURNOLEADING: 'Angka jam tanpa nol di depan'
MINUTENOLEADING: 'Angka menit tanpa nol di depan'
MONTHNOLEADING: 'Angka bulan tanpa nol di depan'
Preview: Pratinjau
SHORTMONTH: 'Nama singkat bulan (misalnya Jun)'
TWODIGITDAY: 'Tanggal dua angka'
TWODIGITHOUR: 'Jam dua angka (00 sampai 23)'
TWODIGITMINUTE: 'Menit dua angka (00 sampai 59)'
TWODIGITMONTH: 'Bulan dua angka (01=Januari, dll)'
TWODIGITSECOND: 'Detik dua angka (00 sampai 59)'
TWODIGITYEAR: 'Tahun dua angka'
Toggle: 'Tampilkan bantuan pemformatan'
MemberImportForm: MemberImportForm:
ResultCreated: 'Membuat {count} anggota' Help1: '<p>Impor pengguna dalam <em>format CSV</em> (comma-separated values). <small><a href="#" class="toggle-advanced">Tampilkan penggunaan mahir</a></small></p>'
ResultDeleted: 'Menghapus %d anggota' Help2: "<div class=\"advanced\">\n\t<h4>Penggunaan mahir</h4>\n\t<ul>\n\t<li>Kolom yang dibolehkan: <em>%s</em></li>\n\t<li>Pengguna yang sudah terdata dihubungkan dengan nilai <em>Kode</em> uniknya, \n\tdan diperbarui dengan nilai apapun dari berkas yang diimpor.</li>\n\t<li>Kelompok dapat dihubungkan dengan kolom <em>Kelompok</em>. Kelompok diidentifikasi dengan properti <em>Kode</em>-nya,\n\tkelompok ganda dapat dipisahkan dengan tanda koma. Kelompok yang sudah terdata tidak terpengaruh.</li>\n\t</ul>\n\
ResultNone: 'Tidak ada perubahan' </div>"
ResultCreated: '{count} pengguna dibuat'
ResultDeleted: '%d pengguna dihapus'
ResultNone: 'Tidak ada pengubahan'
ResultUpdated: '{count} pengguna diperbarui'
MemberPassword: MemberPassword:
PLURALNAME: 'Kata kunci anggota' PLURALNAME: 'Kata Kunci'
SINGULARNAME: 'Kata Kunci'
MemberTableField: MemberTableField:
APPLY_FILTER: 'Terapkan saring' APPLY_FILTER: 'Terapkan Saring'
ModelAdmin: ModelAdmin:
DELETE: Hapus DELETE: Hapus
DELETEDRECORDS: 'Menghapus {count} rekod' DELETEDRECORDS: '{count} data dihapus.'
EMPTYBEFOREIMPORT: 'Ganti data'
IMPORT: 'Impor dari CSV'
IMPORTEDRECORDS: '{count} data diimpor.'
NOCSVFILE: 'Mohon pilih berkas CSV untuk diimpor'
NOIMPORT: 'Tidak ada yang terimpor'
RESET: Reset
Title: 'Model Data' Title: 'Model Data'
UPDATEDRECORDS: '{count} data diperbarui.'
ModelAdmin_ImportSpec_ss: ModelAdmin_ImportSpec_ss:
IMPORTSPECFIELDS: 'Kolom database' IMPORTSPECFIELDS: 'Kolom database'
IMPORTSPECRELATIONS: Hubungan IMPORTSPECLINK: 'Tampilkan Spesifikasi untuk %s'
IMPORTSPECRELATIONS: Keterkaitan
IMPORTSPECTITLE: 'Spesifikasi untuk %s'
ModelAdmin_Tools_ss: ModelAdmin_Tools_ss:
FILTER: Saring FILTER: Saring
IMPORT: Impor IMPORT: Impor
@ -215,46 +454,135 @@ id:
MoneyField: MoneyField:
FIELDLABELAMOUNT: Jumlah FIELDLABELAMOUNT: Jumlah
FIELDLABELCURRENCY: Mata Uang FIELDLABELCURRENCY: Mata Uang
NullableField:
IsNullLabel: 'N/A'
NumericField:
VALIDATION: '''{value}'' bukan angka, hanya angka yang dapat diterima isian ini'
Pagination:
Page: Laman
View: Tampilkan
PasswordValidator:
LOWCHARSTRENGTH: 'Mohon tingkatkan kekuatan kata kunci dengan menambah beberapa karakter berikut ini: %s'
PREVPASSWORD: 'Anda sudah pernah menggunakan kata kunci tersebut, mohon pilih kata kunci yang baru'
TOOSHORT: 'Kata kunci terlalu singkat, setidaknya harus terdiri dari %s karakter atau lebih'
Permission: Permission:
AdminGroup: Pengurus AdminGroup: Pengelola
FULLADMINRIGHTS: 'Hak-hak administratif yang penuh' CMS_ACCESS_CATEGORY: 'Akses CMS'
FULLADMINRIGHTS: 'Hak pengelolaan penuh'
FULLADMINRIGHTS_HELP: 'Abaikan dan kesampingkan semua perijinan yang terhubung lainnya.'
PLURALNAME: Perijinan
SINGULARNAME: Perijinan
PermissionCheckboxSetField:
AssignedTo: 'dihubungkan ke "{title}"'
FromGroup: 'diwarisi dari kelompok "{title}"'
FromRole: 'diwarisi dari peran "{title}"'
FromRoleOnGroup: 'diwarisi dari peran "%s" pada kelompok "%s"'
PermissionRole: PermissionRole:
OnlyAdminCanApply: 'Hanya untuk pengelola'
PLURALNAME: Peran
SINGULARNAME: Peran
Title: Judul Title: Judul
PermissionRoleCode:
PermsError: 'Tidak dapat menghubungkan kode "%s" dengan perijinan khusus (memerlukan akses PENGELOLA)'
PLURALNAME: 'Kode Perijinan Peran'
SINGULARNAME: 'Kode Perijinan Peran'
Permissions:
PERMISSIONS_CATEGORY: 'Perijinan peran dan akses'
UserPermissionsIntro: 'Menghubungkan kelompok ke pengguna ini akan menyesuaikan perijinannya. Lihat bagian Kelompok untuk rincian perijinan pada kelompok individu.'
PhoneNumberField: PhoneNumberField:
VALIDATION: 'Harap masukkan nomor telepon yang valid' VALIDATION: 'Mohon isikan nomer telpon yang benar'
Security: Security:
ALREADYLOGGEDIN: 'Anda tidak memiliki akses ke halaman ini. Jika anda memiliki keanggotaan yang dapat mengakses halaman ini, anda dapat masuk di bawah ini.' ALREADYLOGGEDIN: 'Anda tidak punya akses ke laman ini. Jika Anda punya akun lain dengan akses ke laman ini, silakan masuk kembali.'
BUTTONSEND: 'Kirimi saya link untuk mengeset ulang password ' BUTTONSEND: 'Kirimkan tautan penggantian kata kunci'
CHANGEPASSWORDBELOW: 'Anda dapat mengubah password anda di bawah ini.' CHANGEPASSWORDBELOW: 'Anda dapat mengganti kata kunci di bawah ini.'
CHANGEPASSWORDHEADER: 'Ubah password anda' CHANGEPASSWORDHEADER: 'Ganti kata kunci'
ENTERNEWPASSWORD: 'Harap masukkan password anda yang baru' ENTERNEWPASSWORD: 'Mohon isikan kata kunci yang baru.'
ERRORPASSWORDPERMISSION: 'Anda harus masuk terlebih dahulu untuk merubah password Anda!' ERRORPASSWORDPERMISSION: 'Anda harus masuk untuk bisa mengganti kata kunci!'
LOGGEDOUT: 'Anda telah keluar. Jika Anda ingin masuk lagi, masukkan surat kepercayaan Anda di bawah ini.' LOGGEDOUT: 'Anda telah keluar. Jika ingin masuk kembali, isikan informasi akun Anda di sini.'
NOTEPAGESECURED: 'Halaman ini diamankan. Masukkan surat kepercayaan Anda di bawah ini dan kami akan mengirim Anda ke jalur yang benar.' LOGIN: 'Masuk'
NOTERESETPASSWORD: 'Masukkan alamat e-mail anda dan kami akan mengirimi anda link yang dapat anda gunakan untuk mengeset ulang password ' LOSTPASSWORDHEADER: 'Kata Kunci yang Terlupa'
NOTEPAGESECURED: 'Laman ini diamankan. Isikan data berikut untuk dikirimkan hak akses Anda.'
NOTERESETLINKINVALID: '<p>Tautan penggantian kata kunci tidak valid atau sudah kadaluarsa.</p><p>Anda dapat meminta yang baru <a href="{link1}">di sini</a> atau mengganti kata kunci setelah Anda <a href="{link2}">masuk</a>.</p>'
NOTERESETPASSWORD: 'Isikan alamat email Anda untuk mendapatkan tautan penggantian kata kunci'
PASSWORDSENTHEADER: 'Tautan penggantian kata kunci dikirimkan ke ''{email}'''
PASSWORDSENTTEXT: 'Terimakasih! Tautan reset telah dikirim ke ''{email}'', berisi informasi akun untuk alamat email ini.'
SecurityAdmin: SecurityAdmin:
GROUPNAME: 'Nama grup' ACCESS_HELP: 'Bolehkan menampilkan, menambah dan mengedit pengguna, termasuk mengatur perijinan dan perannya.'
MEMBERS: Anggota-anggota APPLY_ROLES: 'Terapkan peran ke kelompok'
PERMISSIONS: Ijin-ijin APPLY_ROLES_HELP: 'Bolehkan mengedit peran yang diberikan ke kelompok. Memerlukan perijinan "Akses ke bagian ''Pengguna''".'
EDITPERMISSIONS: 'Kelola perijinan untuk kelompok'
EDITPERMISSIONS_HELP: 'Bolehkan mengedit Perijinan dan Alamat IP untuk kelompok. Memerlukan perijinan "Akses ke bagian ''Pengamanan''".'
GROUPNAME: 'Nama kelompok'
IMPORTGROUPS: 'Impor kelompok'
IMPORTUSERS: 'Impor pengguna'
MEMBERS: Pengguna
MENUTITLE: Pengamanan
MemberListCaution: 'Perhatian: Menghapus pengguna dari daftar ini akan menghapus mereka dari semua kelompok dan database'
NEWGROUP: 'Kelompok Baru'
PERMISSIONS: Perijinan
ROLES: Peran
ROLESDESCRIPTION: 'Peran adalah set perijinan yang sudah ditentukan, dan dapat dihubungkan ke kelompok.<br />Dapat diwariskan dari kelompok induk jika diperlukan.'
TABROLES: Peran
Users: Pengguna
SecurityAdmin_MemberImportForm:
BtnImport: 'Impor dari CSV'
FileFieldLabel: 'Berkas CSV <small>(Ekstensi yang dibolehkan: *.csv)</small>'
SilverStripeNavigator:
Auto: Otomatis
ChangeViewMode: 'Ganti modus tampil'
Desktop: Desktop
DualWindowView: 'Jendela Ganda'
Edit: Edit
EditView: 'Modus edit'
Mobile: Tampilan selular
PreviewState: 'Status Pratinjau'
PreviewView: 'Modus pratinjau'
Responsive: Tampilan responsif
SplitView: 'Modus terpisah'
Tablet: Tablet
ViewDeviceWidth: 'Pilih lebar pratinjau'
Width: lebar
SiteTree: SiteTree:
TABMAIN: Utama TABMAIN: Utama
TableListField: TableListField:
CSVEXPORT: 'Ekspor ke CSV' CSVEXPORT: 'Ekspor ke CSV'
Print: Cetak
TableListField_PageControls_ss:
OF: dari
TimeField:
VALIDATEFORMAT: 'Isikan format waktu yang benar ({format})'
ToggleField: ToggleField:
LESS: kurang LESS: kurang
MORE: lebih MORE: lebih
UploadField: UploadField:
ATTACHFILE: 'Lampirkan berkas'
ATTACHFILES: 'Lampirkan berkas'
AttachFile: 'Lampirkan berkas'
CHOOSEANOTHERFILE: 'Pilih berkas lain'
CHOOSEANOTHERINFO: 'Ganti berkas ini dengan berkas lain dari penyimpanan'
DELETE: 'Hapus dari berkas'
DELETEINFO: 'Hapus berkas secara permanen dari penyimpanan'
DOEDIT: Simpan DOEDIT: Simpan
EDIT: Ubah DROPFILE: 'taruh berkas'
EDITINFO: 'Ubah berkas ini' DROPFILES: 'taruh berkas'
FIELDNOTSET: 'Informasi berkas tidak ditemukan' Dimensions: Dimensi
FROMCOMPUTER: 'Dari komputermu' EDIT: Edit
EDITINFO: 'Edit berkas ini'
FIELDNOTSET: 'Informasi berkas tidak ada'
FROMCOMPUTER: 'Dari komputer Anda'
FROMCOMPUTERINFO: 'Pilih dari berkas' FROMCOMPUTERINFO: 'Pilih dari berkas'
FROMFILES: 'Dari berkas' FROMFILES: 'Dari berkas'
HOTLINKINFO: 'Informasi: Gambar ini akan ditautlangsungkan. Mohon pastikan Anda mendapat ijin dari pemilik situs untuk melakukannya.'
MAXNUMBEROFFILES: 'Jumlah maksimal {count} berkas terlampaui.'
MAXNUMBEROFFILESONE: 'Hanya dapat mengunggah satu berkas'
MAXNUMBEROFFILESSHORT: 'Dapat mengunggah {count} berkas'
OVERWRITEWARNING: 'Berkas dengan nama yang sama sudah ada' OVERWRITEWARNING: 'Berkas dengan nama yang sama sudah ada'
REMOVE: Memindahkan REMOVE: Buang
STARTALL: 'Jalankan semua' REMOVEINFO: 'Hapus berkas ini dari sini, tapi jangan hapus dari penyimpanan'
Saved: Disimpan STARTALL: 'Mulai semua'
UPLOADSINTO: 'simpan ke /{path}' Saved: Tersimpan
UPLOADSINTO: 'disimpan ke /{path}'
Versioned: Versioned:
has_many_Versions: Versi-versi has_many_Versions: Versi
CheckboxSetField:
SOURCE_VALIDATION: 'Mohon pilih nilai dari daftar yang ada. ''{value}'' bukan pilihan valid'

588
lang/id_ID.yml Normal file
View File

@ -0,0 +1,588 @@
id_ID:
AssetAdmin:
NEWFOLDER: FolderBaru
SHOWALLOWEDEXTS: 'Tampilkan ekstensi yang dibolehkan'
AssetTableField:
CREATED: 'Pertama diunggah'
DIM: Dimensi
FILENAME: Nama berkas
FOLDER: Folder
LASTEDIT: 'Terakhir diubah'
OWNER: Pemilik
SIZE: 'Ukuran berkas'
TITLE: Judul
TYPE: 'Jenis berkas'
URL: URL
AssetUploadField:
ChooseFiles: 'Pilih berkas'
DRAGFILESHERE: 'Tarik berkas ke sini'
DROPAREA: 'Area Taruh'
EDITALL: 'Edit semua'
EDITANDORGANIZE: 'Edit & kelola'
EDITINFO: 'Edit berkas'
FILES: Berkas
FROMCOMPUTER: 'Pilih berkas dari komputer Anda'
FROMCOMPUTERINFO: 'Unggah dari komputer Anda'
TOTAL: Total
TOUPLOAD: 'Pilih berkas untuk diunggah...'
UPLOADINPROGRESS: 'Mohon tunggu... sedang mengunggah'
UPLOADOR: ATAU
BBCodeParser:
ALIGNEMENT: Perataan
ALIGNEMENTEXAMPLE: 'rata kanan'
BOLD: 'Teks Tebal'
BOLDEXAMPLE: Tebal
CODE: 'Blok Kode'
CODEDESCRIPTION: 'Blok kode tanpa format'
CODEEXAMPLE: 'Blok kode'
COLORED: 'Teks berwarna'
COLOREDEXAMPLE: 'teks biru'
EMAILLINK: 'Tautan email'
EMAILLINKDESCRIPTION: 'Buat tautan ke alamat email'
IMAGE: Gambar
IMAGEDESCRIPTION: 'Tampilkan gambar pada entri'
ITALIC: 'Teks Miring'
ITALICEXAMPLE: Miring
LINK: 'Tautan situs'
LINKDESCRIPTION: 'Tautan ke situs atau URL lain'
STRUCK: 'Teks Coret'
STRUCKEXAMPLE: Coret
UNDERLINE: 'Teks Garis Bawah'
UNDERLINEEXAMPLE: Garis Bawah
UNORDERED: 'daftar acak'
UNORDEREDDESCRIPTION: 'Daftar acak'
UNORDEREDEXAMPLE1: 'Item acak 1'
BackLink_Button_ss:
Back: Kembali
BasicAuth:
ENTERINFO: 'Mohon isikan nama pengguna dan kata kunci.'
ERRORNOTADMIN: 'Pengguna tersebut bukan pengelola.'
ERRORNOTREC: 'Nama pengguna dan kata kunci tidak dikenal'
Boolean:
ANY: Semua
NOANSWER: 'Tidak'
YESANSWER: 'Ya'
CMSLoadingScreen_ss:
LOADING: Memuat...
REQUIREJS: 'CMS memerlukan pengaktifan JavaScript.'
CMSMain:
ACCESS: 'Akses ke bagian ''{title}'''
ACCESSALLINTERFACES: 'Akses ke semua bagian CMS'
ACCESSALLINTERFACESHELP: 'Kesampingkan pengaturan akses yang spesifik.'
SAVE: Simpan
CMSPageHistoryController_versions_ss:
PREVIEW: 'Pratinjau situs'
CMSProfileController:
MENUTITLE: 'Profil Saya'
ChangePasswordEmail_ss:
CHANGEPASSWORDTEXT1: 'Anda mengganti kata kunci menjadi'
CHANGEPASSWORDTEXT2: 'Anda sekarang dapat menggunakannya untuk masuk:'
EMAIL: Email
HELLO: Hai
PASSWORD: Kata kunci
CheckboxField:
NOANSWER: 'Tidak'
YESANSWER: 'Ya'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Mohon pilih nilai dari daftar yang ada. ''{value}'' bukan pilihan valid'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Lupa kata kunci?'
BUTTONLOGIN: 'Masuk kembali'
BUTTONLOGOUT: 'Keluar'
PASSWORDEXPIRED: '<p>Kata kunci Anda telah kadaluarsa. <a target="_top" href="{link}">Mohon buat yang baru.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Pengguna tidak dikenal. <a target="_top" href="{link}">Mohon otentikasi ulang di sini</a> untuk melanjutkan.</p>'
LoginMessage: '<p>Jika ada pekerjaan yang belum tersimpan, Anda dapat kembali dengan masuk di sini.</p>'
SUCCESS: Berhasil
SUCCESSCONTENT: '<p>Berhasil masuk. Jika tidak secara otomatis diarahkan, klik <a target="_top" href="{link}">di sini</a></p>'
TimedOutTitleAnonymous: 'Sesi Anda sudah habis.'
TimedOutTitleMember: 'Hai {name}!<br />Sesi Anda sudah habis.'
ConfirmedPasswordField:
ATLEAST: 'Kata kunci harus setidaknya terdiri dari {min} karakter.'
BETWEEN: 'Kata kunci harus terdiri dari minimal {min} sampai {max} karakter.'
MAXIMUM: 'Kata kunci tidak boleh lebih dari {max} karakter.'
SHOWONCLICKTITLE: 'Ganti Kata Kunci'
ContentController:
NOTLOGGEDIN: 'Belum masuk'
CreditCardField:
FIRST: pertama
FOURTH: keempat
SECOND: kedua
THIRD: ketiga
CurrencyField:
CURRENCYSYMBOL: $
DataObject:
PLURALNAME: 'Obyek Data'
SINGULARNAME: 'Obyek Data'
Date:
DAY: hari
DAYS: hari
HOUR: jam
HOURS: jam
LessThanMinuteAgo: 'kurang dari semenit'
MIN: mnt
MINS: mnt
MONTH: bulan
MONTHS: bulan
SEC: dtk
SECS: dtk
TIMEDIFFAGO: '{difference} yang lalu'
TIMEDIFFIN: 'pada {difference}'
YEAR: tahun
YEARS: tahun
DateField:
NOTSET: 'tidak diatur'
TODAY: hari ini
VALIDDATEFORMAT2: 'Mohon isikan format tanggal yang valid ({format})'
VALIDDATEMAXDATE: 'Tanggal Anda harus lebih lama atau sama dengan tanggal maksimum ({date})'
VALIDDATEMINDATE: 'Tanggal Anda harus lebih baru atau sama dengan tanggal minimum ({date})'
DatetimeField:
NOTSET: 'Tidak diatur'
Director:
INVALID_REQUEST: 'Permintaan tidak valid'
DropdownField:
CHOOSE: (Pilih)
CHOOSESEARCH: '(Pilih atau Cari)'
SOURCE_VALIDATION: 'Mohon pilih nilai dari daftar yang ada. ''{value}'' bukan pilihan valid'
EmailField:
VALIDATION: 'Mohon isikan alamat email'
Enum:
ANY: Semua
File:
AviType: 'Berkas video AVI'
Content: Konten
CssType: 'Berkas CSS'
DmgType: 'Imej cakram Apple'
DocType: 'Dokumen Word'
Filename: Nama berkas
GifType: 'Gambar GIF - bagus untuk diagram'
GzType: 'Berkas kompresi GZIP'
HtlType: 'Berkas HTML'
HtmlType: 'Berkas HTML'
INVALIDEXTENSION: 'Ekstensi tidak dibolehkan (valid: {extensions})'
INVALIDEXTENSIONSHORT: 'Ekstensi tidak dibolehkan'
IcoType: 'Gambar ikon'
JpgType: 'Gambar JPEG - bagus untuk foto'
JsType: 'Berkas Javascript'
Mp3Type: 'Berkas audio MP3'
MpgType: 'Berkas video MPEG'
NOFILESIZE: 'Ukuran berkas nol byte.'
NOVALIDUPLOAD: 'Berkas tidak diunggah dengan benar'
Name: Nama
PLURALNAME: Berkas
PdfType: 'Berkas PDF Adobe Acrobat'
PngType: 'Gambar PNG - bagus untuk format serba-bisa'
SINGULARNAME: Berkas
TOOLARGE: 'Ukuran berkas terlalu besar, maksimal {size} dibolehkan'
TOOLARGESHORT: 'Ukuran berkas melebihi {size}'
TiffType: 'Format gambar tertanda'
Title: Judul
WavType: 'Berkas audio WAV'
XlsType: 'Dokumen Excel'
ZipType: 'Berkas kompresi ZIP'
Filesystem:
SYNCRESULTS: 'Penyelarasan selesai: {createdcount} item dibuat, {deletedcount} item dihapus'
Folder:
PLURALNAME: Folder
SINGULARNAME: Folder
ForgotPasswordEmail_ss:
HELLO: Hai
TEXT1: 'Berikut ini '
TEXT2: 'tautan ganti kata kunci'
TEXT3: untuk
Form:
CSRF_FAILED_MESSAGE: 'Kemungkinan ada masalah teknis. Mohon klik tombol kembali, muat ulang browser, dan coba lagi.'
FIELDISREQUIRED: '{name} wajib diisi'
SubmitBtnLabel: Lanjut
VALIDATIONCREDITNUMBER: 'Mohon pastikan Anda sudah mengisi nomer kartu kredit {number} dengan benar'
VALIDATIONNOTUNIQUE: 'Nilai yang diisikan tidak unik'
VALIDATIONPASSWORDSDONTMATCH: 'Kata kunci tidak sesuai'
VALIDATIONPASSWORDSNOTEMPTY: 'Kata kunci tidak boleh kosong'
VALIDATIONSTRONGPASSWORD: 'Kata kunci harus setidaknya terdiri dari satu angka dan satu karakter alfanumerik'
VALIDATOR: Validasi
VALIDCURRENCY: 'Mohon isikan mata uang yang benar'
CSRF_EXPIRED_MESSAGE: 'Sesi Anda sudah habis. Mohon kirim ulang formulir.'
FormField:
Example: 'misalnya %s'
NONE: tidak ada
GridAction:
DELETE_DESCRIPTION: Hapus
Delete: Hapus
UnlinkRelation: Unlink
GridField:
Add: 'Tambah {name}'
Filter: Saring
FilterBy: 'Saring menurut '
Find: Cari
LEVELUP: 'Ke atas'
LinkExisting: 'Tautan yang Ada'
NewRecord: '%s baru'
NoItemsFound: 'Tidak ada data'
PRINTEDAT: 'Dicetak pada'
PRINTEDBY: 'Dicetak oleh'
PlaceHolder: 'Cari {type}'
PlaceHolderWithLabels: 'Cari {type} menurut {name}'
RelationSearch: 'Cari yang terkait'
ResetFilter: Reset
GridFieldAction_Delete:
DeletePermissionsFailure: 'Tidak ada ijin menghapus'
EditPermissionsFailure: 'Tidak ada ijin membuka tautan'
GridFieldDetailForm:
CancelBtn: Batal
Create: Buat
Delete: Hapus
DeletePermissionsFailure: 'Tidak ada ijin menghapus'
Deleted: '%s %s dihapus'
Save: Simpan
Saved: 'Simpan {name} {link}'
GridFieldEditButton_ss:
EDIT: Edit
GridFieldItemEditView:
Go_back: 'Kembali'
Group:
AddRole: 'Tambahkan peran untuk kelompok ini'
Code: 'Kode Kelompok'
DefaultGroupTitleAdministrators: Pengelola
DefaultGroupTitleContentAuthors: 'Penulis Konten'
Description: Deskripsi
GroupReminder: 'Jika Anda memilih kelompok induk, kelompok ini akan mengambil perannya'
HierarchyPermsError: 'Tidak dapat menghubungkan kelompok induk "%s" dengan perijinan khusus (memerlukan akses PENGELOLA)'
Locked: 'Terkunci?'
NoRoles: 'Tidak ada peran'
PLURALNAME: Kelompok
Parent: 'Kelompok Induk'
RolesAddEditLink: 'Kelola peran'
SINGULARNAME: Kelompok
Sort: 'Urutkan'
has_many_Permissions: Perijinan
many_many_Members: Pengguna
GroupImportForm:
Help1: '<p>Impor satu atau lebih kelompok di format <em>CSV</em> (comma-separated values). <small><a href="#" class="toggle-advanced">Tampilkan penggunaan mahir</a></small></p>'
Help2: "<div class=\"advanced\">\n\t<h4>Penggunaan mahir</h4>\n\t<ul>\n\t<li>Kolom yang dibolehkan: <em>%s</em></li>\n\t<li>Kelompok yang sudah terdata dihubungkan dengan nilai <em>Kode</em> uniknya, dan diperbarui dengan nilai apapun dari berkas yang diimpor</li>\n\t<li>Hirarki kelompok dapat dibuat dengan kolom <em>ParentCode</em>.</li>\n\t<li>Kode perijinan dapat dihubungkan dengan kolom <em>PermissionCode</em>. Perijinan yang sudah ada tidak akan terpengaruh.</li>\n\t</ul>\n</div>"
ResultCreated: '{count} kelompok dibuat'
ResultDeleted: '%d kelompok dihapus'
ResultUpdated: '%d kelompok diperbarui'
Hierarchy:
InfiniteLoopNotAllowed: 'Putaran berulang ditemukan pada hirarki "{type}". Mohon ganti induk untuk mengatasi masalah ini'
HtmlEditorField:
ADDURL: 'Tambah URL'
ADJUSTDETAILSDIMENSIONS: 'Rincian &amp; dimensi'
ANCHORSCANNOTACCESSPAGE: 'Anda tidak dijinkan mengakses konten laman yang diminta.'
ANCHORSPAGENOTFOUND: 'Laman yang diminta tidak ditemukan.'
ANCHORVALUE: Jangkar
BUTTONADDURL: 'Tambah URL'
BUTTONINSERT: Sisip
BUTTONINSERTLINK: 'Sisip tautan'
BUTTONREMOVELINK: 'Hapus tautan'
BUTTONUpdate: Perbarui
CAPTIONTEXT: 'Keterangan'
CSSCLASS: 'Perataan / gaya teks'
CSSCLASSCENTER: 'Rata tengah, mandiri'
CSSCLASSLEFT: 'Rata kiri, dengan teks menyesuaikan.'
CSSCLASSLEFTALONE: 'Rata kiri, mandiri.'
CSSCLASSRIGHT: 'Rata kanan, degan teks menyesuaikan.'
DETAILS: Rincian
EMAIL: 'Alamat email'
FILE: Berkas
FOLDER: Folder
FROMCMS: 'Dari CMS'
FROMCOMPUTER: 'Dari komputer Anda'
FROMWEB: 'Dari situs lain'
FindInFolder: 'Temukan di Folder'
IMAGEALT: 'Teks alternatif (alt)'
IMAGEALTTEXT: 'Teks alternatif (alt) - pengganti jika gambar tidak tampil'
IMAGEALTTEXTDESC: 'Tampil ke pembaca layar, atau jika gambar tidak tampil'
IMAGEDIMENSIONS: Dimensi
IMAGEHEIGHTPX: Tinggi
IMAGETITLE: 'Teks judul (tooltip) - untuk informasi tambahan tentang gambar'
IMAGETITLETEXT: 'Teks gambar (tooltip)'
IMAGETITLETEXTDESC: 'Untuk informasi tambahan tentang gambar'
IMAGEWIDTHPX: Lebar
INSERTMEDIA: 'Sisipkan Media'
LINK: 'Sisipkan Tautan'
LINKANCHOR: 'Jangkar pada laman ini'
LINKDESCR: 'Deskripsi tautan'
LINKEMAIL: 'Alamat email'
LINKEXTERNAL: 'Situs lain'
LINKFILE: 'Unduh berkas'
LINKINTERNAL: 'Laman pada situs'
LINKOPENNEWWIN: 'Buka tautan di jendela baru?'
LINKTO: 'Tautan ke'
PAGE: Laman
URL: URL
URLNOTANOEMBEDRESOURCE: 'URL ''{url}'' tidak dapat dijadikan sumber daya media.'
UpdateMEDIA: 'Perbarui Media'
SUBJECT: 'Subyek email'
Image:
PLURALNAME: Berkas
SINGULARNAME: Berkas
Image_Cached:
PLURALNAME: Berkas
SINGULARNAME: Berkas
Image_iframe_ss:
TITLE: 'Iframe Pengunggahan Gambar'
LeftAndMain:
CANT_REORGANISE: 'Anda tidak diijinkan mengubah laman Tingkat Atas. Pengubahan Anda tidak tersimpan.'
DELETED: Terhapus.
DropdownBatchActionsDefault: Tindakan
HELP: Bantuan
PAGETYPE: 'Jenis laman:'
PERMAGAIN: 'Anda telah keluar dari situs. Jika ingin kembali masuk, isikan nama pengguna dan kata kunci di bawah ini.'
PERMALREADY: 'Maaf, Anda tidak dapat mengakses laman tersebut. Jika Anda ingin menggunakan akun lain, silakan masuk di sini'
PERMDEFAULT: 'Mohon pilih metode otentikasi dan isikan informasi login Anda.'
PLEASESAVE: 'Mohon Simpan Laman: Laman ini tidak dapat diperbarui karena belum disimpan.'
PreviewButton: Pratinjau
REORGANISATIONSUCCESSFUL: 'Pengaturan ulang struktur situs berhasil.'
SAVEDUP: Tersimpan.
ShowAsList: 'tampilkan sebagai daftar'
TooManyPages: 'Terlalu banyak laman'
ValidationError: 'Kesalahan validasi'
VersionUnknown: Tidak diketahui
LeftAndMain_Menu_ss:
Hello: Hai
LOGOUT: 'Keluar'
ListboxField:
SOURCE_VALIDATION: 'Mohon pilih nilai dari daftar yang ada. ''{value}'' bukan pilihan valid'
LoginAttempt:
Email: 'Alamat Email'
IP: 'Alamat IP'
PLURALNAME: 'Upaya Masuk'
SINGULARNAME: 'Upaya Masuk'
Status: Status
Member:
ADDGROUP: 'Tambah kelompok'
BUTTONCHANGEPASSWORD: 'Ganti Kata Kunci'
BUTTONLOGIN: 'Masuk'
BUTTONLOGINOTHER: 'Masuk dengan akun lain'
BUTTONLOSTPASSWORD: 'Saya lupa kata kuncinya'
CANTEDIT: 'Tidak ada ijin'
CONFIRMNEWPASSWORD: 'Konfirmasi Penggantian Kata Kunci'
CONFIRMPASSWORD: 'Konfirmasi Kata Kunci'
DATEFORMAT: 'Format tanggal'
DefaultAdminFirstname: 'Pengelola Utama'
DefaultDateTime: default
EMAIL: Email
EMPTYNEWPASSWORD: 'Kata kunci baru tidak boleh kosong, mohon coba lagi'
ENTEREMAIL: 'Mohon isikan alamat email untuk menerima tautan penggantian kata kunci.'
ERRORLOCKEDOUT2: 'Akun Anda untuk sementara diblok karena terlalu banyak upaya masuk yang gagal. Mohon coba lagi setelah {count} menit.'
ERRORNEWPASSWORD: 'Anda mengisikan kata kunci baru yang tidak sama, coba lagi'
ERRORPASSWORDNOTMATCH: 'Kata kunci Anda tidak sama, mohon coba lagi'
ERRORWRONGCRED: 'Rincian yang diisikan tidak benar. Mohon coba lagi.'
FIRSTNAME: 'Nama Depan'
INTERFACELANG: 'Bahasa Antarmuka'
INVALIDNEWPASSWORD: 'Kata kunci berikut tidak dapat diterima: {password}'
LOGGEDINAS: 'Anda masuk sebagai {name}.'
NEWPASSWORD: 'Kata Kunci Baru'
NoPassword: 'Tidak ada kata kunci untuk pengguna ini.'
PASSWORD: Kata kunci
PASSWORDEXPIRED: 'Kata kunci Anda telah kadaluarsa. Mohon buat yang baru.'
PLURALNAME: Pengguna
REMEMBERME: 'Ingat akun saya?'
SINGULARNAME: Pengguna
SUBJECTPASSWORDCHANGED: 'Kata kunci Anda telah diganti'
SUBJECTPASSWORDRESET: 'Tautan penggantian kata kunci Anda'
SURNAME: Nama Belakang
TIMEFORMAT: 'Format waktu'
VALIDATIONMEMBEREXISTS: 'Pengguna dengan %s yang sama sudah ada'
ValidationIdentifierFailed: 'Tidak dapat menimpa pengguna #{id} dengan pengenal yang sama ({name} = {value}))'
WELCOMEBACK: 'Selamat Datang kembali, {firstname}'
YOUROLDPASSWORD: 'Kata kunci lama'
belongs_many_many_Groups: Kelompok
db_LastVisited: 'Kunjungan Terakhir'
db_Locale: 'Lokal Antarmuka'
db_LockedOutUntil: 'Kunci sampai'
db_NumVisit: 'Jumlah Kunjungan'
db_Password: Kata kunci
db_PasswordExpiry: 'Tanggal Kadaluarsa'
MemberAuthenticator:
TITLE: 'E-mail &amp; Kata Kunci'
MemberDatetimeOptionsetField:
AMORPM: 'AM (Ante meridiem) atau PM (Post meridiem)'
Custom: Custom
DATEFORMATBAD: 'Format tanggal tidak benar'
DAYNOLEADING: 'Angka tanggal tanpa nol di depan'
DIGITSDECFRACTIONSECOND: 'Satu atau lebih angka merepresentasikan pecahan desimal dari detik'
FOURDIGITYEAR: 'Tahun empat angka'
FULLNAMEMONTH: 'Nama lengkap bulan (misalnya Juni)'
HOURNOLEADING: 'Angka jam tanpa nol di depan'
MINUTENOLEADING: 'Angka menit tanpa nol di depan'
MONTHNOLEADING: 'Angka bulan tanpa nol di depan'
Preview: Pratinjau
SHORTMONTH: 'Nama singkat bulan (misalnya Jun)'
TWODIGITDAY: 'Tanggal dua angka'
TWODIGITHOUR: 'Jam dua angka (00 sampai 23)'
TWODIGITMINUTE: 'Menit dua angka (00 sampai 59)'
TWODIGITMONTH: 'Bulan dua angka (01=Januari, dll)'
TWODIGITSECOND: 'Detik dua angka (00 sampai 59)'
TWODIGITYEAR: 'Tahun dua angka'
Toggle: 'Tampilkan bantuan pemformatan'
MemberImportForm:
Help1: '<p>Impor pengguna dalam <em>format CSV</em> (comma-separated values). <small><a href="#" class="toggle-advanced">Tampilkan penggunaan mahir</a></small></p>'
Help2: "<div class=\"advanced\">\n\t<h4>Penggunaan mahir</h4>\n\t<ul>\n\t<li>Kolom yang dibolehkan: <em>%s</em></li>\n\t<li>Pengguna yang sudah terdata dihubungkan dengan nilai <em>Kode</em> uniknya, \n\tdan diperbarui dengan nilai apapun dari berkas yang diimpor.</li>\n\t<li>Kelompok dapat dihubungkan dengan kolom <em>Kelompok</em>. Kelompok diidentifikasi dengan properti <em>Kode</em>-nya,\n\tkelompok ganda dapat dipisahkan dengan tanda koma. Kelompok yang sudah terdata tidak terpengaruh.</li>\n\t</ul>\n\
</div>"
ResultCreated: '{count} pengguna dibuat'
ResultDeleted: '%d pengguna dihapus'
ResultNone: 'Tidak ada pengubahan'
ResultUpdated: '{count} pengguna diperbarui'
MemberPassword:
PLURALNAME: 'Kata Kunci'
SINGULARNAME: 'Kata Kunci'
MemberTableField:
APPLY_FILTER: 'Terapkan Saring'
ModelAdmin:
DELETE: Hapus
DELETEDRECORDS: '{count} data dihapus.'
EMPTYBEFOREIMPORT: 'Ganti data'
IMPORT: 'Impor dari CSV'
IMPORTEDRECORDS: '{count} data diimpor.'
NOCSVFILE: 'Mohon pilih berkas CSV untuk diimpor'
NOIMPORT: 'Tidak ada yang terimpor'
RESET: Reset
Title: 'Model Data'
UPDATEDRECORDS: '{count} data diperbarui.'
ModelAdmin_ImportSpec_ss:
IMPORTSPECFIELDS: 'Kolom database'
IMPORTSPECLINK: 'Tampilkan Spesifikasi untuk %s'
IMPORTSPECRELATIONS: Keterkaitan
IMPORTSPECTITLE: 'Spesifikasi untuk %s'
ModelAdmin_Tools_ss:
FILTER: Saring
IMPORT: Impor
ModelSidebar_ss:
IMPORT_TAB_HEADER: Impor
SEARCHLISTINGS: Cari
MoneyField:
FIELDLABELAMOUNT: Jumlah
FIELDLABELCURRENCY: Mata Uang
NullableField:
IsNullLabel: 'N/A'
NumericField:
VALIDATION: '''{value}'' bukan angka, hanya angka yang dapat diterima isian ini'
Pagination:
Page: Laman
View: Tampilkan
PasswordValidator:
LOWCHARSTRENGTH: 'Mohon tingkatkan kekuatan kata kunci dengan menambah beberapa karakter berikut ini: %s'
PREVPASSWORD: 'Anda sudah pernah menggunakan kata kunci tersebut, mohon pilih kata kunci yang baru'
TOOSHORT: 'Kata kunci terlalu singkat, setidaknya harus terdiri dari %s karakter atau lebih'
Permission:
AdminGroup: Pengelola
CMS_ACCESS_CATEGORY: 'Akses CMS'
FULLADMINRIGHTS: 'Hak pengelolaan penuh'
FULLADMINRIGHTS_HELP: 'Abaikan dan kesampingkan semua perijinan yang terhubung lainnya.'
PLURALNAME: Perijinan
SINGULARNAME: Perijinan
PermissionCheckboxSetField:
AssignedTo: 'dihubungkan ke "{title}"'
FromGroup: 'diwarisi dari kelompok "{title}"'
FromRole: 'diwarisi dari peran "{title}"'
FromRoleOnGroup: 'diwarisi dari peran "%s" pada kelompok "%s"'
PermissionRole:
OnlyAdminCanApply: 'Hanya untuk pengelola'
PLURALNAME: Peran
SINGULARNAME: Peran
Title: Judul
PermissionRoleCode:
PermsError: 'Tidak dapat menghubungkan kode "%s" dengan perijinan khusus (memerlukan akses PENGELOLA)'
PLURALNAME: 'Kode Perijinan Peran'
SINGULARNAME: 'Kode Perijinan Peran'
Permissions:
PERMISSIONS_CATEGORY: 'Perijinan peran dan akses'
UserPermissionsIntro: 'Menghubungkan kelompok ke pengguna ini akan menyesuaikan perijinannya. Lihat bagian Kelompok untuk rincian perijinan pada kelompok individu.'
PhoneNumberField:
VALIDATION: 'Mohon isikan nomer telpon yang benar'
Security:
ALREADYLOGGEDIN: 'Anda tidak punya akses ke laman ini. Jika Anda punya akun lain dengan akses ke laman ini, silakan masuk kembali.'
BUTTONSEND: 'Kirimkan tautan penggantian kata kunci'
CHANGEPASSWORDBELOW: 'Anda dapat mengganti kata kunci di bawah ini.'
CHANGEPASSWORDHEADER: 'Ganti kata kunci'
ENTERNEWPASSWORD: 'Mohon isikan kata kunci yang baru.'
ERRORPASSWORDPERMISSION: 'Anda harus masuk untuk bisa mengganti kata kunci!'
LOGGEDOUT: 'Anda telah keluar. Jika ingin masuk kembali, isikan informasi akun Anda di sini.'
LOGIN: 'Masuk'
LOSTPASSWORDHEADER: 'Kata Kunci yang Terlupa'
NOTEPAGESECURED: 'Laman ini diamankan. Isikan data berikut untuk dikirimkan hak akses Anda.'
NOTERESETLINKINVALID: '<p>Tautan penggantian kata kunci tidak valid atau sudah kadaluarsa.</p><p>Anda dapat meminta yang baru <a href="{link1}">di sini</a> atau mengganti kata kunci setelah Anda <a href="{link2}">masuk</a>.</p>'
NOTERESETPASSWORD: 'Isikan alamat email Anda untuk mendapatkan tautan penggantian kata kunci'
PASSWORDSENTHEADER: 'Tautan penggantian kata kunci dikirimkan ke ''{email}'''
PASSWORDSENTTEXT: 'Terimakasih! Tautan reset telah dikirim ke ''{email}'', berisi informasi akun untuk alamat email ini.'
SecurityAdmin:
ACCESS_HELP: 'Bolehkan menampilkan, menambah dan mengedit pengguna, termasuk mengatur perijinan dan perannya.'
APPLY_ROLES: 'Terapkan peran ke kelompok'
APPLY_ROLES_HELP: 'Bolehkan mengedit peran yang diberikan ke kelompok. Memerlukan perijinan "Akses ke bagian ''Pengguna''".'
EDITPERMISSIONS: 'Kelola perijinan untuk kelompok'
EDITPERMISSIONS_HELP: 'Bolehkan mengedit Perijinan dan Alamat IP untuk kelompok. Memerlukan perijinan "Akses ke bagian ''Pengamanan''".'
GROUPNAME: 'Nama kelompok'
IMPORTGROUPS: 'Impor kelompok'
IMPORTUSERS: 'Impor pengguna'
MEMBERS: Pengguna
MENUTITLE: Pengamanan
MemberListCaution: 'Perhatian: Menghapus pengguna dari daftar ini akan menghapus mereka dari semua kelompok dan database'
NEWGROUP: 'Kelompok Baru'
PERMISSIONS: Perijinan
ROLES: Peran
ROLESDESCRIPTION: 'Peran adalah set perijinan yang sudah ditentukan, dan dapat dihubungkan ke kelompok.<br />Dapat diwariskan dari kelompok induk jika diperlukan.'
TABROLES: Peran
Users: Pengguna
SecurityAdmin_MemberImportForm:
BtnImport: 'Impor dari CSV'
FileFieldLabel: 'Berkas CSV <small>(Ekstensi yang dibolehkan: *.csv)</small>'
SilverStripeNavigator:
Auto: Otomatis
ChangeViewMode: 'Ganti modus tampil'
Desktop: Desktop
DualWindowView: 'Jendela Ganda'
Edit: Edit
EditView: 'Modus edit'
Mobile: Tampilan selular
PreviewState: 'Status Pratinjau'
PreviewView: 'Modus pratinjau'
Responsive: Tampilan responsif
SplitView: 'Modus terpisah'
Tablet: Tablet
ViewDeviceWidth: 'Pilih lebar pratinjau'
Width: lebar
SiteTree:
TABMAIN: Utama
TableListField:
CSVEXPORT: 'Ekspor ke CSV'
Print: Cetak
TableListField_PageControls_ss:
OF: dari
TimeField:
VALIDATEFORMAT: 'Isikan format waktu yang benar ({format})'
ToggleField:
LESS: kurang
MORE: lebih
UploadField:
ATTACHFILE: 'Lampirkan berkas'
ATTACHFILES: 'Lampirkan berkas'
AttachFile: 'Lampirkan berkas'
CHOOSEANOTHERFILE: 'Pilih berkas lain'
CHOOSEANOTHERINFO: 'Ganti berkas ini dengan berkas lain dari penyimpanan'
DELETE: 'Hapus dari berkas'
DELETEINFO: 'Hapus berkas secara permanen dari penyimpanan'
DOEDIT: Simpan
DROPFILE: 'taruh berkas'
DROPFILES: 'taruh berkas'
Dimensions: Dimensi
EDIT: Edit
EDITINFO: 'Edit berkas ini'
FIELDNOTSET: 'Informasi berkas tidak ada'
FROMCOMPUTER: 'Dari komputer Anda'
FROMCOMPUTERINFO: 'Pilih dari berkas'
FROMFILES: 'Dari berkas'
HOTLINKINFO: 'Informasi: Gambar ini akan ditautlangsungkan. Mohon pastikan Anda mendapat ijin dari pemilik situs untuk melakukannya.'
MAXNUMBEROFFILES: 'Jumlah maksimal {count} berkas terlampaui.'
MAXNUMBEROFFILESONE: 'Hanya dapat mengunggah satu berkas'
MAXNUMBEROFFILESSHORT: 'Dapat mengunggah {count} berkas'
OVERWRITEWARNING: 'Berkas dengan nama yang sama sudah ada'
REMOVE: Buang
REMOVEINFO: 'Hapus berkas ini dari sini, tapi jangan hapus dari penyimpanan'
STARTALL: 'Mulai semua'
Saved: Tersimpan
UPLOADSINTO: 'disimpan ke /{path}'
Versioned:
has_many_Versions: Versi
CheckboxSetField:
SOURCE_VALIDATION: 'Mohon pilih nilai dari daftar yang ada. ''{value}'' bukan pilihan valid'

View File

@ -60,6 +60,8 @@ lt:
ERRORNOTREC: 'Toks vartotojo vardas / slaptažodis neatpažintas' ERRORNOTREC: 'Toks vartotojo vardas / slaptažodis neatpažintas'
Boolean: Boolean:
ANY: Bet koks ANY: Bet koks
NOANSWER: 'Ne'
YESANSWER: 'Taip'
CMSLoadingScreen_ss: CMSLoadingScreen_ss:
LOADING: Keliama... LOADING: Keliama...
REQUIREJS: 'Šiai TVS būtina įjungti JavaScript.' REQUIREJS: 'Šiai TVS būtina įjungti JavaScript.'
@ -78,6 +80,23 @@ lt:
EMAIL: El. paštas EMAIL: El. paštas
HELLO: Sveiki HELLO: Sveiki
PASSWORD: Slaptažodis PASSWORD: Slaptažodis
CheckboxField:
NOANSWER: 'Ne'
YESANSWER: 'Taip'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Prašome pasirinkti reikšmę iš pateikto sąrašo. ''{value}'' yra negalima reikšmė.'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Pamiršote slaptažodį?'
BUTTONLOGIN: 'Prisijungti'
BUTTONLOGOUT: 'Atsijungti'
PASSWORDEXPIRED: '<p>Jūsų slaptažodžio galiojimas pasibaigė. <a target="_top" href="{link}">Prašome sukurti naują.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Blogas vartotojas. Norėdami tęsti, prašome <a target="_top" href="{link}">prisijungti</a> iš naujo.</p>'
LoginMessage: '<p>Jei dar neišsaugojote padarytus pakeitimus, jūs galėsite tęsti darbą, prisijungę žemiau esančioje formoje.</p>'
SUCCESS: Sėkmingai
SUCCESSCONTENT: '<p>Sėkmingai prisijungėte. Jeigu jūsų automatiškai nenukreipia, <a target="_top" href="{link}">spauskite čia</a></p>'
TimedOutTitleAnonymous: 'Jūsų prisijungimo galiojimas pasibaigė.'
TimedOutTitleMember: 'Sveiki, {name}!<br />Jūsų prisijungimo galiojimas pasibaigė.'
ConfirmedPasswordField: ConfirmedPasswordField:
ATLEAST: 'Slaptažodžiai privalo būti bent {min} simbolių ilgio.' ATLEAST: 'Slaptažodžiai privalo būti bent {min} simbolių ilgio.'
BETWEEN: 'Slaptažodžiai privalo būti nuo {min} iki {max} simbolių ilgio.' BETWEEN: 'Slaptažodžiai privalo būti nuo {min} iki {max} simbolių ilgio.'
@ -124,6 +143,7 @@ lt:
DropdownField: DropdownField:
CHOOSE: (Pasirinkti) CHOOSE: (Pasirinkti)
CHOOSESEARCH: '(Pasirinkti arba Ieškoti)' CHOOSESEARCH: '(Pasirinkti arba Ieškoti)'
SOURCE_VALIDATION: 'Prašome pasirinkti reikšmę iš pateikto sąrašo. ''{value}'' yra negalima reikšmė.'
EmailField: EmailField:
VALIDATION: 'Prašome suvesti el. pašto adresą' VALIDATION: 'Prašome suvesti el. pašto adresą'
Enum: Enum:
@ -171,7 +191,9 @@ lt:
TEXT2: 'slaptažodžio atstatymo nuoroda' TEXT2: 'slaptažodžio atstatymo nuoroda'
TEXT3: svetainei TEXT3: svetainei
Form: Form:
CSRF_FAILED_MESSAGE: "Iškilo techninė problema. Prašome paspausti mygtuką Atgal,\nperkraukite naršyklės langą ir bandykite vėl." CSRF_FAILED_MESSAGE: 'Iškilo techninė problema. Prašome paspausti mygtuką Atgal,
perkraukite naršyklės langą ir bandykite vėl.'
FIELDISREQUIRED: '{name} yra privalomas' FIELDISREQUIRED: '{name} yra privalomas'
SubmitBtnLabel: Vykdyti SubmitBtnLabel: Vykdyti
VALIDATIONCREDITNUMBER: 'Prašome įsitikinti, ar teisingai suvedėte kreditinės kortelės numerį {number}' VALIDATIONCREDITNUMBER: 'Prašome įsitikinti, ar teisingai suvedėte kreditinės kortelės numerį {number}'
@ -238,7 +260,8 @@ lt:
many_many_Members: Vartotojai many_many_Members: Vartotojai
GroupImportForm: GroupImportForm:
Help1: '<p>Importuoti vieną ar kelias grupes <em>CSV</em> formatu (kableliu atskirtos reikšmės). <small><a href="#" class="toggle-advanced">Rodyti detalesnį aprašymą</a></small></p>' Help1: '<p>Importuoti vieną ar kelias grupes <em>CSV</em> formatu (kableliu atskirtos reikšmės). <small><a href="#" class="toggle-advanced">Rodyti detalesnį aprašymą</a></small></p>'
Help2: "<div class=\"advanced\">\n<h4>Detalesnis aprašymas</h4>\n<ul>\n<li>Galimi stulpeliai: <em>%s</em></li>\n<li>Esamos grupės yra surišamos su jų unikalia <em>Code</em> reikšme ir atnaujinamos duomenimis iš importuojamos bylos</li>\n<li>Grupių hierarchija gali būti sukurta naudojant <em>ParentCode</em> stulpelį.</li>\n<li>Leidimų gali būti priskirti naudojant <em>PermissionCode</em> stulpelį. Esami leidimai nebus pakeisti.</li>\n</ul>\n</div>" Help2: "<div class=\"advanced\">\n\t<h4>Sudėtingesni pasirinkimai</h4>\n\t<ul>\n\t<li>Galimi stulpeliai: <em>%s</em></li>\n\t<li>Esamos grupės yra surišamos su jų unikalia <em>Code</em> reikšme ir atnaujinamos duomenimis iš importuojamos bylos</li>\n\t<li>Grupių hierarchija gali būti sukurta naudojant <em>ParentCode</em> stulpelį.</li>\n\t<li>Leidimų kodai gali būti priskirti naudojant <em>PermissionCode</em> stulpelį. Esami leidimai nebus pakeisti.</li>\n\t</ul>\n\
</div>"
ResultCreated: 'Sukurta {count} grupių' ResultCreated: 'Sukurta {count} grupių'
ResultDeleted: 'Ištrinta %d grupių' ResultDeleted: 'Ištrinta %d grupių'
ResultUpdated: 'Atnaujinta %d grupių' ResultUpdated: 'Atnaujinta %d grupių'
@ -247,6 +270,8 @@ lt:
HtmlEditorField: HtmlEditorField:
ADDURL: 'Pridėti URL' ADDURL: 'Pridėti URL'
ADJUSTDETAILSDIMENSIONS: 'Detalesnė inf. ir matmenys' ADJUSTDETAILSDIMENSIONS: 'Detalesnė inf. ir matmenys'
ANCHORSCANNOTACCESSPAGE: 'Neturite teisių pasiekti pasirinkto puslapio turinį.'
ANCHORSPAGENOTFOUND: 'Pasirinktas puslapis nerastas.'
ANCHORVALUE: Nuoroda ANCHORVALUE: Nuoroda
BUTTONADDURL: 'Pridėti URL' BUTTONADDURL: 'Pridėti URL'
BUTTONINSERT: Įterpti BUTTONINSERT: Įterpti
@ -290,6 +315,7 @@ lt:
URL: URL adresas URL: URL adresas
URLNOTANOEMBEDRESOURCE: 'Nepavyko URL nuorodos ''{url}'' panaudoti media turiniui.' URLNOTANOEMBEDRESOURCE: 'Nepavyko URL nuorodos ''{url}'' panaudoti media turiniui.'
UpdateMEDIA: 'Atnaujinti media' UpdateMEDIA: 'Atnaujinti media'
SUBJECT: 'El. laiško tema'
Image: Image:
PLURALNAME: Bylos PLURALNAME: Bylos
SINGULARNAME: Byla SINGULARNAME: Byla
@ -318,6 +344,8 @@ lt:
LeftAndMain_Menu_ss: LeftAndMain_Menu_ss:
Hello: Sveiki Hello: Sveiki
LOGOUT: 'Atsijungti' LOGOUT: 'Atsijungti'
ListboxField:
SOURCE_VALIDATION: 'Prašome pasirinkti reikšmę iš pateikto sąrašo. ''{value}'' yra negalima reikšmė.'
LoginAttempt: LoginAttempt:
Email: 'E. pašto adresas' Email: 'E. pašto adresas'
IP: 'IP adresas' IP: 'IP adresas'
@ -350,6 +378,7 @@ lt:
NEWPASSWORD: 'Naujas slaptažodis' NEWPASSWORD: 'Naujas slaptažodis'
NoPassword: 'Šis vartotojas neturi slaptažodžio.' NoPassword: 'Šis vartotojas neturi slaptažodžio.'
PASSWORD: Slaptažodis PASSWORD: Slaptažodis
PASSWORDEXPIRED: 'Jūsų slaptažodžio galiojimas pasibaigė. Prašome sukurti naują.'
PLURALNAME: Vartotojai PLURALNAME: Vartotojai
REMEMBERME: 'Prisiminti jungiantis kitą kartą?' REMEMBERME: 'Prisiminti jungiantis kitą kartą?'
SINGULARNAME: Vartotojas SINGULARNAME: Vartotojas
@ -456,8 +485,8 @@ lt:
SINGULARNAME: Rolė SINGULARNAME: Rolė
Title: Pavadinimas Title: Pavadinimas
PermissionRoleCode: PermissionRoleCode:
PLURALNAME: 'Leidimų rolių kodai'
PermsError: 'Nepavyko priskirto kodo "%s" su priskirtais leidimais (būtina ADMIN prieiga)' PermsError: 'Nepavyko priskirto kodo "%s" su priskirtais leidimais (būtina ADMIN prieiga)'
PLURALNAME: 'Leidimų rolių kodai'
SINGULARNAME: 'Leidimų rolių kodai' SINGULARNAME: 'Leidimų rolių kodai'
Permissions: Permissions:
PERMISSIONS_CATEGORY: 'Rolės ir priėjimo leidimai' PERMISSIONS_CATEGORY: 'Rolės ir priėjimo leidimai'
@ -557,3 +586,5 @@ lt:
UPLOADSINTO: 'saugo į /{path}' UPLOADSINTO: 'saugo į /{path}'
Versioned: Versioned:
has_many_Versions: Versijos has_many_Versions: Versijos
CheckboxSetField:
SOURCE_VALIDATION: 'Prašome pasirinkti reikšmę iš pateikto sąrašo. ''{value}'' yra negalima reikšmė.'

View File

@ -60,6 +60,8 @@ sk:
ERRORNOTREC: 'Toto používateľské meno / heslo nebolo rozpoznané' ERRORNOTREC: 'Toto používateľské meno / heslo nebolo rozpoznané'
Boolean: Boolean:
ANY: Ktorýkoľvek ANY: Ktorýkoľvek
NOANSWER: 'Nie'
YESANSWER: 'Áno'
CMSLoadingScreen_ss: CMSLoadingScreen_ss:
LOADING: Načíta sa ... LOADING: Načíta sa ...
REQUIREJS: 'CMS vyžaduje, aby ste mali JavaScript zapnutý.' REQUIREJS: 'CMS vyžaduje, aby ste mali JavaScript zapnutý.'
@ -78,6 +80,23 @@ sk:
EMAIL: E-mail EMAIL: E-mail
HELLO: Dobrý deň HELLO: Dobrý deň
PASSWORD: Heslo PASSWORD: Heslo
CheckboxField:
NOANSWER: 'Nie'
YESANSWER: 'Áno'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Prosím vyberte hodnotu v zozname. {value} nie je platná voľba'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Zabudnuté heslo?'
BUTTONLOGIN: 'Prihlásiť sa späť'
BUTTONLOGOUT: 'Odhlásiť sa'
PASSWORDEXPIRED: '<p>Vaše heslo bolo expirované. <a target="_top" href="{link}">Prosím zvoľte nové heslo.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Neplatný užívateľ. <a target="_top" href="{link}">Prosím overte sa znovu tu</a> pre pokračovanie.</p>'
LoginMessage: '<p>Ak máte akúkoľvek neuloženú prácu, môžete sa vrátiť na miesto, kde ste prestali, prihlásením sa späť nižšie.</p>'
SUCCESS: Úspešné
SUCCESSCONTENT: '<p>Úspešné prihlásenie. Ak nebudete automaticky presmerovaní <a target="_top" href="{link}">kliknite tu</a></p>'
TimedOutTitleAnonymous: 'Čas Vášho sedenia vypršal.'
TimedOutTitleMember: 'Ahoj {name}!<br />Čas Vášho sedenia vypršal.'
ConfirmedPasswordField: ConfirmedPasswordField:
ATLEAST: 'Heslá musia byť nejmenej {min} znakov dlhé.' ATLEAST: 'Heslá musia byť nejmenej {min} znakov dlhé.'
BETWEEN: 'Heslá musia byť {min} až {max} znakov dlhé.' BETWEEN: 'Heslá musia byť {min} až {max} znakov dlhé.'
@ -124,6 +143,7 @@ sk:
DropdownField: DropdownField:
CHOOSE: (Vyberte si) CHOOSE: (Vyberte si)
CHOOSESEARCH: '(Vybrať alebo vyhľadať)' CHOOSESEARCH: '(Vybrať alebo vyhľadať)'
SOURCE_VALIDATION: 'Prosím vyberte hodnotu v zozname. {value} nie je platná voľba'
EmailField: EmailField:
VALIDATION: 'Prosím zadajte email adresu' VALIDATION: 'Prosím zadajte email adresu'
Enum: Enum:
@ -171,7 +191,7 @@ sk:
TEXT2: 'odkaz na resetovanie hesla' TEXT2: 'odkaz na resetovanie hesla'
TEXT3: pre TEXT3: pre
Form: Form:
CSRF_FAILED_MESSAGE: "Zdá sa, že nastal technický problém. Prosím, kliknite na tlačidlo Späť,, \n\t\t\t\t\taktualizujte prehliadač a skúste to znova." CSRF_FAILED_MESSAGE: 'Vyzerá to, že to musí být technický problem. Kliknite prosím na tlačítko späť, obnovte váš prehliadač, a skúste opäť.'
FIELDISREQUIRED: '{name} je požadované' FIELDISREQUIRED: '{name} je požadované'
SubmitBtnLabel: Choď SubmitBtnLabel: Choď
VALIDATIONCREDITNUMBER: 'Uistite sa, že ste zadali číslo {number} kreditnej karty správne' VALIDATIONCREDITNUMBER: 'Uistite sa, že ste zadali číslo {number} kreditnej karty správne'
@ -238,7 +258,7 @@ sk:
many_many_Members: Členovia many_many_Members: Členovia
GroupImportForm: GroupImportForm:
Help1: 'Importovať jednu alebo viac skupín v CSV formáte (čiarkov oddelené hodnoty). Zobraziť pokročilé použitie' Help1: 'Importovať jednu alebo viac skupín v CSV formáte (čiarkov oddelené hodnoty). Zobraziť pokročilé použitie'
Help2: "<div class=\"advanced\">\n<h4>Pokročilé použitie</h4>\n<ul>\n<li>Povolené stĺpce: <em>%s</em></li>\n<li>Existujúce skupiny sú porovnávané ich unikátnou vlastnostou <em>Code</em>, a aktualizované s novými hodnotami z\nimportovaného súboru.</li>\n<li>Hierarchia skupín môže byť tvorená použitím stĺpce <em>ParentCode</em>.</li>\n<li>Kódy oprávnení môžu byť priradené stĺpcom <em>PermissionCode</em>. Existujúce oprávnenia nie sú smazáné.</li>\n</ul>\n</div>" Help2: "<div class=\"advanced\">\n\t<h4>Pokročilé použitie</h4>\n\t<ul>\n\t<li>Povolené stĺpce: <em>%s</em></li>\n\t<li>Existujúce skupiny sú porovnávané s ich unikátnou <em>Code</em> hodnotou, a aktualizované s novými hodnotami z importovaného súbory</li>\n\t<li>Skupina hierarchií môže byť tvorená použitím <em>ParentCode</em> stĺpce.</li>\n\t<li>Kódy oprávnení môžu byť priradené <em>PermissionCode</em> stĺpcom. Existujúce oprávnenia nie sú smazáné.</li>\n\t</ul>\n</div>"
ResultCreated: 'Vytvorených {count} skupín' ResultCreated: 'Vytvorených {count} skupín'
ResultDeleted: 'Zmazané %d skupiny' ResultDeleted: 'Zmazané %d skupiny'
ResultUpdated: 'Aktualizované %d skupiny' ResultUpdated: 'Aktualizované %d skupiny'
@ -247,6 +267,8 @@ sk:
HtmlEditorField: HtmlEditorField:
ADDURL: 'Pridať URL' ADDURL: 'Pridať URL'
ADJUSTDETAILSDIMENSIONS: 'Detaily &amp; rozmery' ADJUSTDETAILSDIMENSIONS: 'Detaily &amp; rozmery'
ANCHORSCANNOTACCESSPAGE: 'Nemáte povolený prístup k obsahu cieľovej stránky.'
ANCHORSPAGENOTFOUND: 'Cielova stránka nebola nájdená.'
ANCHORVALUE: Odkaz ANCHORVALUE: Odkaz
BUTTONADDURL: 'Pridať url' BUTTONADDURL: 'Pridať url'
BUTTONINSERT: Vložiť BUTTONINSERT: Vložiť
@ -290,6 +312,7 @@ sk:
URL: URL URL: URL
URLNOTANOEMBEDRESOURCE: 'URL ''{url}'' nemôže byť vložené do zdroja médií.' URLNOTANOEMBEDRESOURCE: 'URL ''{url}'' nemôže byť vložené do zdroja médií.'
UpdateMEDIA: 'Aktualizovať média' UpdateMEDIA: 'Aktualizovať média'
SUBJECT: 'Predmet emailu'
Image: Image:
PLURALNAME: Súbory PLURALNAME: Súbory
SINGULARNAME: Súbor SINGULARNAME: Súbor
@ -318,6 +341,8 @@ sk:
LeftAndMain_Menu_ss: LeftAndMain_Menu_ss:
Hello: Ahoj Hello: Ahoj
LOGOUT: 'Odhlásiť sa' LOGOUT: 'Odhlásiť sa'
ListboxField:
SOURCE_VALIDATION: 'Prosím vyberte hodnotu v zozname. {value} nie je platná voľba.'
LoginAttempt: LoginAttempt:
Email: 'Emailová adresa' Email: 'Emailová adresa'
IP: 'IP adreasa' IP: 'IP adreasa'
@ -350,6 +375,7 @@ sk:
NEWPASSWORD: 'Nové heslo' NEWPASSWORD: 'Nové heslo'
NoPassword: 'Nie je tu heslo pre tohto člena.' NoPassword: 'Nie je tu heslo pre tohto člena.'
PASSWORD: Heslo PASSWORD: Heslo
PASSWORDEXPIRED: 'Vaše heslo bolo expirované. Zvoľte nové heslo.'
PLURALNAME: Členovia PLURALNAME: Členovia
REMEMBERME: 'Pamätať si ma nabudúce?' REMEMBERME: 'Pamätať si ma nabudúce?'
SINGULARNAME: Člen SINGULARNAME: Člen
@ -456,8 +482,8 @@ sk:
SINGULARNAME: Úloha SINGULARNAME: Úloha
Title: Názov Title: Názov
PermissionRoleCode: PermissionRoleCode:
PLURALNAME: 'Kódy právomocí úloh'
PermsError: 'Nie je možné pripojiť kód "%s" s privilegovanými právami (vyžaduje ADMIN prístup)' PermsError: 'Nie je možné pripojiť kód "%s" s privilegovanými právami (vyžaduje ADMIN prístup)'
PLURALNAME: 'Kódy právomocí úloh'
SINGULARNAME: 'Kód právomocí úloh' SINGULARNAME: 'Kód právomocí úloh'
Permissions: Permissions:
PERMISSIONS_CATEGORY: 'Úlohy a prístupové práva' PERMISSIONS_CATEGORY: 'Úlohy a prístupové práva'
@ -557,3 +583,5 @@ sk:
UPLOADSINTO: 'uloží do /{path}' UPLOADSINTO: 'uloží do /{path}'
Versioned: Versioned:
has_many_Versions: verzie has_many_Versions: verzie
CheckboxSetField:
SOURCE_VALIDATION: 'Prosím vyberte hodnotu v zozname. ''{value}'' nie je platná voľba'

553
lang/sr@latin.yml Normal file
View File

@ -0,0 +1,553 @@
sr@latin:
AssetAdmin:
NEWFOLDER: Nova fascikla
SHOWALLOWEDEXTS: 'Prikaži dozvoljene ekstenzije'
AssetTableField:
CREATED: 'Prvo dostavljeno'
DIM: Dimenzije
FILENAME: Ime datoteke
FOLDER: Fascikla
LASTEDIT: 'Poslednje promenjeno'
OWNER: Vlasnik
SIZE: 'Veličina'
TITLE: Naslov
TYPE: 'Tip'
URL: URL
AssetUploadField:
ChooseFiles: 'Izaberi datoteke'
DRAGFILESHERE: 'Prevuci datoteke ovde'
DROPAREA: 'Područje za ispuštanje'
EDITALL: 'Izmeni sve'
EDITANDORGANIZE: 'Izmeni i organizuj'
EDITINFO: 'Izmeni datoteke'
FILES: Datoteke
FROMCOMPUTER: 'Izaberite datoteke sa Vašeg računara'
FROMCOMPUTERINFO: 'Postavi sa Vašeg računara'
TOTAL: Ukupno
TOUPLOAD: 'Izaberite datoteke za postavljanje...'
UPLOADINPROGRESS: 'Molimo Vas da sačekate... Postavljanje je u toku'
UPLOADOR: ILI
BBCodeParser:
ALIGNEMENT: Poravnanje
ALIGNEMENTEXAMPLE: 'poravnat uz desnu stranu'
BOLD: 'Podebljan tekst'
BOLDEXAMPLE: Podebljano
CODE: 'Blok kôda'
CODEDESCRIPTION: 'Blok neformatizovanog kôda'
CODEEXAMPLE: 'Blok kôda'
COLORED: 'Obojen tekst'
COLOREDEXAMPLE: 'plavi tekst'
EMAILLINK: 'Veza e-pošte'
EMAILLINKDESCRIPTION: 'Napravite link do adrese e-pošte'
IMAGE: Slika
IMAGEDESCRIPTION: 'Prikaži sliku u mojoj poruci'
ITALIC: 'Iskošen tekst'
ITALICEXAMPLE: Iskošeno
LINK: 'Link veb sajta'
LINKDESCRIPTION: 'Link do drugog veb sajta ili URL'
STRUCK: 'Precrtan tekst'
STRUCKEXAMPLE: Precrtano
UNDERLINE: 'Podvučen tekst'
UNDERLINEEXAMPLE: Podvučeno
UNORDERED: 'Neuređena lista'
UNORDEREDDESCRIPTION: 'Neuređena lista'
UNORDEREDEXAMPLE1: 'stavka 1 neuređene liste'
BackLink_Button_ss:
Back: Nazad
BasicAuth:
ENTERINFO: 'Unesite korisničko ime i lozinku.'
ERRORNOTADMIN: 'Ovaj korisnik nije administrator.'
ERRORNOTREC: 'To korisničko ime / lozinka nije prepoznato'
Boolean:
ANY: Bilo koja
CMSLoadingScreen_ss:
LOADING: Učitavanje...
REQUIREJS: 'CMS zahteva omogućen JavaScript.'
CMSMain:
ACCESS: 'Pristup ''{title}'' sekciji'
ACCESSALLINTERFACES: 'Pristup svim sekcijama CMS-a'
ACCESSALLINTERFACESHELP: 'Nadjačava specifičnija podešavanja pristupa.'
SAVE: Sačuvaj
CMSPageHistoryController_versions_ss:
PREVIEW: 'Prethodni pregled veb sajta'
CMSProfileController:
MENUTITLE: 'Moj profil'
ChangePasswordEmail_ss:
CHANGEPASSWORDTEXT1: 'Promenili ste svoju lozinku za '
CHANGEPASSWORDTEXT2: 'Sada možete da koristite sledeće podatke za prijavljivanje:'
EMAIL: E-pošta
HELLO: Zdravo
PASSWORD: Lozinka
ConfirmedPasswordField:
ATLEAST: 'Lozinka mora sadržati najmanje {min} znakova.'
BETWEEN: 'Lozinka mora sadržati najmanje {min}, a najviše {max} znakova.'
MAXIMUM: 'Lozinka može sadržati najviše {max} znakova.'
SHOWONCLICKTITLE: 'Promeni lozinku'
ContentController:
NOTLOGGEDIN: 'Niste prijavljeni'
CreditCardField:
FIRST: prvi
FOURTH: četvrti
SECOND: drugi
THIRD: treći
CurrencyField:
CURRENCYSYMBOL: din.
DataObject:
PLURALNAME: 'Objekti podataka'
SINGULARNAME: 'Objekat podataka'
Date:
DAY: dan
DAYS: dan/a
HOUR: sat
HOURS: sata/a
LessThanMinuteAgo: 'manje od minute'
MIN: minut
MINS: minuta
MONTH: mesec
MONTHS: meseci
SEC: sekunda
SECS: sekundi
TIMEDIFFAGO: '{difference} ranije'
TIMEDIFFIN: 'kroz {difference}'
YEAR: godina
YEARS: godinâ
DateField:
NOTSET: 'nije podešeno'
TODAY: danas
VALIDDATEFORMAT2: 'Molimo Vas da unesete ispravan format datuma ({format})'
VALIDDATEMAXDATE: 'Datum ne sme biti posle ({date})'
VALIDDATEMINDATE: 'Datum ne sme biti pre ({date})'
DatetimeField:
NOTSET: 'nije podešeno'
Director:
INVALID_REQUEST: 'Pogrešan zahtev'
DropdownField:
CHOOSE: (izaberite)
CHOOSESEARCH: '(Izaberi ili Pronađi)'
EmailField:
VALIDATION: 'Unesite adresu e-pošte'
Enum:
ANY: Bilo koji
File:
AviType: 'AVI video datotoeka'
Content: Sadržaj
CssType: 'CSS datoteka'
DmgType: 'Apple slika diska'
DocType: 'Word dokument'
Filename: Ime datoteke
GifType: 'GIF slika - dobro za dijagrame'
GzType: 'GZIP arhiva'
HtlType: 'HTML datoteka'
HtmlType: 'HTML datoteka'
INVALIDEXTENSION: 'Ekstenzija nije dozvoljena (dozvoljene: {extensions})'
INVALIDEXTENSIONSHORT: 'Ekstenzija nije dozvoljena'
IcoType: 'Ikona'
JpgType: 'JPEG slika - dobro za fotografije'
JsType: 'Javascript datoteka'
Mp3Type: 'MP3 audio datoteka'
MpgType: 'MPEG video datoteka'
NOFILESIZE: 'Datoteka je veličine 0 B.'
NOVALIDUPLOAD: 'Datoteka za prenos nije valjana'
Name: Ime
PLURALNAME: Datoteke
PdfType: 'Adobe Acrobat PDF datoteka'
PngType: 'PNG slika - dobar format opšte namene'
SINGULARNAME: Datoteka
TOOLARGE: 'Datoteka je prevelika; maksimalna dozvoljena veličina je {size}'
TOOLARGESHORT: 'Veličina datoteke premašuje {size}'
TiffType: 'Označeni format slike'
Title: Naslov
WavType: 'WAV audio datoteka'
XlsType: 'Excel dokument'
ZipType: 'ZIP arhiva'
Filesystem:
SYNCRESULTS: 'Sinhronizacija je završena: {createdcount} stavki je kreirano, {deletedcount} stavki je izbrisano'
Folder:
PLURALNAME: Fascikle
SINGULARNAME: Fascikla
ForgotPasswordEmail_ss:
HELLO: Zdravo
TEXT1: 'Evo ga Vaš'
TEXT2: 'link za resetovanje lozinke'
TEXT3: za
Form:
FIELDISREQUIRED: '{name} je obavezno'
SubmitBtnLabel: Idi
VALIDATIONCREDITNUMBER: 'Uverite se da ste ispravno uneli {number} broj kreditne kartice'
VALIDATIONNOTUNIQUE: 'Uneta vrednost nije jedinstvena'
VALIDATIONPASSWORDSDONTMATCH: 'Lozinke se ne poklapaju'
VALIDATIONPASSWORDSNOTEMPTY: 'Polja za lozinke ne smeju da budu prazna'
VALIDATIONSTRONGPASSWORD: 'Lozinke moraju ra sadrže bar jednu cifru i bar jedan alfanumerički znak'
VALIDATOR: Proverivač ispravnosti
VALIDCURRENCY: 'Unesite ispravnu valutu'
FormField:
Example: 'npr. %s'
NONE: bez
GridAction:
DELETE_DESCRIPTION: Izbriši
Delete: Izbriši
UnlinkRelation: Raskini link
GridField:
Add: 'Dodaj {name}'
Filter: Filter
FilterBy: 'Filtriraj po'
Find: Pronađi
LEVELUP: 'Nivo iznad'
LinkExisting: 'Postojanje linka'
NewRecord: 'Novi %s'
NoItemsFound: 'Nijedna stavka nije pronađena'
PRINTEDAT: 'Odštampano'
PRINTEDBY: 'Odštampao'
PlaceHolder: 'Pronađi {type}'
PlaceHolderWithLabels: 'Pronađi {type} po {name}'
RelationSearch: 'Pretraživanje relacije'
ResetFilter: Vrati u pređašnje stanje
GridFieldAction_Delete:
DeletePermissionsFailure: 'Nemate dozvolu za brisanje'
EditPermissionsFailure: 'Nemate dozvolu da raskinete link sa zapisom'
GridFieldDetailForm:
CancelBtn: Odustani
Create: Kreiraj
Delete: Izbriši
DeletePermissionsFailure: 'Nemate pravo brisanja'
Deleted: 'Izbrisano %s %s'
Save: Sačuvaj
Saved: 'Sačuvano {name} {link}'
GridFieldEditButton_ss:
EDIT: Izmeni
GridFieldItemEditView:
Go_back: 'Vrati se nazad'
Group:
AddRole: 'Dodaj ulogu za ovu grupu'
Code: 'Kôd grupe'
DefaultGroupTitleAdministrators: Administratori
DefaultGroupTitleContentAuthors: 'Autori sadržaja'
Description: Opis
GroupReminder: 'Ako izaberete roditeljsku grupu, ova grupa će preuzeti sve njene uloge'
HierarchyPermsError: 'Nije moguće dodeliti roditeljsku grupu "%s" sa privilegovanim dozvolama (zahteva Administratorski pristup)'
Locked: 'Zaključano?'
NoRoles: 'Uloge nisu pronađene'
PLURALNAME: Grupe
Parent: 'Roditeljska grupa'
RolesAddEditLink: 'Upravljaj ulogama'
SINGULARNAME: Grupa
Sort: 'Poredak sortiranja'
has_many_Permissions: Dozvole
many_many_Members: Članovi
GroupImportForm:
Help1: '<p>Uvezi jednu ili više grupa u <em>CSV</em> formatu (zarezima razdvojene vrednosti). <small><a href="#" class="toggle-advanced">Prikaži napredno korišćenje</a></small></p>'
ResultCreated: 'Kreirano {count} grupa'
ResultDeleted: 'Izbrisao %d grupa'
ResultUpdated: 'Ažurirano %d grupa'
Hierarchy:
InfiniteLoopNotAllowed: 'Otkrivena je beskonačna petlja u okviru "{type}" hijerarhije. Promenite roditelja da bi ste razrešili situaciju'
HtmlEditorField:
ADDURL: 'Dodaj URL'
ADJUSTDETAILSDIMENSIONS: 'Detalji &amp; dimenzije'
ANCHORVALUE: Sidro
BUTTONADDURL: 'Dodaj URL'
BUTTONINSERT: Umetni
BUTTONINSERTLINK: 'Umetni link'
BUTTONREMOVELINK: 'Ukloni link'
BUTTONUpdate: Ažuriraj
CAPTIONTEXT: 'Tekst oznake'
CSSCLASS: 'Poravnanje / stil'
CSSCLASSCENTER: 'Centrirano, samo za sebe.'
CSSCLASSLEFT: 'Sa leve strane, sa tekstom prelomljenim okolo.'
CSSCLASSLEFTALONE: 'Sa leve strane, samo za sebe'
CSSCLASSRIGHT: 'Sa desne strane, sa tekstom prelomljenim okolo.'
DETAILS: Detalji
EMAIL: 'Adresa e-pošte'
FILE: Datoteka
FOLDER: Fascikla
FROMCMS: 'Iz CMS-a'
FROMCOMPUTER: 'Sa Vašeg računara'
FROMWEB: 'Sa veba'
FindInFolder: 'Pronađi u fascikli'
IMAGEALT: 'Alternativni tekst (alt)'
IMAGEALTTEXT: 'Alternativni tekst (alt) - prikazuje se ako slika ne može biti prikazana'
IMAGEALTTEXTDESC: 'Prikazuje se čitačima ekrana ili ako slika ne može biti prikazana'
IMAGEDIMENSIONS: Dimenzije
IMAGEHEIGHTPX: Visina
IMAGETITLE: 'Tekst naslova (tooltip) - za dodatne informacije o slici'
IMAGETITLETEXT: 'Tekst naslova (tooltip)'
IMAGETITLETEXTDESC: 'Za dodatne informacije o slici'
IMAGEWIDTHPX: Širina
INSERTMEDIA: 'Umetni medijski resurs'
LINK: 'Link'
LINKANCHOR: 'Sidro na ovoj strani'
LINKDESCR: 'Opis linka'
LINKEMAIL: 'Adresa e-pošte'
LINKEXTERNAL: 'drugi vebsajt'
LINKFILE: 'Preuzmi datoteku'
LINKINTERNAL: 'stranu na sajtu'
LINKOPENNEWWIN: 'Otvoriti link u novom prozoru?'
LINKTO: 'Poveži na'
PAGE: Stranica
URL: URL
URLNOTANOEMBEDRESOURCE: 'URL ''{url}'' ne može biti pretvoren u medijski resurs.'
UpdateMEDIA: 'Ažuriraj medijski resurs'
Image:
PLURALNAME: Datoteke
SINGULARNAME: Datoteka
Image_Cached:
PLURALNAME: Datoteke
SINGULARNAME: Datoteka
Image_iframe_ss:
TITLE: 'Iframe za dostavljanje slika'
LeftAndMain:
CANT_REORGANISE: 'Nemate pravo da menjate stranice vršnog nivoa. Vaše izmene nisu sačuvane.'
DELETED: Izbrisano
DropdownBatchActionsDefault: Akcije
HELP: Pomoć
PAGETYPE: 'Tip stranice'
PERMAGAIN: 'Odjavljeni ste sa CMS-a. Ukoliko želite da se ponovo prijavite, unesite korisničko ime i lozinku.'
PERMALREADY: 'Ne možete da pristupite ovom delu CMS-a. Ako želite da se prijavite kao neko drugi, uradite to ispod'
PERMDEFAULT: 'Izaberite metodu autentifikacije i unesite podatke za pristup CMS-u.'
PreviewButton: Prethodni pregled
REORGANISATIONSUCCESSFUL: 'Stablo sajta je uspešno reorganizovano.'
SAVEDUP: Sačuvano.
ShowAsList: 'prikaži u vidu liste'
TooManyPages: 'Previše stranica'
ValidationError: 'Grešla pri proveri ispravnosti'
VersionUnknown: Nepoznato
LeftAndMain_Menu_ss:
Hello: Zdravo
LOGOUT: 'Odjavi se'
LoginAttempt:
Email: 'Adresa e-pošte'
IP: 'IP adresa'
PLURALNAME: 'Pokušaji prijave'
SINGULARNAME: 'Pokušaj prijave'
Status: Status
Member:
ADDGROUP: 'Dodaj grupu'
BUTTONCHANGEPASSWORD: 'Izmeni lozinku'
BUTTONLOGIN: 'Prijavi se'
BUTTONLOGINOTHER: 'Prijavite se kao neko drugi'
BUTTONLOSTPASSWORD: 'Zaboravio sam lozinku'
CANTEDIT: 'Nemate dozvolu da uradite to'
CONFIRMNEWPASSWORD: 'Potvrdite novu lozinku'
CONFIRMPASSWORD: 'Potvrdite lozinku'
DATEFORMAT: 'Format datuma'
DefaultAdminFirstname: 'Podrazumevani administrator'
DefaultDateTime: podrazumevano
EMAIL: E-pošta
EMPTYNEWPASSWORD: 'Nova lozinka ne može biti prazna. Pokušajte ponovo.'
ENTEREMAIL: 'Unesite adresu e-pošte da bi ste dobili link za resetovanje lozinke.'
ERRORLOCKEDOUT2: 'Vaš nalog je privremeno suspendovan zbog velikog broja neuspešnih pokušaja prijave. Pokušajte ponovo za {count} minuta.'
ERRORNEWPASSWORD: 'Nova lozinka koju ste uneli se ne poklapa. Pokušajte ponovo.'
ERRORPASSWORDNOTMATCH: 'Vaša trenutna lozinka se ne poklapa. Pokušajte ponovo.'
ERRORWRONGCRED: 'Pruženi detalji izgleda nisu korektni. Pokušajte ponovo.'
FIRSTNAME: 'Ime'
INTERFACELANG: 'Jezik interfejsa'
INVALIDNEWPASSWORD: 'Nismo mogli da prihvatimo lozinku: {password}'
LOGGEDINAS: 'Prijavljeni ste kao {name}.'
NEWPASSWORD: 'Nova lozinka'
NoPassword: 'Ne postoji lozinka za tog člana.'
PASSWORD: Lozinka
PLURALNAME: Članovi
REMEMBERME: 'Zapamti me za sledeći put'
SINGULARNAME: Član
SUBJECTPASSWORDCHANGED: 'Vaša lozinka je promenjena'
SUBJECTPASSWORDRESET: 'Link za resetovanje Vaše lozinke'
SURNAME: Prezime
TIMEFORMAT: 'Format vremena'
VALIDATIONMEMBEREXISTS: 'Već postoji član sa ovom adresom e-pošte'
ValidationIdentifierFailed: 'Nije moguće prepisati preko postojećeg člana #{id} sa istim identifikatorom ({name} = {value}))'
WELCOMEBACK: 'Dobro došli ponovo, {firstname}'
YOUROLDPASSWORD: 'Vaša stara lozinka'
belongs_many_many_Groups: Grupe
db_LastVisited: 'Datum poslednje posete'
db_Locale: 'Lokalitet interfejsa'
db_LockedOutUntil: 'Zaključan do'
db_NumVisit: 'Broj poseta'
db_Password: Lozinka
db_PasswordExpiry: 'Datum isteka lozinke'
MemberAuthenticator:
TITLE: 'Pošalji lozinku'
MemberDatetimeOptionsetField:
AMORPM: 'AM (Ante meridiem) or PM (Post meridiem)'
Custom: Prilagođen
DATEFORMATBAD: 'Neispravan format datuma'
DAYNOLEADING: 'Dan u mesecu bez vodeće nule'
DIGITSDECFRACTIONSECOND: 'Jedna ili više cifara koje predstaljaju deseti deo sekunde'
FOURDIGITYEAR: 'Četvorocifrena godina'
FULLNAMEMONTH: 'Puno ime meseca (npr. Jun)'
HOURNOLEADING: 'Sati bez vodeće nule'
MINUTENOLEADING: 'Minute bez vodeće nule'
MONTHNOLEADING: 'Mesec bez vodeće nule'
Preview: Prethodni pregled
SHORTMONTH: 'Kratko ime meseca (npr. Sept)'
TWODIGITDAY: 'Dvocifreni dan meseca'
TWODIGITHOUR: 'Dve cifre sati (00 do 23)'
TWODIGITMINUTE: 'Dve cifre minuta (00 do 59)'
TWODIGITMONTH: 'Dvocifreni mesec (01=Januar itd)'
TWODIGITSECOND: 'Dve cifre sekundi (00 do 59)'
TWODIGITYEAR: 'Dvocifrena godina'
Toggle: 'Prikaži pomoć za formatiranje'
MemberImportForm:
Help1: '<p>Uvezi korisnike u <em>CSV</em> formatu (zarezima razdvojene vrednosti). <small><a href="#" class="toggle-advanced">Prikaži napredno korišćenje</a></small></p>'
Help2: "<div class=\"advanced\"> <h4>Napredno korišćenje</h4> <ul> <li>Dozvoljene kolone: <em>%s</em></li> <li>Postojeće korisnici se prepoznaju po sopstvenom jedinstvenom svojstvu <em>Kôd</em> i ažuriraju novim vrednostima iz uvezene datoteke</li> <li>Grupe mogu biti dodeljene pomoću kolone <em>Grupe</em>. Grupe se identifikuju putem njihovog svojstva <em>Kôd</em>, a više grupa se razdvaja zarezom. Postojeće članstvo u grupama se ne briše.</li>\n</ul></div>"
ResultCreated: 'Kreirano {count} članova'
ResultDeleted: 'Izbrisano %d članova'
ResultNone: 'Bez promena'
ResultUpdated: 'Ažurirano {count} članova'
MemberPassword:
PLURALNAME: 'Lozinke članova'
SINGULARNAME: 'Lozinka člana'
MemberTableField:
APPLY_FILTER: 'Primeni filter'
ModelAdmin:
DELETE: Izbriši
DELETEDRECORDS: 'Izbrisano {count} zapisa'
EMPTYBEFOREIMPORT: 'Premesti podatke'
IMPORT: 'Uvezi iz CSV'
IMPORTEDRECORDS: 'Uvezeno {count} zapisa'
NOCSVFILE: 'Izaberite CSV datoteku za uvoz'
NOIMPORT: 'Nema ničega za uvoz'
RESET: Vrati u pređašnje stanje
Title: 'Modeli podataka'
UPDATEDRECORDS: 'Ažurirano {count} zapisa'
ModelAdmin_ImportSpec_ss:
IMPORTSPECFIELDS: 'Kolone baze podataka'
IMPORTSPECLINK: 'Prikaži specifikaciju za %s'
IMPORTSPECRELATIONS: Relacije
IMPORTSPECTITLE: 'Specifikacija za %s'
ModelAdmin_Tools_ss:
FILTER: Filter
IMPORT: Uvezi
ModelSidebar_ss:
IMPORT_TAB_HEADER: Uvezi
SEARCHLISTINGS: Pretraga
MoneyField:
FIELDLABELAMOUNT: Iznos
FIELDLABELCURRENCY: Valuta
NullableField:
IsNullLabel: 'je Null'
NumericField:
VALIDATION: '''{value}'' nije broj. Samo brojevi mogu biti prihvaćeni za ovo polje'
Pagination:
Page: Stranica
View: Pregled
PasswordValidator:
LOWCHARSTRENGTH: 'Pojačajte lozinku dodavanjem nekih od sledećih znakova: %s'
PREVPASSWORD: 'Već ste koristili navedenu lozinku u prošlosti. Stoga, izaberite drugu lozinku'
TOOSHORT: 'Lozinka je prekratka. Lozinka mora sadržati bar %s znakova'
Permission:
AdminGroup: Administrator
CMS_ACCESS_CATEGORY: 'Pristup CMS-u'
FULLADMINRIGHTS: 'Puna administrativna prava'
FULLADMINRIGHTS_HELP: 'Nadjačava sve druge dodeljene dozvole.'
PLURALNAME: Dozvole
SINGULARNAME: Dozvola
PermissionCheckboxSetField:
AssignedTo: 'dodeljeno "{title}"'
FromGroup: 'nasleđeno od grupe "{title}"'
FromRole: 'nasleđeno od uloge "{title}"'
FromRoleOnGroup: 'nasleđeno iz uloge "%s" za grupu "%s"'
PermissionRole:
OnlyAdminCanApply: 'Može primenjivati samo administrator'
PLURALNAME: Uloge
SINGULARNAME: Uloga
Title: Naslov
PermissionRoleCode:
PermsError: 'Nije moguće dodeliti kôd "%s" sa privilegovanim dozvolama (zahteva Administratorski pristup)'
SINGULARNAME: 'Kôd uloge za dozvole'
Permissions:
PERMISSIONS_CATEGORY: 'Uloge i prava pristupa'
UserPermissionsIntro: 'Dodavanjem ovog korisnika u grupu biće prilagođena i njegova prava pristupa. Podrobnija objašnjenja o pravima pristupa za pojedinačne grupe možete pronaći u sekciji "Grupe".'
PhoneNumberField:
VALIDATION: 'Unesite ispravan broj telefona'
Security:
ALREADYLOGGEDIN: 'Nemate dozvolu za pristup ovoj strani. Ukoliko imate drugi nalog kojim možete da pristupite ovoj strani, prijavite se.'
BUTTONSEND: 'Pošalji mi link za resetovanje lozinke'
CHANGEPASSWORDBELOW: 'Ovde možete da promenite svoju lozinku.'
CHANGEPASSWORDHEADER: 'Promeni moju lozinku'
ENTERNEWPASSWORD: 'Unesite novu lozinku.'
ERRORPASSWORDPERMISSION: 'Morate da budete prijavljeni da biste promenili svoju lozinku!'
LOGGEDOUT: 'Odjavljeni ste. Ukoliko želite da se ponovo prijavite, unesite svoje podatke.'
LOGIN: 'Prijavljivanje'
NOTEPAGESECURED: 'Ova strana je obezbeđena. Unesite svoje podatke i mi ćemo vam poslati sadržaj.'
NOTERESETLINKINVALID: '<p>Link za resetovanje lozinke je pogrešan ili je isteklo vreme za njegovo korišćenje.</p><p>Možete da zahtevate novi <a href="{link1}">ovde</a> ili da promenite Vašu lozinku nakon što se <a href="{link2}">prijavite</a>.</p>'
NOTERESETPASSWORD: 'Unesite svoju adresu e-pošte i mi ćemo vam poslati link pomoću kojeg možete da promenite svoju lozinku'
PASSWORDSENTHEADER: 'Link za resetovanje lozinke poslat je na adresu e-pošte: ''{email}'''
PASSWORDSENTTEXT: 'Hvala Vam! Link za resetovanje lozinke je poslat ne adresu e-pošte ''{email}''. Poruka će stići primaocu samo ako postoji registrovan nalog sa tom adresom e-pošte.'
SecurityAdmin:
ACCESS_HELP: 'Omogućava špregled, dodavanje i izmene korisnika, kao i dodeljivanje prava pristupa i uloga korisnicima.'
APPLY_ROLES: 'Dodaj uloge grupama'
APPLY_ROLES_HELP: 'Mogućnost izmena ulogâ grupâ. Zahteva dozvolu za pristup odeljku "Korisnici".'
EDITPERMISSIONS: 'Upravljaj pravima pristupa grupâ'
EDITPERMISSIONS_HELP: 'Mogućnost menjanja Prava pristupa i IP adresa grupâ. Zahteva dozvolu za pristup odeljku "Bezbednost".'
GROUPNAME: 'Ime grupe'
IMPORTGROUPS: 'Uvezi grupe'
IMPORTUSERS: 'Uvezi korisnike'
MEMBERS: Članovi
MENUTITLE: Bezbednost
MemberListCaution: 'Pažnja: Uklanjanje članova iz ove liste ukloniće ih iz svih grupa i iz baze podataka'
NEWGROUP: 'Nova grupa'
PERMISSIONS: Dozvole
ROLES: Uloge
ROLESDESCRIPTION: 'Uloge su predefinisani skupovi ovlašćenja i mogu biti dodeljene grupama.<br />Nasleđuju se od roditeljskih grupa ako je potrebno.'
TABROLES: Uloge
Users: Korisnici
SecurityAdmin_MemberImportForm:
BtnImport: 'Uvezi iz CSV'
FileFieldLabel: 'CSV datoteka <small>(Dozvoljene ekstenzije: *.csv)</small>'
SilverStripeNavigator:
Auto: Auto
ChangeViewMode: 'Promeni môd pregleda'
Desktop: Radna površina
DualWindowView: 'Dvostruki prozor'
Edit: Izmeni
EditView: 'Môd izmena'
Mobile: Mobilno
PreviewState: 'Stanje prethodnog pregleda'
PreviewView: 'Môd prethodnog pregleda'
Responsive: Prilagodljiv
SplitView: 'Razdeljeni môd'
Tablet: Tablet
ViewDeviceWidth: 'Izaberite širinu prethodnog pregleda'
Width: širina
SiteTree:
TABMAIN: Glavno
TableListField:
CSVEXPORT: 'Izvezi u CSV'
Print: Štampaj
TableListField_PageControls_ss:
OF: od
TimeField:
VALIDATEFORMAT: 'Unesite ispravan format vremena ({format})'
ToggleField:
LESS: manje
MORE: više
UploadField:
ATTACHFILE: 'Priključi datoteku'
ATTACHFILES: 'Priključi datoteke'
AttachFile: 'Priključi datoteku(e)'
CHOOSEANOTHERFILE: 'Izaberi drugu datoteku'
CHOOSEANOTHERINFO: 'Zameni ovu datoteku drugom sa servera'
DELETE: 'Izbriši iz datoteka'
DELETEINFO: 'Trajno izbriši ovu datoteku sa servera'
DOEDIT: Sačuvaj
DROPFILE: 'ispusti datoteku'
DROPFILES: 'ispusti datoteke'
Dimensions: Dimenzije
EDIT: Izmeni
EDITINFO: 'Izmeni ovu datoteku'
FIELDNOTSET: 'Informacije o datoteci nisu pronađene'
FROMCOMPUTER: 'Sa Vašeg računara'
FROMCOMPUTERINFO: 'Izaberi među datotekama'
FROMFILES: 'Iz datoteka'
HOTLINKINFO: 'Napomena: ova slika će biti ubačena pomoću hotlinka. Uverite se da imate ovlašćenje kreatora origilanog sajta da uradite to.'
MAXNUMBEROFFILES: 'Maksimalan broj datoteka ({count}) je premašen.'
MAXNUMBEROFFILESONE: 'Može postaviti samo jednu datoteku'
MAXNUMBEROFFILESSHORT: 'Može postaviti samo {count} datoteka'
OVERWRITEWARNING: 'Datoteka sa istim imenom već postoji'
REMOVE: Ukloni
REMOVEINFO: 'Uklonu ovu datoteku odavde, ali je ne briši sa servera'
STARTALL: 'Započni sve'
Saved: Sačuvano.
UPLOADSINTO: 'postalja u /{path}'
Versioned:
has_many_Versions: Verzije

View File

@ -60,6 +60,8 @@ sv:
ERRORNOTREC: 'Användarnamnet / lösenordet hittas inte' ERRORNOTREC: 'Användarnamnet / lösenordet hittas inte'
Boolean: Boolean:
ANY: Vilken som helst ANY: Vilken som helst
NOANSWER: 'Nej'
YESANSWER: 'Ja'
CMSLoadingScreen_ss: CMSLoadingScreen_ss:
LOADING: Laddar... LOADING: Laddar...
REQUIREJS: 'CMS:et kräver att du har javascript aktiverat.' REQUIREJS: 'CMS:et kräver att du har javascript aktiverat.'
@ -78,6 +80,22 @@ sv:
EMAIL: E-post EMAIL: E-post
HELLO: Hej HELLO: Hej
PASSWORD: Lösenord PASSWORD: Lösenord
CheckboxField:
NOANSWER: 'Nej'
YESANSWER: 'Ja'
CheckboxFieldSetField:
SOURCE_VALIDATION: 'Vänligen välj att värde i listan. {value} är inget giltigt val'
CMSMemberLoginForm:
BUTTONFORGOTPASSWORD: 'Glömt lösenord?'
BUTTONLOGIN: 'Logga in igen'
BUTTONLOGOUT: 'Logga ut'
PASSWORDEXPIRED: '<p>Ditt lösenard har gått ut. <a target="_top" href="{link}">Vänligen ange ett nytt.</a></p>'
CMSSecurity:
INVALIDUSER: '<p>Ogiltig användare. <a target="_top" href="{link}">Vänligen ange dina inloggnings-uppgifter igen</a> för att fortsätta.</p>'
LoginMessage: '<p>Om du har osparade ändringar kan du fortsätta där du slutade genom att logga in igen nedan.</p>'
SUCCESSCONTENT: '<p>Inloggningen lyckades. <a target="_top" href="{link}">Klicka här</a> om du inte skickas vidare automatiskt.</p>'
TimedOutTitleAnonymous: 'Din session har upphört.'
TimedOutTitleMember: 'Hej {name}!<br />Din session har upphört.'
ConfirmedPasswordField: ConfirmedPasswordField:
ATLEAST: 'Lösenord måste vara minst {min} tecken långa.' ATLEAST: 'Lösenord måste vara minst {min} tecken långa.'
BETWEEN: 'Lösenord måste vara {min} till {max} tecken långa.' BETWEEN: 'Lösenord måste vara {min} till {max} tecken långa.'
@ -124,6 +142,7 @@ sv:
DropdownField: DropdownField:
CHOOSE: (Välj) CHOOSE: (Välj)
CHOOSESEARCH: '(Välj eller sök)' CHOOSESEARCH: '(Välj eller sök)'
SOURCE_VALIDATION: 'Vänligen välj att värde i listan. {value} är inget giltigt val'
EmailField: EmailField:
VALIDATION: 'Var snäll och ange en epostadress' VALIDATION: 'Var snäll och ange en epostadress'
Enum: Enum:
@ -171,7 +190,7 @@ sv:
TEXT2: 'Återställningslänk för lösenord' TEXT2: 'Återställningslänk för lösenord'
TEXT3: för TEXT3: för
Form: Form:
CSRF_FAILED_MESSAGE: "Ett tekniskt fel uppstod. Var god klicka på bakåt-knappen,\n⇥⇥⇥⇥⇥ladda om webbläsaren och försök igen." CSRF_FAILED_MESSAGE: 'Ett tekniskt fel uppstod. Var god klicka på bakåt-knappen, ladda om sidan i webbläsaren och försök igen'
FIELDISREQUIRED: '{name} är obligatoriskt' FIELDISREQUIRED: '{name} är obligatoriskt'
SubmitBtnLabel: Kör SubmitBtnLabel: Kör
VALIDATIONCREDITNUMBER: 'Kontrollera att du angav kortnummret {number} rätt' VALIDATIONCREDITNUMBER: 'Kontrollera att du angav kortnummret {number} rätt'
@ -238,7 +257,6 @@ sv:
many_many_Members: Medlemmar many_many_Members: Medlemmar
GroupImportForm: GroupImportForm:
Help1: '<p>Importera en eller flera grupper i <em>CSV</em>- format (komma-separerade värden). <small><a href="#" class ="toggle-advanced">Visa avancerade val</a></small></p>' Help1: '<p>Importera en eller flera grupper i <em>CSV</em>- format (komma-separerade värden). <small><a href="#" class ="toggle-advanced">Visa avancerade val</a></small></p>'
Help2: "<div class=\"advanced\">\n<h4>Avancerat </h4>\n<ul>\n<li>Tillåtna kolumner: <em>%s</em></li>\n<li>Existerade användare matchas av deras unika <em>kod</em>-attribut och uppdateras med alla nya värden från den importerade filen</li>\n<li>Grupper kan anges i <em>Grupp</em>-kolumnen. Grupper identiferas av deras <em>Code</em>-attribut. Anges flera grupper separeras dessa med kommatecken. Existerande användarrättigheter till grupperna tas inte bort.</li>\n</ul>\n</div>"
ResultCreated: 'Skapade {count} grupper' ResultCreated: 'Skapade {count} grupper'
ResultDeleted: 'Raderade %d grupper' ResultDeleted: 'Raderade %d grupper'
ResultUpdated: 'Uppdaterade %d grupper' ResultUpdated: 'Uppdaterade %d grupper'
@ -247,6 +265,8 @@ sv:
HtmlEditorField: HtmlEditorField:
ADDURL: 'Lägg till URL' ADDURL: 'Lägg till URL'
ADJUSTDETAILSDIMENSIONS: 'Detaljer &amp; dimensioner' ADJUSTDETAILSDIMENSIONS: 'Detaljer &amp; dimensioner'
ANCHORSCANNOTACCESSPAGE: 'Du har inte tillåtelse att se innehållet på sidan'
ANCHORSPAGENOTFOUND: 'Målsidan hittades inte'
ANCHORVALUE: Ankare ANCHORVALUE: Ankare
BUTTONADDURL: 'Lägg till URL' BUTTONADDURL: 'Lägg till URL'
BUTTONINSERT: Infoga BUTTONINSERT: Infoga
@ -290,6 +310,7 @@ sv:
URL: URL URL: URL
URLNOTANOEMBEDRESOURCE: 'URLen ''{url}'' gick inte att omvandla till ett media.' URLNOTANOEMBEDRESOURCE: 'URLen ''{url}'' gick inte att omvandla till ett media.'
UpdateMEDIA: 'Uppdatera media' UpdateMEDIA: 'Uppdatera media'
SUBJECT: 'Ämne'
Image: Image:
PLURALNAME: Filer PLURALNAME: Filer
SINGULARNAME: Fil SINGULARNAME: Fil
@ -307,7 +328,7 @@ sv:
PERMAGAIN: 'Du har blivit utloggad. Om du vill logga in igen anger du dina uppgifter nedan.' PERMAGAIN: 'Du har blivit utloggad. Om du vill logga in igen anger du dina uppgifter nedan.'
PERMALREADY: 'Tyvärr så har du inte tillträde till den delen av CMSet. Om du vill logga in med en annan användare kan du göra det nedan' PERMALREADY: 'Tyvärr så har du inte tillträde till den delen av CMSet. Om du vill logga in med en annan användare kan du göra det nedan'
PERMDEFAULT: 'Var god välj en inloggningsmetod och fyll i dina uppgifter för att logga in i CMSet.' PERMDEFAULT: 'Var god välj en inloggningsmetod och fyll i dina uppgifter för att logga in i CMSet.'
PLEASESAVE: 'Var god spara sidan. Den kan inte uppdateras för att den har inte sparats ännu.' PLEASESAVE: 'Var god spara sidan. Den kan inte uppdateras eftersom den har inte sparats ännu.'
PreviewButton: Förhandsgranska PreviewButton: Förhandsgranska
REORGANISATIONSUCCESSFUL: 'Omorganisationen av sidträdet luyckades.' REORGANISATIONSUCCESSFUL: 'Omorganisationen av sidträdet luyckades.'
SAVEDUP: Sparad. SAVEDUP: Sparad.
@ -318,6 +339,8 @@ sv:
LeftAndMain_Menu_ss: LeftAndMain_Menu_ss:
Hello: Hej Hello: Hej
LOGOUT: 'Logga ut' LOGOUT: 'Logga ut'
ListboxField:
SOURCE_VALIDATION: 'Vänligen välj att värde i listan. {value} är inget giltigt val'
LoginAttempt: LoginAttempt:
Email: 'E-postadress' Email: 'E-postadress'
IP: 'IP-adress' IP: 'IP-adress'
@ -350,6 +373,7 @@ sv:
NEWPASSWORD: 'Nytt lösenord' NEWPASSWORD: 'Nytt lösenord'
NoPassword: 'Det finns inget lösenord för den här medlemmen' NoPassword: 'Det finns inget lösenord för den här medlemmen'
PASSWORD: Lösenord PASSWORD: Lösenord
PASSWORDEXPIRED: 'Ditt lösenord har gått ut. Vänligen ange ett nytt.'
PLURALNAME: Medlemmar PLURALNAME: Medlemmar
REMEMBERME: 'Kom ihåg mig nästa gång?' REMEMBERME: 'Kom ihåg mig nästa gång?'
SINGULARNAME: Medlem SINGULARNAME: Medlem
@ -456,7 +480,6 @@ sv:
SINGULARNAME: Roll SINGULARNAME: Roll
Title: Rollnamn Title: Rollnamn
PermissionRoleCode: PermissionRoleCode:
PLURALNAME: 'Koder för rollrättigheter'
PermsError: 'Koden "%s" kan inte ges privilegierad tillgång (adminrättigheter krävs)' PermsError: 'Koden "%s" kan inte ges privilegierad tillgång (adminrättigheter krävs)'
SINGULARNAME: 'Kodför rollrättigheter' SINGULARNAME: 'Kodför rollrättigheter'
Permissions: Permissions:
@ -557,3 +580,5 @@ sv:
UPLOADSINTO: 'sparas till /{path}' UPLOADSINTO: 'sparas till /{path}'
Versioned: Versioned:
has_many_Versions: Versioner has_many_Versions: Versioner
CheckboxSetField:
SOURCE_VALIDATION: 'Vänligen välj att värde i listan. {value} är inget giltigt val'

View File

@ -116,7 +116,11 @@ class DataDifferencer extends ViewableData {
$toTitle = ''; $toTitle = '';
if($this->toRecord->hasMethod($relName)) { if($this->toRecord->hasMethod($relName)) {
$relObjTo = $this->toRecord->$relName(); $relObjTo = $this->toRecord->$relName();
$toTitle = $relObjTo->hasMethod('Title') || $relObjTo->hasField('Title') ? $relObjTo->Title : ''; if($relObjTo) {
$toTitle = ($relObjTo->hasMethod('Title') || $relObjTo->hasField('Title')) ? $relObjTo->Title : '';
} else {
$toTitle = '';
}
} }
if(!$this->fromRecord) { if(!$this->fromRecord) {
@ -134,7 +138,12 @@ class DataDifferencer extends ViewableData {
$fromTitle = ''; $fromTitle = '';
if($this->fromRecord->hasMethod($relName)) { if($this->fromRecord->hasMethod($relName)) {
$relObjFrom = $this->fromRecord->$relName(); $relObjFrom = $this->fromRecord->$relName();
$fromTitle = $relObjFrom->hasMethod('Title') || $relObjFrom->hasField('Title') ? $relObjFrom->Title : ''; if($relObjFrom) {
$fromTitle = ($relObjFrom->hasMethod('Title') || $relObjFrom->hasField('Title')) ? $relObjFrom->Title : '';
} else {
$fromTitle = '';
}
} }
if(isset($relObjFrom) && $relObjFrom instanceof Image) { if(isset($relObjFrom) && $relObjFrom instanceof Image) {
// TODO Use CMSThumbnail (see above) // TODO Use CMSThumbnail (see above)

View File

@ -180,6 +180,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
protected static $_cache_get_one; protected static $_cache_get_one;
protected static $_cache_get_class_ancestry; protected static $_cache_get_class_ancestry;
protected static $_cache_composite_fields = array(); protected static $_cache_composite_fields = array();
protected static $_cache_is_composite_field = array();
protected static $_cache_custom_database_fields = array(); protected static $_cache_custom_database_fields = array();
protected static $_cache_field_labels = array(); protected static $_cache_field_labels = array();
@ -360,14 +361,25 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @return string Class name of composite field if it exists * @return string Class name of composite field if it exists
*/ */
public static function is_composite_field($class, $name, $aggregated = true) { public static function is_composite_field($class, $name, $aggregated = true) {
if(!isset(DataObject::$_cache_composite_fields[$class])) self::cache_composite_fields($class); $key = $class . '_' . $name . '_' . (string)$aggregated;
if(isset(DataObject::$_cache_composite_fields[$class][$name])) { if(!isset(DataObject::$_cache_is_composite_field[$key])) {
return DataObject::$_cache_composite_fields[$class][$name]; $isComposite = null;
} else if($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') { if(!isset(DataObject::$_cache_composite_fields[$class])) {
return self::is_composite_field($parentClass, $name); self::cache_composite_fields($class);
}
if(isset(DataObject::$_cache_composite_fields[$class][$name])) {
$isComposite = DataObject::$_cache_composite_fields[$class][$name];
} elseif($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
$isComposite = self::is_composite_field($parentClass, $name);
}
DataObject::$_cache_is_composite_field[$key] = ($isComposite) ? $isComposite : false;
} }
return DataObject::$_cache_is_composite_field[$key] ?: null;
} }
/** /**
@ -3184,6 +3196,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
DataObject::$_cache_db = array(); DataObject::$_cache_db = array();
DataObject::$_cache_get_one = array(); DataObject::$_cache_get_one = array();
DataObject::$_cache_composite_fields = array(); DataObject::$_cache_composite_fields = array();
DataObject::$_cache_is_composite_field = array();
DataObject::$_cache_custom_database_fields = array(); DataObject::$_cache_custom_database_fields = array();
DataObject::$_cache_get_class_ancestry = array(); DataObject::$_cache_get_class_ancestry = array();
DataObject::$_cache_field_labels = array(); DataObject::$_cache_field_labels = array();

View File

@ -315,7 +315,7 @@ class Hierarchy extends DataExtension {
} }
// Set jstree open state, or mark it as a leaf (closed) if there are no children // Set jstree open state, or mark it as a leaf (closed) if there are no children
if(!$this->$numChildrenMethod()) { if(!$this->owner->$numChildrenMethod()) {
$classes .= " jstree-leaf closed"; $classes .= " jstree-leaf closed";
} elseif($this->isTreeOpened()) { } elseif($this->isTreeOpened()) {
$classes .= " jstree-open"; $classes .= " jstree-open";

View File

@ -10,7 +10,7 @@ interface SS_List extends ArrayAccess, Countable, IteratorAggregate {
/** /**
* Returns all the items in the list in an array. * Returns all the items in the list in an array.
* *
* @return arary * @return array
*/ */
public function toArray(); public function toArray();

View File

@ -44,8 +44,9 @@ class SS_Transliterator extends Object {
'þ'=>'b', 'ÿ'=>'y', 'Ŕ'=>'R', 'ŕ'=>'r', 'þ'=>'b', 'ÿ'=>'y', 'Ŕ'=>'R', 'ŕ'=>'r',
'Ā'=>'A', 'ā'=>'a', 'Ē'=>'E', 'ē'=>'e', 'Ī'=>'I', 'ī'=>'i', 'Ō'=>'O', 'ō'=>'o', 'Ū'=>'U', 'ū'=>'u', 'Ā'=>'A', 'ā'=>'a', 'Ē'=>'E', 'ē'=>'e', 'Ī'=>'I', 'ī'=>'i', 'Ō'=>'O', 'ō'=>'o', 'Ū'=>'U', 'ū'=>'u',
'œ'=>'oe', 'ß'=>'ss', 'ij'=>'ij', 'ą'=>'a','ę'=>'e', 'ė'=>'e', 'į'=>'i','ų'=>'u','ū'=>'u', 'Ą'=>'A', 'œ'=>'oe', 'ß'=>'ss', 'ij'=>'ij', 'ą'=>'a','ę'=>'e', 'ė'=>'e', 'į'=>'i','ų'=>'u','ū'=>'u', 'Ą'=>'A',
'Ę'=>'E', 'Ė'=>'E', 'Į'=>'I','Ų'=>'U','Ū'=>'u', 'Ę'=>'E', 'Ė'=>'E', 'Į'=>'I','Ų'=>'U','Ū'=>'U',
"ľ"=>"l", "Ľ"=>"L", "ť"=>"t", "Ť"=>"T", "ů"=>"u", "Ů"=>"U", "ľ"=>"l", "Ľ"=>"L", "ť"=>"t", "Ť"=>"T", "ů"=>"u", "Ů"=>"U",
'ł'=>'l', 'Ł'=>'L', 'ń'=>'n', 'Ń'=>'N', 'ś'=>'s', 'Ś'=>'S', 'ź'=>'z', 'Ź'=>'Z', 'ż'=>'z', 'Ż'=>'Z',
); );
return strtr($source, $table); return strtr($source, $table);

View File

@ -105,15 +105,18 @@ class SQLSelect extends SQLConditionalExpression {
* *
* <code> * <code>
* // pass fields to select as single parameter array * // pass fields to select as single parameter array
* $query->setSelect(array("Col1","Col2"))->setFrom("MyTable"); * $query->setSelect(array('"Col1"', '"Col2"'))->setFrom('"MyTable"');
* *
* // pass fields to select as multiple parameters * // pass fields to select as multiple parameters
* $query->setSelect("Col1", "Col2")->setFrom("MyTable"); * $query->setSelect('"Col1"', '"Col2"')->setFrom('"MyTable"');
*
* // Set a list of selected fields as aliases
* $query->setSelect(array('Name' => '"Col1"', 'Details' => '"Col2"')->setFrom('"MyTable"');
* </code> * </code>
* *
* @param string|array $fields * @param string|array $fields Field names should be ANSI SQL quoted. Array keys should be unquoted.
* @param bool $clear Clear existing select fields? * @param boolean $clear Clear existing select fields?
* @return self Self reference * @return $this Self reference
*/ */
public function setSelect($fields) { public function setSelect($fields) {
$this->select = array(); $this->select = array();
@ -128,17 +131,10 @@ class SQLSelect extends SQLConditionalExpression {
/** /**
* Add to the list of columns to be selected by the query. * Add to the list of columns to be selected by the query.
* *
* <code> * @see setSelect for example usage
* // pass fields to select as single parameter array
* $query->addSelect(array("Col1","Col2"))->setFrom("MyTable");
* *
* // pass fields to select as multiple parameters * @param string|array $fields Field names should be ANSI SQL quoted. Array keys should be unquoted.
* $query->addSelect("Col1", "Col2")->setFrom("MyTable"); * @return $this Self reference
* </code>
*
* @param string|array $fields
* @param bool $clear Clear existing select fields?
* @return self Self reference
*/ */
public function addSelect($fields) { public function addSelect($fields) {
if (func_num_args() > 1) { if (func_num_args() > 1) {
@ -146,9 +142,13 @@ class SQLSelect extends SQLConditionalExpression {
} else if(!is_array($fields)) { } else if(!is_array($fields)) {
$fields = array($fields); $fields = array($fields);
} }
foreach($fields as $idx => $field) { foreach($fields as $idx => $field) {
$this->selectField($field, is_numeric($idx) ? null : $idx); if(preg_match('/^(.*) +AS +"([^"]*)"/i', $field, $matches)) {
Deprecation::notice("3.0", "Use selectField() to specify column aliases");
$this->selectField($matches[1], $matches[2]);
} else {
$this->selectField($field, is_numeric($idx) ? null : $idx);
}
} }
return $this; return $this;
@ -157,10 +157,10 @@ class SQLSelect extends SQLConditionalExpression {
/** /**
* Select an additional field. * Select an additional field.
* *
* @param $field string The field to select (escaped SQL statement) * @param string $field The field to select (ansi quoted SQL identifier or statement)
* @param $alias string The alias of that field (escaped SQL statement). * @param string|null $alias The alias of that field (unquoted SQL identifier).
* Defaults to the unquoted column name of the $field parameter. * Defaults to the unquoted column name of the $field parameter.
* @return self Self reference * @return $this Self reference
*/ */
public function selectField($field, $alias = null) { public function selectField($field, $alias = null) {
if(!$alias) { if(!$alias) {
@ -264,9 +264,9 @@ class SQLSelect extends SQLConditionalExpression {
* @example $sql->setOrderBy(array("Column" => "ASC", "ColumnTwo" => "DESC")); * @example $sql->setOrderBy(array("Column" => "ASC", "ColumnTwo" => "DESC"));
* *
* @param string|array $clauses Clauses to add (escaped SQL statement) * @param string|array $clauses Clauses to add (escaped SQL statement)
* @param string $dir Sort direction, ASC or DESC * @param string $direction Sort direction, ASC or DESC
* *
* @return self Self reference * @return $this Self reference
*/ */
public function setOrderBy($clauses = null, $direction = null) { public function setOrderBy($clauses = null, $direction = null) {
$this->orderby = array(); $this->orderby = array();
@ -284,7 +284,7 @@ class SQLSelect extends SQLConditionalExpression {
* *
* @param string|array $clauses Clauses to add (escaped SQL statements) * @param string|array $clauses Clauses to add (escaped SQL statements)
* @param string $direction Sort direction, ASC or DESC * @param string $direction Sort direction, ASC or DESC
* @return self Self reference * @return $this Self reference
*/ */
public function addOrderBy($clauses = null, $direction = null) { public function addOrderBy($clauses = null, $direction = null) {
if(empty($clauses)) return $this; if(empty($clauses)) return $this;
@ -350,9 +350,9 @@ class SQLSelect extends SQLConditionalExpression {
/** /**
* Extract the direction part of a single-column order by clause. * Extract the direction part of a single-column order by clause.
* *
* @param String * @param string $value
* @param String * @param string $defaultDirection
* @return Array A two element array: array($column, $direction) * @return array A two element array: array($column, $direction)
*/ */
private function getDirectionFromString($value, $defaultDirection = null) { private function getDirectionFromString($value, $defaultDirection = null) {
if(preg_match('/^(.*)(asc|desc)$/i', $value, $matches)) { if(preg_match('/^(.*)(asc|desc)$/i', $value, $matches)) {
@ -550,7 +550,7 @@ class SQLSelect extends SQLConditionalExpression {
$clone->setSelect(array("count($column)")); $clone->setSelect(array("count($column)"));
} }
$clone->setGroupBy(array());; $clone->setGroupBy(array());
return $clone->execute()->value(); return $clone->execute()->value();
} }

View File

@ -9,15 +9,15 @@
* @package framework * @package framework
* @subpackage misc * @subpackage misc
*/ */
class ShortcodeParser { class ShortcodeParser extends Object {
public function img_shortcode($attrs) { public function img_shortcode($attrs) {
return "<img src='".$attrs['src']."'>"; return "<img src='".$attrs['src']."'>";
} }
private static $instances = array(); protected static $instances = array();
private static $active_instance = 'default'; protected static $active_instance = 'default';
// -------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------
@ -33,7 +33,7 @@ class ShortcodeParser {
*/ */
public static function get($identifier = 'default') { public static function get($identifier = 'default') {
if(!array_key_exists($identifier, self::$instances)) { if(!array_key_exists($identifier, self::$instances)) {
self::$instances[$identifier] = new ShortcodeParser(); self::$instances[$identifier] = static::create();
} }
return self::$instances[$identifier]; return self::$instances[$identifier];
@ -45,7 +45,7 @@ class ShortcodeParser {
* @return ShortcodeParser * @return ShortcodeParser
*/ */
public static function get_active() { public static function get_active() {
return self::get(self::$active_instance); return static::get(self::$active_instance);
} }
/** /**
@ -140,15 +140,15 @@ class ShortcodeParser {
} }
} }
private static $marker_class = '--ss-shortcode-marker'; protected static $marker_class = '--ss-shortcode-marker';
private static $block_level_elements = array( protected static $block_level_elements = array(
'address', 'article', 'aside', 'audio', 'blockquote', 'canvas', 'dd', 'div', 'dl', 'fieldset', 'figcaption', 'address', 'article', 'aside', 'audio', 'blockquote', 'canvas', 'dd', 'div', 'dl', 'fieldset', 'figcaption',
'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'ol', 'output', 'p', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'ol', 'output', 'p',
'pre', 'section', 'table', 'ul' 'pre', 'section', 'table', 'ul'
); );
private static $attrrx = ' protected static $attrrx = '
([^\s\/\'"=,]+) # Name ([^\s\/\'"=,]+) # Name
\s* = \s* \s* = \s*
(?: (?:
@ -158,11 +158,11 @@ class ShortcodeParser {
) )
'; ';
private static function attrrx() { protected static function attrrx() {
return '/'.self::$attrrx.'/xS'; return '/'.self::$attrrx.'/xS';
} }
private static $tagrx = ' protected static $tagrx = '
# HTML Tag # HTML Tag
<(?<element>(?:"[^"]*"[\'"]*|\'[^\']*\'[\'"]*|[^\'">])+)> <(?<element>(?:"[^"]*"[\'"]*|\'[^\']*\'[\'"]*|[^\'">])+)>
@ -182,7 +182,7 @@ class ShortcodeParser {
(?<cesc2>\]?) (?<cesc2>\]?)
'; ';
private static function tagrx() { protected static function tagrx() {
return '/'.sprintf(self::$tagrx, self::$attrrx).'/xS'; return '/'.sprintf(self::$tagrx, self::$attrrx).'/xS';
} }
@ -207,7 +207,7 @@ class ShortcodeParser {
protected function extractTags($content) { protected function extractTags($content) {
$tags = array(); $tags = array();
if(preg_match_all(self::tagrx(), $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { if(preg_match_all(static::tagrx(), $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
foreach($matches as $match) { foreach($matches as $match) {
// Ignore any elements // Ignore any elements
if (empty($match['open'][0]) && empty($match['close'][0])) continue; if (empty($match['open'][0]) && empty($match['close'][0])) continue;
@ -216,7 +216,7 @@ class ShortcodeParser {
$attrs = array(); $attrs = array();
if (!empty($match['attrs'][0])) { if (!empty($match['attrs'][0])) {
preg_match_all(self::attrrx(), $match['attrs'][0], $attrmatches, PREG_SET_ORDER); preg_match_all(static::attrrx(), $match['attrs'][0], $attrmatches, PREG_SET_ORDER);
foreach ($attrmatches as $attr) { foreach ($attrmatches as $attr) {
list($whole, $name, $value) = array_values(array_filter($attr)); list($whole, $name, $value) = array_values(array_filter($attr));

View File

@ -332,10 +332,14 @@ $gf_grid_x: 16px;
} }
} }
&:hover { &:hover {
background: #FFFAD6 !important; background: #FFFAD6;
} }
&:first-child { &:first-child {
background: transparent; background: transparent;
&:hover {
background: #FFFAD6;
}
} }
&.ss-gridfield-even { &.ss-gridfield-even {
background: $gf_colour_zebra; background: $gf_colour_zebra;
@ -343,9 +347,16 @@ $gf_grid_x: 16px;
&.ss-gridfield-last { &.ss-gridfield-last {
border-bottom: none; border-bottom: none;
} }
&:hover {
background: #FFFAD6;
}
} }
&.even { &.even {
background: $gf_colour_zebra; background: $gf_colour_zebra;
&:hover {
background: #FFFAD6;
}
} }
th { th {

View File

@ -108,12 +108,12 @@ class ChangePasswordForm extends Form {
$member->FailedLoginCount = null; $member->FailedLoginCount = null;
$member->write(); $member->write();
if (isset($_REQUEST['BackURL']) if (!empty($_REQUEST['BackURL'])
&& $_REQUEST['BackURL']
// absolute redirection URLs may cause spoofing // absolute redirection URLs may cause spoofing
&& Director::is_site_url($_REQUEST['BackURL']) && Director::is_site_url($_REQUEST['BackURL'])
) { ) {
return $this->controller->redirect($_REQUEST['BackURL']); $url = Director::absoluteURL($_REQUEST['BackURL']);
return $this->controller->redirect($url);
} }
else { else {
// Redirect to default location - the login form saying "You are logged in as..." // Redirect to default location - the login form saying "You are logged in as..."

View File

@ -194,7 +194,7 @@ JS;
* ) * )
* *
* @param array $data * @param array $data
* @return void * @return SS_HTTPResponse
*/ */
protected function logInUserAndRedirect($data) { protected function logInUserAndRedirect($data) {
Session::clear('SessionForms.MemberLoginForm.Email'); Session::clear('SessionForms.MemberLoginForm.Email');
@ -213,18 +213,21 @@ JS;
} }
// Absolute redirection URLs may cause spoofing // Absolute redirection URLs may cause spoofing
if(isset($_REQUEST['BackURL']) && $_REQUEST['BackURL'] && Director::is_site_url($_REQUEST['BackURL']) ) { if(!empty($_REQUEST['BackURL'])) {
return $this->controller->redirect($_REQUEST['BackURL']); $url = $_REQUEST['BackURL'];
} if(Director::is_site_url($url) ) {
$url = Director::absoluteURL($url);
// Spoofing attack, redirect to homepage instead of spoofing url } else {
if(isset($_REQUEST['BackURL']) && $_REQUEST['BackURL'] && !Director::is_site_url($_REQUEST['BackURL'])) { // Spoofing attack, redirect to homepage instead of spoofing url
return $this->controller->redirect(Director::absoluteBaseURL()); $url = Director::absoluteBaseURL();
}
return $this->controller->redirect($url);
} }
// If a default login dest has been set, redirect to that. // If a default login dest has been set, redirect to that.
if (Security::config()->default_login_dest) { if ($url = Security::config()->default_login_dest) {
return $this->controller->redirect(Director::absoluteBaseURL() . Security::config()->default_login_dest); $url = Controller::join_links(Director::absoluteBaseURL(), $url);
return $this->controller->redirect($url);
} }
// Redirect the user to the page where they came from // Redirect the user to the page where they came from

View File

@ -329,14 +329,15 @@ class ControllerTest extends FunctionalTest {
public function testRedirectBackByReferer() { public function testRedirectBackByReferer() {
$internalRelativeUrl = '/some-url'; $internalRelativeUrl = '/some-url';
$internalAbsoluteUrl = Controller::join_links(Director::absoluteBaseURL(), '/some-url');
$response = $this->get('ControllerTest_Controller/redirectbacktest', null, $response = $this->get('ControllerTest_Controller/redirectbacktest', null,
array('Referer' => $internalRelativeUrl)); array('Referer' => $internalRelativeUrl));
$this->assertEquals(302, $response->getStatusCode()); $this->assertEquals(302, $response->getStatusCode());
$this->assertEquals($internalRelativeUrl, $response->getHeader('Location'), $this->assertEquals($internalAbsoluteUrl, $response->getHeader('Location'),
"Redirects on internal relative URLs" "Redirects on internal relative URLs"
); );
$internalAbsoluteUrl = Director::absoluteBaseURL() . '/some-url';
$response = $this->get('ControllerTest_Controller/redirectbacktest', null, $response = $this->get('ControllerTest_Controller/redirectbacktest', null,
array('Referer' => $internalAbsoluteUrl)); array('Referer' => $internalAbsoluteUrl));
$this->assertEquals(302, $response->getStatusCode()); $this->assertEquals(302, $response->getStatusCode());
@ -354,9 +355,11 @@ class ControllerTest extends FunctionalTest {
public function testRedirectBackByBackUrl() { public function testRedirectBackByBackUrl() {
$internalRelativeUrl = '/some-url'; $internalRelativeUrl = '/some-url';
$internalAbsoluteUrl = Controller::join_links(Director::absoluteBaseURL(), '/some-url');
$response = $this->get('ControllerTest_Controller/redirectbacktest?BackURL=' . urlencode($internalRelativeUrl)); $response = $this->get('ControllerTest_Controller/redirectbacktest?BackURL=' . urlencode($internalRelativeUrl));
$this->assertEquals(302, $response->getStatusCode()); $this->assertEquals(302, $response->getStatusCode());
$this->assertEquals($internalRelativeUrl, $response->getHeader('Location'), $this->assertEquals($internalAbsoluteUrl, $response->getHeader('Location'),
"Redirects on internal relative URLs" "Redirects on internal relative URLs"
); );

View File

@ -25,9 +25,11 @@ class DirectorTest extends SapphireTest {
if(!self::$originalRequestURI) { if(!self::$originalRequestURI) {
self::$originalRequestURI = $_SERVER['REQUEST_URI']; self::$originalRequestURI = $_SERVER['REQUEST_URI'];
} }
$_SERVER['REQUEST_URI'] = 'http://www.mysite.com';
$this->originalGet = $_GET; $this->originalGet = $_GET;
$this->originalSession = $_SESSION; $this->originalSession = $_SESSION;
$_SESSION = array();
Config::inst()->update('Director', 'rules', array( Config::inst()->update('Director', 'rules', array(
'DirectorTestRule/$Action/$ID/$OtherID' => 'DirectorTestRequest_Controller', 'DirectorTestRule/$Action/$ID/$OtherID' => 'DirectorTestRequest_Controller',
@ -136,27 +138,47 @@ class DirectorTest extends SapphireTest {
} }
public function testAlternativeBaseURL() { public function testAlternativeBaseURL() {
// Get original protocol and hostname
$rootURL = Director::protocolAndHost();
// relative base URLs - you should end them in a / // relative base URLs - you should end them in a /
Config::inst()->update('Director', 'alternate_base_url', '/relativebase/'); Config::inst()->update('Director', 'alternate_base_url', '/relativebase/');
$_SERVER['REQUEST_URI'] = "$rootURL/relativebase/sub-page/";
$this->assertEquals('/relativebase/', Director::baseURL()); $this->assertEquals('/relativebase/', Director::baseURL());
$this->assertEquals(Director::protocolAndHost() . '/relativebase/', Director::absoluteBaseURL()); $this->assertEquals($rootURL . '/relativebase/', Director::absoluteBaseURL());
$this->assertEquals(Director::protocolAndHost() . '/relativebase/subfolder/test', $this->assertEquals(
Director::absoluteURL('subfolder/test')); $rootURL . '/relativebase/subfolder/test',
Director::absoluteURL('subfolder/test')
);
// absolute base URLs - you should end them in a / // absolute base URLs - you should end them in a /
Config::inst()->update('Director', 'alternate_base_url', 'http://www.example.org/'); Config::inst()->update('Director', 'alternate_base_url', 'http://www.example.org/');
$_SERVER['REQUEST_URI'] = "http://www.example.org/"; $_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
$this->assertEquals('http://www.example.org/', Director::baseURL()); $this->assertEquals('http://www.example.org/', Director::baseURL());
$this->assertEquals('http://www.example.org/', Director::absoluteBaseURL()); $this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
$this->assertEquals('http://www.example.org/', Director::absoluteURL('')); $this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL(''));
$this->assertEquals('http://www.example.org/subfolder/test', Director::absoluteURL('subfolder/test')); $this->assertEquals('http://www.example.org/', Director::absoluteURL('', true));
/*
* See Legacy behaviour in testAbsoluteURL - sub-pages with '/' in the string are not correctly evaluated
$this->assertEquals(
'http://www.example.org/sub-page/subfolder/test',
Director::absoluteURL('subfolder/test')
);*/
$this->assertEquals(
'http://www.example.org/subfolder/test',
Director::absoluteURL('subfolder/test', true)
);
// Setting it to false restores functionality // Setting it to false restores functionality
Config::inst()->update('Director', 'alternate_base_url', false); Config::inst()->update('Director', 'alternate_base_url', false);
$_SERVER['REQUEST_URI'] = $rootURL;
$this->assertEquals(BASE_URL.'/', Director::baseURL()); $this->assertEquals(BASE_URL.'/', Director::baseURL());
$this->assertEquals(Director::protocolAndHost().BASE_URL.'/', Director::absoluteBaseURL(BASE_URL)); $this->assertEquals($rootURL.BASE_URL.'/', Director::absoluteBaseURL(BASE_URL));
$this->assertEquals(Director::protocolAndHost().BASE_URL . '/subfolder/test', $this->assertEquals(
Director::absoluteURL('subfolder/test')); $rootURL.BASE_URL . '/subfolder/test',
Director::absoluteURL('subfolder/test')
);
} }
/** /**
@ -418,6 +440,9 @@ class DirectorTest extends SapphireTest {
} }
public function testIsHttps() { public function testIsHttps() {
if(!TRUSTED_PROXY) {
$this->markTestSkipped('Test cannot be run without trusted proxy');
}
// nothing available // nothing available
$headers = array( $headers = array(
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL' 'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'

View File

@ -32,18 +32,26 @@ class ParameterConfirmationTokenTest extends SapphireTest {
return array($answer, $slash); return array($answer, $slash);
} }
protected $oldHost = null;
public function setUp() { public function setUp() {
parent::setUp(); parent::setUp();
$this->oldHost = $_SERVER['HTTP_HOST'];
$_GET['parameterconfirmationtokentest_notoken'] = 'value'; $_GET['parameterconfirmationtokentest_notoken'] = 'value';
$_GET['parameterconfirmationtokentest_empty'] = ''; $_GET['parameterconfirmationtokentest_empty'] = '';
$_GET['parameterconfirmationtokentest_withtoken'] = '1'; $_GET['parameterconfirmationtokentest_withtoken'] = '1';
$_GET['parameterconfirmationtokentest_withtokentoken'] = 'dummy'; $_GET['parameterconfirmationtokentest_withtokentoken'] = 'dummy';
$_GET['parameterconfirmationtokentest_nulltoken'] = '1';
$_GET['parameterconfirmationtokentest_nulltokentoken'] = null;
$_GET['parameterconfirmationtokentest_emptytoken'] = '1';
$_GET['parameterconfirmationtokentest_emptytokentoken'] = '';
} }
public function tearDown() { public function tearDown() {
foreach($_GET as $param) { foreach($_GET as $param) {
if(stripos($param, 'parameterconfirmationtokentest_') === 0) unset($_GET[$param]); if(stripos($param, 'parameterconfirmationtokentest_') === 0) unset($_GET[$param]);
} }
$_SERVER['HTTP_HOST'] = $this->oldHost;
parent::tearDown(); parent::tearDown();
} }
@ -52,24 +60,32 @@ class ParameterConfirmationTokenTest extends SapphireTest {
$emptyParameter = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_empty'); $emptyParameter = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_empty');
$withToken = new ParameterConfirmationTokenTest_ValidToken('parameterconfirmationtokentest_withtoken'); $withToken = new ParameterConfirmationTokenTest_ValidToken('parameterconfirmationtokentest_withtoken');
$withoutParameter = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_noparam'); $withoutParameter = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_noparam');
$nullToken = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_nulltoken');
$emptyToken = new ParameterConfirmationTokenTest_Token('parameterconfirmationtokentest_emptytoken');
// Check parameter // Check parameter
$this->assertTrue($withoutToken->parameterProvided()); $this->assertTrue($withoutToken->parameterProvided());
$this->assertTrue($emptyParameter->parameterProvided()); // even if empty, it's still provided $this->assertTrue($emptyParameter->parameterProvided()); // even if empty, it's still provided
$this->assertTrue($withToken->parameterProvided()); $this->assertTrue($withToken->parameterProvided());
$this->assertFalse($withoutParameter->parameterProvided()); $this->assertFalse($withoutParameter->parameterProvided());
$this->assertTrue($nullToken->parameterProvided());
$this->assertTrue($emptyToken->parameterProvided());
// Check token // Check token
$this->assertFalse($withoutToken->tokenProvided()); $this->assertFalse($withoutToken->tokenProvided());
$this->assertFalse($emptyParameter->tokenProvided()); $this->assertFalse($emptyParameter->tokenProvided());
$this->assertTrue($withToken->tokenProvided()); $this->assertTrue($withToken->tokenProvided()); // Actually forced to true for this test
$this->assertFalse($withoutParameter->tokenProvided()); $this->assertFalse($withoutParameter->tokenProvided());
$this->assertFalse($nullToken->tokenProvided());
$this->assertFalse($emptyToken->tokenProvided());
// Check if reload is required // Check if reload is required
$this->assertTrue($withoutToken->reloadRequired()); $this->assertTrue($withoutToken->reloadRequired());
$this->assertTrue($emptyParameter->reloadRequired()); $this->assertTrue($emptyParameter->reloadRequired());
$this->assertFalse($withToken->reloadRequired()); $this->assertFalse($withToken->reloadRequired());
$this->assertFalse($withoutParameter->reloadRequired()); $this->assertFalse($withoutParameter->reloadRequired());
$this->assertTrue($nullToken->reloadRequired());
$this->assertTrue($emptyToken->reloadRequired());
// Check suppression // Check suppression
$this->assertTrue(isset($_GET['parameterconfirmationtokentest_notoken'])); $this->assertTrue(isset($_GET['parameterconfirmationtokentest_notoken']));

View File

@ -9,7 +9,9 @@ class FixtureBlueprintTest extends SapphireTest {
protected $extraDataObjects = array( protected $extraDataObjects = array(
'FixtureFactoryTest_DataObject', 'FixtureFactoryTest_DataObject',
'FixtureFactoryTest_DataObjectRelation' 'FixtureFactoryTest_DataObjectRelation',
'FixtureBlueprintTest_SiteTree',
'FixtureBlueprintTest_Page'
); );
public function testCreateWithRelationshipExtraFields() { public function testCreateWithRelationshipExtraFields() {
@ -180,6 +182,40 @@ class FixtureBlueprintTest extends SapphireTest {
$this->assertEquals(99, $obj->ID); $this->assertEquals(99, $obj->ID);
} }
public function testCreateWithLastEdited() {
$extpectedDate = '2010-12-14 16:18:20';
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$obj = $blueprint->createObject('lastedited', array('LastEdited' => $extpectedDate));
$this->assertNotNull($obj);
$this->assertEquals($extpectedDate, $obj->LastEdited);
$obj = FixtureFactoryTest_DataObject::get()->byID($obj->ID);
$this->assertEquals($extpectedDate, $obj->LastEdited);
}
public function testCreateWithClassAncestry() {
$data = array(
'Title' => 'My Title',
'Created' => '2010-12-14 16:18:20',
'LastEdited' => '2010-12-14 16:18:20',
'PublishDate' => '2015-12-09 06:03:00'
);
$blueprint = new FixtureBlueprint('FixtureBlueprintTest_Article');
$obj = $blueprint->createObject('home', $data);
$this->assertNotNull($obj);
$this->assertEquals($data['Title'], $obj->Title);
$this->assertEquals($data['Created'], $obj->Created);
$this->assertEquals($data['LastEdited'], $obj->LastEdited);
$this->assertEquals($data['PublishDate'], $obj->PublishDate);
$obj = FixtureBlueprintTest_Article::get()->byID($obj->ID);
$this->assertNotNull($obj);
$this->assertEquals($data['Title'], $obj->Title);
$this->assertEquals($data['Created'], $obj->Created);
$this->assertEquals($data['LastEdited'], $obj->LastEdited);
$this->assertEquals($data['PublishDate'], $obj->PublishDate);
}
public function testCallbackOnBeforeCreate() { public function testCallbackOnBeforeCreate() {
$blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject'); $blueprint = new FixtureBlueprint('FixtureFactoryTest_DataObject');
$this->_called = 0; $this->_called = 0;
@ -232,3 +268,32 @@ class FixtureBlueprintTest extends SapphireTest {
} }
} }
/**
* @package framework
* @subpackage tests
*/
class FixtureBlueprintTest_SiteTree extends DataObject implements TestOnly {
private static $db = array(
"Title" => "Varchar"
);
}
/**
* @package framework
* @subpackage tests
*/
class FixtureBlueprintTest_Page extends FixtureBlueprintTest_SiteTree {
private static $db = array(
'PublishDate' => 'SS_DateTime'
);
}
/**
* @package framework
* @subpackage tests
*/
class FixtureBlueprintTest_Article extends FixtureBlueprintTest_Page {
}

View File

@ -393,9 +393,13 @@ class FileTest extends SapphireTest {
$this->objFromFixture('Member', 'frontend')->logIn(); $this->objFromFixture('Member', 'frontend')->logIn();
$this->assertFalse($file->canEdit(), "Permissionless users can't edit files"); $this->assertFalse($file->canEdit(), "Permissionless users can't edit files");
// Test cms non-asset user // Test global CMS section users
$this->objFromFixture('Member', 'cms')->logIn(); $this->objFromFixture('Member', 'cms')->logIn();
$this->assertFalse($file->canEdit(), "Basic CMS users can't edit files"); $this->assertTrue($file->canEdit(), "Users with all CMS section access can edit files");
// Test cms access users without file access
$this->objFromFixture('Member', 'security')->logIn();
$this->assertFalse($file->canEdit(), "Security CMS users can't edit files");
// Test asset-admin user // Test asset-admin user
$this->objFromFixture('Member', 'assetadmin')->logIn(); $this->objFromFixture('Member', 'assetadmin')->logIn();

View File

@ -35,6 +35,8 @@ Permission:
Code: CMS_ACCESS_LeftAndMain Code: CMS_ACCESS_LeftAndMain
assetadmin: assetadmin:
Code: CMS_ACCESS_AssetAdmin Code: CMS_ACCESS_AssetAdmin
securityadmin:
Code: CMS_ACCESS_SecurityAdmin
Group: Group:
admins: admins:
Title: Administrators Title: Administrators
@ -42,9 +44,12 @@ Group:
cmsusers: cmsusers:
Title: 'CMS Users' Title: 'CMS Users'
Permissions: =>Permission.cmsmain Permissions: =>Permission.cmsmain
securityusers:
Title: 'Security Users'
Permissions: =>Permission.securityadmin
assetusers: assetusers:
Title: 'Asset Users' Title: 'Asset Users'
Permissions: =>Permission.cmsmain, =>Permission.assetadmin Permissions: =>Permission.assetadmin
Member: Member:
frontend: frontend:
Email: frontend@example.com Email: frontend@example.com
@ -57,3 +62,6 @@ Member:
assetadmin: assetadmin:
Email: assetadmin@silverstripe.com Email: assetadmin@silverstripe.com
Groups: =>Group.assetusers Groups: =>Group.assetusers
security:
Email: security@silverstripe.com
Groups: =>Group.securityusers

View File

@ -785,6 +785,36 @@ class FieldListTest extends SapphireTest {
$this->assertEquals(2, $tabB2->fieldPosition('B_insertafter')); $this->assertEquals(2, $tabB2->fieldPosition('B_insertafter'));
$this->assertEquals(3, $tabB2->fieldPosition('B_post')); $this->assertEquals(3, $tabB2->fieldPosition('B_post'));
} }
/**
* FieldList::changeFieldOrder() should place specified fields in given
* order then add any unspecified remainders at the end. Can be given an
* array or list of arguments.
*/
public function testChangeFieldOrder() {
$fieldNames = array('A','B','C','D','E');
$setArray = new FieldList();
$setArgs = new FieldList();
foreach ($fieldNames as $fN) {
$setArray->push(new TextField($fN));
$setArgs->push(new TextField($fN));
}
$setArray->changeFieldOrder(array('D','B','E'));
$this->assertEquals(0, $setArray->fieldPosition('D'));
$this->assertEquals(1, $setArray->fieldPosition('B'));
$this->assertEquals(2, $setArray->fieldPosition('E'));
$this->assertEquals(3, $setArray->fieldPosition('A'));
$this->assertEquals(4, $setArray->fieldPosition('C'));
$setArgs->changeFieldOrder('D','B','E');
$this->assertEquals(0, $setArgs->fieldPosition('D'));
$this->assertEquals(1, $setArgs->fieldPosition('B'));
$this->assertEquals(2, $setArgs->fieldPosition('E'));
$this->assertEquals(3, $setArgs->fieldPosition('A'));
$this->assertEquals(4, $setArgs->fieldPosition('C'));
unset($setArray, $setArgs);
}
public function testFieldPosition() { public function testFieldPosition() {
$set = new FieldList( $set = new FieldList(

View File

@ -14,13 +14,13 @@ class MoneyFieldTest extends SapphireTest {
$o = new MoneyFieldTest_Object(); $o = new MoneyFieldTest_Object();
$m = new Money(); $m = new Money();
$m->setAmount(1.23); $m->setAmount(123456.78);
$m->setCurrency('EUR'); $m->setCurrency('EUR');
$f = new MoneyField('MyMoney', 'MyMoney', $m); $f = new MoneyField('MyMoney', 'MyMoney', $m);
$f->saveInto($o); $f->saveInto($o);
$this->assertEquals($o->MyMoney->getAmount(), 1.23); $this->assertEquals(123456.78, $o->MyMoney->getAmount());
$this->assertEquals($o->MyMoney->getCurrency(), 'EUR'); $this->assertEquals('EUR', $o->MyMoney->getCurrency());
} }
public function testSetValueAsMoney() { public function testSetValueAsMoney() {
@ -29,13 +29,13 @@ class MoneyFieldTest extends SapphireTest {
$f = new MoneyField('MyMoney', 'MyMoney'); $f = new MoneyField('MyMoney', 'MyMoney');
$m = new Money(); $m = new Money();
$m->setAmount(1.23); $m->setAmount(123456.78);
$m->setCurrency('EUR'); $m->setCurrency('EUR');
$f->setValue($m); $f->setValue($m);
$f->saveInto($o); $f->saveInto($o);
$this->assertEquals($o->MyMoney->getAmount(), 1.23); $this->assertEquals(123456.78, $o->MyMoney->getAmount());
$this->assertEquals($o->MyMoney->getCurrency(), 'EUR'); $this->assertEquals('EUR', $o->MyMoney->getCurrency());
} }
public function testSetValueAsArray() { public function testSetValueAsArray() {
@ -43,11 +43,11 @@ class MoneyFieldTest extends SapphireTest {
$f = new MoneyField('MyMoney', 'MyMoney'); $f = new MoneyField('MyMoney', 'MyMoney');
$f->setValue(array('Currency'=>'EUR','Amount'=>1.23)); $f->setValue(array('Currency'=>'EUR','Amount'=>123456.78));
$f->saveInto($o); $f->saveInto($o);
$this->assertEquals($o->MyMoney->getAmount(), 1.23); $this->assertEquals(123456.78, $o->MyMoney->getAmount());
$this->assertEquals($o->MyMoney->getCurrency(), 'EUR'); $this->assertEquals('EUR', $o->MyMoney->getCurrency());
} }
/** /**
@ -59,12 +59,13 @@ class MoneyFieldTest extends SapphireTest {
$o = new MoneyFieldTest_CustomSetter_Object(); $o = new MoneyFieldTest_CustomSetter_Object();
$f = new MoneyField('CustomMoney', 'Test Money Field'); $f = new MoneyField('CustomMoney', 'Test Money Field');
$f->setValue(array('Currency'=>'EUR','Amount'=>1.23)); $f->setValue(array('Currency'=>'EUR','Amount'=>123456.78));
$f->saveInto($o); $f->saveInto($o);
$this->assertEquals($o->MyMoney->getAmount(), (2 * 1.23) ); $this->assertEquals((2 * 123456.78), $o->MyMoney->getAmount());
$this->assertEquals($o->MyMoney->getCurrency(), 'EUR'); $this->assertEquals('EUR', $o->MyMoney->getCurrency());
} }
} }
class MoneyFieldTest_Object extends DataObject implements TestOnly { class MoneyFieldTest_Object extends DataObject implements TestOnly {

View File

@ -14,6 +14,21 @@ class SQLQueryTest extends SapphireTest {
'SQLQueryTestChild' 'SQLQueryTestChild'
); );
public function testCount() {
//basic counting
$qry = SQLQueryTest_DO::get()->dataQuery()->getFinalisedQuery();
$qry->setGroupBy('Common');
$ids = $this->allFixtureIDs('SQLQueryTest_DO');
$this->assertEquals(count($ids), $qry->count('"SQLQueryTest_DO"."ID"'));
//test with `having`
if (DB::get_conn() instanceof MySQLDatabase) {
$qry->setHaving('"Date" > 2012-02-01');
$this->assertEquals(1, $qry->count('"SQLQueryTest_DO"."ID"'));
}
}
public function testEmptyQueryReturnsNothing() { public function testEmptyQueryReturnsNothing() {
$query = new SQLQuery(); $query = new SQLQuery();
$this->assertSQLEquals('', $query->sql($parameters)); $this->assertSQLEquals('', $query->sql($parameters));
@ -578,6 +593,35 @@ class SQLQueryTest extends SapphireTest {
} }
} }
public function testSelect() {
$query = new SQLQuery('"Title"', '"MyTable"');
$query->addSelect('"TestField"');
$this->assertEquals(
'SELECT "Title", "TestField" FROM "MyTable"',
$query->sql()
);
// Test replacement of select
$query->setSelect(array(
'Field' => '"Field"',
'AnotherAlias' => '"AnotherField"'
));
$this->assertEquals(
'SELECT "Field", "AnotherField" AS "AnotherAlias" FROM "MyTable"',
$query->sql()
);
// Check that ' as ' selects don't get mistaken as aliases
$query->addSelect(array(
'Relevance' => "MATCH (Title, MenuTitle) AGAINST ('Two as One')"
));
$this->assertEquals(
'SELECT "Field", "AnotherField" AS "AnotherAlias", MATCH (Title, MenuTitle) AGAINST (' .
'\'Two as One\') AS "Relevance" FROM "MyTable"',
$query->sql()
);
}
/** /**
* Test passing in a LIMIT with OFFSET clause string. * Test passing in a LIMIT with OFFSET clause string.
*/ */

View File

@ -309,13 +309,19 @@ class SecurityTest extends FunctionalTest {
/* UNEXPIRED PASSWORD GO THROUGH WITHOUT A HITCH */ /* UNEXPIRED PASSWORD GO THROUGH WITHOUT A HITCH */
$goodResponse = $this->doTestLoginForm('sam@silverstripe.com' , '1nitialPassword'); $goodResponse = $this->doTestLoginForm('sam@silverstripe.com' , '1nitialPassword');
$this->assertEquals(302, $goodResponse->getStatusCode()); $this->assertEquals(302, $goodResponse->getStatusCode());
$this->assertEquals(Director::baseURL() . 'test/link', $goodResponse->getHeader('Location')); $this->assertEquals(
Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
$goodResponse->getHeader('Location')
);
$this->assertEquals($this->idFromFixture('Member', 'test'), $this->session()->inst_get('loggedInAs')); $this->assertEquals($this->idFromFixture('Member', 'test'), $this->session()->inst_get('loggedInAs'));
/* EXPIRED PASSWORDS ARE SENT TO THE CHANGE PASSWORD FORM */ /* EXPIRED PASSWORDS ARE SENT TO THE CHANGE PASSWORD FORM */
$expiredResponse = $this->doTestLoginForm('expired@silverstripe.com' , '1nitialPassword'); $expiredResponse = $this->doTestLoginForm('expired@silverstripe.com' , '1nitialPassword');
$this->assertEquals(302, $expiredResponse->getStatusCode()); $this->assertEquals(302, $expiredResponse->getStatusCode());
$this->assertEquals(Director::baseURL() . 'Security/changepassword', $expiredResponse->getHeader('Location')); $this->assertEquals(
Controller::join_links(Director::baseURL(), 'Security/changepassword'),
$expiredResponse->getHeader('Location')
);
$this->assertEquals($this->idFromFixture('Member', 'expiredpassword'), $this->assertEquals($this->idFromFixture('Member', 'expiredpassword'),
$this->session()->inst_get('loggedInAs')); $this->session()->inst_get('loggedInAs'));
@ -323,7 +329,10 @@ class SecurityTest extends FunctionalTest {
$this->mainSession->followRedirection(); $this->mainSession->followRedirection();
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword'); $changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
$this->assertEquals(302, $changedResponse->getStatusCode()); $this->assertEquals(302, $changedResponse->getStatusCode());
$this->assertEquals(Director::baseURL() . 'test/link', $changedResponse->getHeader('Location')); $this->assertEquals(
Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
$changedResponse->getHeader('Location')
);
} }
public function testChangePasswordForLoggedInUsers() { public function testChangePasswordForLoggedInUsers() {
@ -333,13 +342,19 @@ class SecurityTest extends FunctionalTest {
$this->get('Security/changepassword?BackURL=test/back'); $this->get('Security/changepassword?BackURL=test/back');
$changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword'); $changedResponse = $this->doTestChangepasswordForm('1nitialPassword', 'changedPassword');
$this->assertEquals(302, $changedResponse->getStatusCode()); $this->assertEquals(302, $changedResponse->getStatusCode());
$this->assertEquals(Director::baseURL() . 'test/back', $changedResponse->getHeader('Location')); $this->assertEquals(
Controller::join_links(Director::absoluteBaseURL(), 'test/back'),
$changedResponse->getHeader('Location')
);
$this->assertEquals($this->idFromFixture('Member', 'test'), $this->session()->inst_get('loggedInAs')); $this->assertEquals($this->idFromFixture('Member', 'test'), $this->session()->inst_get('loggedInAs'));
// Check if we can login with the new password // Check if we can login with the new password
$goodResponse = $this->doTestLoginForm('sam@silverstripe.com' , 'changedPassword'); $goodResponse = $this->doTestLoginForm('sam@silverstripe.com' , 'changedPassword');
$this->assertEquals(302, $goodResponse->getStatusCode()); $this->assertEquals(302, $goodResponse->getStatusCode());
$this->assertEquals(Director::baseURL() . 'test/link', $goodResponse->getHeader('Location')); $this->assertEquals(
Controller::join_links(Director::absoluteBaseURL(), 'test/link'),
$goodResponse->getHeader('Location')
);
$this->assertEquals($this->idFromFixture('Member', 'test'), $this->session()->inst_get('loggedInAs')); $this->assertEquals($this->idFromFixture('Member', 'test'), $this->session()->inst_get('loggedInAs'));
} }

View File

@ -410,7 +410,7 @@ class SimpleUrl {
*/ */
function asString() { function asString() {
$path = $this->_path; $path = $this->_path;
$scheme = $identity = $host = $encoded = $fragment = ''; $scheme = $identity = $host = $port = $encoded = $fragment = '';
if ($this->_username && $this->_password) { if ($this->_username && $this->_password) {
$identity = $this->_username . ':' . $this->_password . '@'; $identity = $this->_username . ':' . $this->_password . '@';
} }
@ -419,13 +419,16 @@ class SimpleUrl {
$scheme .= "://"; $scheme .= "://";
$host = $this->getHost(); $host = $this->getHost();
} }
if ($this->getPort() && $this->getPort() != 80 ) {
$port = ':'.$this->getPort();
}
if (substr($this->_path, 0, 1) == '/') { if (substr($this->_path, 0, 1) == '/') {
$path = $this->normalisePath($this->_path); $path = $this->normalisePath($this->_path);
} }
$encoded = $this->getEncodedRequest(); $encoded = $this->getEncodedRequest();
$fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; $fragment = $this->getFragment() ? '#'. $this->getFragment() : '';
$coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY();
return "$scheme$identity$host$path$encoded$fragment$coords"; return "$scheme$identity$host$port$path$encoded$fragment$coords";
} }
/** /**

View File

@ -1,8 +1,7 @@
<?php <?php
/** /**
* Requirements tracker, for javascript and css. * Requirements tracker for JavaScript and CSS.
* @todo Document the requirements tracker, and discuss it with the others.
* *
* @package framework * @package framework
* @subpackage view * @subpackage view
@ -10,7 +9,7 @@
class Requirements implements Flushable { class Requirements implements Flushable {
/** /**
* Triggered early in the request when someone requests a flush. * Triggered early in the request when a flush is requested
*/ */
public static function flush() { public static function flush() {
self::delete_all_combined_files(); self::delete_all_combined_files();
@ -18,7 +17,8 @@ class Requirements implements Flushable {
/** /**
* Enable combining of css/javascript files. * Enable combining of css/javascript files.
* @param boolean $enable *
* @param bool $enable
*/ */
public static function set_combined_files_enabled($enable) { public static function set_combined_files_enabled($enable) {
self::backend()->set_combined_files_enabled($enable); self::backend()->set_combined_files_enabled($enable);
@ -26,14 +26,16 @@ class Requirements implements Flushable {
/** /**
* Checks whether combining of css/javascript files is enabled. * Checks whether combining of css/javascript files is enabled.
* @return boolean *
* @return bool
*/ */
public static function get_combined_files_enabled() { public static function get_combined_files_enabled() {
return self::backend()->get_combined_files_enabled(); return self::backend()->get_combined_files_enabled();
} }
/** /**
* Set the relative folder e.g. "assets" for where to store combined files * Set the relative folder e.g. 'assets' for where to store combined files
*
* @param string $folder Path to folder * @param string $folder Path to folder
*/ */
public static function set_combined_files_folder($folder) { public static function set_combined_files_folder($folder) {
@ -41,8 +43,10 @@ class Requirements implements Flushable {
} }
/** /**
* Set whether we want to suffix requirements with the time / * Set whether to add caching query params to the requests for file-based requirements.
* location on to the requirements * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by
* filemtime. This has the benefit of allowing the browser to cache the URL infinitely,
* while automatically busting this cache every time the file is changed.
* *
* @param bool * @param bool
*/ */
@ -51,7 +55,7 @@ class Requirements implements Flushable {
} }
/** /**
* Return whether we want to suffix requirements * Check whether we want to suffix requirements
* *
* @return bool * @return bool
*/ */
@ -60,9 +64,10 @@ class Requirements implements Flushable {
} }
/** /**
* Instance of requirements for storage * Instance of the requirements for storage. You can create your own backend to change the
* default JS and CSS inclusion behaviour.
* *
* @var Requirements * @var Requirements_Backend
*/ */
private static $backend = null; private static $backend = null;
@ -76,74 +81,78 @@ class Requirements implements Flushable {
/** /**
* Setter method for changing the Requirements backend * Setter method for changing the Requirements backend
* *
* @param Requirements $backend * @param Requirements_Backend $backend
*/ */
public static function set_backend(Requirements_Backend $backend) { public static function set_backend(Requirements_Backend $backend) {
self::$backend = $backend; self::$backend = $backend;
} }
/** /**
* Register the given javascript file as required. * Register the given JavaScript file as required.
*
* See {@link Requirements_Backend::javascript()} for more info
* *
* @param string $file Relative to docroot
*/ */
public static function javascript($file) { public static function javascript($file) {
self::backend()->javascript($file); self::backend()->javascript($file);
} }
/** /**
* Add the javascript code to the header of the page * Register the given JavaScript code into the list of requirements
* *
* See {@link Requirements_Backend::customScript()} for more info * @param string $script The script content as a string (without enclosing <script> tag)
* @param script The script content * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
* @param uniquenessID Use this to ensure that pieces of code only get added once.
*/ */
public static function customScript($script, $uniquenessID = null) { public static function customScript($script, $uniquenessID = null) {
self::backend()->customScript($script, $uniquenessID); self::backend()->customScript($script, $uniquenessID);
} }
/** /**
* Include custom CSS styling to the header of the page. * Return all registered custom scripts
* *
* See {@link Requirements_Backend::customCSS()} * @return array
*/
public static function get_custom_scripts() {
return self::backend()->get_custom_scripts();
}
/**
* Register the given CSS styles into the list of requirements
* *
* @param string $script CSS selectors as a string (without <style> tag enclosing selectors). * @param string $script CSS selectors as a string (without enclosing <style> tag)
* @param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
*/ */
public static function customCSS($script, $uniquenessID = null) { public static function customCSS($script, $uniquenessID = null) {
self::backend()->customCSS($script, $uniquenessID); self::backend()->customCSS($script, $uniquenessID);
} }
/** /**
* Add the following custom code to the <head> section of the page. * Add the following custom HTML code to the <head> section of the page
* See {@link Requirements_Backend::insertHeadTags()}
* *
* @param string $html * @param string $html Custom HTML code
* @param string $uniquenessID * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
*/ */
public static function insertHeadTags($html, $uniquenessID = null) { public static function insertHeadTags($html, $uniquenessID = null) {
self::backend()->insertHeadTags($html, $uniquenessID); self::backend()->insertHeadTags($html, $uniquenessID);
} }
/** /**
* Load the given javascript template with the page. * Include the content of the given JavaScript file in the list of requirements. Dollar-sign
* See {@link Requirements_Backend::javascriptTemplate()} * variables will be interpolated with values from $vars similar to a .ss template.
* *
* @param file The template file to load. * @param string $file The template file to load, relative to docroot
* @param vars The array of variables to load. These variables are loaded via string search & replace. * @param string[]|int[] $vars The array of variables to interpolate.
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
*/ */
public static function javascriptTemplate($file, $vars, $uniquenessID = null) { public static function javascriptTemplate($file, $vars, $uniquenessID = null) {
self::backend()->javascriptTemplate($file, $vars, $uniquenessID); self::backend()->javascriptTemplate($file, $vars, $uniquenessID);
} }
/** /**
* Register the given stylesheet file as required. * Register the given stylesheet into the list of requirements.
* See {@link Requirements_Backend::css()}
* *
* @param $file String Filenames should be relative to the base, eg, 'framework/javascript/tree/tree.css' * @param string $file The CSS file to load, relative to site root
* @param $media String Comma-separated list of media-types (e.g. "screen,projector") * @param string $media Comma-separated list of media types to use in the link tag
* @see http://www.w3.org/TR/REC-CSS2/media.html * (e.g. 'screen,projector')
*/ */
public static function css($file, $media = null) { public static function css($file, $media = null) {
self::backend()->css($file, $media); self::backend()->css($file, $media);
@ -152,116 +161,167 @@ class Requirements implements Flushable {
/** /**
* Registers the given themeable stylesheet as required. * Registers the given themeable stylesheet as required.
* *
* A CSS file in the current theme path name "themename/css/$name.css" is * A CSS file in the current theme path name 'themename/css/$name.css' is first searched for,
* first searched for, and it that doesn't exist and the module parameter is * and it that doesn't exist and the module parameter is set then a CSS file with that name in
* set then a CSS file with that name in the module is used. * the module is used.
* *
* NOTE: This API is experimental and may change in the future. * @param string $name The name of the file - eg '/css/File.css' would have the name 'File'
* * @param string $module The module to fall back to if the css file does not exist in the
* @param string $name The name of the file - e.g. "/css/File.css" would have * current theme.
* the name "File". * @param string $media Comma-separated list of media types to use in the link tag
* @param string $module The module to fall back to if the css file does not * (e.g. 'screen,projector')
* exist in the current theme.
* @param string $media The CSS media attribute.
*/ */
public static function themedCSS($name, $module = null, $media = null) { public static function themedCSS($name, $module = null, $media = null) {
return self::backend()->themedCSS($name, $module, $media); return self::backend()->themedCSS($name, $module, $media);
} }
/** /**
* Clear either a single or all requirements. * Clear either a single or all requirements
* Caution: Clearing single rules works only with customCSS and customScript if you specified a {@uniquenessID}.
* *
* See {@link Requirements_Backend::clear()} * Caution: Clearing single rules added via customCSS and customScript only works if you
* originally specified a $uniquenessID.
* *
* @param $file String * @param string|int $fileOrID
*/ */
public static function clear($fileOrID = null) { public static function clear($fileOrID = null) {
self::backend()->clear($fileOrID); self::backend()->clear($fileOrID);
} }
/**
* Blocks inclusion of a specific file
* See {@link Requirements_Backend::block()}
*
* @param unknown_type $fileOrID
*/
public static function block($fileOrID) {
self::backend()->block($fileOrID);
}
/**
* Removes an item from the blocking-list.
* See {@link Requirements_Backend::unblock()}
*
* @param string $fileOrID
*/
public static function unblock($fileOrID) {
self::backend()->unblock($fileOrID);
}
/**
* Removes all items from the blocking-list.
* See {@link Requirements_Backend::unblock_all()}
*/
public static function unblock_all() {
self::backend()->unblock_all();
}
/** /**
* Restore requirements cleared by call to Requirements::clear * Restore requirements cleared by call to Requirements::clear
* See {@link Requirements_Backend::restore()}
*/ */
public static function restore() { public static function restore() {
self::backend()->restore(); self::backend()->restore();
} }
/** /**
* Update the given HTML content with the appropriate include tags for the registered * Block inclusion of a specific file
* requirements.
* See {@link Requirements_Backend::includeInHTML()} for more information.
* *
* @param string $templateFilePath Absolute path for the *.ss template file * The difference between this and {@link clear} is that the calling order does not matter;
* @param string $content HTML content that has already been parsed from the $templateFilePath * {@link clear} must be called after the initial registration, whereas {@link block} can be
* through {@link SSViewer}. * used in advance. This is useful, for example, to block scripts included by a superclass
* @return string HTML content thats augumented with the requirements before the closing <head> tag. * without having to override entire functions and duplicate a lot of code.
*
* Note that blocking should be used sparingly because it's hard to trace where an file is
* being blocked from.
*
* @param string|int $fileOrID
*/
public static function block($fileOrID) {
self::backend()->block($fileOrID);
}
/**
* Remove an item from the block list
*
* @param string|int $fileOrID
*/
public static function unblock($fileOrID) {
self::backend()->unblock($fileOrID);
}
/**
* Removes all items from the block list
*/
public static function unblock_all() {
self::backend()->unblock_all();
}
/**
* Update the given HTML content with the appropriate include tags for the registered
* requirements. Needs to receive a valid HTML/XHTML template in the $content parameter,
* including a head and body tag.
*
* @param string $templateFile No longer used, only retained for compatibility
* @param string $content HTML content that has already been parsed from the $templateFile
* through {@link SSViewer}
* @return string HTML content augmented with the requirements tags
*/ */
public static function includeInHTML($templateFile, $content) { public static function includeInHTML($templateFile, $content) {
return self::backend()->includeInHTML($templateFile, $content); return self::backend()->includeInHTML($templateFile, $content);
} }
/**
* Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the given
* HTTP Response
*
* @param SS_HTTPResponse $response
*/
public static function include_in_response(SS_HTTPResponse $response) { public static function include_in_response(SS_HTTPResponse $response) {
return self::backend()->include_in_response($response); return self::backend()->include_in_response($response);
} }
/** /**
* Add i18n files from the given javascript directory. * Add i18n files from the given javascript directory. SilverStripe expects that the given
* directory will contain a number of JavaScript files named by language: en_US.js, de_DE.js,
* etc.
* *
* @param String * @param string $langDir The JavaScript lang directory, relative to the site root, e.g.,
* @param Boolean * 'framework/javascript/lang'
* @param Boolean * @param bool $return Return all relative file paths rather than including them in
* requirements
* @param bool $langOnly Only include language files, not the base libraries
* *
* See {@link Requirements_Backend::add_i18n_javascript()} for more information. * @return array
*/ */
public static function add_i18n_javascript($langDir, $return = false, $langOnly = false) { public static function add_i18n_javascript($langDir, $return = false, $langOnly = false) {
return self::backend()->add_i18n_javascript($langDir, $return, $langOnly); return self::backend()->add_i18n_javascript($langDir, $return, $langOnly);
} }
/** /**
* Concatenate several css or javascript files into a single dynamically generated file. * Concatenate several css or javascript files into a single dynamically generated file. This
* See {@link Requirements_Backend::combine_files()} for more info. * increases performance by fewer HTTP requests.
* *
* @param string $combinedFileName * The combined file is regenerated based on every file modification time. Optionally a
* @param array $files * rebuild can be triggered by appending ?flush=1 to the URL. If all files to be combined are
* JavaScript, we use the external JSMin library to minify the JavaScript.
*
* All combined files will have a comment on the start of each concatenated file denoting their
* original position. For easier debugging, we only minify JavaScript if not in development
* mode ({@link Director::isDev()}).
*
* CAUTION: You're responsible for ensuring that the load order for combined files is
* retained - otherwise combining JavaScript files can lead to functional errors in the
* JavaScript logic, and combining CSS can lead to incorrect inheritance. You can also
* only include each file once across all includes and comibinations in a single page load.
*
* CAUTION: Combining CSS Files discards any "media" information.
*
* Example for combined JavaScript:
* <code>
* Requirements::combine_files(
* 'foobar.js',
* array(
* 'mysite/javascript/foo.js',
* 'mysite/javascript/bar.js',
* )
* );
* </code>
*
* Example for combined CSS:
* <code>
* Requirements::combine_files(
* 'foobar.css',
* array(
* 'mysite/javascript/foo.css',
* 'mysite/javascript/bar.css',
* )
* );
* </code>
*
* @param string $combinedFileName Filename of the combined file relative to docroot
* @param array $files Array of filenames relative to docroot
* @param string $media * @param string $media
*
* @return bool|void
*/ */
public static function combine_files($combinedFileName, $files, $media = null) { public static function combine_files($combinedFileName, $files, $media = null) {
self::backend()->combine_files($combinedFileName, $files, $media); self::backend()->combine_files($combinedFileName, $files, $media);
} }
/** /**
* Returns all combined files. * Return all combined files; keys are the combined file names, values are lists of
* See {@link Requirements_Backend::get_combine_files()} * files being combined.
* *
* @return array * @return array
*/ */
@ -270,8 +330,7 @@ class Requirements implements Flushable {
} }
/** /**
* Deletes all dynamically generated combined files from the filesystem. * Delete all dynamically generated combined files from the filesystem
* See {@link Requirements_Backend::delete_combine_files()}
* *
* @param string $combinedFileName If left blank, all combined files are deleted. * @param string $combinedFileName If left blank, all combined files are deleted.
*/ */
@ -281,7 +340,7 @@ class Requirements implements Flushable {
/** /**
* Deletes all generated combined files in the configured combined files directory, * Deletes all generated combined files in the configured combined files directory,
* but doesn't delete the directory itself. * but doesn't delete the directory itself
*/ */
public static function delete_all_combined_files() { public static function delete_all_combined_files() {
return self::backend()->delete_all_combined_files(); return self::backend()->delete_all_combined_files();
@ -295,44 +354,36 @@ class Requirements implements Flushable {
} }
/** /**
* See {@link combine_files()}. * Do the heavy lifting involved in combining (and, in the case of JavaScript minifying) the
* combined files.
*/ */
public static function process_combined_files() { public static function process_combined_files() {
return self::backend()->process_combined_files(); return self::backend()->process_combined_files();
} }
/** /**
* Returns all custom scripts * Set whether you want to write the JS to the body of the page rather than at the end of the
* See {@link Requirements_Backend::get_custom_scripts()} * head tag.
* *
* @return array * @param bool
*/
public static function get_custom_scripts() {
return self::backend()->get_custom_scripts();
}
/**
* Set whether you want to write the JS to the body of the page or
* in the head section
*
* @see Requirements_Backend::set_write_js_to_body()
* @param boolean
*/ */
public static function set_write_js_to_body($var) { public static function set_write_js_to_body($var) {
self::backend()->set_write_js_to_body($var); self::backend()->set_write_js_to_body($var);
} }
/** /**
* Set the javascript to be forced to end of the HTML, or use the default. * Set whether to force the JavaScript to end of the body. Useful if you use inline script tags
* Useful if you use inline <script> tags, that don't need the javascripts * that don't rely on scripts included via {@link Requirements::javascript()).
* included via Requirements::require();
* *
* @param boolean $var If true, force the javascripts to be included at the bottom. * @param boolean $var If true, force the JavaScript to be included at the bottom of the page
*/ */
public static function set_force_js_to_bottom($var) { public static function set_force_js_to_bottom($var) {
self::backend()->set_force_js_to_bottom($var); self::backend()->set_force_js_to_bottom($var);
} }
/**
* Output debugging information
*/
public static function debug() { public static function debug() {
return self::backend()->debug(); return self::backend()->debug();
} }
@ -346,72 +397,70 @@ class Requirements implements Flushable {
class Requirements_Backend { class Requirements_Backend {
/** /**
* Do we want requirements to suffix onto the requirement link * Whether to add caching query params to the requests for file-based requirements.
* tags for caching or is it disabled. Getter / Setter available * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by
* through {@link Requirements::set_suffix_requirements()} * filemtime. This has the benefit of allowing the browser to cache the URL infinitely,
* while automatically busting this cache every time the file is changed.
* *
* @var bool * @var bool
*/ */
protected $suffix_requirements = true; protected $suffix_requirements = true;
/** /**
* Enable combining of css/javascript files. * Whether to combine CSS and JavaScript files
* *
* @var boolean * @var bool
*/ */
protected $combined_files_enabled = true; protected $combined_files_enabled = true;
/** /**
* Paths to all required .js files relative to the webroot. * Paths to all required JavaScript files relative to docroot
* *
* @var array $javascript * @var array $javascript
*/ */
protected $javascript = array(); protected $javascript = array();
/** /**
* Paths to all required .css files relative to the webroot. * Paths to all required CSS files relative to the docroot.
* *
* @var array $css * @var array $css
*/ */
protected $css = array(); protected $css = array();
/** /**
* All custom javascript code that is inserted * All custom javascript code that is inserted into the page's HTML
* directly at the bottom of the HTML <head> tag.
* *
* @var array $customScript * @var array $customScript
*/ */
protected $customScript = array(); protected $customScript = array();
/** /**
* All custom CSS rules which are inserted * All custom CSS rules which are inserted directly at the bottom of the HTML <head> tag
* directly at the bottom of the HTML <head> tag.
* *
* @var array $customCSS * @var array $customCSS
*/ */
protected $customCSS = array(); protected $customCSS = array();
/** /**
* All custom HTML markup which is added before * All custom HTML markup which is added before the closing <head> tag, e.g. additional
* the closing <head> tag, e.g. additional metatags. * metatags.
* This is preferred to entering tags directly into
*/ */
protected $customHeadTags = array(); protected $customHeadTags = array();
/** /**
* Remembers the filepaths of all cleared Requirements * Remembers the file paths or uniquenessIDs of all Requirements cleared through
* through {@link clear()}. * {@link clear()}, so that they can be restored later.
* *
* @var array $disabled * @var array $disabled
*/ */
protected $disabled = array(); protected $disabled = array();
/** /**
* The filepaths (relative to webroot) or * The file paths (relative to docroot) or uniquenessIDs of any included requirements which
* uniquenessIDs of any included requirements * should be blocked when executing {@link inlcudeInHTML()}. This is useful, for example,
* which should be blocked when executing {@link inlcudeInHTML()}. * to block scripts included by a superclass without having to override entire functions and
* This is useful to e.g. prevent core classes to modifying * duplicate a lot of code.
* Requirements without subclassing the entire functionality. *
* Use {@link unblock()} or {@link unblock_all()} to revert changes. * Use {@link unblock()} or {@link unblock_all()} to revert changes.
* *
* @var array $blocked * @var array $blocked
@ -419,82 +468,93 @@ class Requirements_Backend {
protected $blocked = array(); protected $blocked = array();
/** /**
* See {@link combine_files()}. * A list of combined files registered via {@link combine_files()}. Keys are the output file
* names, values are lists of input files.
* *
* @var array $combine_files * @var array $combine_files
*/ */
public $combine_files = array(); public $combine_files = array();
/** /**
* Using the JSMin library to minify any * Use the JSMin library to minify any javascript file passed to {@link combine_files()}.
* javascript file passed to {@link combine_files()}.
* *
* @var boolean * @var bool
*/ */
public $combine_js_with_jsmin = true; public $combine_js_with_jsmin = true;
/** /**
* Setting for whether or not a file header should be written when * Whether or not file headers should be written when combining files
* combining files.
* *
* @var boolean * @var boolean
*/ */
public $write_header_comment = true; public $write_header_comment = true;
/** /**
* @var string By default, combined files are stored in assets/_combinedfiles. * Where to save combined files. By default they're placed in assets/_combinedfiles, however
* Set this by calling Requirements::set_combined_files_folder() * this may be an issue depending on your setup, especially for CSS files which often contain
* relative paths.
*
* @var string
*/ */
protected $combinedFilesFolder = null; protected $combinedFilesFolder = null;
/** /**
* Put all javascript includes at the bottom of the template * Put all JavaScript includes at the bottom of the template before the closing <body> tag,
* before the closing <body> tag instead of the <head> tag. * rather than the default behaviour of placing them at the end of the <head> tag. This means
* This means script downloads won't block other HTTP-requests, * script downloads won't block other HTTP requests, which can be a performance improvement.
* which can be a performance improvement.
* Caution: Doesn't work when modifying the DOM from those external
* scripts without listening to window.onload/document.ready
* (e.g. toplevel document.write() calls).
* *
* @see http://developer.yahoo.com/performance/rules.html#js_bottom * @var bool
*
* @var boolean
*/ */
public $write_js_to_body = true; public $write_js_to_body = true;
/** /**
* Force the javascripts to the bottom of the page, even if there's a * Force the JavaScript to the bottom of the page, even if there's a script tag in the body already
* <script> tag in the body already
* *
* @var boolean * @var boolean
*/ */
protected $force_js_to_bottom = false; protected $force_js_to_bottom = false;
/**
* Enable or disable the combination of CSS and JavaScript files
*
* @param $enable
*/
public function set_combined_files_enabled($enable) { public function set_combined_files_enabled($enable) {
$this->combined_files_enabled = (bool) $enable; $this->combined_files_enabled = (bool) $enable;
} }
/**
* Check whether file combination is enabled.
*
* @return bool
*/
public function get_combined_files_enabled() { public function get_combined_files_enabled() {
return $this->combined_files_enabled; return $this->combined_files_enabled;
} }
/** /**
* @param String $folder * Set the folder to save combined files in. By default they're placed in assets/_combinedfiles,
* however this may be an issue depending on your setup, especially for CSS files which often
* contain relative paths.
*
* @param string $folder
*/ */
public function setCombinedFilesFolder($folder) { public function setCombinedFilesFolder($folder) {
$this->combinedFilesFolder = $folder; $this->combinedFilesFolder = $folder;
} }
/** /**
* @return String Folder relative to the webroot * @return string Folder relative to the webroot
*/ */
public function getCombinedFilesFolder() { public function getCombinedFilesFolder() {
return ($this->combinedFilesFolder) ? $this->combinedFilesFolder : ASSETS_DIR . '/_combinedfiles'; return ($this->combinedFilesFolder) ? $this->combinedFilesFolder : ASSETS_DIR . '/_combinedfiles';
} }
/** /**
* Set whether we want to suffix requirements with the time / * Set whether to add caching query params to the requests for file-based requirements.
* location on to the requirements * Eg: themes/myTheme/js/main.js?m=123456789. The parameter is a timestamp generated by
* filemtime. This has the benefit of allowing the browser to cache the URL infinitely,
* while automatically busting this cache every time the file is changed.
* *
* @param bool * @param bool
*/ */
@ -503,7 +563,7 @@ class Requirements_Backend {
} }
/** /**
* Return whether we want to suffix requirements * Check whether we want to suffix requirements
* *
* @return bool * @return bool
*/ */
@ -512,45 +572,47 @@ class Requirements_Backend {
} }
/** /**
* Set whether you want the files written to the head or the body. It * Set whether you want to write the JS to the body of the page rather than at the end of the
* writes to the body by default which can break some scripts * head tag.
* *
* @param boolean * @param bool
*/ */
public function set_write_js_to_body($var) { public function set_write_js_to_body($var) {
$this->write_js_to_body = $var; $this->write_js_to_body = $var;
} }
/** /**
* Forces the javascript to the end of the body, just before the closing body-tag. * Forces the JavaScript requirements to the end of the body, right before the closing tag
* *
* @param boolean * @param bool
*/ */
public function set_force_js_to_bottom($var) { public function set_force_js_to_bottom($var) {
$this->force_js_to_bottom = $var; $this->force_js_to_bottom = $var;
} }
/**
* Register the given javascript file as required.
* Filenames should be relative to the base, eg, 'framework/javascript/loader.js'
*/
/**
* Register the given JavaScript file as required.
*
* @param string $file Relative to docroot
*/
public function javascript($file) { public function javascript($file) {
$this->javascript[$file] = true; $this->javascript[$file] = true;
} }
/** /**
* Returns an array of all included javascript * Returns an array of all required JavaScript
* *
* @return array * @return array
*/ */
public function get_javascript() { public function get_javascript() {
return array_keys(array_diff_key($this->javascript,$this->blocked)); return array_keys(array_diff_key($this->javascript, $this->blocked));
} }
/** /**
* Add the javascript code to the header of the page * Register the given JavaScript code into the list of requirements
* @todo Make Requirements automatically put this into a separate file :-) *
* @param script The script content * @param string $script The script content as a string (without enclosing <script> tag)
* @param uniquenessID Use this to ensure that pieces of code only get added once. * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
*/ */
public function customScript($script, $uniquenessID = null) { public function customScript($script, $uniquenessID = null) {
if($uniquenessID) $this->customScript[$uniquenessID] = $script; if($uniquenessID) $this->customScript[$uniquenessID] = $script;
@ -560,10 +622,27 @@ class Requirements_Backend {
} }
/** /**
* Include custom CSS styling to the header of the page. * Return all registered custom scripts
* *
* @param string $script CSS selectors as a string (without <style> tag enclosing selectors). * @return array
* @param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header */
public function get_custom_scripts() {
$requirements = "";
if($this->customScript) {
foreach($this->customScript as $script) {
$requirements .= "$script\n";
}
}
return $requirements;
}
/**
* Register the given CSS styles into the list of requirements
*
* @param string $script CSS selectors as a string (without enclosing <style> tag)
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
*/ */
public function customCSS($script, $uniquenessID = null) { public function customCSS($script, $uniquenessID = null) {
if($uniquenessID) $this->customCSS[$uniquenessID] = $script; if($uniquenessID) $this->customCSS[$uniquenessID] = $script;
@ -571,10 +650,10 @@ class Requirements_Backend {
} }
/** /**
* Add the following custom code to the <head> section of the page. * Add the following custom HTML code to the <head> section of the page
* *
* @param string $html * @param string $html Custom HTML code
* @param string $uniquenessID * @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
*/ */
public function insertHeadTags($html, $uniquenessID = null) { public function insertHeadTags($html, $uniquenessID = null) {
if($uniquenessID) $this->customHeadTags[$uniquenessID] = $html; if($uniquenessID) $this->customHeadTags[$uniquenessID] = $html;
@ -582,9 +661,12 @@ class Requirements_Backend {
} }
/** /**
* Load the given javascript template with the page. * Include the content of the given JavaScript file in the list of requirements. Dollar-sign
* @param file The template file to load. * variables will be interpolated with values from $vars similar to a .ss template.
* @param vars The array of variables to load. These variables are loaded via string search & replace. *
* @param string $file The template file to load, relative to docroot
* @param string[]|int[] $vars The array of variables to interpolate.
* @param string|int $uniquenessID A unique ID that ensures a piece of code is only added once
*/ */
public function javascriptTemplate($file, $vars, $uniquenessID = null) { public function javascriptTemplate($file, $vars, $uniquenessID = null) {
$script = file_get_contents(Director::getAbsFile($file)); $script = file_get_contents(Director::getAbsFile($file));
@ -601,11 +683,11 @@ class Requirements_Backend {
} }
/** /**
* Register the given stylesheet file as required. * Register the given stylesheet into the list of requirements.
* *
* @param $file String Filenames should be relative to the base, eg, 'framework/javascript/tree/tree.css' * @param string $file The CSS file to load, relative to site root
* @param $media String Comma-separated list of media-types (e.g. "screen,projector") * @param string $media Comma-separated list of media types to use in the link tag
* @see http://www.w3.org/TR/REC-CSS2/media.html * (e.g. 'screen,projector')
*/ */
public function css($file, $media = null) { public function css($file, $media = null) {
$this->css[$file] = array( $this->css[$file] = array(
@ -613,28 +695,22 @@ class Requirements_Backend {
); );
} }
/**
* Get the list of registered CSS file requirements, excluding blocked files
*
* @return array
*/
public function get_css() { public function get_css() {
return array_diff_key($this->css, $this->blocked); return array_diff_key($this->css, $this->blocked);
} }
/** /**
* Needed to actively prevent the inclusion of a file, * Clear either a single or all requirements
* e.g. when using your own jQuery version.
* Blocking should only be used as an exception, because
* it is hard to trace back. You can just block items with an
* ID, so make sure you add an unique identifier to customCSS() and customScript().
* *
* @param string $fileOrID * Caution: Clearing single rules added via customCSS and customScript only works if you
*/ * originally specified a $uniquenessID.
public function block($fileOrID) {
$this->blocked[$fileOrID] = $fileOrID;
}
/**
* Clear either a single or all requirements.
* Caution: Clearing single rules works only with customCSS and customScript if you specified a {@uniquenessID}.
* *
* @param $file String * @param string|int $fileOrID
*/ */
public function clear($fileOrID = null) { public function clear($fileOrID = null) {
if($fileOrID) { if($fileOrID) {
@ -659,21 +735,6 @@ class Requirements_Backend {
} }
} }
/**
* Removes an item from the blocking-list.
* CAUTION: Does not "re-add" any previously blocked elements.
* @param string $fileOrID
*/
public function unblock($fileOrID) {
if(isset($this->blocked[$fileOrID])) unset($this->blocked[$fileOrID]);
}
/**
* Removes all items from the blocking-list.
*/
public function unblock_all() {
$this->blocked = array();
}
/** /**
* Restore requirements cleared by call to Requirements::clear * Restore requirements cleared by call to Requirements::clear
*/ */
@ -684,18 +745,48 @@ class Requirements_Backend {
$this->customCSS = $this->disabled['customCSS']; $this->customCSS = $this->disabled['customCSS'];
$this->customHeadTags = $this->disabled['customHeadTags']; $this->customHeadTags = $this->disabled['customHeadTags'];
} }
/**
* Block inclusion of a specific file
*
* The difference between this and {@link clear} is that the calling order does not matter;
* {@link clear} must be called after the initial registration, whereas {@link block} can be
* used in advance. This is useful, for example, to block scripts included by a superclass
* without having to override entire functions and duplicate a lot of code.
*
* Note that blocking should be used sparingly because it's hard to trace where an file is
* being blocked from.
*
* @param string|int $fileOrID
*/
public function block($fileOrID) {
$this->blocked[$fileOrID] = $fileOrID;
}
/**
* Remove an item from the block list
*
* @param string|int $fileOrID
*/
public function unblock($fileOrID) {
if(isset($this->blocked[$fileOrID])) unset($this->blocked[$fileOrID]);
}
/**
* Removes all items from the block list
*/
public function unblock_all() {
$this->blocked = array();
}
/** /**
* Update the given HTML content with the appropriate include tags for the registered * Update the given HTML content with the appropriate include tags for the registered
* requirements. Needs to receive a valid HTML/XHTML template in the $content parameter, * requirements. Needs to receive a valid HTML/XHTML template in the $content parameter,
* including a <head> tag. The requirements will insert before the closing <head> tag automatically. * including a head and body tag.
* *
* @todo Calculate $prefix properly * @param string $templateFile No longer used, only retained for compatibility
* * @param string $content HTML content that has already been parsed from the $templateFile
* @param string $templateFilePath Absolute path for the *.ss template file * through {@link SSViewer}
* @param string $content HTML content that has already been parsed from the $templateFilePath * @return string HTML content augmented with the requirements tags
* through {@link SSViewer}.
* @return string HTML content thats augumented with the requirements before the closing <head> tag.
*/ */
public function includeInHTML($templateFile, $content) { public function includeInHTML($templateFile, $content) {
if( if(
@ -715,8 +806,7 @@ class Requirements_Backend {
} }
} }
// add all inline javascript *after* including external files which // Add all inline JavaScript *after* including external files they might rely on
// they might rely on
if($this->customScript) { if($this->customScript) {
foreach(array_diff_key($this->customScript,$this->blocked) as $script) { foreach(array_diff_key($this->customScript,$this->blocked) as $script) {
$jsRequirements .= "<script type=\"text/javascript\">\n//<![CDATA[\n"; $jsRequirements .= "<script type=\"text/javascript\">\n//<![CDATA[\n";
@ -746,9 +836,8 @@ class Requirements_Backend {
// Remove all newlines from code to preserve layout // Remove all newlines from code to preserve layout
$jsRequirements = preg_replace('/>\n*/', '>', $jsRequirements); $jsRequirements = preg_replace('/>\n*/', '>', $jsRequirements);
// We put script tags into the body, for performance. // Forcefully put the scripts at the bottom of the body instead of before the first
// We forcefully put it at the bottom instead of before // script tag.
// the first script-tag occurence
$content = preg_replace("/(<\/body[^>]*>)/i", $jsRequirements . "\\1", $content); $content = preg_replace("/(<\/body[^>]*>)/i", $jsRequirements . "\\1", $content);
// Put CSS at the bottom of the head // Put CSS at the bottom of the head
@ -757,7 +846,6 @@ class Requirements_Backend {
// Remove all newlines from code to preserve layout // Remove all newlines from code to preserve layout
$jsRequirements = preg_replace('/>\n*/', '>', $jsRequirements); $jsRequirements = preg_replace('/>\n*/', '>', $jsRequirements);
// We put script tags into the body, for performance.
// If your template already has script tags in the body, then we try to put our script // If your template already has script tags in the body, then we try to put our script
// tags just before those. Otherwise, we put it at the bottom. // tags just before those. Otherwise, we put it at the bottom.
$p2 = stripos($content, '<body'); $p2 = stripos($content, '<body');
@ -766,7 +854,7 @@ class Requirements_Backend {
$commentTags = array(); $commentTags = array();
$canWriteToBody = ($p1 !== false) $canWriteToBody = ($p1 !== false)
&& &&
//check that the script tag is not inside a html comment tag // Check that the script tag is not inside a html comment tag
!( !(
preg_match('/.*(?|(<!--)|(-->))/U', $content, $commentTags, 0, $p1) preg_match('/.*(?|(<!--)|(-->))/U', $content, $commentTags, 0, $p1)
&& &&
@ -791,7 +879,10 @@ class Requirements_Backend {
} }
/** /**
* Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the HTTP response * Attach requirements inclusion to X-Include-JS and X-Include-CSS headers on the given
* HTTP Response
*
* @param SS_HTTPResponse $response
*/ */
public function include_in_response(SS_HTTPResponse $response) { public function include_in_response(SS_HTTPResponse $response) {
$this->process_combined_files(); $this->process_combined_files();
@ -819,12 +910,17 @@ class Requirements_Backend {
} }
/** /**
* Add i18n files from the given javascript directory. SilverStripe expects that the given directory * Add i18n files from the given javascript directory. SilverStripe expects that the given
* will contain a number of java script files named by language: en_US.js, de_DE.js, etc. * directory will contain a number of JavaScript files named by language: en_US.js, de_DE.js,
* etc.
* *
* @param String The javascript lang directory, relative to the site root, e.g., 'framework/javascript/lang' * @param string $langDir The JavaScript lang directory, relative to the site root, e.g.,
* @param Boolean Return all relative file paths rather than including them in requirements * 'framework/javascript/lang'
* @param Boolean Only include language files, not the base libraries * @param bool $return Return all relative file paths rather than including them in
* requirements
* @param bool $langOnly Only include language files, not the base libraries
*
* @return array
*/ */
public function add_i18n_javascript($langDir, $return = false, $langOnly = false) { public function add_i18n_javascript($langDir, $return = false, $langOnly = false) {
$files = array(); $files = array();
@ -863,10 +959,10 @@ class Requirements_Backend {
} }
/** /**
* Finds the path for specified file. * Finds the path for specified file
* *
* @param string $fileOrUrl * @param string $fileOrUrl
* @return string|boolean * @return string|bool
*/ */
protected function path_for_file($fileOrUrl) { protected function path_for_file($fileOrUrl) {
if(preg_match('{^//|http[s]?}', $fileOrUrl)) { if(preg_match('{^//|http[s]?}', $fileOrUrl)) {
@ -896,27 +992,22 @@ class Requirements_Backend {
} }
/** /**
* Concatenate several css or javascript files into a single dynamically generated * Concatenate several css or javascript files into a single dynamically generated file. This
* file (stored in {@link Director::baseFolder()}). This increases performance * increases performance by fewer HTTP requests.
* by fewer HTTP requests.
* *
* The combined file is regenerated * The combined file is regenerated based on every file modification time. Optionally a
* based on every file modification time. Optionally a rebuild can be triggered * rebuild can be triggered by appending ?flush=1 to the URL. If all files to be combined are
* by appending ?flush=1 to the URL. * JavaScript, we use the external JSMin library to minify the JavaScript. This can be
* If all files to be combined are javascript, we use the external JSMin library * controlled using {@link $combine_js_with_jsmin}.
* to minify the javascript. This can be controlled by {@link $combine_js_with_jsmin}.
* *
* All combined files will have a comment on the start of each concatenated file * All combined files will have a comment on the start of each concatenated file denoting their
* denoting their original position. For easier debugging, we recommend to only * original position. For easier debugging, we only minify JavaScript if not in development
* minify javascript if not in development mode ({@link Director::isDev()}). * mode ({@link Director::isDev()}).
* *
* CAUTION: You're responsible for ensuring that the load order for combined files * CAUTION: You're responsible for ensuring that the load order for combined files is
* is retained - otherwise combining javascript files can lead to functional errors * retained - otherwise combining JavaScript files can lead to functional errors in the
* in the javascript logic, and combining css can lead to wrong styling inheritance. * JavaScript logic, and combining CSS can lead to incorrect inheritance. You can also
* Depending on the javascript logic, you also have to ensure that files are not included * only include each file once across all includes and combinations in a single page load.
* in more than one combine_files() call.
* Best practice is to include every javascript file in exactly *one* combine_files()
* directive to avoid the issues mentioned above - this is enforced by this function.
* *
* CAUTION: Combining CSS Files discards any "media" information. * CAUTION: Combining CSS Files discards any "media" information.
* *
@ -925,9 +1016,9 @@ class Requirements_Backend {
* Requirements::combine_files( * Requirements::combine_files(
* 'foobar.js', * 'foobar.js',
* array( * array(
* 'mysite/javascript/foo.js', * 'mysite/javascript/foo.js',
* 'mysite/javascript/bar.js', * 'mysite/javascript/bar.js',
* ) * )
* ); * );
* </code> * </code>
* *
@ -935,22 +1026,18 @@ class Requirements_Backend {
* <code> * <code>
* Requirements::combine_files( * Requirements::combine_files(
* 'foobar.css', * 'foobar.css',
* array( * array(
* 'mysite/javascript/foo.css', * 'mysite/javascript/foo.css',
* 'mysite/javascript/bar.css', * 'mysite/javascript/bar.css',
* ) * )
* ); * );
* </code> * </code>
* *
* @see http://code.google.com/p/jsmin-php/ * @param string $combinedFileName Filename of the combined file relative to docroot
* @param array $files Array of filenames relative to docroot
* @param string $media
* *
* @todo Should we enforce unique inclusion of files, or leave it to the developer? Can auto-detection cause * @return bool|void
* breaks?
*
* @param string $combinedFileName Filename of the combined file (will be stored in {@link Director::baseFolder()}
* by default)
* @param array $files Array of filenames relative to the webroot
* @param string $media Comma-separated list of media-types (e.g. "screen,projector").
*/ */
public function combine_files($combinedFileName, $files, $media = null) { public function combine_files($combinedFileName, $files, $media = null) {
// duplicate check // duplicate check
@ -1004,8 +1091,10 @@ class Requirements_Backend {
$this->combine_files[$combinedFileName] = $files; $this->combine_files[$combinedFileName] = $files;
} }
/** /**
* Returns all combined files. * Return all combined files; keys are the combined file names, values are lists of
* files being combined.
*
* @return array * @return array
*/ */
public function get_combine_files() { public function get_combine_files() {
@ -1013,7 +1102,7 @@ class Requirements_Backend {
} }
/** /**
* Deletes all dynamically generated combined files from the filesystem. * Delete all dynamically generated combined files from the filesystem
* *
* @param string $combinedFileName If left blank, all combined files are deleted. * @param string $combinedFileName If left blank, all combined files are deleted.
*/ */
@ -1043,16 +1132,19 @@ class Requirements_Backend {
} }
} }
/**
* Clear all registered CSS and JavaScript file combinations
*/
public function clear_combined_files() { public function clear_combined_files() {
$this->combine_files = array(); $this->combine_files = array();
} }
/** /**
* See {@link combine_files()} * Do the heavy lifting involved in combining (and, in the case of JavaScript minifying) the
* * combined files.
*/ */
public function process_combined_files() { public function process_combined_files() {
// The class_exists call prevents us from loading SapphireTest.php (slow) just to know that // The class_exists call prevents us loading SapphireTest.php (slow) just to know that
// SapphireTest isn't running :-) // SapphireTest isn't running :-)
if(class_exists('SapphireTest', false)) $runningTest = SapphireTest::is_running_test(); if(class_exists('SapphireTest', false)) $runningTest = SapphireTest::is_running_test();
else $runningTest = false; else $runningTest = false;
@ -1077,7 +1169,7 @@ class Requirements_Backend {
// Work out the relative URL for the combined files from the base folder // Work out the relative URL for the combined files from the base folder
$combinedFilesFolder = ($this->getCombinedFilesFolder()) ? ($this->getCombinedFilesFolder() . '/') : ''; $combinedFilesFolder = ($this->getCombinedFilesFolder()) ? ($this->getCombinedFilesFolder() . '/') : '';
// Figure out which ones apply to this pageview // Figure out which ones apply to this request
$combinedFiles = array(); $combinedFiles = array();
$newJSRequirements = array(); $newJSRequirements = array();
$newCSSRequirements = array(); $newCSSRequirements = array();
@ -1112,8 +1204,9 @@ class Requirements_Backend {
Filesystem::makeFolder(dirname($combinedFilePath)); Filesystem::makeFolder(dirname($combinedFilePath));
} }
// If the file isn't writeable, don't even bother trying to make the combined file and return (falls back // If the file isn't writeable, don't even bother trying to make the combined file and return. The
// to uncombined). Complex test because is_writable fails if the file doesn't exist yet. // files will be included individually instead. This is a complex test because is_writable fails
// if the file doesn't exist yet.
if((file_exists($combinedFilePath) && !is_writable($combinedFilePath)) if((file_exists($combinedFilePath) && !is_writable($combinedFilePath))
|| (!file_exists($combinedFilePath) && !is_writable(dirname($combinedFilePath))) || (!file_exists($combinedFilePath) && !is_writable(dirname($combinedFilePath)))
) { ) {
@ -1133,7 +1226,7 @@ class Requirements_Backend {
} }
$refresh = $srcLastMod > filemtime($combinedFilePath); $refresh = $srcLastMod > filemtime($combinedFilePath);
} else { } else {
// file doesn't exist, or refresh was explicitly required // File doesn't exist, or refresh was explicitly required
$refresh = true; $refresh = true;
} }
@ -1151,8 +1244,7 @@ class Requirements_Backend {
} }
if ($this->write_header_comment) { if ($this->write_header_comment) {
// write a header comment for each file for easier identification and debugging // Write a header comment for each file for easier identification and debugging. The semicolon between each file is required for jQuery to be combined properly and protects against unterminated statements.
// also the semicolon between each file is required for jQuery to be combinable properly
$combinedData .= "/****** FILE: $file *****/\n"; $combinedData .= "/****** FILE: $file *****/\n";
} }
@ -1168,7 +1260,7 @@ class Requirements_Backend {
} }
if($failedToMinify){ if($failedToMinify){
// Failed to minify, use unminified. This warning is raised at the end to allow code execution // Failed to minify, use unminified files instead. This warning is raised at the end to allow code execution
// to complete in case this warning is caught inside a try-catch block. // to complete in case this warning is caught inside a try-catch block.
user_error('Failed to minify '.$file.', exception: '.$e->getMessage(), E_USER_WARNING); user_error('Failed to minify '.$file.', exception: '.$e->getMessage(), E_USER_WARNING);
} }
@ -1181,12 +1273,19 @@ class Requirements_Backend {
} }
} }
// @todo Alters the original information, which means you can't call this // Note: Alters the original information, which means you can't call this method repeatedly - it will behave
// method repeatedly - it will behave different on the second call! // differently on the subsequent calls
$this->javascript = $newJSRequirements; $this->javascript = $newJSRequirements;
$this->css = $newCSSRequirements; $this->css = $newCSSRequirements;
} }
/**
* Minify the given $content according to the file type indicated in $filename
*
* @param string $filename
* @param string $content
* @return string
*/
protected function minifyFile($filename, $content) { protected function minifyFile($filename, $content) {
// if we have a javascript file and jsmin is enabled, minify the content // if we have a javascript file and jsmin is enabled, minify the content
$isJS = stripos($filename, '.js'); $isJS = stripos($filename, '.js');
@ -1200,20 +1299,18 @@ class Requirements_Backend {
return $content; return $content;
} }
public function get_custom_scripts() {
$requirements = "";
if($this->customScript) {
foreach($this->customScript as $script) {
$requirements .= "$script\n";
}
}
return $requirements;
}
/** /**
* @see Requirements::themedCSS() * Registers the given themeable stylesheet as required.
*
* A CSS file in the current theme path name 'themename/css/$name.css' is first searched for,
* and it that doesn't exist and the module parameter is set then a CSS file with that name in
* the module is used.
*
* @param string $name The name of the file - eg '/css/File.css' would have the name 'File'
* @param string $module The module to fall back to if the css file does not exist in the
* current theme.
* @param string $media Comma-separated list of media types to use in the link tag
* (e.g. 'screen,projector')
*/ */
public function themedCSS($name, $module = null, $media = null) { public function themedCSS($name, $module = null, $media = null) {
$theme = SSViewer::get_theme_folder(); $theme = SSViewer::get_theme_folder();
@ -1234,6 +1331,9 @@ class Requirements_Backend {
} }
} }
/**
* Output debugging information.
*/
public function debug() { public function debug() {
Debug::show($this->javascript); Debug::show($this->javascript);
Debug::show($this->css); Debug::show($this->css);

View File

@ -1,11 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration> <configuration>
<system.webServer> <system.webServer>
<security> <rewrite>
<requestFiltering> <rules>
<hiddenSegments> <rule name="Block Scripts" stopProcessing="true">
<add segment="silverstripe_version" /> <match url="([^\\/]+)\.(php|php3|php4|php5|phtml|inc)$" />
</hiddenSegments> <conditions trackAllCaptures="true">
</requestFiltering> <add input="{REQUEST_FILENAME}" pattern="\b(main|rpc|tiny_mce_gzip)\.php$" negate="true" />
</security> </conditions>
</system.webServer> <action type="AbortRequest" />
</rule>
<rule name="Block Version" stopProcessing="true">
<match url="\bsilverstripe_version$" />
<action type="AbortRequest" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration> </configuration>