mirror of
https://github.com/silverstripe/silverstripe-translatable
synced 2024-10-22 09:05:59 +00:00
BUGFIX 3.0 compatibility: Query manipulation, CMS links, separation of settings fields, new place for language selector on tree
This commit is contained in:
parent
0cea59a2a7
commit
610874755c
@ -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;
|
||||
}
|
||||
|
@ -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('<p class="message">%s</p>',
|
||||
_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 = '<ul>';
|
||||
foreach($alreadyTranslatedLocales as $langCode) {
|
||||
$existingTranslation = $this->owner->getTranslation($langCode);
|
||||
if($existingTranslation) {
|
||||
if($existingTranslation && $existingTranslation->hasMethod('CMSEditLink')) {
|
||||
$existingTransHTML .= sprintf('<li><a href="%s">%s</a></li>',
|
||||
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('<p class="message">%s</p>',
|
||||
_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');
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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'
|
||||
|
Loading…
x
Reference in New Issue
Block a user