Skip to content

Instantly share code, notes, and snippets.

@ADmad
Created December 1, 2015 14:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ADmad/11b5b3e605b7c84b5e3f to your computer and use it in GitHub Desktop.
Save ADmad/11b5b3e605b7c84b5e3f to your computer and use it in GitHub Desktop.
<?php
App::uses('Hash', 'Utility');
/**
* Translation behavior.
*
* @license MIT
* @url http://code.google.com/p/alkemann
* @author Alexander Morland aka alkemann
* @author Ronny Vindenes
* @modified 2. january 2009
* @version 1.0
* @package Multilingual.Model.Behavior
*/
class MultilingualBehavior extends ModelBehavior {
/**
* Shadow table prefix
* Only change this value if it causes table name crashes
*
* @access protected
* @var string
*/
protected $_suffix = '_locales';
/**
* Default setting values
*
* @access public
* @var array
*/
public $defaults = array(
'default' => '',
'fields' => null,
'useDbConfig' => null,
'model' => false
);
/**
* Constructor
*/
public function __construct() {
if (empty($this->defaults['default'])) {
$this->defaults['default'] = (string)Configure::read('Languages.default');
}
}
/**
* Configure the behavior through the Model::actsAs property
*
* @param object $Model
* @param array $config
*/
public function setup(Model $Model, $config = null) {
if (is_array($config)) {
$this->settings[$Model->alias] = array_merge($this->defaults, $config);
} else {
$this->settings[$Model->alias] = $this->defaults;
}
if (!isset($Model->locale)) {
$Model->locale = null;
}
$Model->LocaleModel = null;
}
/**
* Get a list of locales that the currently Model->id is translated to.
*
* @param object $Model
* @return array list of locales that this Model->id is translated to
*/
public function locales(Model $Model) {
if (empty($Model->id)) {
return null;
}
$this->createLocaleModel($Model, true);
$Model->LocaleModel->displayField = 'locale';
$list = $Model->LocaleModel->find('list', array(
'conditions' => array($Model->primaryKey => $Model->id)
));
return $list;
}
/**
* Get all translations of a record.
*
* @param object $Model Model instance
* @param mixed $id Record id, if null $Model->id is used
* @param array $options Find options
* @return mixed Translated records indexed by locale
*/
public function translations(Model $Model, $id = null, $options = array()) {
$this->createLocaleModel($Model, true);
if (empty($id)) {
$id = $Model->id;
}
if (isset($options['conditions'])) {
$options['conditions'] = array_merge(
array($Model->primaryKey => $id),
$options['conditions']
);
} else {
$options['conditions'] = array($Model->primaryKey => $id);
}
$mOptions = $options;
if (isset($options['fields'])) {
$options['fields'][] = 'locale';
}
$data = $Model->LocaleModel->find('all', $options);
$data = (array)Hash::combine($data,
'{n}.' . $Model->LocaleModel->alias . '.locale',
'{n}.' . $Model->LocaleModel->alias
);
$locale = $Model->locale;
$mOptions['locale'] = $this->defaults['default'];
$mOptions['recursive'] = -1;
$defaultTranslation = $Model->find('first', $mOptions);
$data[$this->defaults['default']] = $defaultTranslation[$Model->alias];
$Model->locale = $locale;
return $data;
}
/**
* Sets the given locale to the mode. Uses the default if no param given.
*
* @param object $Model
* @param string $locale
* @return string the locale set. ie can use this to get default
*/
public function setLocale(Model $Model, $locale = null) {
if (!is_string($locale)) {
$locale = $this->settings[$Model->alias]['default'];
}
$Model->locale = $locale;
return $locale;
}
/**
* When a model row is deleted, this will delete locales for that Id
*
* @param object $Model
*/
public function afterDelete(Model $Model) {
$this->createLocaleModel($Model);
if ($Model->LocaleModel) {
$Model->LocaleModel->deleteAll(array($Model->primaryKey => $Model->id));
}
}
/**
* If locale is set, assumes the result have joined locale data and will merge the results
*
* @param object $Model
* @param array $result
* @return array modified result
*/
public function afterFind(Model $Model, $result, $primary = false) {
// Prevent potential problems when behavior callbacks
// start getting triggered for associated models
if (!$primary) {
return $result;
}
if (empty($result) || $this->type == 'count') { // !$Model->LocaleModel ||
return $result;
}
if (is_string($Model->locale) && $Model->locale != $this->settings[$Model->alias]['default']) {
$localeAlias = 'I18n__' . $Model->alias . '__' . $Model->locale;
foreach ($result as $key => $data) {
if (empty($data[$localeAlias]['locale'])) {
$result[$key][$Model->alias]['locale'] = $this->settings[$Model->alias]['default'];
unset($result[$key][$localeAlias]);
} else {
foreach ($data[$localeAlias] as $field => $value) {
$result[$key][$Model->alias][$field] = $value;
}
unset($result[$key][$localeAlias]);
}
if (!empty($this->related[$Model->alias]['belongsTo'])) {
foreach ($this->related[$Model->alias]['belongsTo'] as $assocAlias => $assoc) {
if (!empty($result[$key][$Model->alias][$assoc['foreignKey']])) {
$Model->$assocAlias->locale = $Model->locale;
$Model->$assocAlias->recursive = $Model->recursive - 1;
// could join if "end of the line", must find if this model also has associated models. can check recursive
$findOptions = array(
'conditions' => array(
$Model->$assocAlias->alias . '.' . $Model->$assocAlias->primaryKey => $result[$key][$Model->alias][$assoc['foreignKey']]
)
);
if (isset($assoc['contain'])) {
$findOptions['contain'] = $this->_cleanKeys($assoc['contain']);
}
$subdata = $Model->$assocAlias->find('first', $findOptions);
$result[$key][$assocAlias] = $subdata[$assocAlias];
unset($subdata[$assocAlias]);
if (!empty($subdata)) {
$result[$key][$assocAlias] = array_merge($result[$key][$assocAlias], $subdata);
}
}
}
}
if (!empty($this->related[$Model->alias]['hasOne'])) {
foreach ($this->related[$Model->alias]['hasOne'] as $assocAlias => $assoc) {
$Model->$assocAlias->locale = $Model->locale;
$Model->$assocAlias->recursive = $Model->recursive - 1;
// could join if "end of the line", must find if this model also has associated models. can check recursive
$findOptions = array(
'conditions' => array(
$assoc['foreignKey'] => $result[$key][$Model->alias][$Model->primaryKey]
)
);
if (!empty($assoc['conditions'])) {
$findOptions['conditions'][] = $assoc['conditions'];
}
if (isset($assoc['contain'])) {
$findOptions['contain'] = $this->_cleanKeys($assoc['contain']);
}
$subdata = $Model->$assocAlias->find('first', $findOptions);
if (!empty($subdata)) {
$result[$key][$assocAlias] = $subdata[$assocAlias];
}
}
}
if (!empty($this->related[$Model->alias]['hasMany'])) {
foreach ($this->related[$Model->alias]['hasMany'] as $assocAlias => $assoc) {
$Model->$assocAlias->locale = $Model->locale;
$Model->$assocAlias->recursive = $Model->recursive - 1;
$findOptions = array(
'conditions' => array(
$assoc['foreignKey'] => $result[$key][$Model->alias][$Model->primaryKey]
)
);
if (!empty($assoc['conditions'])) {
$findOptions['conditions'] = array_merge($findOptions['conditions'], $assoc['conditions']);
}
if (!empty($assoc['limit'])) {
$findOptions['limit'] = $assoc['limit'];
}
if (isset($assoc['contain'])) {
$findOptions['contain'] = $this->_cleanKeys($assoc['contain']);
}
$subdata = $Model->$assocAlias->find('all', $findOptions);
foreach ($subdata as $aKey => $aRecord) {
unset($subdata[$aKey][$assocAlias]);
$subdata[$aKey] = array_merge($aRecord[$assocAlias], $subdata[$aKey]);
}
$result[$key][$assocAlias] = $subdata;
}
}
}
} else {
foreach ($result as $key => $data) {
$result[$key][$Model->alias]['locale'] = $this->settings[$Model->alias]['default'];
}
}
return $result;
}
/**
* Will save localeData if it is set in beforeSave
*
* @param object $Model
* @param boolean $created true if an add and false on edit
*/
public function afterSave(Model $Model, $created, $options = array()) {
if (!$Model->LocaleModel || $created) {
return true;
}
if (isset($Model->localeData) && !empty($Model->localeData)) {
$exist = $Model->LocaleModel->find('first', array(
'conditions' => array(
'locale' => $Model->locale,
$Model->primaryKey => $Model->id
),
'fields' => array(
$Model->LocaleModel->primaryKey,
'locale',
$Model->primaryKey
)
));
$data = array($Model->LocaleModel->alias => $Model->localeData);
$Model->LocaleModel->create($data);
$Model->LocaleModel->set('locale', $Model->locale);
if (!empty($exist)) {
$id = $exist[$Model->LocaleModel->alias][$Model->LocaleModel->primaryKey];
$Model->LocaleModel->set($Model->LocaleModel->primaryKey, $id);
$Model->LocaleModel->id = $id;
}
$Model->LocaleModel->save();
unset($Model->localeData);
}
}
/**
* Deletes locale and not live data if Model::locale is set.
* Always return true when deleting locales.
*
* @param unknown_type $Model
* @return boolean false if deleting a locale
*/
public function beforeDelete(Model $Model, $cascade = true) {
$this->createLocaleModel($Model, true);
if (is_string($Model->locale) && $Model->locale != $this->settings[$Model->alias]['default']) {
$localeData = $Model->LocaleModel->find('first', array(
'fields' => array('trans_id', $Model->primaryKey),
'conditions' => array(
'locale' => $Model->locale,
$Model->primaryKey => $Model->id
),
'recursive' => -1
));
if (!empty($localeData)) {
$success = $Model->LocaleModel->delete($localeData[$Model->LocaleModel->alias]['trans_id']);
}
return false;
} else {
$success = $Model->LocaleModel->deleteAll(array(
$Model->LocaleModel->alias . '.' . $Model->primaryKey => $Model->id
));
}
return true;
}
/**
* If locale is set and find type is first all or list, will join in the locales table
*
* @param object $Model
* @param array $query
* @return array $query
*/
public function beforeFind(Model $Model, $query) {
$this->type = $Model->findQueryType;
$this->related[$Model->alias] = array();
if (!empty($query['locale'])) {
$Model->locale = $query['locale'];
unset($query['locale']);
}
if (is_string($Model->locale)
&& $Model->locale != $this->settings[$Model->alias]['default']
&& $this->type == 'first'
&& is_string($query['fields'])
&& in_array($query['fields'], $this->settings[$Model->alias]['fields'])
) {
$this->createLocaleModel($Model, true);
$localeAlias = 'I18n__' . $Model->alias . '__' . $Model->locale;
$db = ConnectionManager::getDataSource($Model->LocaleModel->useDbConfig);
$field = $query['fields'];
$query['fields'] = array($Model->alias . '.' . $query['fields']);
$query['fields'][] = $localeAlias . '.' . $field;
$query['fields'][] = $localeAlias . '.locale';
$query['fields'][] = $localeAlias . '.trans_id';
$query['joins'][] = array(
'type' => 'LEFT',
'alias' => $localeAlias,
'table' => $Model->LocaleModel->table,
'conditions' => array(
$Model->alias . '.' . $Model->primaryKey => $db->identifier($localeAlias . '.' . $Model->primaryKey),
$localeAlias . '.locale' => $Model->locale
)
);
} elseif (is_string($Model->locale)
&& $Model->locale != $this->settings[$Model->alias]['default']
&& in_array($this->type, array('first', 'all', 'list', 'threaded'))
) {
$this->createLocaleModel($Model, true);
$localeAlias = 'I18n__' . $Model->alias . '__' . $Model->locale;
$db = ConnectionManager::getDataSource($Model->useDbConfig);
if (empty($query['fields'])) {
$query['fields'] = array($Model->alias . '.*');
} elseif (is_string($query['fields'])) {
$query['fields'] = array($query['fields']);
}
foreach ($this->settings[$Model->alias]['fields'] as $key => $field) {
$query['fields'][] = $localeAlias . '.' . $field;
}
$query['fields'][] = $localeAlias . '.locale';
$query['fields'][] = $localeAlias . '.trans_id';
$query['joins'][] = array(
'type' => 'LEFT',
'alias' => $localeAlias,
'table' => $Model->LocaleModel->table,
'conditions' => array(
$Model->alias . '.' . $Model->primaryKey => $db->identifier($localeAlias . '.' . $Model->primaryKey),
$localeAlias . '.locale' => $Model->locale
)
);
$recursive = empty($query['recursive']) ? $Model->recursive : $query['recursive'];
$Model->recursive = $recursive;
if ($recursive > -1) {
foreach ($Model->belongsTo as $assocAlias => $assocArray) {
if (!isset($query['contain'])
|| (isset($query['contain'])
&& (isset($query['contain'][$assocAlias])
|| in_array($assocAlias, $query['contain'])
)
)
) {
if (isset($Model->{$assocAlias}->Behaviors->Multilingual)) {
// @todo: Fix this. The unbind causes problems if the 'fields' or 'order' contains fields on that model
//$Model->unbindModel(array('belongsTo'=>array($assocAlias)),true);
if (isset($query['contain'][$assocAlias])) {
$assocArray['contain'] = $query['contain'][$assocAlias];
}
$this->related[$Model->alias]['belongsTo'][$assocAlias] = $assocArray;
} else {
if (empty($assocArray['fields'])) {
$query['fields'][] = $assocAlias . '.*';
} else {
foreach ($assocArray['fields'] as $field) {
$query['fields'][] = $assocAlias . '.' . $field;
}
}
}
}
}
foreach ($Model->hasOne as $assocAlias => $assocArray) {
if (isset($Model->{$assocAlias}->Behaviors->Multilingual)) {
// @todo: Fix this. The unbind causes problems if the 'fields' or 'order' contains fields on that model
//$Model->unbindModel(array('hasOne'=>array($assocAlias)),true);
$this->related[$Model->alias]['hasOne'][$assocAlias] = $assocArray;
} else {
if (empty($assocArray['fields'])) {
$query['fields'][] = $assocAlias . '.*';
} else {
foreach ($assocArray['fields'] as $field) {
$query['fields'][] = $assocAlias . '.' . $field;
}
}
}
}
}
if ($recursive > 0) {
foreach ($Model->hasMany as $assocAlias => $assocArray) {
if (isset($Model->{$assocAlias}->Behaviors->Multilingual)) {
$Model->unbindModel(array('hasMany' => array($assocAlias)), true);
$this->related[$Model->alias]['hasMany'][$assocAlias] = $assocArray;
if (isset($query['contain']) && isset($query['contain'][$assocAlias])) {
$this->related[$Model->alias]['hasMany'][$assocAlias]['contain'] = $query['contain'][$assocAlias];
}
}
}
}
}
return $query;
}
/**
* If LocaleModel and locale is set, will save these fields
* and allow any other fields to continue on with the save process.
*
* @param object $Model
* @param array $options
* @return boolean true to continue save process
*/
public function beforeSave(Model $Model, $options = array()) {
if (!$Model->id || !is_string($Model->locale) || $Model->locale == $this->settings[$Model->alias]['default']) {
return true;
}
$this->createLocaleModel($Model);
if ($Model->LocaleModel) {
$Model->LocaleModel->create();
$Model->localeData = array();
foreach ($this->settings[$Model->alias]['fields'] as $field) {
if (isset($Model->data[$Model->alias][$field])) {
$Model->localeData[$field] = $Model->data[$Model->alias][$field];
unset($Model->data[$Model->alias][$field]);
}
}
if (!empty($Model->localeData)) {
$Model->localeData[$Model->primaryKey] = $Model->id;
}
}
return true;
}
/**
* creates a model to access the locale models
*
* @param object $Model
* @param boolean $triggerError Trigger error if unable to create Locale model
* @return boolean True if model created successfully
* @throws Exception
*/
public function createLocaleModel(Model $Model, $triggerError = false) {
if (is_object($Model->LocaleModel)) {
return true;
}
if ($this->settings[$Model->alias]['useDbConfig']) {
$dbConfig = $this->settings[$Model->alias]['useDbConfig'];
} else {
$dbConfig = $Model->useDbConfig;
}
$table = $Model->useTable . $this->_suffix;
$db = ConnectionManager::getDataSource($dbConfig);
$prefix = $Model->tablePrefix ? $Model->tablePrefix : $db->config['prefix'];
$tables = $db->listSources();
$fullTableName = $prefix . $table;
if ($prefix && empty($db->config['prefix'])) {
$table = $fullTableName;
}
if (!in_array($fullTableName, $tables)) {
if ($triggerError) {
throw new Exception(__('Unable to create locale model for %s', $Model->alias));
}
$Model->LocaleModel = false;
return false;
}
if (is_string($this->settings[$Model->alias]['model'])) {
$Model->LocaleModel = new $this->settings[$Model->alias]['model'](false, $table, $dbConfig);
} else {
$Model->LocaleModel = new Model(false, $table, $dbConfig);
$Model->LocaleModel->alias = 'I18n__' . $Model->alias;
$Model->LocaleModel->primaryKey = 'trans_id';
}
if (is_null($this->settings[$Model->alias]['fields'])) {
$this->settings[$Model->alias]['fields'] = array_diff(
array_keys($Model->LocaleModel->schema()),
array('trans_id', $Model->primaryKey, 'locale')
);
if (empty($this->settings[$Model->alias]['fields'])) {
$this->settings[$Model->alias]['fields'] = null;
}
}
return true;
}
/**
* Keep only model alias keys and remove keys like "fields", "order" etc.
*
* @param mixed $data
* @return mixed
*/
protected function _cleanKeys($data) {
if (is_array($data)) {
foreach ($data as $cK => $cV) {
if (is_string($cK)) {
if (strtoupper($cK{0}) !== $cK{0}) {
unset($data[$cK]);
}
}
}
}
return $data;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment