15/11/2011 1:57pm

PHP | Working Example - Localizable


<?php

/**
* Alternative way of having localizable dataobjects
*
* This class is thought to be used with languages (fr, en...) more than
* locales (fr_FR,en_US..) because using locales force you to use longer
* names, type more etc and most of the time, you don't need them.
* However, to keep everything consistent with Translatable, in the database, the locale is
* used, not the language.
*/
class Localizable extends DataObjectDecorator {
/**
* The current locale
* @var string
*/
public static $currentLocale;
/**
* Available locales
* @var array
*/
public static $availableLocales = array();
/**
* Caching locale objects
* @var array
*/
public static $cache = array();
/**
* Use the field that is not localized with the same name
* if value is empty
* @var bool
*/
public static $useFallback = false;
/**
* Should we try to return a localized value instead of the original
* field (set to true in Page.php)
* @var bool
*/
public static $automaticLocalization = false;

/**
* create a sister table called <table>_locale
*
* Create a static property called localizableFields to specify the fields
* that should be localizable
*/
function augmentDatabase() {
$tableName = $this->getLocaleTable();
if(!$tableName) {
return;
}
$fieldSchema = array(
'ParentID' => 'Int',
'Locale' => 'DBLocale'
);
$indexSchema = array(
'ParentID' => true,
'Locale' => true
);

$fieldSchema = array_merge($fieldSchema, $this->getLocalizableFields());

DB::requireTable($tableName, $fieldSchema, $indexSchema);
}

/**
* Update the cms fields with localizable fields
* @param FieldSet $fields
*/
function updateCMSFields(FieldSet &$fields) {
$fields->addFieldToTab('Root',$this->createLocalizableTab());
}

/**
* Add a localizable tab to a fieldset
* @param FieldSet $fields
*/
function createLocalizableTab() {
$tabs = new TabSet('Localizable');

//one tab for each lang
foreach(self::getLocales() as $locale) {
$lang = self::localeToLang($locale);
$tab = new Tab($lang);
$tabs->push($tab);

foreach($this->getLocalizableFields() as $name => $type) {
// if(false) {
if(class_exists($type)) {
$classObj = new $type();
/* @var $formField FormField */
$formField = $classObj->scaffoldFormField($name);
$formField->setName($this->makeFieldName($name,$lang));
//HTML EDITOR need to have a unique title for some reason
$formField->setTitle($name . ' ' . $lang);
//pass value
$formField->setValue($this->getLocaleValue($name,$lang));
}
else {
$formField = new TextField($this->makeFieldName($name,$lang));
}
$tab->push($formField);
}
}

return $tabs;
}

/**
* Get a localized value for a given field
*
* @param string $name the name of the field
* @param string $locale (optional) the lang or locale for the field
* @return mixed
*/
function getLocaleValue($name,$locale = null) {
if(strlen($locale) == 2) {
$locale = self::langToLocale($locale);
}
if($locale == null) {
$locale = self::getCurrentLocale();
}
//if it's the current locale, we have it through the augmentSQL
if($locale == self::getCurrentLocale()) {
$field = $name . '_' . self::getCurrentLang();
return $this->owner->$field;
}

$class = $this->getClass();

if(!isset(self::$cache[$class][$this->owner->ID][$locale])) {
self::$cache[$class][$this->owner->ID][$locale] = $this->fetchLocaleRecord($locale);
}

$localeRecord = self::$cache[$class][$this->owner->ID][$locale];
return $localeRecord[$name];
}

/**
* Method used by __get in the dataobject
*
* Check if the field in translated in the current locale
*
* @param string $fieldName
* @return bool
*/
function hasLocalization($fieldName) {
if(!in_array($fieldName, array_keys($this->getLocalizableFields()))) {
return false;
}
$v = $this->getLocaleValue($fieldName);
if(empty($v)) {
return false;
}
return true;
}

/**
* Method used by __get in the dataobject
*
* Return the value of the translated field in the current locale
*
* @param string $fieldName
* @return mixed
*/
function localize($fieldName) {
$v = $this->getLocaleValue($fieldName);
if(empty($v) && self::$useFallback) {
return $this->owner->$fieldName;
}
return $v;
}

/**
* Fetch the localized fields for the dataobject
* @return array
*/
function fetchLocaleRecord($locale = null) {
if(strlen($locale) == 2) {
$locale = self::langToLocale($locale);
}
if($locale == null) {
$locale = self::getCurrentLocale();
}

$sql = "SELECT * FROM " . $this->getLocaleTable() . " WHERE ParentID = " . $this->owner->ID . " AND Locale = '" . $locale . "'";
$result = DB::query($sql);
$record = $result->record();

return $record;
}


/**
* Join the locale data for the current locale (avoiding multiple queries
* to fetch localized data)
*
* @param SQLQuery $query
*/
function augmentSQL(SQLQuery &$query) {
// $onPredicate = '"' . $this->getClass() . '"."ID" = "l"."ParentID"';
$onPredicate = '"' . $this->getClass() . '"."ID" = "l"."ParentID" AND "l"."Locale" = ' . "'" . $this->getCurrentLocale() . "'";
$fields = array();
$lang = $this->getCurrentLang();
foreach($this->getLocalizableFields() as $name => $type) {
$fields[] = "l." . $name . ' AS ' . $name . '_' . $lang;
}
$fields = array_merge($query->select,$fields);
$query->select($fields);
$query->leftJoin($this->getLocaleTable(), $onPredicate, 'l');
// $query->where("l.Locale = '" . $this->getCurrentLocale() . "'");
// $query->where("l.Locale = '" . $this->getCurrentLocale() . "' OR l.locale IS NULL");
}

/**
* On write, write in the _locale table all localizable fields
* @param array $manipulation
*/
function augmentWrite(&$manipulation) {
if(isset($_POST['localizable'])) {
//check if we need to create the db rows
$sql = 'SELECT count(ID) FROM ' . $this->getLocaleTable() . ' WHERE ParentID = ' . $this->owner->ID;
$res = DB::query($sql);

if($res->value() == 0) {
foreach(self::getLocales() as $locale) {
$sql = "INSERT INTO " . $this->getLocaleTable() . " (ParentID,Locale) VALUES (".$this->owner->ID.",'".$locale."')";
DB::query($sql);
}
}

$localizable = $_POST['localizable'];
foreach($localizable as $lang => $fields) {
$sql = 'UPDATE ' . $this->getLocaleTable() . ' SET ';
//fields
foreach($fields as $k => $v) {
$sql .= $k . " = '" . Convert::raw2sql($v) . "'";
}
//where
$sql .= "WHERE ParentID = " . $this->owner->ID . " AND Locale = '" . self::langToLocale($lang) . "'";

$res = DB::query($sql);
}
}
}

/**
* Map a lang to a locale (only if you have a 1-1 mapping)
* @param string $lang
* @return string
*/
static function langToLocale($lang) {
foreach(self::getLocales() as $loc) {
if(self::localeToLang($loc) == $lang) {
return $loc;
}
}
}

/**
* Map a locale to a locale (only if you have a 1-1 mapping)
* @param string $locale
* @return string
*/
static function localeToLang($locale) {
return substr($locale, 0,2);
}

/**
* Create a field name that is language depend
* @param string $name
* @param string $lang (optional)
* @return string
*/
function makeFieldName($name, $lang = null) {
if($lang == null) {
$lang = $this->getCurrentLang();
}
// return 'localizable_' . $lang . '_' . $name;
return 'localizable[' . $lang . '][' . $name . ']';
}

/**
* Get class of the owner, the ss way
* @return string
*/
function getClass() {
return ClassInfo::baseDataClass($this->owner->class);
}

/**
* Get the name of the sister table for storing localized data
* @return string
*/
function getLocaleTable() {
$baseDataClass = $this->getClass();
if($this->owner->class != $baseDataClass) return;

$tableName = $baseDataClass . '_locale';
return $tableName;
}

/**
* Get current lang (two first characters of the locale)
* @return string
*/
static function getCurrentLang() {
return self::localeToLang(self::getCurrentLocale());
}

/**
* Get current locale
* @return string
*/
static function getCurrentLocale() {
if(empty (self::$currentLocale)) {
self::$currentLocale = Translatable::get_current_locale();
}
return self::$currentLocale;
}

/**
* Get current locales (same as Translatable)
* @return array
*/
static function getLocales() {
if(empty(self::$availableLocales)) {
self::$availableLocales = Translatable::get_allowed_locales();
}
return self::$availableLocales;
}

/**
* Set the available locales
* @param array $locales
*/
static function setLocales($locales) {
self::$availableLocales = $locales;
}

/**
* Get localizable fields in the dataobject, defined as an array
*
* Example :
* array(
* 'Field' => 'Type'
* )
*
* @return array
*/
function getLocalizableFields() {
$class = $this->getClass();
return $class::$localizableFields;
}
}


Post Comment