diff --git a/code/controller/TranslatableCMSMainExtension.php b/code/controller/TranslatableCMSMainExtension.php
index df5c0ac..a07d4cd 100644
--- a/code/controller/TranslatableCMSMainExtension.php
+++ b/code/controller/TranslatableCMSMainExtension.php
@@ -14,10 +14,14 @@ class TranslatableCMSMainExtension extends Extension {
// $Lang serves as a "context" which can be inspected by Translatable - hence it
// has the same name as the database property on Translatable.
$req = $this->owner->getRequest();
+ $id = $req->param('ID');
if($req->requestVar("Locale")) {
$this->owner->Locale = $req->requestVar("Locale");
} elseif($req->requestVar("locale")) {
$this->owner->Locale = $req->requestVar("locale");
+ } else if($id && is_numeric($id)) {
+ $record = DataObject::get_by_id($this->owner->stat('tree_class'), $id);
+ if($record && $record->Locale) $this->owner->Locale = $record->Locale;
} else {
$this->owner->Locale = Translatable::default_locale();
}
@@ -46,14 +50,15 @@ class TranslatableCMSMainExtension extends Extension {
/**
* Create a new translation from an existing item, switch to this language and reload the tree.
*/
- function createtranslation($request) {
+ function createtranslation($data, $form) {
+ $request = $this->owner->getRequest();
+
// Protect against CSRF on destructive action
if(!SecurityToken::inst()->checkRequest($request)) return $this->owner->httpError(400);
- $langCode = Convert::raw2sql($request->getVar('newlang'));
- $originalLangID = (int)$request->getVar('ID');
-
- $record = $this->owner->getRecord($originalLangID);
+ $langCode = Convert::raw2sql($request->postVar('NewTransLang'));
+ $record = $this->owner->getRecord($request->postVar('ID'));
+ if(!$record) return $this->httpError(404);
$this->owner->Locale = $langCode;
Translatable::set_current_locale($langCode);
@@ -67,13 +72,25 @@ class TranslatableCMSMainExtension extends Extension {
$url = sprintf(
"%s/%d/?locale=%s",
- $this->owner->Link('show'),
+ singleton('CMSPageEditController')->Link('show'),
$translatedRecord->ID,
$langCode
);
return Director::redirect($url);
}
+
+ function updateLink(&$link) {
+ if($this->owner->Locale) $link = Controller::join_links($link, '?locale=' . $this->owner->Locale);
+ }
+
+ function updateLinkWithSearch(&$link) {
+ if($this->owner->Locale) $link = Controller::join_links($link, '?locale=' . $this->owner->Locale);
+ }
+
+ function updateExtraTreeTools(&$html) {
+ $html = $this->LangForm()->forTemplate() . $html;
+ }
/**
* Returns a form with all languages with languages already used appearing first.
@@ -104,14 +121,15 @@ class TranslatableCMSMainExtension extends Extension {
$form = new Form(
$this->owner,
'LangForm',
- new FieldSet(
+ new FieldList(
$field
),
- new FieldSet(
+ new FieldList(
new FormAction('selectlang', _t('CMSMain_left.ss.GO','Go'))
)
);
$form->unsetValidator();
+ $form->addExtraClass('nostyle');
return $form;
}
diff --git a/code/model/Translatable.php b/code/model/Translatable.php
index b1b6f4d..f23d125 100755
--- a/code/model/Translatable.php
+++ b/code/model/Translatable.php
@@ -516,7 +516,7 @@ class Translatable extends DataExtension implements PermissionProvider {
}
}
- function extraStatics() {
+ function extraStatics($class = null, $extension = null) {
return array(
"db" => array(
"Locale" => "DBLocale",
@@ -542,7 +542,6 @@ class Translatable extends DataExtension implements PermissionProvider {
// with other extensions like Versioned
$locale = ($this->owner->ID && $this->owner->Locale) ? $this->owner->Locale : Translatable::get_current_locale();
$baseTable = ClassInfo::baseDataClass($this->owner->class);
- $where = $query->where;
if(
$locale
// unless the filter has been temporarily disabled
@@ -551,13 +550,13 @@ class Translatable extends DataExtension implements PermissionProvider {
&& !$query->filtersOnID()
// the query contains this table
// @todo Isn't this always the case?!
- && array_search($baseTable, array_keys($query->from)) !== false
+ && array_search($baseTable, array_keys($query->getFrom())) !== false
// or we're already filtering by Lang (either from an earlier augmentSQL() call or through custom SQL filters)
- && !preg_match('/("|\'|`)Locale("|\'|`)/', $query->getFilter())
+ && !preg_match('/("|\'|`)Locale("|\'|`)/', implode(' ', $query->getWhere()))
//&& !$query->filtersOnFK()
) {
$qry = sprintf('"%s"."Locale" = \'%s\'', $baseTable, Convert::raw2sql($locale));
- $query->where[] = $qry;
+ $query->addWhere($qry);
}
}
@@ -900,92 +899,15 @@ class Translatable extends DataExtension implements PermissionProvider {
* readonly fields, so you can translation INTO the "default language" while
* seeing readonly fields as well.
*/
- function updateCMSFields(FieldSet &$fields) {
- if(!class_exists('SiteTree')) return;
- // Don't apply these modifications for normal DataObjects - they rely on CMSMain logic
- if(!($this->owner instanceof SiteTree)) return;
-
- // used in CMSMain->init() to set language state when reading/writing record
- $fields->push(new HiddenField("Locale", "Locale", $this->owner->Locale) );
-
- // Don't allow translation of virtual pages because of data inconsistencies (see #5000)
- if(class_exists('VirtualPage')){
- $excludedPageTypes = array('VirtualPage');
- foreach($excludedPageTypes as $excludedPageType) {
- if(is_a($this->owner, $excludedPageType)) return;
- }
- }
-
- $excludeFields = array(
- 'ViewerGroups',
- 'EditorGroups',
- 'CanViewType',
- 'CanEditType'
- );
+ function updateCMSFields(FieldList $fields) {
+ $this->addTranslatableFields($fields);
- // if a language other than default language is used, we're in "translation mode",
- // hence have to modify the original fields
- $creating = false;
- $baseClass = $this->owner->class;
- $allFields = $fields->toArray();
- while( ($p = get_parent_class($baseClass)) != "DataObject") $baseClass = $p;
-
- // try to get the record in "default language"
- $originalRecord = $this->owner->getTranslation(Translatable::default_locale());
- // if no translation in "default language", fall back to first translation
- if(!$originalRecord) {
- $translations = $this->owner->getTranslations();
- $originalRecord = ($translations) ? $translations->First() : null;
- }
-
- $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;
$alreadyTranslatedLocales = array_combine($alreadyTranslatedLocales, $alreadyTranslatedLocales);
-
- if($originalRecord && $isTranslationMode) {
- $originalLangID = Session::get($this->owner->ID . '_originalLangID');
-
- // Remove parent page dropdown
- $fields->removeByName("ParentType");
- $fields->removeByName("ParentID");
-
- $translatableFieldNames = $this->getTranslatableFields();
- $allDataFields = $fields->dataFields();
-
- $transformation = new Translatable_Transformation($originalRecord);
-
- // 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(), $excludeFields)) continue;
-
- if(in_array($dataField->Name(), $translatableFieldNames)) {
- // 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());
- }
- }
-
- } elseif($this->owner->isNew()) {
- $fields->addFieldsToTab(
- 'Root',
- new Tab(_t('Translatable.TRANSLATIONS', 'Translations'),
- new LiteralField('SaveBeforeCreatingTranslationNote',
- sprintf('
%s
',
- _t('Translatable.NOTICENEWPAGE', 'Please save this page before creating a translation')
- )
- )
- )
- );
- }
$fields->addFieldsToTab(
'Root',
@@ -1012,9 +934,9 @@ class Translatable extends DataExtension implements PermissionProvider {
$existingTransHTML = '';
foreach($alreadyTranslatedLocales as $langCode) {
$existingTranslation = $this->owner->getTranslation($langCode);
- if($existingTranslation) {
+ if($existingTranslation && $existingTranslation->hasMethod('CMSEditLink')) {
$existingTransHTML .= sprintf('- %s
',
- sprintf('admin/show/%d/?locale=%s', $existingTranslation->ID, $langCode),
+ sprintf('%s/?locale=%s', $existingTranslation->CMSEditLink(), $langCode),
i18n::get_locale_name($langCode)
);
}
@@ -1026,12 +948,94 @@ class Translatable extends DataExtension implements PermissionProvider {
);
}
- $langDropdown->addExtraClass('languageDropdown');
+ $langDropdown->addExtraClass('languageDropdown no-change-track');
$createButton->addExtraClass('createTranslationButton');
}
function updateSettingsFields(&$fields) {
- return $this->updateCMSFields();
+ $this->addTranslatableFields($fields);
+ }
+
+ protected function addTranslatableFields(&$fields) {
+ if(!class_exists('SiteTree')) return;
+ // Don't apply these modifications for normal DataObjects - they rely on CMSMain logic
+ if(!($this->owner instanceof SiteTree)) return;
+
+ // used in CMSMain->init() to set language state when reading/writing record
+ $fields->push(new HiddenField("Locale", "Locale", $this->owner->Locale) );
+
+ // Don't allow translation of virtual pages because of data inconsistencies (see #5000)
+ if(class_exists('VirtualPage')){
+ $excludedPageTypes = array('VirtualPage');
+ foreach($excludedPageTypes as $excludedPageType) {
+ if(is_a($this->owner, $excludedPageType)) return;
+ }
+ }
+
+ // TODO Remove hardcoding for SiteTree properties
+ $excludeFields = array(
+ 'ViewerGroups',
+ 'EditorGroups',
+ 'CanViewType',
+ 'CanEditType'
+ );
+
+ // if a language other than default language is used, we're in "translation mode",
+ // hence have to modify the original fields
+ $creating = false;
+ $baseClass = $this->owner->class;
+ $allFields = $fields->toArray();
+ while( ($p = get_parent_class($baseClass)) != "DataObject") $baseClass = $p;
+
+ // try to get the record in "default language"
+ $originalRecord = $this->owner->getTranslation(Translatable::default_locale());
+ // if no translation in "default language", fall back to first translation
+ if(!$originalRecord) {
+ $translations = $this->owner->getTranslations();
+ $originalRecord = ($translations) ? $translations->First() : null;
+ }
+
+ $isTranslationMode = $this->owner->Locale != Translatable::default_locale();
+
+ if($originalRecord && $isTranslationMode) {
+ $originalLangID = Session::get($this->owner->ID . '_originalLangID');
+
+ // Remove parent page dropdown
+ $fields->removeByName("ParentType");
+ $fields->removeByName("ParentID");
+
+ $translatableFieldNames = $this->getTranslatableFields();
+ $allDataFields = $fields->dataFields();
+
+ $transformation = new Translatable_Transformation($originalRecord);
+
+ // 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->getName(), $excludeFields)) continue;
+
+ if(in_array($dataField->getName(), $translatableFieldNames)) {
+ // if the field is translatable, perform transformation
+ $fields->replaceField($dataField->getName(), $transformation->transformFormField($dataField));
+ } else {
+ // else field shouldn't be editable in translation-mode, make readonly
+ $fields->replaceField($dataField->getName(), $dataField->performReadonlyTransformation());
+ }
+ }
+
+ } elseif($this->owner->isNew()) {
+ $fields->addFieldsToTab(
+ 'Root',
+ new Tab(_t('Translatable.TRANSLATIONS', 'Translations'),
+ new LiteralField('SaveBeforeCreatingTranslationNote',
+ sprintf('%s
',
+ _t('Translatable.NOTICENEWPAGE', 'Please save this page before creating a translation')
+ )
+ )
+ )
+ );
+ }
}
/**
@@ -1089,24 +1093,20 @@ class Translatable extends DataExtension implements PermissionProvider {
// exclude the language of the current owner
$filter .= sprintf(' AND "%s"."Locale" != \'%s\'', $baseDataClass, $this->owner->Locale);
}
- $join = sprintf('LEFT JOIN "%s_translationgroups" ON "%s_translationgroups"."OriginalID" = "%s"."ID"',
- $baseDataClass,
- $baseDataClass,
- $baseDataClass
- );
$currentStage = Versioned::current_stage();
+ $joinOnClause = sprintf('"%s_translationgroups"."OriginalID" = "%s"."ID"', $baseDataClass, $baseDataClass);
if($this->owner->hasExtension("Versioned")) {
if($stage) Versioned::reading_stage($stage);
$translations = Versioned::get_by_stage(
$this->owner->class,
Versioned::current_stage(),
$filter,
- null,
- $join
- );
+ null
+ )->leftJoin("{$baseDataClass}_translationgroups", $joinOnClause);
if($stage) Versioned::reading_stage($currentStage);
} else {
- $translations = DataObject::get($this->owner->class, $filter, null, $join);
+ $translations = DataObject::get($this->owner->class, $filter)
+ ->leftJoin("{$baseDataClass}_translationgroups", $joinOnClause);
}
self::enable_locale_filter();
@@ -1577,7 +1577,7 @@ class Translatable_Transformation extends FormTransformation {
}
protected function baseTransform($nonEditableField, $originalField) {
- $fieldname = $originalField->Name();
+ $fieldname = $originalField->getName();
$nonEditableField_holder = new CompositeField($nonEditableField);
$nonEditableField_holder->setName($fieldname.'_holder');
diff --git a/css/CMSMain.Translatable.css b/css/CMSMain.Translatable.css
index 82aaf03..9268f5c 100644
--- a/css/CMSMain.Translatable.css
+++ b/css/CMSMain.Translatable.css
@@ -11,3 +11,17 @@ ul.tree span.untranslated a:visited {
.right form#Form_EditForm div.createTranslation {
margin-left: 0;
}
+
+.CMSMain #Form_LangForm #Locale .middleColumn,
+.CMSMain #Form_LangForm #Locale label {
+ display: inline-block;
+}
+
+.CMSMain #Form_LangForm .Actions {
+ display: none;
+}
+
+/* Language dropdown has its own spacing */
+.cms-content-toolbar {
+ margin-bottom: 7px;
+}
\ No newline at end of file
diff --git a/javascript/CMSMain.Translatable.js b/javascript/CMSMain.Translatable.js
index 5138626..0b3ef6d 100755
--- a/javascript/CMSMain.Translatable.js
+++ b/javascript/CMSMain.Translatable.js
@@ -22,19 +22,23 @@
if(newLocale) self.val(newLocale);
});
- // whenever a new value is selected, reload the whole CMS in the new locale
- this.find(':input[name=Locale]').bind('change', function(e) {
- var url = document.location.href;
- url += (url.indexOf('?') != -1) ? '&' : '?';
- // TODO Replace existing locale GET params
- url += 'locale=' + $(e.target).val();
- document.location = url;
- return false;
- });
-
this._super();
}
});
+
+ /**
+ * whenever a new value is selected, reload the whole CMS in the new locale
+ */
+ $('.CMSMain #Form_LangForm :input[name=Locale]').entwine({
+ onchange: function(e) {
+ var url = $.path.addSearchParams(
+ document.location.href.replace(/locale=[^&]*/, ''),
+ {locale: $(e.target).val()}
+ );
+ $('.cms-container').loadPanel(url);
+ return false;
+ }
+ });
/**
* Class: .CMSMain .createTranslation
@@ -51,18 +55,8 @@
$('.CMSMain :input[name=action_createtranslation]').entwine({
onclick: function() {
- var form = this.parents('form'), locale = form.find(':input[name=NewTransLang]').val();
- var params = {
- 'ID': form.find(':input[name=ID]').val(),
- 'newlang': locale,
- 'locale': locale,
- 'SecurityID': form.find(':input[name=SecurityID]').val()
- };
- // redirect to new URL
- // TODO This should really be a POST request
- // TODO Fix hardcode URL
- document.location.href = $('base').attr('href') + 'admin/createtranslation?' + $.param(params);
-
+ this.parents('form').trigger('submit', [this]);
+ e.preventDefault();
return false;
}
});
diff --git a/tests/unit/TranslatableSearchFormTest.php b/tests/unit/TranslatableSearchFormTest.php
index 28b9544..fbddae9 100644
--- a/tests/unit/TranslatableSearchFormTest.php
+++ b/tests/unit/TranslatableSearchFormTest.php
@@ -28,10 +28,11 @@ class TranslatableSearchFormTest extends FunctionalTest {
function setUpOnce() {
// HACK Postgres doesn't refresh TSearch indexes when the schema changes after CREATE TABLE
- if(is_a(DB::getConn(), 'PostgreSQLDatabase')) {
- self::kill_temp_db();
- }
-
+ // MySQL will need a different table type
+ self::kill_temp_db();
+ FulltextSearchable::enable();
+ self::create_temp_db();
+ $this->resetDBSchema(true);
parent::setUpOnce();
}
diff --git a/tests/unit/TranslatableSiteConfigTest.php b/tests/unit/TranslatableSiteConfigTest.php
index e63e97d..08f7866 100644
--- a/tests/unit/TranslatableSiteConfigTest.php
+++ b/tests/unit/TranslatableSiteConfigTest.php
@@ -35,7 +35,7 @@ class TranslatableSiteConfigTest extends SapphireTest {
$configEn = SiteConfig::current_site_config();
$configFr = SiteConfig::current_site_config('fr_FR');
- $this->assertType('SiteConfig', $configFr);
+ $this->assertInstanceOf('SiteConfig', $configFr);
$this->assertEquals($configFr->Locale, 'fr_FR');
$this->assertEquals($configFr->Title, $configEn->Title, 'Copies title from existing config');
}
diff --git a/tests/unit/TranslatableTest.php b/tests/unit/TranslatableTest.php
index 961fae3..e8d17c8 100755
--- a/tests/unit/TranslatableTest.php
+++ b/tests/unit/TranslatableTest.php
@@ -209,7 +209,7 @@ class TranslatableTest extends FunctionalTest {
function testTranslationCantHaveSameURLSegmentAcrossLanguages() {
$origPage = $this->objFromFixture('Page', 'testpage_en');
$translatedPage = $origPage->createTranslation('de_DE');
- $this->assertEquals($translatedPage->URLSegment, 'testpage-de-DE');
+ $this->assertEquals($translatedPage->URLSegment, 'testpage-de-de');
$translatedPage->URLSegment = 'testpage';
$translatedPage->write();
@@ -222,7 +222,7 @@ class TranslatableTest extends FunctionalTest {
// first test with default language
$fields = $pageOrigLang->getCMSFields();
- $this->assertType(
+ $this->assertInstanceOf(
'TextField',
$fields->dataFieldByName('Title'),
'Translatable doesnt modify fields if called in default language (e.g. "non-translation mode")'
@@ -235,13 +235,13 @@ class TranslatableTest extends FunctionalTest {
// then in "translation mode"
$pageTranslated = $pageOrigLang->createTranslation('fr_FR');
$fields = $pageTranslated->getCMSFields();
- $this->assertType(
+ $this->assertInstanceOf(
'TextField',
$fields->dataFieldByName('Title'),
'Translatable leaves original formfield intact in "translation mode"'
);
$readonlyField = $fields->dataFieldByName('Title')->performReadonlyTransformation();
- $this->assertType(
+ $this->assertInstanceOf(
$readonlyField->class,
$fields->dataFieldByName('Title_original'),
'Translatable adds the original value as a ReadonlyField in "translation mode"'
@@ -795,7 +795,7 @@ class TranslatableTest extends FunctionalTest {
"Users with canEdit() and TRANSLATE_ALL permission can't create a new translation if locale is not in Translatable::get_allowed_locales()"
);
- $this->assertType(
+ $this->assertInstanceOf(
'Page',
$testPage->createTranslation('ja_JP')
);
@@ -965,7 +965,7 @@ class TranslatableTest_DataObject extends DataObject implements TestOnly {
class TranslatableTest_Extension extends DataExtension implements TestOnly {
- function extraStatics() {
+ function extraStatics($class = null, $extension = null) {
return array(
'db' => array(
'TranslatableDecoratedProperty' => 'Text'