Xataface  2.0alpha2
Xataface Application Framework
 All Data Structures Namespaces Files Functions Variables Groups Pages
TranslationTool.php
Go to the documentation of this file.
1 <?php
2 define('TRANSLATION_STATUS_UNTRANSLATED',8);
3 define('TRANSLATION_STATUS_UNKNOWN',0);
4 define('TRANSLATION_STATUS_SOURCE',1);
5 define('TRANSLATION_STATUS_MACHINE',2);
6 define('TRANSLATION_STATUS_UNVERIFIED', 3);
7 define('TRANSLATION_STATUS_APPROVED',4);
8 define('TRANSLATION_STATUS_NEEDS_UPDATE',5);
9 define('TRANSLATION_STATUS_NEEDS_UPDATE_MACHINE',6);
10 define('TRANSLATION_STATUS_NEEDS_UPDATE_UNVERIFIED',7);
11 define('TRANSLATION_STATUS_EXTERNAL', 9);
13 
17  var $schema = array(
18  'id'=>array('Field'=>'id','Type'=>'int(11)','Key'=>'PRI','Null'=>'NOT NULL','Extra'=>'auto_increment'),
19  'record_id'=>array('Field'=>'record_id','Type'=>'varchar(126)','Key'=>'record_key','Null'=>'NOT NULL','Extra'=>''),
20  'language'=>array('Field'=>'language','Type'=>'varchar(2)','Key'=>'record_key','Null'=>'NOT NULL','Extra'=>''),
21  'table'=>array('Field'=>'table','Type'=>'varchar(128)','Key'=>'record_key', 'Null'=>'NOT NULL','Extra'=>''),
22  'version'=>array('Field'=>'version','Type'=>'int(11)','Key'=>'','Null'=>'NOT NULL','Default'=>1,'Extra'=>''),
23  'translation_status'=>array('Field'=>'translation_status','Type'=>'int(11)','Key'=>'','Null'=>'NOT NULL','Default'=>0,'Extra'=>''),
24  'last_modified'=>array('Field'=>'last_modified','Type'=>'datetime','Key'=>'','Null'=>'','Default'=>'0000-00-00','Extra'=>'')
25  );
26 
27  var $submission_schema = array(
28  'id'=>array('Field'=>'id','Type'=>'int(11)','Key'=>'PRI','Null'=>'NOT NULL','Extra'=>'auto_increment'),
29  'record_id'=>array('Field'=>'record_id','Type'=>'varchar(126)','Key'=>'record_key','Null'=>'NOT NULL','Extra'=>''),
30  'language'=>array('Field'=>'language','Type'=>'varchar(2)','Key'=>'record_key','Null'=>'NOT NULL','Extra'=>''),
31  'url'=>array('Field'=>'url','Type'=>'text','Key'=>'','Null'=>'NOT NULL','Default'=>'','Extra'=>''),
32  'original_text'=>array('Field'=>'original_text','Type'=>'text','Key'=>'','Null'=>'NOT NULL','Default'=>'','Extra'=>''),
33  'translated_text'=>array('Field'=>'translated_text','Type'=>'text','Key'=>'','Null'=>'NOT NULL','Default'=>'','Extra'=>''),
34  'translated_by'=>array('Field'=>'translated_by','Type'=>'varchar(128)','Key'=>'','Null'=>'NOT NULL','Default'=>'','Extra'=>''),
35  'date_submitted'=>array('Field'=>'last_modified','Type'=>'timestamp','Key'=>'','Null'=>'','Default'=>'','Extra'=>'')
36  );
37 
43  TRANSLATION_STATUS_UNTRANSLATED => 'Untranslated',
44  TRANSLATION_STATUS_UNKNOWN => 'Unknown translation status',
45  TRANSLATION_STATUS_SOURCE => 'Source translation',
46  TRANSLATION_STATUS_MACHINE => 'Machine translation',
47  TRANSLATION_STATUS_UNVERIFIED => 'Unverified translation',
48  TRANSLATION_STATUS_APPROVED => 'Approved translation',
49  TRANSLATION_STATUS_NEEDS_UPDATE => 'Out-of-date',
50  TRANSLATION_STATUS_NEEDS_UPDATE_MACHINE =>'Out-of-date (Machine translation)',
51  TRANSLATION_STATUS_NEEDS_UPDATE_UNVERIFIED =>'Out-of-date (Unverified)',
52  TRANSLATION_STATUS_EXTERNAL => 'Managed Externally'
53  );
54 
56 
57  }
58 
69  function getHTMLStatusSelector(&$record, $language, $name, $onchange=''){
70  $trec =& $this->getTranslationRecord($record, $language);
71  if ( !$trec ){
72  // no translation currently exists, so we will set the default
73  // value to -1, the flag for no translation yet.
74  $default = -1;
75  } else {
76  $default = $trec->val('translation_status');
77  }
78  $out = array();
79  $out[] = <<<END
80  <select name="$name" onchange='$onchange'>
81 END;
82  if ( $default == -1 ){
83  $out[] = <<<END
84  <option value="-1" selected>No translation provided</option>
85 END;
86  }
87  foreach ( $this->translation_status_codes as $key=>$val){
88  if ( $default == $key ) $selected = "selected";
89  else $selected = '';
90  $out[] = <<<END
91  <option value="$key" $selected>$val</option>
92 END;
93  }
94  $out[] = "</select>";
95  return implode("\n", $out);
96  }
97 
103  $sql = "create table if not exists `dataface__translations` (";
104  $cols = array();
105  $primary_key_cols = array();
106  $other_keys = array();
107  foreach ($this->schema as $field){
108  $default = (isset($field['Default']) ? "DEFAULT '{$field['Default']}'" : '');
109  $cols[] = "`{$field['Field']}` {$field['Type']} {$field['Extra']} {$field['Null']} {$default}";
110  if ( strcasecmp($field['Key'],'PRI') === 0 ){
111  $primary_key_cols[$field['Field']] = $field;
112  } else if ( $field['Key'] ){
113  $other_keys[$field['Key']][$field['Field']] = $field;
114  }
115  }
116 
117  $sql .= implode(',',$cols).", PRIMARY KEY (`".implode('`,`',array_keys($primary_key_cols))."`)";
118  if ( count($other_keys) > 0 ){
119  $sql .=', ';
120  foreach ($other_keys as $key_name=>$key){
121  $sql .= "KEY `$key_name` (`".implode('`,`',array_keys($key))."`)";
122  }
123  }
124  $sql .= ")";
125 
126  $res = mysql_query($sql, $app->db());
127  if ( !$res ) {throw new Exception(mysql_error($app->db()), E_USER_ERROR);}
128  return true;
129 
130  }
133  if ( !Dataface_Table::tableExists('dataface__translations',false) ){
134  $this->createTranslationsTable();
135  }
136 
137  $res = mysql_query("show columns from `dataface__translations`", $app->db());
138  if ( !$res ) throw new Exception(mysql_error($app->db()), E_USER_ERROR);
139  $cols = array();
140  while ( $row = mysql_fetch_assoc($res) ){
141  $cols[$row['Field']] = $row;
142  }
143  foreach ($this->schema as $field ){
144  if (!isset($cols[$field['Field']]) ){
145  $default = (isset($field['Default']) ? "DEFAULT '{$field['Default']}'" : '');
146  $sql = "alter table `dataface__translations` add column `{$field['Field']}` {$field['Type']} {$field['Null']} {$default}";
147  $res = mysql_query($sql, $app->db());
148  if (!$res ) throw new Exception(mysql_error($app->db()), E_USER_ERROR);
149  }
150  }
151 
152  return true;
153 
154  }
155 
161  $sql = "create table if not exists `dataface__translation_submissions` (";
162  $cols = array();
163  $primary_key_cols = array();
164  $other_keys = array();
165  foreach ($this->submission_schema as $field){
166  $default = (isset($field['Default']) ? "DEFAULT '{$field['Default']}'" : '');
167  $cols[] = "`{$field['Field']}` {$field['Type']} {$field['Extra']} {$field['Null']} {$default}";
168  if ( strcasecmp($field['Key'],'PRI') === 0 ){
169  $primary_key_cols[$field['Field']] = $field;
170  } else if ( $field['Key'] ){
171  $other_keys[$field['Key']][$field['Field']] = $field;
172  }
173  }
174 
175  $sql .= implode(',',$cols).", PRIMARY KEY (`".implode('`,`',array_keys($primary_key_cols))."`)";
176  if ( count($other_keys) > 0 ){
177  $sql .=', ';
178  foreach ($other_keys as $key_name=>$key){
179  $sql .= "KEY `$key_name` (`".implode('`,`',array_keys($key))."`)";
180  }
181  }
182  $sql .= ")";
183 
184  $res = mysql_query($sql, $app->db());
185  if ( !$res ) {echo $sql;throw new Exception(mysql_error($app->db()), E_USER_ERROR);}
186  return true;
187 
188  }
191  if ( !Dataface_Table::tableExists('dataface__translation_submissions',false) ){
193  }
194 
195  $res = mysql_query("show columns from `dataface__translation_submissions`", $app->db());
196  if ( !$res ) throw new Exception(mysql_error($app->db()), E_USER_ERROR);
197  $cols = array();
198  while ( $row = mysql_fetch_assoc($res) ){
199  $cols[$row['Field']] = $row;
200  }
201  foreach ($this->submission_schema as $field ){
202  if (!isset($cols[$field['Field']]) ){
203  $default = (isset($field['Default']) ? "DEFAULT '{$field['Default']}'" : '');
204  $sql = "alter table `dataface__translation_submissions` add column `{$field['Field']}` {$field['Type']} {$field['Null']} {$default}";
205  $res = mysql_query($sql, $app->db());
206  if (!$res ) throw new Exception(mysql_error($app->db()), E_USER_ERROR);
207  }
208  }
209 
210  return true;
211 
212  }
213 
214  function submitTranslation(&$record, $params=array()){
215  if ( !Dataface_Table::tableExists('dataface__translation_submissions',false) ){
217  }
218  $trec = new Dataface_Record('dataface__translation_submissions', array());
219  $trec->setValues($params);
220  $trec->save();
221  }
226  function getRecordId(&$record){
227  if (!$record ){
228  throw new Exception("No record provided");
229  }
230  $vals = $record->strvals(array_keys($record->_table->keys()));
231  $parts = array();
232  foreach ($vals as $key=>$val){
233  $parts[] = urlencode($key).'='.urlencode($val);
234  }
235  return implode('&',$parts);
236 
237  }
238 
239  function translateRecord(&$record, $sourceLanguage, $destLanguage){}
240  function translateRecords(&$records, $sourceLanguage, $destLanguage){}
241 
242  function getTranslationId(&$record, $language){
244  $sql = "select `id` from `dataface__translations` where `record_id`='".addslashes($this->getRecordId($record))."' and `table`='".addslashes($record->_table->tablename)."' and `language`='".addslashes($language)."' limit 1";
245  $res = mysql_query($sql, $app->db());
246  if ( !$res ){
247  $this->updateTranslationsTable();
248  $res = mysql_query($sql, $app->db());
249  if ( !$res ){
250  throw new Exception(mysql_error($app->db()), E_USER_ERROR);
251  }
252  }
253  if ( mysql_num_rows($res) === 0 ){
254  @mysql_free_result($res);
255  $sql = "insert into `dataface__translations` (`record_id`,`language`,`table`,`last_modified`) VALUES (
256  '".addslashes($this->getRecordId($record))."',
257  '".addslashes($language)."',
258  '".addslashes($record->_table->tablename)."',
259  NOW()
260  )";
261  $res = mysql_query($sql, $app->db());
262  if ( !$res ) {
263  $this->updateTranslationsTable();
264  $res = mysql_query($sql, $app->db());
265  if ( !$res ){
266  throw new Exception(mysql_error($app->db()), E_USER_ERROR);
267  }
268  }
269  $id = mysql_insert_id($app->db());
270  } else {
271  list($id) = mysql_fetch_row($res);
272  @mysql_free_result($res);
273  }
274 
275  return $id;
276  }
277 
286  function getTranslationRecord(&$record, $language){
288  $id = $this->getTranslationId($record, $language);
289  $trecord =& df_get_record('dataface__translations', array('id'=>$id));
290  if ( !isset($trecord) ) {
291  $this->updateTranslationsTable();
292  $trecord =& df_get_record('dataface__translations', array('id'=>$id));
293  if ( !isset($trecord) ) throw new Exception("Error loading translation record for translation id '$id'", E_USER_ERROR);
294  }
295  return $trecord;
296  }
297 
305  function getCanonicalVersion(&$record, $language){
307  $trecord =& $this->getTranslationRecord($record, $language);
308  if ( PEAR::isError($trecord) ) return $trecord;
309  return $trecord->val('version');
310  }
311 
318  function markNewCanonicalVersion(&$record, $language=null){
320  $trecord =& $this->getTranslationRecord($record, $language);
321  $trecord->setValue('version', $trecord->val('version')+1);
322  $trecord->setValue('translation_status', TRANSLATION_STATUS_SOURCE);
323  $res = $trecord->save();
324  if ( PEAR::isError($res) ){
325  return $res;
326  }
327 
328  $this->invalidateTranslations($record);
329  return $this->getCanonicalVersion($record, $language);
330 
331  }
332 
343  function invalidateTranslations(&$record){
344 
345  $records = df_get_records('dataface__translations',array('record_id'=>'='.$this->getRecordId($record),'table'=>$record->_table->tablename));
346  if ( PEAR::isError($records) ){
347  throw new Exception($records->toString(), E_USER_ERROR);
348  }
349  while ($records->hasNext()){
350  $trecord =& $records->next();
351  $update=true;
352  switch($trecord->val('translation_status')){
354  $trecord->setValue('translation_status', TRANSLATION_STATUS_NEEDS_UPDATE_MACHINE);
355  break;
357  $trecord->setValue('translation_status', TRANSLATION_STATUS_NEEDS_UPDATE_UNVERIFIED);
358  break;
360  $trecord->setValue('translation_status', TRANSLATION_STATUS_NEEDS_UPDATE);
361  break;
362  default:
363  $update = false;
364  // no update necessary
365 
366  }
367 
368  if ( $update ){
369  $res = $trecord->save();
370  if ( PEAR::isError($res) ) return $res;
371  }
372  unset($trecord);
373  }
374 
375  return true;
376  }
377 
378  function setTranslationStatus(&$record, $language, $status){
379  $trecord =& $this->getTranslationRecord($record, $language);
380  $trecord->setValue('translation_status', $status);
381  if ( $status == TRANSLATION_STATUS_APPROVED || $status == TRANSLATION_STATUS_MACHINE){
383  $def_record =& $this->getTranslationRecord($record, $app->_conf['default_language']);
384  if ( $def_record ){
385  $trecord->setValue('version', $def_record->val('version'));
386  }
387  }
388  $trecord->save();
389  }
390 
401  function untranslate(&$record, $language, $fieldname=null){
402  $trecord =& $this->getTranslationRecord($record, $language);
404  switch ($trecord->val('translation_status')){
407 
408  $keyvals = $record->strvals(array_keys($record->_table->keys()));
409  $clauses = array();
410  foreach ($keyvals as $key=>$val){
411  $clauses[] = "`{$key}`='".addslashes($val)."'";
412  }
413  if ( count($clauses) === 0 ) throw new Exception("Error trying to untranslate record: '".$record->getTitle()."'. The table '".$record->_table->tablename."' does not appear to have a primary key.");
414  if ( isset($fieldname) ){
415  $sql = "update `{$record->_table->tablename}_{$language}` set `{$fieldname}`=NULL where ".implode(' and ', $clauses)." limit 1";
416  } else {
417  $sql = "delete from `{$record->_table->tablename}_{$language}` where ".implode(' and ', $clauses)." limit 1";
418  }
419  $res = mysql_query($sql, $app->db());
420  if ( !$res ) throw new Exception(mysql_error($app->db()), E_USER_ERROR);
421  return true;
422  }
423  return false;
424  }
425 
426 
427 
439  function getChanges(&$record,$version, $lang=null,$fieldname=null){
441  if ( !isset($lang) ) $lang = $app->_conf['lang'];
442  list($major_version,$minor_version) = explode('.', $version);
443  $trecord = $this->getTranslationRecord($record, $lang);
444 
445  import('Dataface/HistoryTool.php');
446  $ht = new Dataface_HistoryTool();
447 
448  $hrecord = $ht->searchArchives($trecord, array('major_version'=>$major_version, 'minor_version'=>$minor_version), $lang);
449  $modified = $hrecord->strval('history__modified');
450  return $ht->getDiffsByDate($record, $modified, null, $lang, $fieldname);
451 
452  }
453 
454 
464  function migrateDefaultLanguage($newDefault, $tables=null){
465 
466  import('Dataface/Utilities.php');
467  import('Dataface/IO.php');
469  $no_fallback = @$app->_conf['default_language_no_fallback'];
470  // Whether or not the application is currently set to disable fallback
471  // to default language.
472 
473  $tables = $this->getMigratableTables();
474 
475  $log = array();
476 
477  foreach ($tables as $tablename){
478 
479 
480  $table =& Dataface_Table::loadTable($tablename);
481  $t_tablename = $tablename.'_'.$app->_conf['default_language'];
482 
483  if ( !$table || PEAR::isError($table) ) continue;
484  $res = mysql_query("create table `{$tablename}_bu_".time()."` select * from `{$tablename}`", $app->db());
485  $sql = "select `".join('`,`', array_keys($table->keys()))."` from `".$tablename."`";
486  $res2 = mysql_query($sql, $app->db());
487  $io = new Dataface_IO($tablename);
488  $io->lang = $newDefault;
489  while ( $rec = mysql_fetch_assoc($res2) ){
490  //foreach (array_keys($rec) as $colkey){
491  // $rec[$colkey] = '='.$rec[$colkey];
492  //}
493  $app->_conf['default_language_no_fallback'] = 1;
494 
495  $record = df_get_record($tablename, $rec, $io);
496  //print_r($record->strvals());
497 
498  $app->_conf['default_language_no_fallback'] = 0;
499 
500  $record2 = new Dataface_Record($tablename, array());
501  $record2->setValues($record->vals());
502 
503  $r = $io->write($record2);
504  if ( PEAR::isError($r) ){
505  $log[$tablename] = "Failed to migrate data from table '{$t_tablename}' to '{$tablename}': ".$r->getMessage()."'";
506 
507  } else {
508  $log[$tablename] = "Successfully migrated data from table '{$t_tablename}' to '{$tablename}'.";
509  }
510  unset($record);
511 
512 
513  }
514  mysql_free_result($res2);
515 
516  $res = mysql_query("create table `{$t_tablename}_bu_".time()."` select * from `{$t_tablename}`", $app->db());
517  $res = mysql_query("truncate `{$t_tablename}`", $app->db());
518 
519  unset($io);
520  unset($table);
521 
522 
523  }
524  return $log;
525  $app->_conf['default_language_no_fallback'] = $no_fallback;
526  }
527 
528 
533  function requiresMigration(){
534 
535  $migrations = $this->getMigratableTables();
536  if ( count($migrations) > 0 ){
537  return "<p>The following tables need to be migrated so that the default language is stored inside the main table - not the translation table:</p>
538  <ul><li>".implode('</li><li>', $migrations)."</li></ul>
539  <p>This migration is necessary because older versions of the query translation extension would automatically store translations
540  in their respective translation tables rather than the main table. However it is desirable to store the default language
541  in the default table so that other translations can fall-back to the correct default translation if the record does not
542  have a translation.</p>";
543  } else {
544  return false;
545  }
546  }
547 
553  if ( @$app->_conf['default_language_no_fallback'] ) return false;
554  // We are still using the old style of translations, so there is no migration required.
555 
556  $migrations = array();
557  $res = mysql_query("show tables", $app->db());
558  $tables = array();
559  while ( $row = mysql_fetch_row($res) ){
560  $tables[] = $row[0];
561  }
562  mysql_free_result($res);
563  foreach ($tables as $tablename){
564  $translation_tablename = $tablename."_".$app->_conf['default_language'];
565  if ( mysql_num_rows($res = mysql_query("show tables like '".addslashes($translation_tablename)."'", $app->db())) > 0){
566  @mysql_free_result($res);
567  list($num) = mysql_fetch_row($res = mysql_query("select count(*) from `".$translation_tablename."`",$app->db()));
568  if ( $num > 0 ){
569  $migrations[] = $tablename;
570  }
571  } else {
572 
573  }
574  mysql_free_result($res);
575  }
576  return $migrations;
577 
578  }
579 
580  function migrate(){
582  return $this->migrateDefaultLanguage($app->_conf['default_language']);
583 
584  }
585 
586  function printTranslationStatusAlert($record, $language=null){
587  if ( !isset($language) ){
589  $language = $app->_conf['lang'];
590  }
591  $trec =& $this->getTranslationRecord($record, $language);
592  if ( !$trec) return;
593  $status = $trec->val('translation_status');
594  switch ($status){
596  $msg = df_translate('machine translation warning',"This section was translated using a machine translator and may contain errors.");
597  break;
598 
600  $msg = df_translate('old machine translation warning', "This section was translated using a machine translator and may contain errors. The original version has also been modified since this translation was completed so this translation may be out of date.");
601  break;
602 
603  case TRANSLATION_STATUS_UNVERIVIED:
604  $msg = df_translate('unverified translation warning', "This translation has not been verified by an administrator yet.");
605  break;
606 
608  $msg = df_translate('old unverified translation warning', "This translation has not been verified by an administrator yet. The original version has also been modified since this translation was completed so this translation may be out of date.");
609  break;
610 
612  $msg = df_translate('old translation warning', "This translation may be out of date as the original version has been modified since this was last translated.");
613  break;
614 
615  }
616  if ( !@$msg ) return;
617  import('Dataface/ActionTool.php');
619  $actions = $at->getActions(array('category'=>'translation_warning_actions','record_id'=>$record->getId()));
620  $actions_html = "<ul class=\"translation_options\">";
621  foreach ($actions as $action){
622  $actions_html .= <<<END
623  <li><a href="{$action['url']}" title="{$action['description']}">{$action['label']}</a></li>
624 END;
625 
626  }
627  $actions_html .= '</ul>';
628  echo <<<END
629 
630  <div class="portalMessage">
631  {$msg}
632  {$actions_html}
633  </div>
634 END;
635  }
636 
637 
638 
639 
640 
641  /*
642  What do we need to do?
643 
644  1. Set Translation status
645  2. Update translation version.
646  3. Untranslate (machine translations only).
647  4. View changes
648  5.
649  */
650 
651 
652 }