Created
December 1, 2015 14:40
-
-
Save ADmad/11b5b3e605b7c84b5e3f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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