mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCEMENT Recursively creating translations for parent pages to ensure that a translated page is still accessible by traversing the tree, e.g. in "cms translation mode" (in Translatable->onBeforeWrite())
ENHANCEMENT Simplified AllChildrenIncludingDeleted() to not require a special augmentAllChildrenIncludingDeleted() implementation: We don't combine untranslated/translated children any longer (which was used in CMS tree view), but rather just show translated records ENHANCEMENT Ensuring uniqueness of URL segments by appending "-<langcode>" to new translations (in Translatable->onBeforeWrite()) ENHANCEMENT Added Translatable->alternateGetByUrl() as a hook into SiteTree::get_by_url() ENHANCEMENT Adding link back to original page in CMS editform for translations BUGFIX Excluding HiddenField instances from Translatable->updateCMSFields() BUGFIX Don't require a record to be written (through exists()) when checking Translatable->isTranslation() or Translatable->hasTranslation() MINOR Don't use createMethod() shortcut for Translatable->AllChildrenIncludingDeleted() MINOR Added Translatable unit tests git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@70306 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
7ccd1bbc24
commit
bcac495926
@ -95,8 +95,7 @@ class Translatable extends DataObjectDecorator {
|
||||
* so we fall back to {@link Translatable::default_lang()}.
|
||||
*/
|
||||
function getLang() {
|
||||
$record = $this->owner->toMap();
|
||||
return (isset($record["Lang"])) ? $record["Lang"] : Translatable::default_lang();
|
||||
return ($this->owner->getField('Lang')) ? $this->owner->getField('Lang') : Translatable::default_lang();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -242,7 +241,8 @@ class Translatable extends DataObjectDecorator {
|
||||
$class = $class."_Live";
|
||||
}
|
||||
|
||||
$id = $this->owner->ID;
|
||||
// if called on a translation, we use $OriginalID, otherwise use $id
|
||||
$id = ($this->owner->Lang && $this->owner->Lang != Translatable::default_lang()) ? $this->owner->OriginalID : $this->owner->ID;
|
||||
if(is_numeric($id)) {
|
||||
$query = new SQLQuery('distinct Lang',"$class","(\"$class\".\"OriginalID\" =$id)");
|
||||
$langs = $query->execute()->column();
|
||||
@ -327,20 +327,7 @@ class Translatable extends DataObjectDecorator {
|
||||
// Has to be executed even with Translatable disabled, as it overwrites the method with same name
|
||||
// on Hierarchy class, and routes through to Hierarchy->doAllChildrenIncludingDeleted() instead.
|
||||
// Caution: There's an additional method for augmentAllChildrenIncludingDeleted()
|
||||
$this->createMethod("AllChildrenIncludingDeleted",
|
||||
"
|
||||
\$context = (isset(\$args[0])) ? \$args[0] : null;
|
||||
\$lang = (\$context) ? \$context : Translatable::current_lang();
|
||||
if(\$obj->getLang() == \$lang && \$obj->isTranslation()) {
|
||||
// if the language matches the context (e.g. CMSMain), and object is translated,
|
||||
// then call method on original language instead
|
||||
return \$obj->getOwner()->getOriginalPage()->doAllChildrenIncludingDeleted(\$context);
|
||||
} else if(\$obj->getOwner()->hasExtension('Hierarchy') ) {
|
||||
return \$obj->getOwner()->extInstance('Hierarchy')->doAllChildrenIncludingDeleted(\$context);
|
||||
} else {
|
||||
return null;
|
||||
}"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function setOwner(Object $owner) {
|
||||
@ -387,7 +374,7 @@ class Translatable extends DataObjectDecorator {
|
||||
$lang = Translatable::current_lang();
|
||||
$baseTable = ClassInfo::baseDataClass($this->owner->class);
|
||||
$where = $query->where;
|
||||
if (
|
||||
if(
|
||||
$lang
|
||||
&& !$query->filtersOnID() // DataObject::get_by_id() should work independently of language
|
||||
&& array_search($baseTable, array_keys($query->from)) !== false
|
||||
@ -440,11 +427,7 @@ class Translatable extends DataObjectDecorator {
|
||||
}
|
||||
|
||||
function isTranslation() {
|
||||
if($this->getLang() && ($this->getLang() != Translatable::default_lang()) && $this->owner->exists()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return ($this->owner->Lang && ($this->owner->Lang != Translatable::default_lang())/* && $this->owner->exists()*/);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -476,6 +459,42 @@ class Translatable extends DataObjectDecorator {
|
||||
$this->contentcontrollerInit($controller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively creates translations for parent pages in this language
|
||||
* if they aren't existing already. This is a necessity to make
|
||||
* nested pages accessible in a translated CMS page tree.
|
||||
* It would be more userfriendly to grey out untranslated pages,
|
||||
* but this involves complicated special cases in AllChildrenIncludingDeleted().
|
||||
*/
|
||||
function onBeforeWrite() {
|
||||
if(!Translatable::is_enabled()) return;
|
||||
|
||||
// Caution: This logic is very sensitve to eternal loops when translation status isn't determined properly
|
||||
if(
|
||||
!$this->owner->ID
|
||||
&& $this->isTranslation()
|
||||
&& $this->owner->ParentID
|
||||
&& !$this->owner->Parent()->hasTranslation($this->owner->Lang)
|
||||
) {
|
||||
$this->owner->Parent()->createTranslation($this->owner->Lang);
|
||||
}
|
||||
|
||||
if(!$this->owner->ID && $this->isTranslation()) {
|
||||
$SQL_URLSegment = Convert::raw2sql($this->owner->URLSegment);
|
||||
$existingOriginalPage = Translatable::get_one_by_lang('SiteTree', Translatable::default_lang(), "URLSegment = '{$SQL_URLSegment}'");
|
||||
if($existingOriginalPage) $this->owner->URLSegment .= "-{$this->owner->Lang}";
|
||||
}
|
||||
}
|
||||
|
||||
function alternateGetByUrl($urlSegment, $extraFilter, $cache = null, $orderby = null) {
|
||||
$SQL_URLSegment = Convert::raw2sql($urlSegment);
|
||||
Translatable::disable();
|
||||
$record = DataObject::get_one('SiteTree', "URLSegment = '{$SQL_URLSegment}'");
|
||||
Translatable::enable();
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
function augmentWrite(&$manipulation) {
|
||||
if(!Translatable::is_enabled()) return;
|
||||
|
||||
@ -512,7 +531,7 @@ class Translatable extends DataObjectDecorator {
|
||||
if(!Translatable::is_enabled()) return;
|
||||
|
||||
// used in CMSMain->init() to set language state when reading/writing record
|
||||
$fields->push(new HiddenField("Lang", "Lang", $this->getLang()) );
|
||||
$fields->push(new HiddenField("Lang", "Lang", $this->owner->Lang) );
|
||||
$fields->push(new HiddenField("OriginalID", "OriginalID", $this->owner->OriginalID) );
|
||||
|
||||
// if a language other than default language is used, we're in "translation mode",
|
||||
@ -521,7 +540,7 @@ class Translatable extends DataObjectDecorator {
|
||||
$baseClass = $this->owner->class;
|
||||
$allFields = $fields->toArray();
|
||||
while( ($p = get_parent_class($baseClass)) != "DataObject") $baseClass = $p;
|
||||
$isTranslationMode = (Translatable::default_lang() != $this->getLang() && $this->getLang());
|
||||
$isTranslationMode = (Translatable::default_lang() != $this->owner->Lang && $this->owner->Lang);
|
||||
|
||||
if($isTranslationMode) {
|
||||
$originalLangID = Session::get($this->owner->ID . '_originalLangID');
|
||||
@ -534,7 +553,7 @@ class Translatable extends DataObjectDecorator {
|
||||
// iterate through sequential list of all datafields in fieldset
|
||||
// (fields are object references, so we can replace them with the translatable CompositeField)
|
||||
foreach($allDataFields as $dataField) {
|
||||
|
||||
if($dataField instanceof HiddenField) continue;
|
||||
if(in_array($dataField->Name(), $translatableFieldNames)) {
|
||||
// if the field is translatable, perform transformation
|
||||
$fields->replaceField($dataField->Name(), $transformation->transformFormField($dataField));
|
||||
@ -543,6 +562,22 @@ class Translatable extends DataObjectDecorator {
|
||||
$fields->replaceField($dataField->Name(), $dataField->performReadonlyTransformation());
|
||||
}
|
||||
}
|
||||
|
||||
// add link back to original page
|
||||
$originalRecordLink = sprintf(
|
||||
_t('Translatable.ORIGINALLINK', 'Show original page in %s', PR_MEDIUM, 'Show in specific language'),
|
||||
i18n::get_language_name(Translatable::default_lang())
|
||||
);
|
||||
$originalRecordHTML = sprintf('<p><a href="%s">%s</a></p>',
|
||||
sprintf('admin/show/%d/?lang=%s', $originalRecord->ID, Translatable::default_lang()),
|
||||
$originalRecordLink
|
||||
);
|
||||
$fields->addFieldsToTab(
|
||||
'Root',
|
||||
new Tab(_t('Translatable.TRANSLATIONS', 'Translations'),
|
||||
new LiteralField('OriginalTranslationLink', $originalRecordHTML)
|
||||
)
|
||||
);
|
||||
} elseif($this->owner->isNew()) {
|
||||
$fields->addFieldsToTab(
|
||||
'Root',
|
||||
@ -678,7 +713,8 @@ class Translatable extends DataObjectDecorator {
|
||||
$newTranslation = new $class;
|
||||
$newTranslation->update($this->owner->toMap());
|
||||
$newTranslation->ID = 0;
|
||||
$newTranslation->setOriginalPage($this->owner->ID);
|
||||
$originalID = ($this->isTranslation()) ? $this->owner->OriginalID : $this->owner->ID;
|
||||
$newTranslation->setOriginalPage($originalID);
|
||||
$newTranslation->Lang = $lang;
|
||||
$newTranslation->write();
|
||||
|
||||
@ -693,16 +729,30 @@ class Translatable extends DataObjectDecorator {
|
||||
* @return boolean
|
||||
*/
|
||||
function hasTranslation($lang) {
|
||||
return ($this->owner->exists()) && (array_search($lang, $this->getTranslatedLangs()) !== false);
|
||||
return (array_search($lang, $this->getTranslatedLangs()) !== false);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
function augmentStageChildren(DataObjectSet $children, $showall = false) {
|
||||
if(!Translatable::is_enabled()) return;
|
||||
|
||||
|
||||
if($this->isTranslation()) {
|
||||
$children->merge($this->getOriginalPage()->stageChildren($showall));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
function AllChildrenIncludingDeleted($context = null) {
|
||||
// if method is called on translated page, we have to get the children from the original.
|
||||
// otherwise it assumes the wrong ParentID connection
|
||||
if($this->owner->isTranslation()) {
|
||||
$children = $this->owner->getOriginalPage()->doAllChildrenIncludingDeleted($context);
|
||||
} else {
|
||||
$children = $this->owner->doAllChildrenIncludingDeleted($context);
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
/**
|
||||
* If called with default language, doesn't affect the results.
|
||||
@ -715,32 +765,32 @@ class Translatable extends DataObjectDecorator {
|
||||
* @param DataObjectSet $untranslatedChildren
|
||||
* @param Object $context
|
||||
*/
|
||||
function augmentAllChildrenIncludingDeleted(DataObjectSet $untranslatedChildren, $context = null) {
|
||||
/*
|
||||
function augmentAllChildrenIncludingDeleted(DataObjectSet $children, $context) {
|
||||
if(!Translatable::is_enabled()) return false;
|
||||
|
||||
$find = array();
|
||||
$replace = array();
|
||||
|
||||
// @todo check usage of $context
|
||||
$lang = ($context) ? $context->Lang : Translatable::current_lang();
|
||||
if($lang != Translatable::default_lang()) {
|
||||
if($untranslatedChildren) {
|
||||
foreach($untranslatedChildren as $untranslatedChild) {
|
||||
// replace original language with translation (if one is present for this language)
|
||||
if($untranslatedChild->hasTranslation($lang)) {
|
||||
$translatedChild = $untranslatedChild->getTranslation($lang);
|
||||
$find[] = $untranslatedChild;
|
||||
$replace[] = $translatedChild;
|
||||
if($context && $context->Lang && $context->Lang != Translatable::default_lang()) {
|
||||
|
||||
if($children) {
|
||||
foreach($children as $child) {
|
||||
if($child->hasTranslation($context->Lang)) {
|
||||
$trans = $child->getTranslation($context->Lang);
|
||||
if($trans) {
|
||||
$find[] = $child;
|
||||
$replace[] = $trans;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach($find as $i => $found) {
|
||||
$untranslatedChildren->replace($found, $replace[$i]);
|
||||
$children->replace($found, $replace[$i]);
|
||||
}
|
||||
// at this point the set contains a mixture of translated and untranslated pages
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a list of languages with at least one element translated in (including the default language)
|
||||
|
@ -273,58 +273,6 @@ class TranslatableTest extends FunctionalTest {
|
||||
$this->assertNotNull(DataObject::get_by_id('Page', $origPage->ID));
|
||||
}
|
||||
|
||||
function testHierarchyAllChildrenIncludingDeleted() {
|
||||
$parentPage = $this->objFromFixture('Page', 'parent');
|
||||
$translatedParentPage = $parentPage->createTranslation('de');
|
||||
$child1Page = $this->objFromFixture('Page', 'child1');
|
||||
$child1Page->publish('Stage', 'Live');
|
||||
$child1PageOrigID = $child1Page->ID;
|
||||
$child1Page->delete();
|
||||
$child2Page = $this->objFromFixture('Page', 'child2');
|
||||
$child3Page = $this->objFromFixture('Page', 'child3');
|
||||
$grandchildPage = $this->objFromFixture('Page', 'grandchild');
|
||||
|
||||
$child1PageTranslated = $child1Page->createTranslation('de');
|
||||
$child1PageTranslated->publish('Stage', 'Live');
|
||||
$child1PageTranslatedOrigID = $child1PageTranslated->ID;
|
||||
$child1PageTranslated->delete();
|
||||
$child2PageTranslated = $child2Page->createTranslation('de');
|
||||
|
||||
Translatable::set_reading_lang('en');
|
||||
$this->assertEquals(
|
||||
$parentPage->AllChildrenIncludingDeleted()->column('ID'),
|
||||
array(
|
||||
$child2Page->ID,
|
||||
$child3Page->ID,
|
||||
$child1PageOrigID
|
||||
),
|
||||
"Showing AllChildrenIncludingDeleted() in default language doesnt show deleted children in other languages"
|
||||
);
|
||||
|
||||
$parentPage->flushCache();
|
||||
Translatable::set_reading_lang('de');
|
||||
$this->assertEquals(
|
||||
$parentPage->AllChildrenIncludingDeleted()->column('ID'),
|
||||
array(
|
||||
$child2Page->ID,
|
||||
$child3Page->ID,
|
||||
$child1PageOrigID
|
||||
),
|
||||
"Showing AllChildrenIncludingDeleted() in translation mode with parent page in default language shows children in default language"
|
||||
);
|
||||
$this->assertEquals(
|
||||
$translatedParentPage->AllChildrenIncludingDeleted()->column('ID'),
|
||||
array(
|
||||
$child2PageTranslated->ID,
|
||||
$child1PageTranslatedOrigID,
|
||||
),
|
||||
"Showing AllChildrenIncludingDeleted() in translation mode with translated parent page shows only translated children"
|
||||
);
|
||||
|
||||
// reset language
|
||||
Translatable::set_reading_lang('en');
|
||||
}
|
||||
|
||||
function testHierarchyChildren() {
|
||||
$parentPage = $this->objFromFixture('Page', 'parent');
|
||||
$child1Page = $this->objFromFixture('Page', 'child1');
|
||||
@ -435,7 +383,83 @@ class TranslatableTest extends FunctionalTest {
|
||||
'Translated object saves decorated database field independently of original object'
|
||||
);
|
||||
}
|
||||
|
||||
function testCreateTranslationTranslatesUntranslatedParents() {
|
||||
$parentPage = $this->objFromFixture('Page', 'parent');
|
||||
$child1Page = $this->objFromFixture('Page', 'child1');
|
||||
$child1PageOrigID = $child1Page->ID;
|
||||
$grandchildPage = $this->objFromFixture('Page', 'grandchild');
|
||||
|
||||
$this->assertFalse($grandchildPage->hasTranslation('de'));
|
||||
$this->assertFalse($child1Page->hasTranslation('de'));
|
||||
$this->assertFalse($parentPage->hasTranslation('de'));
|
||||
|
||||
$translatedGrandChildPage = $grandchildPage->createTranslation('de');
|
||||
$this->assertTrue($grandchildPage->hasTranslation('de'));
|
||||
$this->assertTrue($child1Page->hasTranslation('de'));
|
||||
$this->assertTrue($parentPage->hasTranslation('de'));
|
||||
}
|
||||
|
||||
function testHierarchyAllChildrenIncludingDeleted() {
|
||||
$parentPage = $this->objFromFixture('Page', 'parent');
|
||||
$translatedParentPage = $parentPage->createTranslation('de');
|
||||
$child1Page = $this->objFromFixture('Page', 'child1');
|
||||
$child1Page->publish('Stage', 'Live');
|
||||
$child1PageOrigID = $child1Page->ID;
|
||||
$child1Page->delete();
|
||||
$child2Page = $this->objFromFixture('Page', 'child2');
|
||||
$child3Page = $this->objFromFixture('Page', 'child3');
|
||||
$grandchildPage = $this->objFromFixture('Page', 'grandchild');
|
||||
|
||||
$child1PageTranslated = $child1Page->createTranslation('de');
|
||||
$child1PageTranslated->publish('Stage', 'Live');
|
||||
$child1PageTranslatedOrigID = $child1PageTranslated->ID;
|
||||
$child1PageTranslated->delete();
|
||||
$child2PageTranslated = $child2Page->createTranslation('de');
|
||||
|
||||
// on original parent in default language
|
||||
Translatable::set_reading_lang('en');
|
||||
SiteTree::flush_and_destroy_cache();
|
||||
$parentPage = $this->objFromFixture('Page', 'parent');
|
||||
$this->assertEquals(
|
||||
$parentPage->AllChildrenIncludingDeleted()->column('ID'),
|
||||
array(
|
||||
$child2Page->ID,
|
||||
$child3Page->ID,
|
||||
$child1PageOrigID // $child1Page was deleted, so the original record doesn't have the ID set
|
||||
),
|
||||
"Showing AllChildrenIncludingDeleted() in default language doesnt show deleted children in other languages"
|
||||
);
|
||||
|
||||
// on original parent in translation mode
|
||||
Translatable::set_reading_lang('de');
|
||||
SiteTree::flush_and_destroy_cache();
|
||||
$parentPage = $this->objFromFixture('Page', 'parent');
|
||||
$this->assertEquals(
|
||||
$parentPage->AllChildrenIncludingDeleted()->column('ID'),
|
||||
array(
|
||||
$child2PageTranslated->ID,
|
||||
$child1PageTranslatedOrigID,
|
||||
),
|
||||
"Showing AllChildrenIncludingDeleted() in translation mode with parent page in default language shows children in default language"
|
||||
);
|
||||
|
||||
// on translated page in translation mode
|
||||
SiteTree::flush_and_destroy_cache();
|
||||
$parentPage = $this->objFromFixture('Page', 'parent');
|
||||
$translatedParentPage = $parentPage->getTranslation('de');
|
||||
$this->assertEquals(
|
||||
$translatedParentPage->AllChildrenIncludingDeleted()->column('ID'),
|
||||
array(
|
||||
$child2PageTranslated->ID,
|
||||
$child1PageTranslatedOrigID,
|
||||
),
|
||||
"Showing AllChildrenIncludingDeleted() in translation mode with translated parent page shows only translated children"
|
||||
);
|
||||
|
||||
// reset language
|
||||
Translatable::set_reading_lang('en');
|
||||
}
|
||||
}
|
||||
|
||||
class TranslatableTest_DataObject extends DataObject implements TestOnly {
|
||||
|
Loading…
Reference in New Issue
Block a user