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