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