diff --git a/core/model/Translatable.php b/core/model/Translatable.php
index 055181ec3..4deee51d4 100755
--- a/core/model/Translatable.php
+++ b/core/model/Translatable.php
@@ -127,6 +127,16 @@
* is stored and represented in UTF-8 (Unicode). Please make sure your database and
* HTML-templates adjust to this.
*
+ *
Permissions
+ *
+ * Authors without administrative access need special permissions.
+ *
+ * - TRANSLATE_ALL: Translate into all locales
+ * - Translate_: Translate a specific locale. Only available for all locales set in
+ * `Translatable::set_allowed_locales()`.
+ *
+ * Note: If user-specific view permissions are required, please overload `SiteTree->canView()`.
+ *
* Uninstalling/Disabling
*
* Disabling Translatable after creating translations will lead to all
@@ -144,7 +154,7 @@
* @package sapphire
* @subpackage i18n
*/
-class Translatable extends DataObjectDecorator {
+class Translatable extends DataObjectDecorator implements PermissionProvider {
/**
* The 'default' language.
@@ -1071,10 +1081,20 @@ class Translatable extends DataObjectDecorator {
function canTranslate($member = null, $locale) {
if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
- return (
+ // check for locale
+ $allowedLocale = (
!is_array(self::get_allowed_locales())
|| in_array($locale, self::get_allowed_locales())
);
+ if(!$allowedLocale) return false;
+
+ // check for generic translation permission
+ if(Permission::checkMember($member, 'TRANSLATE_ALL')) return true;
+
+ // check for locale specific translate permission
+ if(!Permission::checkMember($member, 'TRANSLATE_' . $locale)) return false;
+
+ return true;
}
/**
@@ -1126,6 +1146,31 @@ class Translatable extends DataObjectDecorator {
}
}
+ function providePermissions() {
+ $locales = self::get_allowed_locales();
+
+ $permissions = array();
+ if($locales) foreach($locales as $locale) {
+ $localeName = i18n::get_locale_name($locale);
+ $permissions['TRANSLATE_' . $locale] = sprintf(
+ _t(
+ 'Translatable.TRANSLATEPERMISSION',
+ 'Translate %s',
+ PR_MEDIUM,
+ 'Translate pages into a language'
+ ),
+ $localeName
+ );
+ }
+
+ $permissions['TRANSLATE_ALL'] = _t(
+ 'Translatable.TRANSLATEALLPERMISSION',
+ 'Translate into all available languages'
+ );
+
+ return $permissions;
+ }
+
/**
* Get a list of languages with at least one element translated in (including the default language)
*
diff --git a/tests/model/TranslatableTest.php b/tests/model/TranslatableTest.php
index 24593794e..3dc7f3df2 100644
--- a/tests/model/TranslatableTest.php
+++ b/tests/model/TranslatableTest.php
@@ -59,7 +59,7 @@ class TranslatableTest extends FunctionalTest {
$cmseditor = $this->objFromFixture('Member', 'cmseditor');
$cmseditor->logIn();
}
-
+
function testTranslationGroups() {
// first in french
$frPage = new SiteTree();
@@ -139,13 +139,13 @@ class TranslatableTest extends FunctionalTest {
$frPage->doUnpublish();
$this->assertEquals($frPage->getTranslationGroup(), $frTranslationGroup);
}
-
+
function testGetTranslationOnSiteTree() {
$origPage = $this->objFromFixture('Page', 'testpage_en');
$translatedPage = $origPage->createTranslation('fr_FR');
$getTranslationPage = $origPage->getTranslation('fr_FR');
-
+
$this->assertNotNull($getTranslationPage);
$this->assertEquals($getTranslationPage->ID, $translatedPage->ID);
}
@@ -189,13 +189,13 @@ class TranslatableTest extends FunctionalTest {
'A translated page without an original doesn\'t return anything through getTranslatedLang()'
);
}
-
+
function testTranslationCantHaveSameURLSegmentAcrossLanguages() {
$origPage = $this->objFromFixture('Page', 'testpage_en');
$translatedPage = $origPage->createTranslation('de_DE');
$translatedPage->URLSegment = 'testpage';
$translatedPage->write();
-
+
$this->assertNotEquals($origPage->URLSegment, $translatedPage->URLSegment);
}
@@ -341,7 +341,7 @@ class TranslatableTest extends FunctionalTest {
$translatedPage->flushCache();
$origPage->flushCache();
-
+
$this->assertNull($origPage->getTranslation('de_DE'));
$this->assertNotNull(DataObject::get_by_id('Page', $origPage->ID));
}
@@ -467,10 +467,10 @@ class TranslatableTest extends FunctionalTest {
function testCreateTranslationOnSiteTree() {
$origPage = $this->objFromFixture('Page', 'testpage_en');
$translatedPage = $origPage->createTranslation('de_DE');
-
+
$this->assertEquals($translatedPage->Locale, 'de_DE');
$this->assertNotEquals($translatedPage->ID, $origPage->ID);
-
+
$subsequentTranslatedPage = $origPage->createTranslation('de_DE');
$this->assertEquals(
$translatedPage->ID,
@@ -478,7 +478,7 @@ class TranslatableTest extends FunctionalTest {
'Subsequent calls to createTranslation() dont cause new records in database'
);
}
-
+
function testTranslatablePropertiesOnDataObject() {
$origObj = $this->objFromFixture('TranslatableTest_DataObject', 'testobject_en');
$translatedObj = $origObj->createTranslation('fr_FR');
@@ -511,12 +511,12 @@ class TranslatableTest extends FunctionalTest {
function testCreateTranslationWithoutOriginal() {
$origParentPage = $this->objFromFixture('Page', 'testpage_en');
$translatedParentPage = $origParentPage->createTranslation('de_DE');
-
+
$translatedPageWithoutOriginal = new SiteTree();
$translatedPageWithoutOriginal->ParentID = $translatedParentPage->ID;
$translatedPageWithoutOriginal->Locale = 'de_DE';
$translatedPageWithoutOriginal->write();
-
+
Translatable::set_current_locale('de_DE');
$this->assertEquals(
$translatedParentPage->stageChildren()->column('ID'),
@@ -535,28 +535,28 @@ class TranslatableTest extends FunctionalTest {
$child1PageOrigID = $child1Page->ID;
$grandChild1Page = $this->objFromFixture('Page', 'grandchild1');
$grandChild2Page = $this->objFromFixture('Page', 'grandchild2');
-
+
$this->assertFalse($grandChild1Page->hasTranslation('de_DE'));
$this->assertFalse($child1Page->hasTranslation('de_DE'));
$this->assertFalse($parentPage->hasTranslation('de_DE'));
-
+
$translatedGrandChild1Page = $grandChild1Page->createTranslation('de_DE');
$translatedGrandChild2Page = $grandChild2Page->createTranslation('de_DE');
$translatedChildPage = $child1Page->getTranslation('de_DE');
$translatedParentPage = $parentPage->getTranslation('de_DE');
-
+
$this->assertTrue($grandChild1Page->hasTranslation('de_DE'));
$this->assertEquals($translatedGrandChild1Page->ParentID, $translatedChildPage->ID);
-
+
$this->assertTrue($grandChild2Page->hasTranslation('de_DE'));
$this->assertEquals($translatedGrandChild2Page->ParentID, $translatedChildPage->ID);
-
+
$this->assertTrue($child1Page->hasTranslation('de_DE'));
$this->assertEquals($translatedChildPage->ParentID, $translatedParentPage->ID);
-
+
$this->assertTrue($parentPage->hasTranslation('de_DE'));
}
-
+
function testHierarchyAllChildrenIncludingDeleted() {
// Original tree in 'en_US':
// parent
@@ -614,7 +614,7 @@ class TranslatableTest extends FunctionalTest {
),
"Showing AllChildrenIncludingDeleted() in default language doesnt show deleted children in other languages"
);
-
+
// on original parent in translation mode
Translatable::set_current_locale('de_DE');
SiteTree::flush_and_destroy_cache();
@@ -640,7 +640,7 @@ class TranslatableTest extends FunctionalTest {
// reset language
Translatable::set_current_locale('en_US');
}
-
+
function testRootUrlDefaultsToTranslatedUrlSegment() {
$origPage = $this->objFromFixture('Page', 'homepage_en');
$origPage->publish('Stage', 'Live');
@@ -726,8 +726,8 @@ class TranslatableTest extends FunctionalTest {
$this->assertNotNull($compareLive);
$this->assertEquals($compareLive->Title, 'Publiziert');
}
-
- function testCanTranslate() {
+
+ function testCanTranslateAllowedLocales() {
$origAllowedLocales = Translatable::get_allowed_locales();
$cmseditor = $this->objFromFixture('Member', 'cmseditor');
@@ -735,17 +735,17 @@ class TranslatableTest extends FunctionalTest {
$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"
+ "Users with canEdit() and TRANSLATE_ALL 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()"
+ "Users with canEdit() and TRANSLATE_ALL 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()"
+ "Users with canEdit() and TRANSLATE_ALL permission can't create a new translation if locale is not in Translatable::get_allowed_locales()"
);
$this->assertType(
@@ -760,6 +760,35 @@ class TranslatableTest extends FunctionalTest {
Translatable::set_allowed_locales($origAllowedLocales);
}
+ function testCanTranslatePermissionCodes() {
+ $origAllowedLocales = Translatable::get_allowed_locales();
+
+ Translatable::set_allowed_locales(array('ja_JP','de_DE'));
+
+ $cmseditor = $this->objFromFixture('Member', 'cmseditor');
+
+ $testPage = $this->objFromFixture('Page', 'testpage_en');
+ $this->assertTrue(
+ $testPage->canTranslate($cmseditor, 'de_DE'),
+ "Users with TRANSLATE_ALL permission can create a new translation"
+ );
+
+ $translator = $this->objFromFixture('Member', 'germantranslator');
+
+ $testPage = $this->objFromFixture('Page', 'testpage_en');
+ $this->assertTrue(
+ $testPage->canTranslate($translator, 'de_DE'),
+ "Users with TRANSLATE_ permission can create a new translation"
+ );
+
+ $this->assertFalse(
+ $testPage->canTranslate($translator, 'ja_JP'),
+ "Users without TRANSLATE_ permission can create a new translation"
+ );
+
+ Translatable::set_allowed_locales($origAllowedLocales);
+ }
+
function testSavePageInCMS() {
$adminUser = $this->objFromFixture('Member', 'admin');
$enPage = $this->objFromFixture('Page', 'testpage_en');
diff --git a/tests/model/TranslatableTest.yml b/tests/model/TranslatableTest.yml
index d14f5b533..43707e499 100644
--- a/tests/model/TranslatableTest.yml
+++ b/tests/model/TranslatableTest.yml
@@ -50,6 +50,8 @@ Group:
Code: cmseditorgroup
admingroup:
Code: admingroup
+ germantranslators:
+ Code: germantranslators
Member:
cmseditor:
FirstName: Editor
@@ -59,10 +61,22 @@ Member:
admin:
FirstName: Admin
Groups: =>Group.admingroup
+ germantranslator:
+ FirstName: German
+ Groups: =>Group.germantranslators
Permission:
+ admincode:
+ Code: ADMIN
+ Group: =>Group.admingroup
cmsmaincode:
Code: CMS_ACCESS_CMSMain
Group: =>Group.cmseditorgroup
- admincode:
- Code: ADMIN
- Group: =>Group.admingroup
\ No newline at end of file
+ translateAllCode:
+ Code: TRANSLATE_ALL
+ Group: =>Group.cmseditorgroup
+ cmsmaincode2:
+ Code: CMS_ACCESS_CMSMain
+ Group: =>Group.germantranslators
+ translateDeCode2:
+ Code: TRANSLATE_de_DE
+ Group: =>Group.germantranslators
\ No newline at end of file