PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Cake/Model/Behavior/TranslateBehavior.php

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