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