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