From 1cdca8a66adb25f5439457eab33ece4fac2a10ba Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Tue, 18 Nov 2008 03:07:09 +0000 Subject: [PATCH] BUGFIX Fixed editable formfields not showing up in translation mode (#3083). Updated Translatable->updateCMSFields() by partially merging wakeless' patch from (r64523) ENHANCEMENT Added Translatable_Transformation (patched in r64523) ENHANCEMENT Added TranslatableTest with minimal assertions about existing form fields in translation mode git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.3@66137 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- core/model/Translatable.php | 219 +++++++++++++++++++------------ tests/model/TranslatableTest.php | 70 ++++++++++ tests/model/TranslatableTest.yml | 11 ++ 3 files changed, 217 insertions(+), 83 deletions(-) create mode 100644 tests/model/TranslatableTest.php create mode 100644 tests/model/TranslatableTest.yml diff --git a/core/model/Translatable.php b/core/model/Translatable.php index 4d5c61125..844a2f475 100755 --- a/core/model/Translatable.php +++ b/core/model/Translatable.php @@ -89,6 +89,10 @@ class Translatable extends DataObjectDecorator { */ protected $original_values = null; + function getLang() { + $record = $this->owner->toMap(); + return (isset($record["Lang"])) ? $record["Lang"] : Translatable::default_lang(); + } /** * Checks if a table given table exists in the db @@ -268,6 +272,20 @@ class Translatable extends DataObjectDecorator { return self::get_one_by_lang($class,self::default_lang(),"`$baseClass`.ID = $originalLangID"); } + function getTranslatedLangs() { + $class = ClassInfo::baseDataClass($this->owner->class); //Base Class + if($this->owner->hasExtension("Versioned") && Versioned::current_stage() == "Live") { + $class = $class."_Live"; + } + + $id = $this->owner->ID; + if(is_numeric($id)) { + $query = new SQLQuery('distinct Lang',"$class","(`$class`.OriginalID =$id)"); + $langs = $query->execute()->column(); + } + return ($langs) ? array_values($langs) : array(); + } + /** * Get a list of languages in which a given element has been translated * @@ -348,7 +366,7 @@ class Translatable extends DataObjectDecorator { } function augmentSQL(SQLQuery &$query) { - if (! $this->stat('enabled')) return false; + if (! $this->stat('enabled', true)) return false; if((($lang = self::current_lang()) && !self::is_default_lang()) || self::$bypass) { foreach($query->from as $table => $dummy) { if(!isset($baseTable)) { @@ -460,7 +478,7 @@ class Translatable extends DataObjectDecorator { } function augmentDatabase() { - if (! $this->stat('enabled')) return false; + if (! $this->stat('enabled', true)) return false; self::set_reading_lang(self::default_lang()); $table = $this->owner->class; @@ -508,7 +526,7 @@ class Translatable extends DataObjectDecorator { * @param SQLQuery $manipulation Query to augment. */ function augmentWrite(&$manipulation) { - if (! $this->stat('enabled')) return false; + if (! $this->stat('enabled', true)) return false; if(($lang = self::current_lang()) && !self::is_default_lang()) { $tables = array_keys($manipulation); foreach($tables as $table) { @@ -564,57 +582,49 @@ class Translatable extends DataObjectDecorator { //-----------------------------------------------------------------------------------------------// - /** - * Change the member dialog in the CMS - * - * This method updates the forms in the cms to allow the translations for - * the defined translatable fields. - */ function updateCMSFields(FieldSet &$fields) { - if (! $this->stat('enabled')) return false; - $creating = false; - $baseClass = $this->owner->class; - while( ($p = get_parent_class($baseClass)) != "DataObject") $baseClass = $p; - $allFields = $this->owner->getAllFields(); - if(!self::is_default_lang()) { - // Get the original version record, to show the original values - if (!is_numeric($allFields['ID'])) { - $originalLangID = Session::get($this->owner->ID . '_originalLangID'); - $creating = true; - } else { - $originalLangID = $allFields['ID']; - } - $originalRecord = self::get_one_by_lang( - $this->owner->class, - self::$default_lang, - "`$baseClass`.ID = ".$originalLangID - ); - $this->original_values = $originalRecord->getAllFields(); - $alltasks = array( 'dup' => array()); - foreach($fields as $field) { - if ($field->isComposite()) { - $innertasks = $this->duplicateOrReplaceFields($field->FieldSet()); - // more efficient and safe than array_merge_recursive - $alltasks['dup'] = array_merge($alltasks['dup'],$innertasks['dup']); - } - } - foreach ($alltasks['dup'] as $fieldname => $newfield) { - // Duplicate the field - $fields->replaceField($fieldname,$newfield); + if(!$this->stat('enabled', true)) return false; + + // add hidden fields for the used language and original record + $fields->push(new HiddenField("Lang", "Lang", $this->getLang()) ); + $fields->push(new HiddenField("OriginalID", "OriginalID", $this->owner->OriginalID) ); + + // if a language other than default language is used, we're in "translation mode", + // hence have to modify the original fields + $isTranslationMode = (Translatable::default_lang() != $this->getLang() && $this->getLang()); + if($isTranslationMode) { + $originalLangID = Session::get($this->owner->ID . '_originalLangID'); + + $translatableFieldNames = $this->getTranslatableFields(); + $allDataFields = $fields->dataFields(); + $transformation = new Translatable_Transformation(Translatable::get_original($this->owner->class, $this->owner->ID)); + + // 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(in_array($dataField->Name(), $translatableFieldNames)) { + //var_dump($dataField->Name()); + // if the field is translatable, perform transformation + $fields->replaceField($dataField->Name(), $transformation->transformFormField($dataField)); + } else { + // else field shouldn't be editable in translation-mode, make readonly + $fields->replaceField($dataField->Name(), $dataField->performReadonlyTransformation()); + } } } else { - $alreadyTranslatedLangs = null; - if (is_numeric($allFields['ID'])) { - $alreadyTranslatedLangs = self::get_langs_by_id($baseClass,$allFields['ID']); - } - if (!$alreadyTranslatedLangs) $alreadyTranslatedLangs = array(); + // if we're not in "translation mode", show a dropdown to create a new translation. + // this action should just be possible when showing the default language, + // you can't create new translations from within a "translation mode" form. + + $alreadyTranslatedLangs = array(); foreach ($alreadyTranslatedLangs as $i => $langCode) { $alreadyTranslatedLangs[$i] = i18n::get_language_name($langCode); } $fields->addFieldsToTab( 'Root', new Tab(_t('Translatable.TRANSLATIONS', 'Translations'), - new HeaderField('CreateTransHeader',_t('Translatable.CREATE', 'Create new translation'), 2), + new HeaderField('CreateTransHeader', _t('Translatable.CREATE', 'Create new translation'), 2), $langDropdown = new LanguageDropdownField("NewTransLang", _t('Translatable.NEWLANGUAGE', 'New language'), $alreadyTranslatedLangs), $createButton = new InlineFormAction('createtranslation',_t('Translatable.CREATEBUTTON', 'Create')) ) @@ -623,7 +633,7 @@ class Translatable extends DataObjectDecorator { $fields->addFieldsToTab( 'Root.Translations', new FieldSet( - new HeaderField('ExistingTransHeader',_t('Translatable.EXISTING', 'Existing translations:'), 3), + new HeaderField('ExistingTransHeader', _t('Translatable.EXISTING', 'Existing translations:'), 3), new LiteralField('existingtrans',implode(', ',$alreadyTranslatedLangs)) ) ); @@ -633,42 +643,6 @@ class Translatable extends DataObjectDecorator { $createButton->includeDefaultJS(false); } } - - protected function duplicateOrReplaceFields(&$fields) { - $tasks = array( - 'dup' => array(), - ); - foreach($fields as $field) { - if ($field->isComposite()) { - $innertasks = $this->duplicateOrReplaceFields($field->FieldSet()); - $tasks['dup'] = array_merge($tasks['dup'],$innertasks['dup']); - } - else if(($fieldname = $field->Name()) && array_key_exists($fieldname,$this->original_values)) { - // Get a copy of the original field to show the untranslated value - if($field instanceof TextareaField) { - $nonEditableField = new ToggleField($fieldname,$field->Title(),'','+','-'); - $nonEditableField->labelMore = '+'; - $nonEditableField->labelLess = '-'; - } else { - $nonEditableField = $field->performDisabledTransformation(); - } - - $nonEditableField_holder = new CompositeField($nonEditableField); - $nonEditableField_holder->setName($fieldname.'_holder'); - $nonEditableField_holder->addExtraClass('originallang_holder'); - $nonEditableField->setValue($this->original_values[$fieldname]); - $nonEditableField->setName($fieldname.'_original'); - $nonEditableField->addExtraClass('originallang'); - if (array_search($fieldname,$this->translatableFields) !== false) { - // Duplicate the field - if ($field->Title()) $nonEditableField->setTitle('Original'); - $nonEditableField_holder->insertBefore($field, $fieldname.'_original'); - $tasks['dup'][$fieldname] = $nonEditableField_holder; - } - } - } - return $tasks; - } /** * Get a list of fields from the tables created by this extension @@ -735,6 +709,17 @@ class Translatable extends DataObjectDecorator { ); return $langFields; } + + /** + * Get the names of all translatable fields on this class + * as a numeric array. + * @todo Integrate with blacklist once branches/translatable is merged back. + * + * @return array + */ + function getTranslatableFields() { + return $this->translatableFields; + } /** * Return the base table - the class that directly extends DataObject. @@ -760,4 +745,72 @@ class Translatable extends DataObjectDecorator { } } -?> + +/** + * Transform a formfield to a "translatable" representation, + * consisting of the original formfield plus a readonly-version + * of the original value, wrapped in a CompositeField. + * + * @param DataObject $original Needs the original record as we populate the readonly formfield with the original value + * + * @package sapphire + * @subpackage misc + */ +class Translatable_Transformation extends FormTransformation { + + /** + * @var DataObject + */ + private $original = null; + + function __construct(DataObject $original) { + $this->original = $original; + parent::__construct(); + } + + /** + * Returns the original DataObject attached to the Transformation + * + * @return DataObject + */ + function getOriginal() { + return $this->original; + } + + /** + * @todo transformTextareaField() not used at the moment + */ + function transformTextareaField(TextareaField $field) { + $nonEditableField = new ToggleField($fieldname,$field->Title(),'','+','-'); + $nonEditableField->labelMore = '+'; + $nonEditableField->labelLess = '-'; + return $this->baseTransform($nonEditableField, $field); + + return $nonEditableField; + } + + function transformFormField(FormField $field) { + $newfield = $field->performReadOnlyTransformation(); + return $this->baseTransform($newfield, $field); + } + + protected function baseTransform($nonEditableField, $originalField) { + $fieldname = $originalField->Name(); + + $nonEditableField_holder = new CompositeField($nonEditableField); + $nonEditableField_holder->setName($fieldname.'_holder'); + $nonEditableField_holder->addExtraClass('originallang_holder'); + + $nonEditableField->setValue($this->original->$fieldname); + $nonEditableField->setName($fieldname.'_original'); + $nonEditableField->addExtraClass('originallang'); + $nonEditableField->setTitle('Original '.$originalField->Title()); + + $nonEditableField_holder->insertBefore($originalField, $fieldname.'_original'); + return $nonEditableField_holder; + } + + +} + +?> \ No newline at end of file diff --git a/tests/model/TranslatableTest.php b/tests/model/TranslatableTest.php new file mode 100644 index 000000000..cd157591a --- /dev/null +++ b/tests/model/TranslatableTest.php @@ -0,0 +1,70 @@ +origTranslatableSettings['enabled'] = Translatable::is_enabled(); + $this->origTranslatableSettings['default_lang'] = Translatable::default_lang(); + Translatable::enable(); + Translatable::set_default_lang("en"); + + // needs to recreate the database schema with *_lang tables + self::kill_temp_db(); + self::create_temp_db(); + + parent::setUp(); + } + + function tearDown() { + if(!$this->origTranslatableSettings['enabled']) Translatable::disable(); + Translatable::set_default_lang($this->origTranslatableSettings['default_lang']); + + self::kill_temp_db(); + self::create_temp_db(); + } + + function testUpdateCMSFieldsOnSiteTree() { + $pageOrigLang = $this->objFromFixture('SiteTree', 'home'); + + // first test with default language + $fields = $pageOrigLang->getCMSFields(); + $this->assertType( + 'TextField', + $fields->dataFieldByName('Title'), + 'Translatable doesnt modify fields if called in default language (e.g. "non-translation mode")' + ); + $this->assertNull( + $fields->dataFieldByName('Title_original'), + 'Translatable doesnt modify fields if called in default language (e.g. "non-translation mode")' + ); + + // then in "translation mode" + $pageTranslated = Translatable::get_one_by_lang('SiteTree',"fr", "ID = $pageOrigLang->ID"); + $fields = $pageTranslated->getCMSFields(); + $this->assertType( + 'TextField', + $fields->dataFieldByName('Title'), + 'Translatable leaves original formfield intact in "translation mode"' + ); + $readonlyField = $fields->dataFieldByName('Title')->performReadonlyTransformation(); + $this->assertType( + $readonlyField->class, + $fields->dataFieldByName('Title_original'), + 'Translatable adds the original value as a ReadonlyField in "translation mode"' + ); + + } +} +?> \ No newline at end of file diff --git a/tests/model/TranslatableTest.yml b/tests/model/TranslatableTest.yml new file mode 100644 index 000000000..b41f5914f --- /dev/null +++ b/tests/model/TranslatableTest.yml @@ -0,0 +1,11 @@ +SiteTree: + home: + Title: Home + URLSegment: /home/ + ShowInMenus: + +SiteTree_lang: + home: + OriginalLangID: =>SiteTree.home + Title: Home fr + Lang: fr \ No newline at end of file