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: