mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
API CHANGE Deprecated Translatable->getTranslatedLangs(), use getTranslatedLocales()
ENHANCEMENT Added Translatable::$allowed_locales to limit the available locales in the CMS for page translations. Added Translatable->canTranslate() and Translatable->canEdit() to determine if a translation is allowed. ENHANCEMENT Added $instance parameter to LanguageDropdownField to call instance-specific canTranslate() on it (optionally) git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@76839 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
84089a1b7d
commit
5b0ee9678f
@ -193,6 +193,15 @@ class Translatable extends DataObjectDecorator {
|
|||||||
*/
|
*/
|
||||||
protected static $enable_lang_filter = true;
|
protected static $enable_lang_filter = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array All locales in which a translation can be created.
|
||||||
|
* This limits the choice in the CMS language dropdown in the
|
||||||
|
* "Translation" tab, as well as the language dropdown above
|
||||||
|
* the CMS tree. If not set, it will default to showing all
|
||||||
|
* common locales.
|
||||||
|
*/
|
||||||
|
protected static $allowed_locales = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Choose the language the site is currently on.
|
* Choose the language the site is currently on.
|
||||||
* If $_GET['locale'] is set, then it will use that language, and store it in the session.
|
* If $_GET['locale'] is set, then it will use that language, and store it in the session.
|
||||||
@ -311,9 +320,9 @@ class Translatable extends DataObjectDecorator {
|
|||||||
* Gets all translations for this specific page.
|
* Gets all translations for this specific page.
|
||||||
* Doesn't include the language of the current record.
|
* Doesn't include the language of the current record.
|
||||||
*
|
*
|
||||||
* @return array Numeric array of all language codes, sorted alphabetically.
|
* @return array Numeric array of all locales, sorted alphabetically.
|
||||||
*/
|
*/
|
||||||
function getTranslatedLangs() {
|
function getTranslatedLocales() {
|
||||||
$langs = array();
|
$langs = array();
|
||||||
|
|
||||||
$baseDataClass = ClassInfo::baseDataClass($this->owner->class); //Base Class
|
$baseDataClass = ClassInfo::baseDataClass($this->owner->class); //Base Class
|
||||||
@ -363,7 +372,7 @@ class Translatable extends DataObjectDecorator {
|
|||||||
*/
|
*/
|
||||||
static function get_langs_by_id($class, $id) {
|
static function get_langs_by_id($class, $id) {
|
||||||
$do = DataObject::get_by_id($class, $id);
|
$do = DataObject::get_by_id($class, $id);
|
||||||
return ($do ? $do->getTranslatedLangs() : array());
|
return ($do ? $do->getTranslatedLocales() : array());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -775,6 +784,12 @@ class Translatable extends DataObjectDecorator {
|
|||||||
|
|
||||||
$isTranslationMode = $this->owner->Locale != Translatable::default_locale();
|
$isTranslationMode = $this->owner->Locale != Translatable::default_locale();
|
||||||
|
|
||||||
|
// Show a dropdown to create a new translation.
|
||||||
|
// This action is possible both when showing the "default language"
|
||||||
|
// and a translation. Include the current locale (record might not be saved yet).
|
||||||
|
$alreadyTranslatedLocales = $this->getTranslatedLocales();
|
||||||
|
$alreadyTranslatedLocales[$this->owner->Locale] = $this->owner->Locale;
|
||||||
|
|
||||||
if($originalRecord && $isTranslationMode) {
|
if($originalRecord && $isTranslationMode) {
|
||||||
$originalLangID = Session::get($this->owner->ID . '_originalLangID');
|
$originalLangID = Session::get($this->owner->ID . '_originalLangID');
|
||||||
|
|
||||||
@ -813,12 +828,6 @@ class Translatable extends DataObjectDecorator {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show a dropdown to create a new translation.
|
|
||||||
// This action is possible both when showing the "default language"
|
|
||||||
// and a translation. Include the current locale (record might not be saved yet).
|
|
||||||
$alreadyTranslatedLangs = $this->getTranslatedLangs();
|
|
||||||
$alreadyTranslatedLangs[$this->owner->Locale] = $this->owner->Locale;
|
|
||||||
|
|
||||||
$fields->addFieldsToTab(
|
$fields->addFieldsToTab(
|
||||||
'Root',
|
'Root',
|
||||||
new Tab('Translations', _t('Translatable.TRANSLATIONS', 'Translations'),
|
new Tab('Translations', _t('Translatable.TRANSLATIONS', 'Translations'),
|
||||||
@ -826,22 +835,22 @@ class Translatable extends DataObjectDecorator {
|
|||||||
$langDropdown = new LanguageDropdownField(
|
$langDropdown = new LanguageDropdownField(
|
||||||
"NewTransLang",
|
"NewTransLang",
|
||||||
_t('Translatable.NEWLANGUAGE', 'New language'),
|
_t('Translatable.NEWLANGUAGE', 'New language'),
|
||||||
$alreadyTranslatedLangs,
|
$alreadyTranslatedLocales,
|
||||||
'SiteTree',
|
'SiteTree',
|
||||||
'Locale-English'
|
$this->owner
|
||||||
),
|
),
|
||||||
$createButton = new InlineFormAction('createtranslation',_t('Translatable.CREATEBUTTON', 'Create'))
|
$createButton = new InlineFormAction('createtranslation',_t('Translatable.CREATEBUTTON', 'Create'))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$createButton->includeDefaultJS(false);
|
$createButton->includeDefaultJS(false);
|
||||||
|
|
||||||
if($alreadyTranslatedLangs) {
|
if($alreadyTranslatedLocales) {
|
||||||
$fields->addFieldToTab(
|
$fields->addFieldToTab(
|
||||||
'Root.Translations',
|
'Root.Translations',
|
||||||
new HeaderField('ExistingTransHeader', _t('Translatable.EXISTING', 'Existing translations:'), 3)
|
new HeaderField('ExistingTransHeader', _t('Translatable.EXISTING', 'Existing translations:'), 3)
|
||||||
);
|
);
|
||||||
$existingTransHTML = '<ul>';
|
$existingTransHTML = '<ul>';
|
||||||
foreach($alreadyTranslatedLangs as $i => $langCode) {
|
foreach($alreadyTranslatedLocales as $i => $langCode) {
|
||||||
$existingTranslation = $this->owner->getTranslation($langCode);
|
$existingTranslation = $this->owner->getTranslation($langCode);
|
||||||
if($existingTranslation) {
|
if($existingTranslation) {
|
||||||
$existingTransHTML .= sprintf('<li><a href="%s">%s</a></li>',
|
$existingTransHTML .= sprintf('<li><a href="%s">%s</a></li>',
|
||||||
@ -857,7 +866,6 @@ class Translatable extends DataObjectDecorator {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$langDropdown->addExtraClass('languageDropdown');
|
$langDropdown->addExtraClass('languageDropdown');
|
||||||
$createButton->addExtraClass('createTranslationButton');
|
$createButton->addExtraClass('createTranslationButton');
|
||||||
}
|
}
|
||||||
@ -966,6 +974,15 @@ class Translatable extends DataObjectDecorator {
|
|||||||
user_error('Translatable::createTranslation(): Please save your record before creating a translation', E_USER_ERROR);
|
user_error('Translatable::createTranslation(): Please save your record before creating a translation', E_USER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// permission check
|
||||||
|
if(!$this->owner->canTranslate(null, $locale)) {
|
||||||
|
throw new Exception(sprintf(
|
||||||
|
'Creating a new translation in locale "%s" is not allowed for this user',
|
||||||
|
$locale
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$existingTranslation = $this->getTranslation($locale);
|
$existingTranslation = $this->getTranslation($locale);
|
||||||
if($existingTranslation) return $existingTranslation;
|
if($existingTranslation) return $existingTranslation;
|
||||||
|
|
||||||
@ -996,6 +1013,31 @@ class Translatable extends DataObjectDecorator {
|
|||||||
return $newTranslation;
|
return $newTranslation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caution: Does not consider the {@link canEdit()} permissions.
|
||||||
|
*
|
||||||
|
* @param DataObject|int $member
|
||||||
|
* @param string $locale
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function canTranslate($member = null, $locale) {
|
||||||
|
if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
|
||||||
|
|
||||||
|
return (
|
||||||
|
!is_array(self::get_allowed_locales())
|
||||||
|
|| in_array($locale, self::get_allowed_locales())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function canEdit($member) {
|
||||||
|
if(!$this->owner->Locale) return true;
|
||||||
|
|
||||||
|
return $this->owner->canTranslate($member, $this->owner->Locale);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns TRUE if the current record has a translation in this language.
|
* Returns TRUE if the current record has a translation in this language.
|
||||||
* Use {@link getTranslation()} to get the actual translated record from
|
* Use {@link getTranslation()} to get the actual translated record from
|
||||||
@ -1005,7 +1047,7 @@ class Translatable extends DataObjectDecorator {
|
|||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
function hasTranslation($locale) {
|
function hasTranslation($locale) {
|
||||||
return (array_search($locale, $this->getTranslatedLangs()) !== false);
|
return (array_search($locale, $this->getTranslatedLocales()) !== false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AllChildrenIncludingDeleted($context = null) {
|
function AllChildrenIncludingDeleted($context = null) {
|
||||||
@ -1082,6 +1124,28 @@ class Translatable extends DataObjectDecorator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define all locales which in which a new translation is allowed.
|
||||||
|
* Checked in {@link canTranslate()}.
|
||||||
|
*
|
||||||
|
* @param array List of allowed locale codes (see {@link i18n::$all_locales}).
|
||||||
|
* Example: array('de_DE','ja_JP')
|
||||||
|
*/
|
||||||
|
static function set_allowed_locales($locales) {
|
||||||
|
self::$allowed_locales = $locales;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all locales which are generally permitted to be translated.
|
||||||
|
* Use {@link canTranslate()} to check if a specific member has permission
|
||||||
|
* to translate a record.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
static function get_allowed_locales() {
|
||||||
|
return self::$allowed_locales;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated 2.4 Use get_homepage_urlsegment_by_locale()
|
* @deprecated 2.4 Use get_homepage_urlsegment_by_locale()
|
||||||
*/
|
*/
|
||||||
@ -1173,6 +1237,13 @@ class Translatable extends DataObjectDecorator {
|
|||||||
return self::choose_site_locale($langAvail);
|
return self::choose_site_locale($langAvail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 2.4 Use getTranslatedLocales()
|
||||||
|
*/
|
||||||
|
function getTranslatedLangs() {
|
||||||
|
return $this->getTranslatedLocales();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,56 +11,50 @@ class LanguageDropdownField extends GroupedDropdownField {
|
|||||||
* Create a new LanguageDropdownField
|
* Create a new LanguageDropdownField
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param string $title
|
* @param string $title
|
||||||
* @param array $dontInclude list of languages that won't be included
|
* @param array $excludeLocales List of locales that won't be included
|
||||||
* @param string $translatingClass Name of the class with translated instances where to look for used languages
|
* @param string $translatingClass Name of the class with translated instances where to look for used languages
|
||||||
* @param string $list Indicates the source language list. Can be either Common-English, Common-Native, Locale-English, Locale-Native
|
* @param string $list Indicates the source language list. Can be either Common-English, Common-Native, Locale-English, Locale-Native
|
||||||
*/
|
*/
|
||||||
function __construct($name, $title, $dontInclude = array(), $translatingClass = 'SiteTree', $list = 'Common-English' ) {
|
function __construct($name, $title, $excludeLocales = array(), $translatingClass = 'SiteTree', $list = 'Common-English', $instance = null) {
|
||||||
$usedlangs = array_diff(
|
$usedLocalesWithTitle = Translatable::get_existing_content_languages($translatingClass);
|
||||||
Translatable::get_existing_content_languages($translatingClass),
|
$usedLocalesWithTitle = array_diff_key($usedLocalesWithTitle, $excludeLocales);
|
||||||
$dontInclude
|
|
||||||
);
|
|
||||||
|
|
||||||
// we accept in dontInclude both language codes and names, so another diff is required
|
if('Common-English' == $list) $allLocalesWithTitle = i18n::get_common_languages();
|
||||||
$usedlangs = array_diff_key(
|
else if('Common-Native' == $list) $allLocalesWithTitle = i18n::get_common_languages(true);
|
||||||
$usedlangs,
|
else if('Locale-English' == $list) $allLocalesWithTitle = i18n::get_common_locales();
|
||||||
array_flip($dontInclude)
|
else if('Locale-Native' == $list) $allLocalesWithTitle = i18n::get_common_locales(true);
|
||||||
);
|
else $allLocalesWithTitle = i18n::get_locale_list();
|
||||||
|
|
||||||
//if (isset($usedlangs[Translatable::default_locale()])) unset($usedlangs[Translatable::default_locale()]);
|
if(isset($allLocales[Translatable::default_locale()])) unset($allLocales[Translatable::default_locale()]);
|
||||||
|
|
||||||
if ('Common-English' == $list) $languageList = i18n::get_common_languages();
|
// Limit to allowed locales if defined
|
||||||
else if ('Common-Native' == $list) $languageList = i18n::get_common_languages(true);
|
// Check for canTranslate() if an $instance is given
|
||||||
else if ('Locale-English' == $list) $languageList = i18n::get_common_locales();
|
$allowedLocales = Translatable::get_allowed_locales();
|
||||||
else if ('Locale-Native' == $list) $languageList = i18n::get_common_locales(true);
|
foreach($allLocalesWithTitle as $locale => $localeTitle) {
|
||||||
else $languageList = i18n::get_locale_list();
|
if(
|
||||||
|
($allowedLocales && !in_array($locale, $allowedLocales))
|
||||||
$alllangs = array_diff(
|
|| ($excludeLocales && in_array($locale, $excludeLocales))
|
||||||
$languageList,
|
|| ($usedLocalesWithTitle && array_key_exists($locale, $usedLocalesWithTitle))
|
||||||
(array)$usedlangs,
|
|| ($instance && !$instance->canTranslate(null, $locale))
|
||||||
$dontInclude
|
) {
|
||||||
);
|
unset($allLocalesWithTitle[$locale]);
|
||||||
$alllangs = array_flip(array_diff(
|
|
||||||
array_flip($alllangs),
|
|
||||||
$dontInclude
|
|
||||||
));
|
|
||||||
if (isset($alllangs[Translatable::default_locale()])) unset($alllangs[Translatable::default_locale()]);
|
|
||||||
|
|
||||||
asort($alllangs);
|
|
||||||
if (count($usedlangs)) {
|
|
||||||
asort($usedlangs);
|
|
||||||
$labelAvail = _t('Form.LANGAVAIL', "Available languages");
|
|
||||||
$labelOther = _t('Form.LANGAOTHER', "Other languages");
|
|
||||||
parent::__construct($name, $title, array(
|
|
||||||
$labelAvail => $usedlangs,
|
|
||||||
$labelOther => $alllangs
|
|
||||||
),
|
|
||||||
reset($usedlangs)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
parent::__construct($name, $title, $alllangs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort by title (array value)
|
||||||
|
asort($allLocalesWithTitle);
|
||||||
|
|
||||||
|
if(count($usedLocalesWithTitle)) {
|
||||||
|
asort($usedLocalesWithTitle);
|
||||||
|
$source = array(
|
||||||
|
_t('Form.LANGAVAIL', "Available languages") => $usedLocalesWithTitle,
|
||||||
|
_t('Form.LANGAOTHER', "Other languages") => $allLocalesWithTitle
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$source = $allLocalesWithTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($name, $title, $source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +63,14 @@ class TranslatableTest extends FunctionalTest {
|
|||||||
parent::tear_down_once();
|
parent::tear_down_once();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
// whenever a translation is created, canTranslate() is checked
|
||||||
|
$cmseditor = $this->objFromFixture('Member', 'cmseditor');
|
||||||
|
$cmseditor->logIn();
|
||||||
|
}
|
||||||
|
|
||||||
function testTranslationGroups() {
|
function testTranslationGroups() {
|
||||||
// first in french
|
// first in french
|
||||||
$frPage = new SiteTree();
|
$frPage = new SiteTree();
|
||||||
@ -711,6 +719,38 @@ class TranslatableTest extends FunctionalTest {
|
|||||||
$this->assertEquals($compareLive->Title, 'Publiziert');
|
$this->assertEquals($compareLive->Title, 'Publiziert');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testCanTranslate() {
|
||||||
|
$origAllowedLocales = Translatable::get_allowed_locales();
|
||||||
|
|
||||||
|
$cmseditor = $this->objFromFixture('Member', 'cmseditor');
|
||||||
|
|
||||||
|
$testPage = $this->objFromFixture('Page', 'testpage_en');
|
||||||
|
$this->assertTrue(
|
||||||
|
$testPage->canTranslate($cmseditor, 'de_DE'),
|
||||||
|
"Users with canEdit() permission can create a new translation if locales are not limited"
|
||||||
|
);
|
||||||
|
|
||||||
|
Translatable::set_allowed_locales(array('ja_JP'));
|
||||||
|
$this->assertTrue(
|
||||||
|
$testPage->canTranslate($cmseditor, 'ja_JP'),
|
||||||
|
"Users with canEdit() permission can create a new translation if locale is in Translatable::get_allowed_locales()"
|
||||||
|
);
|
||||||
|
$this->assertFalse(
|
||||||
|
$testPage->canTranslate($cmseditor, 'de_DE'),
|
||||||
|
"Users with canEdit() permission can't create a new translation if locale is not in Translatable::get_allowed_locales()"
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertType(
|
||||||
|
'Page',
|
||||||
|
$testPage->createTranslation('ja_JP')
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
$testPage->createTranslation('de_DE');
|
||||||
|
$this->setExpectedException("Exception");
|
||||||
|
} catch(Exception $e) {}
|
||||||
|
|
||||||
|
Translatable::set_allowed_locales($origAllowedLocales);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TranslatableTest_DataObject extends DataObject implements TestOnly {
|
class TranslatableTest_DataObject extends DataObject implements TestOnly {
|
||||||
|
@ -45,3 +45,16 @@ TranslatableTest_Page:
|
|||||||
Title: En
|
Title: En
|
||||||
TranslatableProperty: en_US
|
TranslatableProperty: en_US
|
||||||
URLSegment: testpage-en
|
URLSegment: testpage-en
|
||||||
|
Group:
|
||||||
|
cmseditorgroup:
|
||||||
|
Code: cmseditorgroup
|
||||||
|
Member:
|
||||||
|
cmseditor:
|
||||||
|
FirstName: Editor
|
||||||
|
Groups: =>Group.cmseditorgroup
|
||||||
|
websiteuser:
|
||||||
|
FirstName: Website User
|
||||||
|
Permission:
|
||||||
|
cmsmaincode:
|
||||||
|
Code: CMS_ACCESS_CMSMain
|
||||||
|
Group: =>Group.cmseditorgroup
|
Loading…
x
Reference in New Issue
Block a user