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