1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
16:
17: App::uses('ModelBehavior', 'Model');
18: App::uses('I18n', 'I18n');
19: App::uses('I18nModel', 'Model');
20:
21: 22: 23: 24: 25: 26:
27: class TranslateBehavior extends ModelBehavior {
28:
29: 30: 31: 32: 33:
34: public $runtime = array();
35:
36: 37: 38: 39: 40:
41: protected $_joinTable;
42:
43: 44: 45: 46: 47:
48: protected $_runtimeModel;
49:
50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66:
67: public function setup(Model $Model, $config = array()) {
68: $db = ConnectionManager::getDataSource($Model->useDbConfig);
69: if (!$db->connected) {
70: trigger_error(
71: __d('cake_dev', 'Datasource %s for TranslateBehavior of model %s is not connected', $Model->useDbConfig, $Model->alias),
72: E_USER_ERROR
73: );
74: return false;
75: }
76:
77: $this->settings[$Model->alias] = array();
78: $this->runtime[$Model->alias] = array('fields' => array());
79: $this->translateModel($Model);
80: return $this->bindTranslation($Model, $config, false);
81: }
82:
83: 84: 85: 86: 87: 88:
89: public function cleanup(Model $Model) {
90: $this->unbindTranslation($Model);
91: unset($this->settings[$Model->alias]);
92: unset($this->runtime[$Model->alias]);
93: }
94:
95: 96: 97: 98: 99: 100: 101:
102: public function beforeFind(Model $Model, $query) {
103: $this->runtime[$Model->alias]['virtualFields'] = $Model->virtualFields;
104: $locale = $this->_getLocale($Model);
105: if (empty($locale)) {
106: return $query;
107: }
108: $db = $Model->getDataSource();
109: $RuntimeModel = $this->translateModel($Model);
110:
111: if (!empty($RuntimeModel->tablePrefix)) {
112: $tablePrefix = $RuntimeModel->tablePrefix;
113: } else {
114: $tablePrefix = $db->config['prefix'];
115: }
116: $joinTable = new StdClass();
117: $joinTable->tablePrefix = $tablePrefix;
118: $joinTable->table = $RuntimeModel->table;
119: $joinTable->schemaName = $RuntimeModel->getDataSource()->getSchemaName();
120:
121: $this->_joinTable = $joinTable;
122: $this->_runtimeModel = $RuntimeModel;
123:
124: if (is_string($query['fields']) && $query['fields'] === "COUNT(*) AS {$db->name('count')}") {
125: $query['fields'] = "COUNT(DISTINCT({$db->name($Model->escapeField())})) {$db->alias}count";
126: $query['joins'][] = array(
127: 'type' => 'INNER',
128: 'alias' => $RuntimeModel->alias,
129: 'table' => $joinTable,
130: 'conditions' => array(
131: $Model->escapeField() => $db->identifier($RuntimeModel->escapeField('foreign_key')),
132: $RuntimeModel->escapeField('model') => $Model->name,
133: $RuntimeModel->escapeField('locale') => $locale
134: )
135: );
136: $conditionFields = $this->_checkConditions($Model, $query);
137: foreach ($conditionFields as $field) {
138: $query = $this->_addJoin($Model, $query, $field, $field, $locale);
139: }
140: unset($this->_joinTable, $this->_runtimeModel);
141: return $query;
142: } elseif (is_string($query['fields'])) {
143: $query['fields'] = String::tokenize($query['fields']);
144: }
145:
146: $fields = array_merge(
147: $this->settings[$Model->alias],
148: $this->runtime[$Model->alias]['fields']
149: );
150: $addFields = array();
151: if (empty($query['fields'])) {
152: $addFields = $fields;
153: } elseif (is_array($query['fields'])) {
154: $isAllFields = (
155: in_array($Model->alias . '.' . '*', $query['fields']) ||
156: in_array($Model->escapeField('*'), $query['fields'])
157: );
158: foreach ($fields as $key => $value) {
159: $field = (is_numeric($key)) ? $value : $key;
160: if (
161: $isAllFields ||
162: in_array($Model->alias . '.' . $field, $query['fields']) ||
163: in_array($field, $query['fields'])
164: ) {
165: $addFields[] = $field;
166: }
167: }
168: }
169:
170: $this->runtime[$Model->alias]['virtualFields'] = $Model->virtualFields;
171: if ($addFields) {
172: foreach ($addFields as $_f => $field) {
173: $aliasField = is_numeric($_f) ? $field : $_f;
174:
175: foreach (array($aliasField, $Model->alias . '.' . $aliasField) as $_field) {
176: $key = array_search($_field, (array)$query['fields']);
177:
178: if ($key !== false) {
179: unset($query['fields'][$key]);
180: }
181: }
182: $query = $this->_addJoin($Model, $query, $field, $aliasField, $locale);
183: }
184: }
185: $this->runtime[$Model->alias]['beforeFind'] = $addFields;
186: unset($this->_joinTable, $this->_runtimeModel);
187: return $query;
188: }
189:
190: 191: 192: 193: 194: 195: 196: 197:
198: protected function _checkConditions(Model $Model, $query) {
199: $conditionFields = array();
200: if (empty($query['conditions']) || (!empty($query['conditions']) && !is_array($query['conditions']))) {
201: return $conditionFields;
202: }
203: foreach ($query['conditions'] as $col => $val) {
204: foreach ($this->settings[$Model->alias] as $field => $assoc) {
205: if (is_numeric($field)) {
206: $field = $assoc;
207: }
208: if (strpos($col, $field) !== false) {
209: $conditionFields[] = $field;
210: }
211: }
212: }
213: return $conditionFields;
214: }
215:
216: 217: 218: 219: 220: 221: 222: 223: 224: 225:
226: protected function _addJoin(Model $Model, $query, $field, $aliasField, $locale) {
227: $db = ConnectionManager::getDataSource($Model->useDbConfig);
228: $RuntimeModel = $this->_runtimeModel;
229: $joinTable = $this->_joinTable;
230: $aliasVirtual = "i18n_{$field}";
231: $alias = "I18n__{$field}";
232: if (is_array($locale)) {
233: foreach ($locale as $_locale) {
234: $aliasVirtualLocale = "{$aliasVirtual}_{$_locale}";
235: $aliasLocale = "{$alias}__{$_locale}";
236: $Model->virtualFields[$aliasVirtualLocale] = "{$aliasLocale}.content";
237: if (!empty($query['fields']) && is_array($query['fields'])) {
238: $query['fields'][] = $aliasVirtualLocale;
239: }
240: $query['joins'][] = array(
241: 'type' => 'LEFT',
242: 'alias' => $aliasLocale,
243: 'table' => $joinTable,
244: 'conditions' => array(
245: $Model->escapeField() => $db->identifier("{$aliasLocale}.foreign_key"),
246: "{$aliasLocale}.model" => $Model->name,
247: "{$aliasLocale}.{$RuntimeModel->displayField}" => $aliasField,
248: "{$aliasLocale}.locale" => $_locale
249: )
250: );
251: }
252: } else {
253: $Model->virtualFields[$aliasVirtual] = "{$alias}.content";
254: if (!empty($query['fields']) && is_array($query['fields'])) {
255: $query['fields'][] = $aliasVirtual;
256: }
257: $query['joins'][] = array(
258: 'type' => 'INNER',
259: 'alias' => $alias,
260: 'table' => $joinTable,
261: 'conditions' => array(
262: "{$Model->alias}.{$Model->primaryKey}" => $db->identifier("{$alias}.foreign_key"),
263: "{$alias}.model" => $Model->name,
264: "{$alias}.{$RuntimeModel->displayField}" => $aliasField,
265: "{$alias}.locale" => $locale
266: )
267: );
268: }
269: return $query;
270: }
271:
272: 273: 274: 275: 276: 277: 278: 279:
280: public function afterFind(Model $Model, $results, $primary = false) {
281: $Model->virtualFields = $this->runtime[$Model->alias]['virtualFields'];
282:
283: $this->runtime[$Model->alias]['virtualFields'] = $this->runtime[$Model->alias]['fields'] = array();
284: if (!empty($this->runtime[$Model->alias]['restoreFields'])) {
285: $this->runtime[$Model->alias]['fields'] = $this->runtime[$Model->alias]['restoreFields'];
286: unset($this->runtime[$Model->alias]['restoreFields']);
287: }
288:
289: $locale = $this->_getLocale($Model);
290:
291: if (empty($locale) || empty($results) || empty($this->runtime[$Model->alias]['beforeFind'])) {
292: return $results;
293: }
294: $beforeFind = $this->runtime[$Model->alias]['beforeFind'];
295:
296: foreach ($results as $key => &$row) {
297: $results[$key][$Model->alias]['locale'] = (is_array($locale)) ? current($locale) : $locale;
298: foreach ($beforeFind as $_f => $field) {
299: $aliasField = is_numeric($_f) ? $field : $_f;
300: $aliasVirtual = "i18n_{$field}";
301: if (is_array($locale)) {
302: foreach ($locale as $_locale) {
303: $aliasVirtualLocale = "{$aliasVirtual}_{$_locale}";
304: if (!isset($row[$Model->alias][$aliasField]) && !empty($row[$Model->alias][$aliasVirtualLocale])) {
305: $row[$Model->alias][$aliasField] = $row[$Model->alias][$aliasVirtualLocale];
306: $row[$Model->alias]['locale'] = $_locale;
307: }
308: unset($row[$Model->alias][$aliasVirtualLocale]);
309: }
310:
311: if (!isset($row[$Model->alias][$aliasField])) {
312: $row[$Model->alias][$aliasField] = '';
313: }
314: } else {
315: $value = '';
316: if (isset($row[$Model->alias][$aliasVirtual])) {
317: $value = $row[$Model->alias][$aliasVirtual];
318: }
319: $row[$Model->alias][$aliasField] = $value;
320: unset($row[$Model->alias][$aliasVirtual]);
321: }
322: }
323: }
324: return $results;
325: }
326:
327: 328: 329: 330: 331: 332: 333: 334:
335: public function beforeValidate(Model $Model, $options = array()) {
336: unset($this->runtime[$Model->alias]['beforeSave']);
337: $this->_setRuntimeData($Model);
338: return true;
339: }
340:
341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351:
352: public function beforeSave(Model $Model, $options = array()) {
353: if (isset($options['validate']) && !$options['validate']) {
354: unset($this->runtime[$Model->alias]['beforeSave']);
355: }
356: if (isset($this->runtime[$Model->alias]['beforeSave'])) {
357: return true;
358: }
359: $this->_setRuntimeData($Model);
360: return true;
361: }
362:
363: 364: 365: 366: 367: 368: 369: 370: 371: 372:
373: protected function _setRuntimeData(Model $Model) {
374: $locale = $this->_getLocale($Model);
375: if (empty($locale)) {
376: return true;
377: }
378: $fields = array_merge($this->settings[$Model->alias], $this->runtime[$Model->alias]['fields']);
379: $tempData = array();
380:
381: foreach ($fields as $key => $value) {
382: $field = (is_numeric($key)) ? $value : $key;
383:
384: if (isset($Model->data[$Model->alias][$field])) {
385: $tempData[$field] = $Model->data[$Model->alias][$field];
386: if (is_array($Model->data[$Model->alias][$field])) {
387: if (is_string($locale) && !empty($Model->data[$Model->alias][$field][$locale])) {
388: $Model->data[$Model->alias][$field] = $Model->data[$Model->alias][$field][$locale];
389: } else {
390: $values = array_values($Model->data[$Model->alias][$field]);
391: $Model->data[$Model->alias][$field] = $values[0];
392: }
393: }
394: }
395: }
396: $this->runtime[$Model->alias]['beforeSave'] = $tempData;
397: }
398:
399: 400: 401: 402: 403: 404: 405:
406: public function afterValidate(Model $Model) {
407: $Model->data[$Model->alias] = array_merge(
408: $Model->data[$Model->alias],
409: $this->runtime[$Model->alias]['beforeSave']
410: );
411: return true;
412: }
413:
414: 415: 416: 417: 418: 419: 420: 421:
422: public function afterSave(Model $Model, $created, $options = array()) {
423: if (!isset($this->runtime[$Model->alias]['beforeValidate']) && !isset($this->runtime[$Model->alias]['beforeSave'])) {
424: return true;
425: }
426: if (isset($this->runtime[$Model->alias]['beforeValidate'])) {
427: $tempData = $this->runtime[$Model->alias]['beforeValidate'];
428: } else {
429: $tempData = $this->runtime[$Model->alias]['beforeSave'];
430: }
431:
432: unset($this->runtime[$Model->alias]['beforeValidate'], $this->runtime[$Model->alias]['beforeSave']);
433: $conditions = array('model' => $Model->name, 'foreign_key' => $Model->id);
434: $RuntimeModel = $this->translateModel($Model);
435:
436: if ($created) {
437: $tempData = $this->_prepareTranslations($Model, $tempData);
438: }
439: $locale = $this->_getLocale($Model);
440:
441: foreach ($tempData as $field => $value) {
442: unset($conditions['content']);
443: $conditions['field'] = $field;
444: if (is_array($value)) {
445: $conditions['locale'] = array_keys($value);
446: } else {
447: $conditions['locale'] = $locale;
448: if (is_array($locale)) {
449: $value = array($locale[0] => $value);
450: } else {
451: $value = array($locale => $value);
452: }
453: }
454: $translations = $RuntimeModel->find('list', array(
455: 'conditions' => $conditions,
456: 'fields' => array(
457: $RuntimeModel->alias . '.locale',
458: $RuntimeModel->alias . '.id'
459: )
460: ));
461: foreach ($value as $_locale => $_value) {
462: $RuntimeModel->create();
463: $conditions['locale'] = $_locale;
464: $conditions['content'] = $_value;
465: if (array_key_exists($_locale, $translations)) {
466: $RuntimeModel->save(array(
467: $RuntimeModel->alias => array_merge(
468: $conditions, array('id' => $translations[$_locale])
469: )
470: ));
471: } else {
472: $RuntimeModel->save(array($RuntimeModel->alias => $conditions));
473: }
474: }
475: }
476: }
477:
478: 479: 480: 481: 482: 483: 484: 485:
486: protected function _prepareTranslations(Model $Model, $data) {
487: $fields = array_merge($this->settings[$Model->alias], $this->runtime[$Model->alias]['fields']);
488: $locales = array();
489: foreach ($data as $key => $value) {
490: if (is_array($value)) {
491: $locales = array_merge($locales, array_keys($value));
492: }
493: }
494: $locales = array_unique($locales);
495: $hasLocales = count($locales) > 0;
496:
497: foreach ($fields as $key => $field) {
498: if (!is_numeric($key)) {
499: $field = $key;
500: }
501: if ($hasLocales && !isset($data[$field])) {
502: $data[$field] = array_fill_keys($locales, '');
503: } elseif (!isset($data[$field])) {
504: $data[$field] = '';
505: }
506: }
507: return $data;
508: }
509:
510: 511: 512: 513: 514: 515:
516: public function afterDelete(Model $Model) {
517: $RuntimeModel = $this->translateModel($Model);
518: $conditions = array('model' => $Model->name, 'foreign_key' => $Model->id);
519: $RuntimeModel->deleteAll($conditions);
520: }
521:
522: 523: 524: 525: 526: 527:
528: protected function _getLocale(Model $Model) {
529: if (!isset($Model->locale) || $Model->locale === null) {
530: $I18n = I18n::getInstance();
531: $I18n->l10n->get(Configure::read('Config.language'));
532: $Model->locale = $I18n->l10n->locale;
533: }
534:
535: return $Model->locale;
536: }
537:
538: 539: 540: 541: 542: 543: 544: 545: 546:
547: public function translateModel(Model $Model) {
548: if (!isset($this->runtime[$Model->alias]['model'])) {
549: if (!isset($Model->translateModel) || empty($Model->translateModel)) {
550: $className = 'I18nModel';
551: } else {
552: $className = $Model->translateModel;
553: }
554:
555: $this->runtime[$Model->alias]['model'] = ClassRegistry::init($className);
556: }
557: if (!empty($Model->translateTable) && $Model->translateTable !== $this->runtime[$Model->alias]['model']->useTable) {
558: $this->runtime[$Model->alias]['model']->setSource($Model->translateTable);
559: } elseif (empty($Model->translateTable) && empty($Model->translateModel)) {
560: $this->runtime[$Model->alias]['model']->setSource('i18n');
561: }
562: return $this->runtime[$Model->alias]['model'];
563: }
564:
565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579:
580: public function bindTranslation(Model $Model, $fields, $reset = true) {
581: if (is_string($fields)) {
582: $fields = array($fields);
583: }
584: $associations = array();
585: $RuntimeModel = $this->translateModel($Model);
586: $default = array(
587: 'className' => $RuntimeModel->alias,
588: 'foreignKey' => 'foreign_key'
589: );
590:
591: foreach ($fields as $key => $value) {
592: if (is_numeric($key)) {
593: $field = $value;
594: $association = null;
595: } else {
596: $field = $key;
597: $association = $value;
598: }
599: if ($association === 'name') {
600: throw new CakeException(
601: __d('cake_dev', 'You cannot bind a translation named "name".')
602: );
603: }
604: $this->_removeField($Model, $field);
605:
606: if ($association === null) {
607: if ($reset) {
608: $this->runtime[$Model->alias]['fields'][] = $field;
609: } else {
610: $this->settings[$Model->alias][] = $field;
611: }
612: } else {
613: if ($reset) {
614: $this->runtime[$Model->alias]['fields'][$field] = $association;
615: $this->runtime[$Model->alias]['restoreFields'][] = $field;
616: } else {
617: $this->settings[$Model->alias][$field] = $association;
618: }
619:
620: foreach (array('hasOne', 'hasMany', 'belongsTo', 'hasAndBelongsToMany') as $type) {
621: if (isset($Model->{$type}[$association]) || isset($Model->__backAssociation[$type][$association])) {
622: trigger_error(
623: __d('cake_dev', 'Association %s is already bound to model %s', $association, $Model->alias),
624: E_USER_ERROR
625: );
626: return false;
627: }
628: }
629: $associations[$association] = array_merge($default, array('conditions' => array(
630: 'model' => $Model->name,
631: $RuntimeModel->displayField => $field
632: )));
633: }
634: }
635:
636: if (!empty($associations)) {
637: $Model->bindModel(array('hasMany' => $associations), $reset);
638: }
639: return true;
640: }
641:
642: 643: 644: 645: 646: 647: 648:
649: protected function _removeField(Model $Model, $field) {
650: if (array_key_exists($field, $this->settings[$Model->alias])) {
651: unset($this->settings[$Model->alias][$field]);
652: } elseif (in_array($field, $this->settings[$Model->alias])) {
653: $this->settings[$Model->alias] = array_merge(array_diff($this->settings[$Model->alias], array($field)));
654: }
655:
656: if (array_key_exists($field, $this->runtime[$Model->alias]['fields'])) {
657: unset($this->runtime[$Model->alias]['fields'][$field]);
658: } elseif (in_array($field, $this->runtime[$Model->alias]['fields'])) {
659: $this->runtime[$Model->alias]['fields'] = array_merge(array_diff($this->runtime[$Model->alias]['fields'], array($field)));
660: }
661: }
662:
663: 664: 665: 666: 667: 668: 669: 670: 671:
672: public function unbindTranslation(Model $Model, $fields = null) {
673: if (empty($fields) && empty($this->settings[$Model->alias])) {
674: return false;
675: }
676: if (empty($fields)) {
677: return $this->unbindTranslation($Model, $this->settings[$Model->alias]);
678: }
679:
680: if (is_string($fields)) {
681: $fields = array($fields);
682: }
683: $associations = array();
684:
685: foreach ($fields as $key => $value) {
686: if (is_numeric($key)) {
687: $field = $value;
688: $association = null;
689: } else {
690: $field = $key;
691: $association = $value;
692: }
693:
694: $this->_removeField($Model, $field);
695:
696: if ($association !== null && (isset($Model->hasMany[$association]) || isset($Model->__backAssociation['hasMany'][$association]))) {
697: $associations[] = $association;
698: }
699: }
700:
701: if (!empty($associations)) {
702: $Model->unbindModel(array('hasMany' => $associations), false);
703: }
704: return true;
705: }
706:
707: }
708: