PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/cake/libs/model/behaviors/translate.php

https://github.com/msadouni/cakephp2x
PHP | 517 lines | 350 code | 46 blank | 121 comment | 79 complexity | fb8f6a43d6d9fb7a80d51d2d806fbc8b MD5 | raw file
  1. <?php
  2. /**
  3. * Short description for file.
  4. *
  5. * Long description for file
  6. *
  7. * PHP Version 5.x
  8. *
  9. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  10. * Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. *
  12. * Licensed under The MIT License
  13. * Redistributions of files must retain the above copyright notice.
  14. *
  15. * @copyright Copyright 2005-2009, Cake Software Foundation, Inc. (http://cakefoundation.org)
  16. * @link http://cakephp.org CakePHP(tm) Project
  17. * @package cake
  18. * @subpackage cake.cake.libs.model.behaviors
  19. * @since CakePHP(tm) v 1.2.0.4525
  20. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  21. */
  22. /**
  23. * Short description for file.
  24. *
  25. * Long description for file
  26. *
  27. * @package cake
  28. * @subpackage cake.cake.libs.model.behaviors
  29. */
  30. class TranslateBehavior extends ModelBehavior {
  31. /**
  32. * Used for runtime configuration of model
  33. */
  34. public $runtime = array();
  35. /**
  36. * Callback
  37. *
  38. * $config for TranslateBehavior should be
  39. * array( 'fields' => array('field_one',
  40. * 'field_two' => 'FieldAssoc', 'field_three'))
  41. *
  42. * With above example only one permanent hasMany will be joined (for field_two
  43. * as FieldAssoc)
  44. *
  45. * $config could be empty - and translations configured dynamically by
  46. * bindTranslation() method
  47. *
  48. * @param array $config
  49. * @return mixed
  50. * @access public
  51. */
  52. public function setup(&$model, $config = array()) {
  53. $db = ConnectionManager::getDataSource($model->useDbConfig);
  54. if (!$db->connected) {
  55. trigger_error(
  56. sprintf(__('Datasource %s for TranslateBehavior of model %s is not connected', true), $model->useDbConfig, $model->alias),
  57. E_USER_ERROR
  58. );
  59. return false;
  60. }
  61. $this->settings[$model->alias] = array();
  62. $this->runtime[$model->alias] = array('fields' => array());
  63. $this->translateModel($model);
  64. return $this->bindTranslation($model, $config, false);
  65. }
  66. /**
  67. * Callback
  68. *
  69. * @return void
  70. * @access public
  71. */
  72. public function cleanup(&$model) {
  73. $this->unbindTranslation($model);
  74. unset($this->settings[$model->alias]);
  75. unset($this->runtime[$model->alias]);
  76. }
  77. /**
  78. * beforeFind Callback
  79. *
  80. * @param array $query
  81. * @return array Modified query
  82. * @access public
  83. */
  84. public function beforeFind(&$model, $query) {
  85. $locale = $this->_getLocale($model);
  86. if (empty($locale)) {
  87. return $query;
  88. }
  89. $db = ConnectionManager::getDataSource($model->useDbConfig);
  90. $tablePrefix = $db->config['prefix'];
  91. $RuntimeModel = $this->translateModel($model);
  92. if (is_string($query['fields']) && 'COUNT(*) AS '.$db->name('count') == $query['fields']) {
  93. $query['fields'] = 'COUNT(DISTINCT('.$db->name($model->alias . '.' . $model->primaryKey) . ')) ' . $db->alias . 'count';
  94. $query['joins'][] = array(
  95. 'type' => 'INNER',
  96. 'alias' => $RuntimeModel->alias,
  97. 'table' => $db->name($tablePrefix . $RuntimeModel->useTable),
  98. 'conditions' => array(
  99. $model->alias . '.' . $model->primaryKey => $db->identifier($RuntimeModel->alias.'.foreign_key'),
  100. $RuntimeModel->alias.'.model' => $model->name,
  101. $RuntimeModel->alias.'.locale' => $locale
  102. )
  103. );
  104. return $query;
  105. }
  106. $autoFields = false;
  107. if (empty($query['fields'])) {
  108. $query['fields'] = array($model->alias.'.*');
  109. $recursive = $model->recursive;
  110. if (isset($query['recursive'])) {
  111. $recursive = $query['recursive'];
  112. }
  113. if ($recursive >= 0) {
  114. foreach (array('hasOne', 'belongsTo') as $type) {
  115. foreach ($model->{$type} as $key => $value) {
  116. if (empty($value['fields'])) {
  117. $query['fields'][] = $key.'.*';
  118. } else {
  119. foreach ($value['fields'] as $field) {
  120. $query['fields'][] = $key.'.'.$field;
  121. }
  122. }
  123. }
  124. }
  125. }
  126. $autoFields = true;
  127. }
  128. $fields = array_merge($this->settings[$model->alias], $this->runtime[$model->alias]['fields']);
  129. $addFields = array();
  130. if (is_array($query['fields'])) {
  131. foreach ($fields as $key => $value) {
  132. $field = (is_numeric($key)) ? $value : $key;
  133. if (in_array($model->alias.'.*', $query['fields']) || $autoFields || in_array($model->alias.'.'.$field, $query['fields']) || in_array($field, $query['fields'])) {
  134. $addFields[] = $field;
  135. }
  136. }
  137. }
  138. if ($addFields) {
  139. foreach ($addFields as $field) {
  140. foreach (array($field, $model->alias.'.'.$field) as $_field) {
  141. $key = array_search($_field, $query['fields']);
  142. if ($key !== false) {
  143. unset($query['fields'][$key]);
  144. }
  145. }
  146. if (is_array($locale)) {
  147. foreach ($locale as $_locale) {
  148. $query['fields'][] = 'I18n__'.$field.'__'.$_locale.'.content';
  149. $query['joins'][] = array(
  150. 'type' => 'LEFT',
  151. 'alias' => 'I18n__'.$field.'__'.$_locale,
  152. 'table' => $db->name($tablePrefix . $RuntimeModel->useTable),
  153. 'conditions' => array(
  154. $model->alias . '.' . $model->primaryKey => $db->identifier("I18n__{$field}__{$_locale}.foreign_key"),
  155. 'I18n__'.$field.'__'.$_locale.'.model' => $model->name,
  156. 'I18n__'.$field.'__'.$_locale.'.'.$RuntimeModel->displayField => $field,
  157. 'I18n__'.$field.'__'.$_locale.'.locale' => $_locale
  158. )
  159. );
  160. }
  161. } else {
  162. $query['fields'][] = 'I18n__'.$field.'.content';
  163. $query['joins'][] = array(
  164. 'type' => 'LEFT',
  165. 'alias' => 'I18n__'.$field,
  166. 'table' => $db->name($tablePrefix . $RuntimeModel->useTable),
  167. 'conditions' => array(
  168. $model->alias . '.' . $model->primaryKey => $db->identifier("I18n__{$field}.foreign_key"),
  169. 'I18n__'.$field.'.model' => $model->name,
  170. 'I18n__'.$field.'.'.$RuntimeModel->displayField => $field
  171. )
  172. );
  173. if (is_string($query['conditions'])) {
  174. $query['conditions'] = $db->conditions($query['conditions'], true, false, $model) . ' AND '.$db->name('I18n__'.$field.'.locale').' = \''.$locale.'\'';
  175. } else {
  176. $query['conditions'][$db->name("I18n__{$field}.locale")] = $locale;
  177. }
  178. }
  179. }
  180. }
  181. if (is_array($query['fields'])) {
  182. $query['fields'] = array_merge($query['fields']);
  183. }
  184. $this->runtime[$model->alias]['beforeFind'] = $addFields;
  185. return $query;
  186. }
  187. /**
  188. * afterFind Callback
  189. *
  190. * @param array $results
  191. * @param boolean $primary
  192. * @return array Modified results
  193. * @access public
  194. */
  195. public function afterFind(&$model, $results, $primary) {
  196. $this->runtime[$model->alias]['fields'] = array();
  197. $locale = $this->_getLocale($model);
  198. if (empty($locale) || empty($results) || empty($this->runtime[$model->alias]['beforeFind'])) {
  199. return $results;
  200. }
  201. $beforeFind = $this->runtime[$model->alias]['beforeFind'];
  202. foreach ($results as $key => $row) {
  203. $results[$key][$model->alias]['locale'] = (is_array($locale)) ? @$locale[0] : $locale;
  204. foreach ($beforeFind as $field) {
  205. if (is_array($locale)) {
  206. foreach ($locale as $_locale) {
  207. if (!isset($results[$key][$model->alias][$field]) && !empty($results[$key]['I18n__'.$field.'__'.$_locale]['content'])) {
  208. $results[$key][$model->alias][$field] = $results[$key]['I18n__'.$field.'__'.$_locale]['content'];
  209. }
  210. unset($results[$key]['I18n__'.$field.'__'.$_locale]);
  211. }
  212. if (!isset($results[$key][$model->alias][$field])) {
  213. $results[$key][$model->alias][$field] = '';
  214. }
  215. } else {
  216. $value = '';
  217. if (!empty($results[$key]['I18n__'.$field]['content'])) {
  218. $value = $results[$key]['I18n__'.$field]['content'];
  219. }
  220. $results[$key][$model->alias][$field] = $value;
  221. unset($results[$key]['I18n__'.$field]);
  222. }
  223. }
  224. }
  225. return $results;
  226. }
  227. /**
  228. * beforeValidate Callback
  229. *
  230. * @return boolean
  231. * @access public
  232. */
  233. public function beforeValidate(&$model) {
  234. $locale = $this->_getLocale($model);
  235. if (empty($locale)) {
  236. return true;
  237. }
  238. $fields = array_merge($this->settings[$model->alias], $this->runtime[$model->alias]['fields']);
  239. $tempData = array();
  240. foreach ($fields as $key => $value) {
  241. $field = (is_numeric($key)) ? $value : $key;
  242. if (isset($model->data[$model->alias][$field])) {
  243. $tempData[$field] = $model->data[$model->alias][$field];
  244. if (is_array($model->data[$model->alias][$field])) {
  245. if (is_string($locale) && !empty($model->data[$model->alias][$field][$locale])) {
  246. $model->data[$model->alias][$field] = $model->data[$model->alias][$field][$locale];
  247. } else {
  248. $values = array_values($model->data[$model->alias][$field]);
  249. $model->data[$model->alias][$field] = $values[0];
  250. }
  251. }
  252. }
  253. }
  254. $this->runtime[$model->alias]['beforeSave'] = $tempData;
  255. return true;
  256. }
  257. /**
  258. * afterSave Callback
  259. *
  260. * @param boolean $created
  261. * @return void
  262. * @access public
  263. */
  264. public function afterSave(&$model, $created) {
  265. if (!isset($this->runtime[$model->alias]['beforeSave'])) {
  266. return true;
  267. }
  268. $locale = $this->_getLocale($model);
  269. $tempData = $this->runtime[$model->alias]['beforeSave'];
  270. unset($this->runtime[$model->alias]['beforeSave']);
  271. $conditions = array('model' => $model->alias, 'foreign_key' => $model->id);
  272. $RuntimeModel = $this->translateModel($model);
  273. foreach ($tempData as $field => $value) {
  274. unset($conditions['content']);
  275. $conditions['field'] = $field;
  276. if (is_array($value)) {
  277. $conditions['locale'] = array_keys($value);
  278. } else {
  279. $conditions['locale'] = $locale;
  280. if (is_array($locale)) {
  281. $value = array($locale[0] => $value);
  282. } else {
  283. $value = array($locale => $value);
  284. }
  285. }
  286. $translations = $RuntimeModel->find('list', array('conditions' => $conditions, 'fields' => array($RuntimeModel->alias . '.locale', $RuntimeModel->alias . '.id')));
  287. foreach ($value as $_locale => $_value) {
  288. $RuntimeModel->create();
  289. $conditions['locale'] = $_locale;
  290. $conditions['content'] = $_value;
  291. if (array_key_exists($_locale, $translations)) {
  292. $RuntimeModel->save(array($RuntimeModel->alias => array_merge($conditions, array('id' => $translations[$_locale]))));
  293. } else {
  294. $RuntimeModel->save(array($RuntimeModel->alias => $conditions));
  295. }
  296. }
  297. }
  298. }
  299. /**
  300. * afterDelete Callback
  301. *
  302. * @return void
  303. * @access public
  304. */
  305. public function afterDelete(&$model) {
  306. $RuntimeModel = $this->translateModel($model);
  307. $conditions = array('model' => $model->alias, 'foreign_key' => $model->id);
  308. $RuntimeModel->deleteAll($conditions);
  309. }
  310. /**
  311. * Get selected locale for model
  312. *
  313. * @return mixed string or false
  314. * @access protected
  315. */
  316. protected function _getLocale(&$model) {
  317. if (!isset($model->locale) || is_null($model->locale)) {
  318. if (!class_exists('I18n')) {
  319. App::import('Core', 'i18n');
  320. }
  321. $I18n = I18n::getInstance();
  322. $I18n->l10n->get(Configure::read('Config.language'));
  323. $model->locale = $I18n->l10n->locale;
  324. }
  325. return $model->locale;
  326. }
  327. /**
  328. * Get instance of model for translations
  329. *
  330. * @return object
  331. * @access public
  332. */
  333. public function &translateModel(&$model) {
  334. if (!isset($this->runtime[$model->alias]['model'])) {
  335. if (!isset($model->translateModel) || empty($model->translateModel)) {
  336. $className = 'I18nModel';
  337. } else {
  338. $className = $model->translateModel;
  339. }
  340. $this->runtime[$model->alias]['model'] = ClassRegistry::init($className, 'Model');
  341. }
  342. if (!empty($model->translateTable) && $model->translateTable !== $this->runtime[$model->alias]['model']->useTable) {
  343. $this->runtime[$model->alias]['model']->setSource($model->translateTable);
  344. } elseif (empty($model->translateTable) && empty($model->translateModel)) {
  345. $this->runtime[$model->alias]['model']->setSource('i18n');
  346. }
  347. return $this->runtime[$model->alias]['model'];
  348. }
  349. /**
  350. * Bind translation for fields, optionally with hasMany association for
  351. * fake field
  352. *
  353. * @param object instance of model
  354. * @param mixed string with field or array(field1, field2=>AssocName, field3)
  355. * @param boolean $reset
  356. * @return bool
  357. */
  358. public function bindTranslation(&$model, $fields, $reset = true) {
  359. if (is_string($fields)) {
  360. $fields = array($fields);
  361. }
  362. $associations = array();
  363. $RuntimeModel = $this->translateModel($model);
  364. $default = array('className' => $RuntimeModel->alias, 'foreignKey' => 'foreign_key');
  365. foreach ($fields as $key => $value) {
  366. if (is_numeric($key)) {
  367. $field = $value;
  368. $association = null;
  369. } else {
  370. $field = $key;
  371. $association = $value;
  372. }
  373. if (array_key_exists($field, $this->settings[$model->alias])) {
  374. unset($this->settings[$model->alias][$field]);
  375. } elseif (in_array($field, $this->settings[$model->alias])) {
  376. $this->settings[$model->alias] = array_merge(array_diff_assoc($this->settings[$model->alias], array($field)));
  377. }
  378. if (array_key_exists($field, $this->runtime[$model->alias]['fields'])) {
  379. unset($this->runtime[$model->alias]['fields'][$field]);
  380. } elseif (in_array($field, $this->runtime[$model->alias]['fields'])) {
  381. $this->runtime[$model->alias]['fields'] = array_merge(array_diff_assoc($this->runtime[$model->alias]['fields'], array($field)));
  382. }
  383. if (is_null($association)) {
  384. if ($reset) {
  385. $this->runtime[$model->alias]['fields'][] = $field;
  386. } else {
  387. $this->settings[$model->alias][] = $field;
  388. }
  389. } else {
  390. if ($reset) {
  391. $this->runtime[$model->alias]['fields'][$field] = $association;
  392. } else {
  393. $this->settings[$model->alias][$field] = $association;
  394. }
  395. foreach (array('hasOne', 'hasMany', 'belongsTo', 'hasAndBelongsToMany') as $type) {
  396. if (isset($model->{$type}[$association]) || isset($model->__backAssociation[$type][$association])) {
  397. trigger_error(
  398. sprintf(__('Association %s is already binded to model %s', true), $association, $model->alias),
  399. E_USER_ERROR
  400. );
  401. return false;
  402. }
  403. }
  404. $associations[$association] = array_merge($default, array('conditions' => array(
  405. 'model' => $model->alias,
  406. $RuntimeModel->displayField => $field
  407. )));
  408. }
  409. }
  410. if (!empty($associations)) {
  411. $model->bindModel(array('hasMany' => $associations), $reset);
  412. }
  413. return true;
  414. }
  415. /**
  416. * Unbind translation for fields, optionally unbinds hasMany association for
  417. * fake field
  418. *
  419. * @param object instance of model
  420. * @param mixed string with field, or array(field1, field2=>AssocName, field3), or null for unbind all original translations
  421. * @return bool
  422. */
  423. public function unbindTranslation(&$model, $fields = null) {
  424. if (empty($fields)) {
  425. return $this->unbindTranslation($model, $this->settings[$model->alias]);
  426. }
  427. if (is_string($fields)) {
  428. $fields = array($fields);
  429. }
  430. $RuntimeModel = $this->translateModel($model);
  431. $associations = array();
  432. foreach ($fields as $key => $value) {
  433. if (is_numeric($key)) {
  434. $field = $value;
  435. $association = null;
  436. } else {
  437. $field = $key;
  438. $association = $value;
  439. }
  440. if (array_key_exists($field, $this->settings[$model->alias])) {
  441. unset($this->settings[$model->alias][$field]);
  442. } elseif (in_array($field, $this->settings[$model->alias])) {
  443. $this->settings[$model->alias] = array_merge(array_diff_assoc($this->settings[$model->alias], array($field)));
  444. }
  445. if (array_key_exists($field, $this->runtime[$model->alias]['fields'])) {
  446. unset($this->runtime[$model->alias]['fields'][$field]);
  447. } elseif (in_array($field, $this->runtime[$model->alias]['fields'])) {
  448. $this->runtime[$model->alias]['fields'] = array_merge(array_diff_assoc($this->runtime[$model->alias]['fields'], array($field)));
  449. }
  450. if (!is_null($association) && (isset($model->hasMany[$association]) || isset($model->__backAssociation['hasMany'][$association]))) {
  451. $associations[] = $association;
  452. }
  453. }
  454. if (!empty($associations)) {
  455. $model->unbindModel(array('hasMany' => $associations), false);
  456. }
  457. return true;
  458. }
  459. }
  460. if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) {
  461. /**
  462. * @package cake
  463. * @subpackage cake.cake.libs.model.behaviors
  464. */
  465. class I18nModel extends AppModel {
  466. public $name = 'I18nModel';
  467. public $useTable = 'i18n';
  468. public $displayField = 'field';
  469. }
  470. }
  471. ?>