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

Conflicts:
	_config/uploadfield.yml
	css/UploadField.css
	forms/UploadField.php
	scss/UploadField.scss
	templates/UploadField.ss
	tests/forms/uploadfield/UploadFieldTest.php
This commit is contained in:
Ingo Schommer 2013-01-11 10:02:39 +01:00
commit f3c0669fec
12 changed files with 113 additions and 94 deletions

View File

@ -5,6 +5,8 @@ UploadField:
autoUpload: true
allowedMaxFileNumber:
canUpload: true
canAttachExisting: 'CMS_ACCESS_AssetAdmin'
replaceExistingFile: false
previewMaxWidth: 80
previewMaxHeight: 60
uploadTemplateName: 'ss-uploadfield-uploadtemplate'

View File

@ -11,7 +11,7 @@
Used in side panels and action tabs
*/
.ss-uploadfield-view-allowed-extensions { padding-top: 20px; clear: both; max-width: 750px; display: block; }
.ss-uploadfield-view-allowed-extensions .toggle { font-style: normal; font-size: 11px; }
.ss-uploadfield-view-allowed-extensions .toggle { font-style: normal; font-size: 12px; }
#AssetUploadField { border-bottom: 0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }

View File

@ -17,7 +17,7 @@ Used in side panels and action tabs
.ss-uploadfield .ss-uploadfield-item { margin: 0; padding: 15px; overflow: auto; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview { height: 60px; line-height: 60px; width: 80px; text-align: center; font-weight: bold; float: left; overflow: hidden; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-preview.ss-uploadfield-dropzone { -webkit-box-shadow: gray 0 0 4px 0 inset; -moz-box-shadow: gray 0 0 4px 0 inset; box-shadow: gray 0 0 4px 0 inset; border: 2px dashed gray; background: #d0d3d5; display: none; margin-right: 15px; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info { float: left; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info { float: left; margin-left: 15px; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name { display: block; line-height: 13px; height: 26px; margin: 0; text-align: left; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name b { font-weight: bold; padding: 0 5px 0 0; }
.ss-uploadfield .ss-uploadfield-item .ss-uploadfield-item-info .ss-uploadfield-item-name .name { font-size: 11px; color: #848484; width: 290px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; -o-text-overflow: ellipsis; display: inline; float: left; }

View File

@ -131,6 +131,9 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
return self::$is_running_test;
}
public static function set_is_running_test($bool) {
self::$is_running_test = $bool;
}
/**
* Set the manifest to be used to look up test classes by helper functions

View File

@ -79,7 +79,7 @@ not PHP's built-in [date()](http://nz.php.net/manual/en/function.date.php).
By default, URLs for pages in SilverStripe (the `SiteTree->URLSegment` property)
are automatically reduced to the allowed allowed subset of ASCII characters.
If characters outside this subsetare added, they are either removed or (if possible) "transliterated".
If characters outside this subset are added, they are either removed or (if possible) "transliterated".
This describes the process of converting from one character set to another
while keeping characters recognizeable. For example, vowels with french accents
are replaced with their base characters, `pâté` becomes `pate`.
@ -103,7 +103,7 @@ Defaults can be applied globally for all field instances through [api:DateField:
and [api:TimeField::set_default_config()]. If no 'locale' default is set on the field, [api:i18n::get_locale()]
will be used.
Important: Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases,
**Important:** Form fields in the CMS are automatically configured according to the profile settings for the logged-in user (`Member->Locale`, `Member->DateFormat` and `Member->TimeFormat`). This means that in most cases,
fields created through [api:DataObject::getCMSFields()] will get their i18n settings from a specific member
The [api:DateField] API can be enhanced by JavaScript, and comes with
@ -130,12 +130,12 @@ language-dependent and use a translator function call instead.
echo _t("Namespace.Entity","This is a string");
All strings passed through the `_t()` function will be collected in a separate language table (see "Collecting entities"
below), which is the starting point for translations.
All strings passed through the `_t()` function will be collected in a separate language table (see [Collecting text](#collecting-text)), which is the starting point for translations.
### The _t() function
The `_t()` function is the main gateway to localized text, and takes four parameters, all but the first being optional.
The `_t()` function is the main gateway to localized text, and takes four parameters, all but the first being optional.
It can be used to translate strings in both PHP files and template files. The usage for each case is described below.
* **$entity:** Unique identifier, composed by a namespace and an entity name, with a dot separating them. Both are arbitrary names, although by convention we use the name of the containing class or template. Use this identifier to reference the same translation elsewhere in your code.
* **$string:** (optional) The original language string to be translated. Only needs to be declared once, and gets picked up the [text collector](#collecting-text).
@ -144,63 +144,45 @@ are very context dependent. This parameter allows the developer to convey this i
to the translator.
* **$array::** (optional) An array of injecting variables into the second parameter
#### Usage in PHP Files
:::php
//Example 4: Using context to hint information about a parameter
// Simple string translation
_t('LeftAndMain.FILESIMAGES','Files & Images');
// Using the natural languate comment parameter to supply additional context information to translators
_t('LeftAndMain.HELLO','Site content','Menu title');
// Using injection to add variables into the translated strings.
_t('CMSMain.RESTORED',
"Restored {value} successfully",
'This is a message when restoring a broken part of the CMS',
array('value' => $itemRestored)
);
### Usage
#### Usage in Template Files
There're two types of files in a module where you can use this _t() function: code files (under code folder) and
template files (under templates)
<div class="hint" markdown='1'>
The preferred template syntax has changed somewhat since [version 2.x](http://doc.silverstripe.org/framework/en/2.4/topics/i18n#usage-2).
</div>
* In code files, in order to ask for a translated string, we have to write a normal php call to this function.
In `.ss` template files, instead of `_t(params)` the syntax `<%t params %>` is used. The syntax for passing parameters to the function is quite different to
the PHP version of the function.
Example:
:::php
_t('LeftAndMain.HELLO','Site content',PR_HIGH,'Menu title');
_t('LeftAndMain.FILESIMAGES','Files & Images',PR_HIGH);
_t('LeftAndMain.NEWSLETTERS','Newsletters');
* In template files these calls are written slightly different to ease readibility, diminish overhead and allow a
cleaner template file. Calls can be placed anywhere, but they are preceded and followed by `<% and %>` as usual in the
SilverStripe templating language, and the first parameter is omitted (namespace in template files is always the file
itself).
Therefore, the following would be a valid use in templates:
* Parameters are space separated, not comma separated
* The original language string and the natural language comment parameters are separated by ` on `.
* The final parameter (which is an array in PHP) is passed as a space separated list of key/value pairs.
:::ss
<a href="http://www.silverstripe.com" title="<% _t('VISIT','Visit www.silverstripe.com') %>">
Using SS templating variables in the translatable string (e.g. $Author, $Date..) is not currently supported.
### Injection Support
Variable injection in `_t()` allows us to dynamically replace parts of a translated string, e.g. by a username or a page-title. The named parameters also allow flexible ordering of placeholders,
which might vary depending on the used language.
:::php
// in PHP-file
_t(
'CMSMain.RESTORED',
"Restored {title} successfully"),
array('title' => $title)
);
:::php
// in SS-template ($Name must be available in the current template-scope)
<%t MYPROJECT.INJECTIONS "Hello {name} {greeting}" name=$Name greeting="good to see you" %>
Note that you can still use `sprintf()` wrapped around a `_t()` call
for your substitutions. In contrast to `sprintf()`, our API has a more translator friendly
placeholder syntax, as well as more graceful fallback if not all placeholders are found
(an outdated translation with less placeholders will throw a notice rather than a fatal error).
// Simple string translation
<%t Namespace.Entity "String to translate" %>
// Using the natural languate comment parameter to supply additional context information to translators
<%t SearchResults.NoResult "There are no results matching your query." is "A message displayed to users when the search produces no results." %>
// Using injection to add variables into the translated strings (note that $Name and $Greeting must be available in the current template scope).
<%t Header.Greeting "Hello {name} {greeting}" name=$Name greeting=$Greeting %>
## Collecting text
@ -215,11 +197,11 @@ If you want to run the text collector for just one module you can use the 'modul
`http://<mysite>/dev/tasks/i18nTextCollectorTask/?module=cms`
<div class="notice" markdown='1'>
**Note**: You'll need to install PHPUnit to run the text collector (see [testing-guide](/topics/testing)).
<div class="hint" markdown='1'>
You'll need to install PHPUnit to run the text collector (see [testing-guide](/topics/testing)).
</div>
## Language definitions (3.x)
## Language definitions
Each module can have one language table per locale, stored by convention in the `lang/` subfolder.
The translation is powered by `[Zend_Translate](http://framework.zend.com/manual/en/zend.translate.html)`,
@ -247,30 +229,11 @@ Note that translations are cached across requests.
The cache can be cleared through the `?flush=1` query parameter,
or explicitly through `Zend_Translate::getCache()->clean(Zend_Cache::CLEANING_MODE_ALL)`.
## Language definitions (2.x)
<div class="hint" markdown='1'>
The format of language definitions has changed significantly in since version 2.x.
</div>
In SilverStripe 2.x, the tables are just PHP files with array notations,
stored based on their locale name (e.g. "en_US.php").
Example: framework/lang/en_US.php (extract)
:::php
// ...
$lang['en_US']['ImageUploader']['ATTACH'] = array(
'Attach %s',
'Attach image/file'
);
$lang['en_US']['UploadField']['NOTEADDFILES'] = 'You can add files once you have saved for the first time.';
// ...
Translation table: framework/lang/de_DE.php (extract)
:::php
$lang['de_DE']['ImageUploader']['ATTACH'] = '%s anhängen';
$lang['de_DE']['UploadField']['NOTEADDFILES'] = 'Sie können Dateien hinzufügen sobald Sie das erste mal gespeichert haben';
In order to enable usage of PHP language definitions in 3.x, you need to register a legacy adapter
In order to enable usage of [version 2.x style language definitions](http://doc.silverstripe.org/framework/en/2.4/topics/i18n#language-tables-in-php) in 3.x, you need to register a legacy adapter
in your `mysite/_config.php`:
:::php

View File

@ -88,6 +88,17 @@ class UploadField extends FileField {
* String values are interpreted as permission codes.
*/
'canUpload' => true,
/**
* @var boolean|string Can the user attach files from the assets archive on the site?
* String values are interpreted as permission codes.
*/
'canAttachExisting' => "CMS_ACCESS_AssetAdmin",
/**
* @var boolean If a second file is uploaded, should it replace the existing one rather than throwing an errror?
* This only applies for has_one relationships, and only replaces the association
* rather than the actual file database record or filesystem entry.
*/
'replaceExistingFile' => false,
/**
* @var int
*/
@ -483,6 +494,10 @@ class UploadField extends FileField {
$tooManyFiles = $record->{$name}()->count() >= $this->getConfig('allowedMaxFileNumber');
// has_one only allows one file at any given time.
} elseif($record->has_one($name)) {
// If we're allowed to replace an existing file, clear out the old one
if($record->$name && $this->getConfig('replaceExistingFile')) {
$record->$name = null;
}
$tooManyFiles = $record->{$name}() && $record->{$name}()->exists();
}
@ -553,6 +568,7 @@ class UploadField extends FileField {
public function attach($request) {
if(!$request->isPOST()) return $this->httpError(403);
if(!$this->managesRelation()) return $this->httpError(403);
if(!$this->canAttachExisting()) return $this->httpError(403);
$return = array();
@ -646,6 +662,11 @@ class UploadField extends FileField {
return (is_bool($can)) ? $can : Permission::check($can);
}
public function canAttachExisting() {
$can = $this->getConfig('canAttachExisting');
return (is_bool($can)) ? $can : Permission::check($can);
}
}
/**
@ -867,7 +888,6 @@ class UploadField_ItemHandler extends RequestHandler {
// Check item permissions
$item = $this->getItem();
if(!$item) return $this->httpError(404);
if(!$item->canEdit()) return $this->httpError(403);
// Only allow actions on files in the managed relation (if one exists)
$items = $this->parent->getItems();

View File

@ -208,4 +208,12 @@ class ManyManyList extends RelationList {
return $result;
}
/**
* @return Array Map of field => fieldtype
*/
function getExtraFields() {
return $this->extraFields;
}
}

View File

@ -32,7 +32,7 @@
.ss-uploadfield-item {
margin: 0;
padding: 15px;
overflow: auto;
overflow: auto;
.ss-uploadfield-item-preview {
height: 60px;
@ -52,6 +52,7 @@
}
.ss-uploadfield-item-info {
float: left;
margin-left: 15px;
.ss-uploadfield-item-name {
display: block;

View File

@ -4,13 +4,12 @@
<span class="toggle-details">
<span class="toggle-details-icon"></span>
</span>
</button>
<% if UploadFieldHasRelation %>
<% end_if %>
<% if UploadFieldHasRelation %>
<button data-href="$UploadFieldRemoveLink" class="ss-uploadfield-item-remove ss-ui-button ui-corner-all" title="<% _t('UploadField.REMOVEINFO', 'Remove this file from here, but do not delete it from the file store') %>" data-icon="plug-disconnect-prohibition">
<% _t('UploadField.REMOVE', 'Remove') %></button>
<% end_if %>
<% end_if %>
<% if canDelete %>
<button data-href="$UploadFieldDeleteLink" class="ss-uploadfield-item-delete ss-ui-button ui-corner-all" title="<% _t('UploadField.DELETEINFO', 'Permanently delete this file from the file store') %>" data-icon="minus-circle"><% _t('UploadField.DELETE', 'Delete from files') %></button>
<% end_if %>

View File

@ -52,14 +52,17 @@
<% end_if %>
</b></label>
<% if canUpload %>
<label class="ss-uploadfield-fromcomputer ss-ui-button ui-corner-all" data-icon="drive-upload">
<% _t('UploadField.FROMCOMPUTER', 'From your computer') %>
<input id="$id" name="$getName" class="$extraClass ss-uploadfield-fromcomputer-fileinput" data-config="$configString" type="file"<% if $multiple %> multiple="multiple"<% end_if %> />
</label>
<label class="ss-uploadfield-fromcomputer ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Upload from your computer') %>" data-icon="drive-upload">
<% _t('UploadField.FROMCOMPUTER', 'From your computer') %>
<input id="$id" name="$getName" class="$extraClass ss-uploadfield-fromcomputer-fileinput" data-config="$configString" type="file"<% if $multiple %> multiple="multiple"<% end_if %> />
</label>
<% else %>
<input style="display: none" id="$id" name="$getName" class="$extraClass ss-uploadfield-fromcomputer-fileinput" data-config="$configString" type="file"<% if $multiple %> multiple="multiple"<% end_if %> />
<% end_if %>
<button class="ss-uploadfield-fromfiles ss-ui-button ui-corner-all" data-icon="network-cloud"><% _t('UploadField.FROMFILES', 'From files') %></button>
<% if canAttachExisting %>
<button class="ss-uploadfield-fromfiles ss-ui-button ui-corner-all" title="<% _t('UploadField.FROMCOMPUTERINFO', 'Select from files') %>" data-icon="network-cloud"><% _t('UploadField.FROMFILES', 'From files') %></button>
<% end_if %>
<% if not $autoUpload %>
<button class="ss-uploadfield-startall ss-ui-button ui-corner-all" data-icon="navigation"><% _t('UploadField.STARTALL', 'Start all') %></button>
<% end_if %>

View File

@ -61,6 +61,8 @@ $controller = new FakeController();
// Get test manifest
TestRunner::use_test_manifest();
SapphireTest::set_is_running_test(true);
// Remove the error handler so that PHPUnit can add its own
restore_error_handler();

View File

@ -482,10 +482,6 @@ class UploadFieldTest extends FunctionalTest {
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$this->assertFalse(
(bool)$parser->getBySelector('#CanUploadFalseField .ss-uploadfield-fromcomputer-fileinput'),
'Removes input file control'
);
$this->assertFalse((bool)$parser->getBySelector('#CanUploadFalseField .ss-uploadfield-dropzone'),
'Removes dropzone');
$this->assertTrue(
@ -512,6 +508,22 @@ class UploadFieldTest extends FunctionalTest {
$this->assertTrue($field->canUpload());
}
public function testCanAttachExisting() {
$this->loginWithPermission('ADMIN');
$response = $this->get('UploadFieldTest_Controller');
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$this->assertTrue(
(bool)$parser->getBySelector('#CanAttachExistingFalseField .ss-uploadfield-fromcomputer-fileinput'),
'Keeps input file control'
);
$this->assertFalse(
(bool)$parser->getBySelector('#CanAttachExistingFalseField .ss-uploadfield-fromfiles'),
'Removes "From files" button'
);
}
public function testIsSaveable() {
$form = $this->getMockForm();
@ -815,6 +827,10 @@ class UploadFieldTest_Controller extends Controller implements TestOnly {
$fieldCanUploadFalse->setConfig('canUpload', false);
$fieldCanUploadFalse->setRecord($record);
$fieldCanAttachExisting = new UploadField('CanAttachExistingFalseField');
$fieldCanAttachExisting->setConfig('canAttachExisting', false);
$fieldCanAttachExisting->setRecord($record);
$form = new Form(
$this,
'Form',
@ -830,7 +846,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly {
$fieldReadonly,
$fieldDisabled,
$fieldSubfolder,
$fieldCanUploadFalse
$fieldCanUploadFalse,
$fieldCanAttachExisting
),
new FieldList(
new FormAction('submit')
@ -847,7 +864,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly {
'ReadonlyField',
'DisabledField',
'SubfolderField',
'CanUploadFalseField'
'CanUploadFalseField',
'CanAttachExistingField'
)
);
return $form;