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 ($isAllFields ||
161: in_array($Model->alias . '.' . $field, $query['fields']) ||
162: in_array($field, $query['fields'])
163: ) {
164: $addFields[] = $field;
165: }
166: }
167: }
168:
169: $this->runtime[$Model->alias]['virtualFields'] = $Model->virtualFields;
170: if ($addFields) {
171: foreach ($addFields as $_f => $field) {
172: $aliasField = is_numeric($_f) ? $field : $_f;
173:
174: foreach (array($aliasField, $Model->alias . '.' . $aliasField) as $_field) {
175: $key = array_search($_field, (array)$query['fields']);
176:
177: if ($key !== false) {
178: unset($query['fields'][$key]);
179: }
180: }
181: $query = $this->_addJoin($Model, $query, $field, $aliasField, $locale);
182: }
183: }
184: $this->runtime[$Model->alias]['beforeFind'] = $addFields;
185: unset($this->_joinTable, $this->_runtimeModel);
186: return $query;
187: }
188:
189: 190: 191: 192: 193: 194: 195: 196:
197: protected function _checkConditions(Model $Model, $query) {
198: $conditionFields = array();
199: if (empty($query['conditions']) || (!empty($query['conditions']) && !is_array($query['conditions']))) {
200: return $conditionFields;
201: }
202: foreach ($query['conditions'] as $col => $val) {
203: foreach ($this->settings[$Model->alias] as $field => $assoc) {
204: if (is_numeric($field)) {
205: $field = $assoc;
206: }
207: if (strpos($col, $field) !== false) {
208: $conditionFields[] = $field;
209: }
210: }
211: }
212: return $conditionFields;
213: }
214:
215: 216: 217: 218: 219: 220: 221: 222: 223: 224:
225: protected function _addJoin(Model $Model, $query, $field, $aliasField, $locale) {
226: $db = ConnectionManager::getDataSource($Model->useDbConfig);
227: $RuntimeModel = $this->_runtimeModel;
228: $joinTable = $this->_joinTable;
229: $aliasVirtual = "i18n_{$field}";
230: $alias = "I18n__{$field}";
231: if (is_array($locale)) {
232: foreach ($locale as $_locale) {
233: $aliasVirtualLocale = "{$aliasVirtual}_{$_locale}";
234: $aliasLocale = "{$alias}__{$_locale}";
235: $Model->virtualFields[$aliasVirtualLocale] = "{$aliasLocale}.content";
236: if (!empty($query['fields']) && is_array($query['fields'])) {
237: $query['fields'][] = $aliasVirtualLocale;
238: }
239: $query['joins'][] = array(
240: 'type' => 'LEFT',
241: 'alias' => $aliasLocale,
242: 'table' => $joinTable,
243: 'conditions' => array(
244: $Model->escapeField() => $db->identifier("{$aliasLocale}.foreign_key"),
245: "{$aliasLocale}.model" => $Model->name,
246: "{$aliasLocale}.{$RuntimeModel->displayField}" => $aliasField,
247: "{$aliasLocale}.locale" => $_locale
248: )
249: );
250: }
251: } else {
252: $Model->virtualFields[$aliasVirtual] = "{$alias}.content";
253: if (!empty($query['fields']) && is_array($query['fields'])) {
254: $query['fields'][] = $aliasVirtual;
255: }
256: $query['joins'][] = array(
257: 'type' => 'INNER',
258: 'alias' => $alias,
259: 'table' => $joinTable,
260: 'conditions' => array(
261: "{$Model->alias}.{$Model->primaryKey}" => $db->identifier("{$alias}.foreign_key"),
262: "{$alias}.model" => $Model->name,
263: "{$alias}.{$RuntimeModel->displayField}" => $aliasField,
264: "{$alias}.locale" => $locale
265: )
266: );
267: }
268: return $query;
269: }
270:
271: 272: 273: 274: 275: 276: 277: 278:
279: public function afterFind(Model $Model, $results, $primary = false) {
280: $Model->virtualFields = $this->runtime[$Model->alias]['virtualFields'];
281:
282: $this->runtime[$Model->alias]['virtualFields'] = $this->runtime[$Model->alias]['fields'] = array();
283: if (!empty($this->runtime[$Model->alias]['restoreFields'])) {
284: $this->runtime[$Model->alias]['fields'] = $this->runtime[$Model->alias]['restoreFields'];
285: unset($this->runtime[$Model->alias]['restoreFields']);
286: }
287:
288: $locale = $this->_getLocale($Model);
289:
290: if (empty($locale) || empty($results) || empty($this->runtime[$Model->alias]['beforeFind'])) {
291: return $results;
292: }
293: $beforeFind = $this->runtime[$Model->alias]['beforeFind'];
294:
295: foreach ($results as $key => &$row) {
296: $results[$key][$Model->alias]['locale'] = (is_array($locale)) ? current($locale) : $locale;
297: foreach ($beforeFind as $_f => $field) {
298: $aliasField = is_numeric($_f) ? $field : $_f;
299: $aliasVirtual = "i18n_{$field}";
300: if (is_array($locale)) {
301: foreach ($locale as $_locale) {
302: $aliasVirtualLocale = "{$aliasVirtual}_{$_locale}";
303: if (!isset($row[$Model->alias][$aliasField]) && !empty($row[$Model->alias][$aliasVirtualLocale])) {
304: $row[$Model->alias][$aliasField] = $row[$Model->alias][$aliasVirtualLocale];
305: $row[$Model->alias]['locale'] = $_locale;
306: }
307: unset($row[$Model->alias][$aliasVirtualLocale]);
308: }
309:
310: if (!isset($row[$Model->alias][$aliasField])) {
311: $row[$Model->alias][$aliasField] = '';
312: }
313: } else {
314: $value = '';
315: if (isset($row[$Model->alias][$aliasVirtual])) {
316: $value = $row[$Model->alias][$aliasVirtual];
317: }
318: $row[$Model->alias][$aliasField] = $value;
319: unset($row[$Model->alias][$aliasVirtual]);
320: }
321: }
322: }
323: return $results;
324: }
325:
326: 327: 328: 329: 330: 331: 332: 333:
334: public function beforeValidate(Model $Model, $options = array()) {
335: unset($this->runtime[$Model->alias]['beforeSave']);
336: $this->_setRuntimeData($Model);
337: return true;
338: }
339:
340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350:
351: public function beforeSave(Model $Model, $options = array()) {
352: if (isset($options['validate']) && !$options['validate']) {
353: unset($this->runtime[$Model->alias]['beforeSave']);
354: }
355: if (isset($this->runtime[$Model->alias]['beforeSave'])) {
356: return true;
357: }
358: $this->_setRuntimeData($Model);
359: return true;
360: }
361:
362: 363: 364: 365: 366: 367: 368: 369: 370: 371:
372: protected function _setRuntimeData(Model $Model) {
373: $locale = $this->_getLocale($Model);
374: if (empty($locale)) {
375: return true;
376: }
377: $fields = array_merge($this->settings[$Model->alias], $this->runtime[$Model->alias]['fields']);
378: $tempData = array();
379:
380: foreach ($fields as $key => $value) {
381: $field = (is_numeric($key)) ? $value : $key;
382:
383: if (isset($Model->data[$Model->alias][$field])) {
384: $tempData[$field] = $Model->data[$Model->alias][$field];
385: if (is_array($Model->data[$Model->alias][$field])) {
386: if (is_string($locale) && !empty($Model->data[$Model->alias][$field][$locale])) {
387: $Model->data[$Model->alias][$field] = $Model->data[$Model->alias][$field][$locale];
388: } else {
389: $values = array_values($Model->data[$Model->alias][$field]);
390: $Model->data[$Model->alias][$field] = $values[0];
391: }
392: }
393: }
394: }
395: $this->runtime[$Model->alias]['beforeSave'] = $tempData;
396: }
397:
398: 399: 400: 401: 402: 403: 404:
405: public function afterValidate(Model $Model) {
406: $Model->data[$Model->alias] = array_merge(
407: $Model->data[$Model->alias],
408: $this->runtime[$Model->alias]['beforeSave']
409: );
410: return true;
411: }
412:
413: 414: 415: 416: 417: 418: 419: 420:
421: public function afterSave(Model $Model, $created, $options = array()) {
422: if (!isset($this->runtime[$Model->alias]['beforeValidate']) && !isset($this->runtime[$Model->alias]['beforeSave'])) {
423: return true;
424: }
425: if (isset($this->runtime[$Model->alias]['beforeValidate'])) {
426: $tempData = $this->runtime[$Model->alias]['beforeValidate'];
427: } else {
428: $tempData = $this->runtime[$Model->alias]['beforeSave'];
429: }
430:
431: unset($this->runtime[$Model->alias]['beforeValidate'], $this->runtime[$Model->alias]['beforeSave']);
432: $conditions = array('model' => $Model->name, 'foreign_key' => $Model->id);
433: $RuntimeModel = $this->translateModel($Model);
434:
435: if ($created) {
436: $tempData = $this->_prepareTranslations($Model, $tempData);
437: }
438: $locale = $this->_getLocale($Model);
439:
440: foreach ($tempData as $field => $value) {
441: unset($conditions['content']);
442: $conditions['field'] = $field;
443: if (is_array($value)) {
444: $conditions['locale'] = array_keys($value);
445: } else {
446: $conditions['locale'] = $locale;
447: if (is_array($locale)) {
448: $value = array($locale[0] => $value);
449: } else {
450: $value = array($locale => $value);
451: }
452: }
453: $translations = $RuntimeModel->find('list', array(
454: 'conditions' => $conditions,
455: 'fields' => array(
456: $RuntimeModel->alias . '.locale',
457: $RuntimeModel->alias . '.id'
458: )
459: ));
460: foreach ($value as $_locale => $_value) {
461: $RuntimeModel->create();
462: $conditions['locale'] = $_locale;
463: $conditions['content'] = $_value;
464: if (array_key_exists($_locale, $translations)) {
465: $RuntimeModel->save(array(
466: $RuntimeModel->alias => array_merge(
467: $conditions, array('id' => $translations[$_locale])
468: )
469: ));
470: } else {
471: $RuntimeModel->save(array($RuntimeModel->alias => $conditions));
472: }
473: }
474: }
475: }
476:
477: 478: 479: 480: 481: 482: 483: 484:
485: protected function _prepareTranslations(Model $Model, $data) {
486: $fields = array_merge($this->settings[$Model->alias], $this->runtime[$Model->alias]['fields']);
487: $locales = array();
488: foreach ($data as $key => $value) {
489: if (is_array($value)) {
490: $locales = array_merge($locales, array_keys($value));
491: }
492: }
493: $locales = array_unique($locales);
494: $hasLocales = count($locales) > 0;
495:
496: foreach ($fields as $key => $field) {
497: if (!is_numeric($key)) {
498: $field = $key;
499: }
500: if ($hasLocales && !isset($data[$field])) {
501: $data[$field] = array_fill_keys($locales, '');
502: } elseif (!isset($data[$field])) {
503: $data[$field] = '';
504: }
505: }
506: return $data;
507: }
508:
509: 510: 511: 512: 513: 514:
515: public function afterDelete(Model $Model) {
516: $RuntimeModel = $this->translateModel($Model);
517: $conditions = array('model' => $Model->name, 'foreign_key' => $Model->id);
518: $RuntimeModel->deleteAll($conditions);
519: }
520:
521: 522: 523: 524: 525: 526:
527: protected function _getLocale(Model $Model) {
528: if (!isset($Model->locale) || $Model->locale === null) {
529: $I18n = I18n::getInstance();
530: $I18n->l10n->get(Configure::read('Config.language'));
531: $Model->locale = $I18n->l10n->locale;
532: }
533:
534: return $Model->locale;
535: }
536:
537: 538: 539: 540: 541: 542: 543: 544: 545:
546: public function translateModel(Model $Model) {
547: if (!isset($this->runtime[$Model->alias]['model'])) {
548: if (!isset($Model->translateModel) || empty($Model->translateModel)) {
549: $className = 'I18nModel';
550: } else {
551: $className = $Model->translateModel;
552: }
553:
554: $this->runtime[$Model->alias]['model'] = ClassRegistry::init($className);
555: }
556: if (!empty($Model->translateTable) && $Model->translateTable !== $this->runtime[$Model->alias]['model']->useTable) {
557: $this->runtime[$Model->alias]['model']->setSource($Model->translateTable);
558: } elseif (empty($Model->translateTable) && empty($Model->translateModel)) {
559: $this->runtime[$Model->alias]['model']->setSource('i18n');
560: }
561: return $this->runtime[$Model->alias]['model'];
562: }
563:
564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578:
579: public function bindTranslation(Model $Model, $fields, $reset = true) {
580: if (is_string($fields)) {
581: $fields = array($fields);
582: }
583: $associations = array();
584: $RuntimeModel = $this->translateModel($Model);
585: $default = array(
586: 'className' => $RuntimeModel->alias,
587: 'foreignKey' => 'foreign_key'
588: );
589:
590: foreach ($fields as $key => $value) {
591: if (is_numeric($key)) {
592: $field = $value;
593: $association = null;
594: } else {
595: $field = $key;
596: $association = $value;
597: }
598: if ($association === 'name') {
599: throw new CakeException(
600: __d('cake_dev', 'You cannot bind a translation named "name".')
601: );
602: }
603: $this->_removeField($Model, $field);
604:
605: if ($association === null) {
606: if ($reset) {
607: $this->runtime[$Model->alias]['fields'][] = $field;
608: } else {
609: $this->settings[$Model->alias][] = $field;
610: }
611: } else {
612: if ($reset) {
613: $this->runtime[$Model->alias]['fields'][$field] = $association;
614: $this->runtime[$Model->alias]['restoreFields'][] = $field;
615: } else {
616: $this->settings[$Model->alias][$field] = $association;
617: }
618:
619: foreach (array('hasOne', 'hasMany', 'belongsTo', 'hasAndBelongsToMany') as $type) {
620: if (isset($Model->{$type}[$association]) || isset($Model->__backAssociation[$type][$association])) {
621: trigger_error(
622: __d('cake_dev', 'Association %s is already bound to model %s', $association, $Model->alias),
623: E_USER_ERROR
624: );
625: return false;
626: }
627: }
628: $associations[$association] = array_merge($default, array('conditions' => array(
629: 'model' => $Model->name,
630: $RuntimeModel->displayField => $field
631: )));
632: }
633: }
634:
635: if (!empty($associations)) {
636: $Model->bindModel(array('hasMany' => $associations), $reset);
637: }
638: return true;
639: }
640:
641: 642: 643: 644: 645: 646: 647:
648: protected function _removeField(Model $Model, $field) {
649: if (array_key_exists($field, $this->settings[$Model->alias])) {
650: unset($this->settings[$Model->alias][$field]);
651: } elseif (in_array($field, $this->settings[$Model->alias])) {
652: $this->settings[$Model->alias] = array_merge(array_diff($this->settings[$Model->alias], array($field)));
653: }
654:
655: if (array_key_exists($field, $this->runtime[$Model->alias]['fields'])) {
656: unset($this->runtime[$Model->alias]['fields'][$field]);
657: } elseif (in_array($field, $this->runtime[$Model->alias]['fields'])) {
658: $this->runtime[$Model->alias]['fields'] = array_merge(array_diff($this->runtime[$Model->alias]['fields'], array($field)));
659: }
660: }
661:
662: 663: 664: 665: 666: 667: 668: 669: 670:
671: public function unbindTranslation(Model $Model, $fields = null) {
672: if (empty($fields) && empty($this->settings[$Model->alias])) {
673: return false;
674: }
675: if (empty($fields)) {
676: return $this->unbindTranslation($Model, $this->settings[$Model->alias]);
677: }
678:
679: if (is_string($fields)) {
680: $fields = array($fields);
681: }
682: $associations = array();
683:
684: foreach ($fields as $key => $value) {
685: if (is_numeric($key)) {
686: $field = $value;
687: $association = null;
688: } else {
689: $field = $key;
690: $association = $value;
691: }
692:
693: $this->_removeField($Model, $field);
694:
695: if ($association !== null && (isset($Model->hasMany[$association]) || isset($Model->__backAssociation['hasMany'][$association]))) {
696: $associations[] = $association;
697: }
698: }
699:
700: if (!empty($associations)) {
701: $Model->unbindModel(array('hasMany' => $associations), false);
702: }
703: return true;
704: }
705:
706: }
707: