![]() |
Xataface 2.0
Xataface Application Framework
|
00001 <?php 00002 import('Dataface/AuthenticationTool.php'); 00064 class Dataface_HistoryTool { 00065 00069 var $meta_fields = array( 00070 'history__id'=>array('Type'=>'int(11)', 'Extra'=>'auto_increment'), 00071 'history__language'=> array('Type'=>'varchar(2)'), 00072 'history__comments'=> array('Type'=>'text'), 00073 'history__user'=>array('Type'=>'varchar(32)'), 00074 'history__state'=>array('Type'=>'int(5)'), 00075 'history__modified'=>array('Type'=>'datetime') 00076 ); 00077 00078 00079 00094 function logRecord(&$record, $comments='', $lang=null, $state=null){ 00095 $app =& Dataface_Application::getInstance(); 00096 00097 if ( !isset($lang) ){ 00098 $lang = $app->_conf['lang']; 00099 } 00100 00101 if ( !isset($state) ){ 00102 $state = 0; 00103 } 00104 00105 00106 $fieldnames = array_keys($record->_table->fields()); 00107 $sql = 'select `'.implode('`,`', $fieldnames).'` from `'.$record->_table->tablename.'` where'; 00108 $keynames = array_keys($record->_table->keys()); 00109 $where_clauses = array(); 00110 foreach ( $keynames as $keyname){ 00111 $where_clauses[] = '`'.$keyname.'`=\''.addslashes($record->strval($keyname)).'\''; 00112 } 00113 $sql .= ' '.implode(' and ', $where_clauses); 00114 00115 if ( @$app->_conf['multilingual_content'] ){ 00116 $db =& Dataface_DB::getInstance(); 00117 $sql = $db->translate_query($sql, $lang); 00118 $sql = $sql[0]; 00119 } 00120 00121 $auth =& Dataface_AuthenticationTool::getInstance(); 00122 $userRecord =& $auth->getLoggedInUser(); 00123 if ( !isset($userRecord) ){ 00124 $user = null; 00125 } else { 00126 $user = $auth->getLoggedInUsername(); 00127 } 00128 00129 00130 $insertsql = "insert into `".$this->logTableName($record->_table->tablename)."` 00131 (`".implode('`,`', $fieldnames)."`, `history__language`,`history__comments`,`history__user`,`history__state`,`history__modified`) 00132 select *, '".addslashes($lang)."','".addslashes($comments)."','".addslashes($user)."','".addslashes($state)."', NOW() 00133 from (".$sql.") as t"; 00134 00135 $res = mysql_query($insertsql, $app->db()); 00136 if ( !$res ){ 00137 $this->updateHistoryTable($record->_table->tablename); 00138 $res = mysql_query($insertsql, $app->db()); 00139 } 00140 if ( !$res ){ 00141 echo $insertsql; 00142 trigger_error(mysql_error($app->db()), E_USER_ERROR); 00143 } 00144 00145 // Now for the individual fields 00146 $hid = mysql_insert_id($app->db()); 00147 foreach ($fieldnames as $fieldname){ 00148 $this->logField($record, $fieldname, $hid); 00149 } 00150 00151 return $hid; 00152 00153 00154 } 00155 00166 function logField(&$record, $fieldname, $history_id){ 00167 $field =& $record->_table->getField($fieldname); 00168 00169 switch(strtolower($field['Type'])){ 00170 case 'container': 00171 $savepath = $field['savepath']; 00172 if ( $savepath{strlen($savepath)-1} != '/' ) $savepath.='/'; 00173 if ( !$record->val($fieldname) ) break; // there is no file currently stored in this field. 00174 if ( !is_readable($savepath.$record->val($fieldname)) ) break; // the file does not exist 00175 if ( !file_exists($savepath) || !is_dir($savepath) ) 00176 trigger_error( 00177 df_translate( 00178 'scripts.Dataface.HistoryTool.logField.ERROR_CONTAINER_FIELD_SAVEPATH_MISSING', 00179 "Field {$fieldname} is a Container field but its corresponding savepath {$savepath} does not exist. Please create the directory {$savepath} and ensure that it is writable by the web server", 00180 array('fieldname'=>$fieldname,'savepath'=>$savepath) 00181 ), E_USER_ERROR); 00182 $histpath = $savepath.'.dataface_history/'; 00183 if ( !file_exists($histpath) ){ 00184 $res = mkdir($histpath, 0777); 00185 if ( !$res ) trigger_error( 00186 df_translate( 00187 'scripts.Dataface.HistoryTool.logField.ERROR_FAILED_TO_MAKE_HISTORY_FOLDER', 00188 "Failed to make history folder {$histpath} to store the history for container field {$fieldname} in table {$record->_table->tablename}. It could be a permissions problem. Please ensure that the {$savepath} directory is writable by the web server.", 00189 array('histpath'=>$histpath,'fieldname'=>$fieldname,'tablename'=>$record->_table->tablename,'savepath'=>$savepath) 00190 ), E_USER_ERROR); 00191 00192 } 00193 if ( !is_dir($histpath) ){ 00194 trigger_error( 00195 df_translate( 00196 'scripts.Dataface.HistoryTool.logField.ERROR_NOT_A_DIRECTORY', 00197 "The history path for the field {$fieldname} in table {$record->_table->tablename} is not a directory. Perhaps a file has been uploaded with the reserved name '.history'. Please delete this file to allow Dataface's history feature to work properly.", 00198 array('fieldname'=>$fieldname, 'tablename'=>$record->_table->tablename) 00199 ), E_USER_ERROR); 00200 } 00201 if ( !is_writable($histpath) ){ 00202 trigger_error("The history folder for field {$fieldname} of table {$record->_table->tablename} is not writable by the web server. Please make it writable by the web server for Dataface's history feature to work properly.", E_USER_ERROR); 00203 } 00204 00205 00206 $destpath = $histpath.$history_id; 00207 if ( file_exists($destpath) ) { return;} // the file already exists... just skip it. 00208 00209 $srcpath = $savepath.$record->val($fieldname); 00210 $res = copy($srcpath,$destpath); 00211 00212 break; 00213 00214 00215 00216 } 00217 } 00218 00219 00226 function logTableName($tablename){ 00227 return $tablename.'__history'; 00228 } 00229 00234 function updateHistoryTable($tablename){ 00235 $app =& Dataface_Application::getInstance(); 00236 $name = $this->logTableName($tablename); 00237 00238 //first check to see if the table exists 00239 if ( mysql_num_rows(mysql_query("show tables like '".$name."'", $app->db())) == 0 ){ 00240 $this->createHistoryTable($tablename); 00241 } 00242 00243 $res = mysql_query("show columns from `".$this->logTableName($tablename)."`", $app->db()); 00244 if ( !$res ) trigger_error(mysql_error($app->db()), E_USER_ERROR); 00245 $history_fields = array(); 00246 while ( $row = mysql_fetch_assoc($res) ){ 00247 $history_fields[$row['Field']] = $row; 00248 } 00249 @mysql_free_result($res); 00250 00251 $table =& Dataface_Table::loadTable($tablename); 00252 $fieldnames = array_keys($table->fields()); 00253 00254 foreach ($fieldnames as $fieldname){ 00255 if ( !isset($history_fields[$fieldname]) ){ 00256 $field =& $table->getField($fieldname); 00257 $type = (( strcasecmp($field['Type'],'container') === 0 ) ? 'varchar(64)' : $field['Type'] ); 00258 $sql = "alter table `".$name."` add column `".$fieldname."` {$type} DEFAULT NULL"; 00259 $res = mysql_query($sql, $app->db()); 00260 if ( !$res ){ 00261 trigger_error(mysql_error($app->db()), E_USER_ERROR); 00262 } 00263 unset($field); 00264 } 00265 } 00266 00267 $meta_fields = $this->meta_fields; 00268 00269 foreach ( array_keys($meta_fields) as $fieldname){ 00270 if ( !isset($history_fields[$fieldname]) ){ 00271 $sql = "alter table `".$name."` add column `".$fieldname."` ".$history_fields[$fieldname]['Type'].' '.@$history_fields[$fieldname]['Extra']; 00272 $res = mysql_query($sql, $app->db()); 00273 if ( !$res ){ 00274 trigger_error(mysql_error($app->db()), E_USER_ERROR); 00275 } 00276 00277 } 00278 } 00279 00280 } 00281 00282 00288 function createHistoryTable($tablename){ 00289 $app =& Dataface_Application::getInstance(); 00290 $sql = "create table `".$this->logTableName($tablename)."` ( 00291 `history__id` int(11) auto_increment NOT NULL, 00292 `history__language` varchar(2) DEFAULT NULL, 00293 `history__comments` text default null, 00294 `history__user` varchar(32) default null, 00295 `history__state` int(5) default 0, 00296 `history__modified` datetime,"; 00297 00298 $table =& Dataface_Table::loadTable($tablename); 00299 $status = $table->getStatus(); 00300 $charset = substr($status['Collation'],0, strpos($status['Collation'],'_')); 00301 $collation = $status['Collation']; 00302 $fieldnames = array_keys($table->fields()); 00303 $fielddefs = array(); 00304 foreach ( $fieldnames as $fieldname){ 00305 $field =& $table->getField($fieldname); 00306 $type = (( strcasecmp($field['Type'],'container') === 0 ) ? 'varchar(64)' : $field['Type'] ); 00307 $fielddefs[] = "`".$fieldname."` ".$type; 00308 unset($field); 00309 } 00310 00311 $sql .= implode(",\n",$fielddefs); 00312 $sql .= ", 00313 PRIMARY KEY (`history__id`), 00314 KEY prikeys using hash (`".implode('`,`', array_keys($table->keys()))."`), 00315 KEY datekeys using btree (`history__modified`)) ".(@$status['Engine'] ? "ENGINE=".$status['Engine']:'')." ".($charset ? "DEFAULT CHARSET=".$charset.($collation ? " COLLATE $collation":''):''); 00316 00317 $res = mysql_query($sql, $app->db()); 00318 if ( !$res ){ 00319 trigger_error(mysql_error($app->db()), E_USER_ERROR); 00320 } 00321 } 00322 00323 00335 function getDiffs($tablename, $id1, $id2=null, $fieldname=null ){ 00336 import('Text/Diff.php'); 00337 import('Text/Diff/Renderer/inline.php'); 00338 $htablename = $tablename.'__history'; 00339 if ( !Dataface_Table::tableExists($htablename) ) 00340 return PEAR::raiseError( 00341 df_translate('scripts.Dataface.HistoryTool.getDiffs.ERROR_HISTORY_TABLE_DOES_NOT_EXIST', 00342 "History table for '{$tablename}' does not exist, so we cannot obtain changes for records of that table.", 00343 array('tablename'=>$tablename) 00344 ), DATAFACE_E_ERROR); 00345 00346 $rec1 = df_get_record($htablename, array('history__id'=>$id1)); 00347 00348 if ( !isset($id2) ){ 00349 // The 2nd id wasn't provided so we assume we want to know the diffs 00350 // against the current state of the record. 00351 $table =& Dataface_Table::loadTable($tablename); 00352 $query = $rec1->strvals(array_keys($table->keys())); 00353 $io = new Dataface_IO($tablename); 00354 $io->lang = $rec1->val('history__language'); 00355 $rec2 = new Dataface_Record($tablename, array()); 00356 $io->read($query, $rec2); 00357 } else { 00358 $rec2 = df_get_record($htablename, array('history__id'=>$id2)); 00359 } 00360 00361 $vals1 = $rec1->strvals(); 00362 $vals2 = $rec2->strvals(); 00363 00364 $vals_diff = array(); 00365 $renderer = new Text_Diff_Renderer_inline(); 00366 foreach ($vals2 as $key=>$val ){ 00367 $diff = new Text_Diff(explode("\n", @$vals1[$key]), explode("\n", $val)); 00368 00369 $vals_diff[$key] = $renderer->render($diff); 00370 } 00371 00372 $diff_rec = new Dataface_Record($htablename, $vals_diff); 00373 if ( isset($fieldname) ) return $diff_rec->val($fieldname); 00374 return $diff_rec; 00375 00376 00377 } 00378 00379 00392 function getDiffsByDate(&$record, $date1, $date2=null, $lang=null, $fieldname=null){ 00393 if ( !isset($date2) ) $date2 = date('Y-m-d H:i:s'); 00394 $time1 = strtotime($date1); 00395 $time2 = strtotime($date2); 00396 if ( $time1 > $time2 ){ 00397 $temp = $date2; 00398 $date2 = $date1; 00399 $date1 = $temp; 00400 } 00401 $app =& Dataface_Application::getInstance(); 00402 if ( !isset($lang) ) $lang = $app->_conf['lang']; 00403 $htablename = $record->_table->tablename.'__history'; 00404 if ( !Dataface_Table::tableExists($htablename) ) 00405 return PEAR::raiseError( 00406 df_translate('scripts.Dataface.HistoryTool.getDiffs.ERROR_HISTORY_TABLE_DOES_NOT_EXIST', 00407 "History table for '{$tablename}' does not exist, so we cannot obtain changes for records of that table.", 00408 array('tablename'=>$tablename) 00409 ), DATAFACE_E_ERROR); 00410 $clauses = array(); 00411 $keyvals = $record->strvals(array_keys($record->_table->keys())); 00412 foreach ($keyvals as $key=>$val){ 00413 $clauses[] = "`{$key}`='".addslashes($val)."'"; 00414 } 00415 $clauses[] = "`history__language`='".addslashes($lang)."'"; 00416 00417 $sql = "select `history__id` from `{$htablename}` where ".implode(' and ',$clauses); 00418 $sql1 = $sql . " and `history__modified` <= '".addslashes($date1)."' order by `history__modified` desc limit 1"; 00419 $sql2 = $sql . " and `history__modified` <= '".addslashes($date2)."' order by `history__modified` desc limit 1"; 00420 00421 $res2 = mysql_query($sql2, $app->db()); 00422 if ( !$res2 ){ 00423 //echo $sql2; 00424 trigger_error(mysql_error($app->db()), E_USER_ERROR); 00425 } 00426 if ( mysql_num_rows($res2) == 0 ){ 00427 if (isset($fieldname) ) return ''; 00428 else return new Dataface_Record($htablename, array()); 00429 } 00430 list($id2) = mysql_fetch_row($res2); 00431 @mysql_free_result($res2); 00432 00433 $res1 = mysql_query($sql1, $app->db()); 00434 if ( !$res1 ){ 00435 //echo $sql1; 00436 trigger_error(mysql_error($app->db()), E_USER_ERROR); 00437 } 00438 if ( mysql_num_rows($res1) == 0 ){ 00439 $rec = df_get_record($htablename, array('history__id'=>$id2)); 00440 if ( !isset($rec) ) 00441 trigger_error( 00442 df_translate( 00443 'scripts.Dataface.HistoryTool.getDiffsByDate.ERROR_FAILED_TO_LOAD_HISTORY_RECORD', 00444 "Failed to load history record with id {$id2}", 00445 array('id'=>$id2) 00446 ), DATAFACE_E_ERROR); 00447 if ( isset($fieldname) ) return $rec->val($fieldname); 00448 return $rec; 00449 } 00450 list($id1) = mysql_fetch_row($res1); 00451 @mysql_free_result($res1); 00452 $out = $this->getDiffs($record->_table->tablename, $id1, $id2, $fieldname); 00453 return $out; 00454 00455 } 00456 00457 00461 function restore(&$record, $id, $fieldname=null){ 00462 $app =& Dataface_Application::getInstance(); 00463 if ( isset($fieldname) ) $fieldnames = array($fieldname); 00464 else $fieldnames = array_keys($record->_table->fields()); 00465 00466 $htablename = $record->_table->tablename.'__history'; 00467 $res = mysql_query("select `".implode('`,`', $fieldnames)."`,`history__language` from `{$htablename}` where `history__id`='".addslashes($id)."'", $app->db()); 00468 if ( !$res ) trigger_error(mysql_error($app->db()), E_USER_ERROR); 00469 if ( mysql_num_rows($res) == 0 ) 00470 return PEAR::raiseError( 00471 df_translate( 00472 'scripts.Dataface.HistoryTool.restore.ERROR_NO_SUCH_RECORD', 00473 "Could not restore record with id {$id} in table {$htablename} because no such record exists. Perhaps the history was cleaned out.", 00474 array('id'=>$id, 'tablename'=>$htablename) 00475 ), DATAFACE_E_ERROR); 00476 $vals = mysql_fetch_assoc($res); 00477 @mysql_free_result($res); 00478 $old_record = new Dataface_Record($record->_table->tablename, $record->getValues()); 00479 $lang = $vals['history__language']; 00480 unset($vals['history__language']); 00481 if ( isset($fieldname) ){ 00482 $record->setValue($fieldname, $vals[$fieldname]); 00483 } else { 00484 $record->setValues($vals); 00485 } 00486 $record->save($lang); 00487 foreach ($fieldnames as $fld){ 00488 $this->restoreField($record,$old_record, $id, $fld); 00489 } 00490 return true; 00491 00492 } 00493 00504 function restoreField(&$record, &$old_record, $id, $fieldname){ 00505 $app =& Dataface_Application::getInstance(); 00506 $htablename = $record->_table->tablename.'__history'; 00507 $field =& $record->_table->getField($fieldname); 00508 switch (strtolower($field['Type'])){ 00509 case 'container': 00510 $savepath = $field['savepath']; 00511 if ( $savepath{strlen($savepath)-1} != '/' ) $savepath .= '/'; 00512 00513 if ( $old_record->val($fieldname) ){ 00514 // we need to delete the existing file 00515 $filepath = $savepath.basename($old_record->val($fieldname)); 00516 if ( file_exists($filepath) ) unlink($filepath); 00517 } 00518 00519 $hsavepath = $savepath.'.dataface_history/'.$id; 00520 if ( !file_exists($hsavepath) || !is_readable($hsavepath) ) return false; 00521 $filename = basename($record->val($fieldname)); 00522 $filepath = $savepath.'/'.$filename; 00523 while ( file_exists($filepath) ){ 00524 $filepath = $savepath.'/'.strval(rand(0,10000)).'_'.$filename; 00525 } 00526 return copy($hsavepath, $filepath); 00527 00528 } 00529 00530 } 00531 00539 function restoreToDate(&$record, $date, $lang=null, $fieldname=null){ 00540 $app =& Dataface_Application::getInstance(); 00541 $id = $this->getPreviousVersion($record, $date, $lang, $fieldname, true); 00542 return $this->restore($record, $id, $fieldname); 00543 /* 00544 $sql = "select * from `{$record->_table->_tablename}__history}` where `history__id` = '{$id}' limit 1"; 00545 00546 $res = mysql_query($sql, $app->db()); 00547 if ( !$res ) trigger_error(mysql_error($app->db()), E_USER_ERROR); 00548 if ( mysql_num_rows($res) == 0 ){ 00549 return PEAR::raiseError("Attempt to restore record \"{$record->getTitle()}\" to nonexistent history record with id '{$id}'", DATAFACE_E_ERROR); 00550 } 00551 $row = mysql_fetch_assoc($res); 00552 @mysql_free_result($res); 00553 $old_record = new Dataface_Record($record->_table->tablename, $record->getValues()); 00554 $record->setValues($row); 00555 $res = $record->save($lang); 00556 foreach ($fieldnames as $fld){ 00557 $this->restoreField($record, $old_record, $id, $fld); 00558 } 00559 00560 return $res; 00561 */ 00562 00563 00564 } 00565 00576 function getPreviousVersion(&$record, $date, $lang=null, $fieldname=null, $idonly=false){ 00577 $app =& Dataface_Application::getInstance(); 00578 if ( !isset($lang) ) $lang = $app->_conf['lang']; 00579 $htablename = $record->_table->tablename.'__history'; 00580 if ( !Dataface_Table::tableExists($htablename) ) 00581 return PEAR::raiseError( 00582 df_translate( 00583 'scripts.Dataface.HistoryTool.getDiffs.ERROR_HISTORY_TABLE_DOES_NOT_EXIST', 00584 "History table for '{$record->_table->tablename}' does not exist, so we cannot obtain changes for records of that table.", 00585 array('tablename'=>$record->_table->tablename) 00586 ), DATAFACE_E_ERROR); 00587 $clauses = array(); 00588 $keyvals = $record->strvals(array_keys($record->_table->keys())); 00589 foreach ($keyvals as $key=>$val){ 00590 $clauses[] = "`{$key}`='".addslashes($val)."'"; 00591 } 00592 $clauses[] = "`history__language`='".addslashes($lang)."'"; 00593 00594 $sql = "select `history__id` from `{$htablename}` where ".implode(' and ',$clauses)." 00595 and `history__modified` <= '".addslashes($date)."' order by `history__modified` desc limit 1"; 00596 00597 $res = mysql_query($sql, $app->db()); 00598 if ( !$res ) trigger_error(mysql_error($app->db()), E_USER_ERROR); 00599 if ( mysql_num_rows($res) == 0 ){ 00600 return null; 00601 } 00602 list($id) = mysql_fetch_row($res); 00603 @mysql_free_result($res); 00604 if ( $idonly ) return $id; 00605 $out = $this->getRecordById($record->_table->tablename, $id); 00606 00607 if ( isset($fieldname) ) return $out->val($fieldname); 00608 return $out; 00609 00610 00611 00612 } 00613 00620 function getRecordById($tablename, $id){ 00621 $htablename = $tablename.'__history'; 00622 if ( !Dataface_Table::tableExists($htablename) ) 00623 return PEAR::raiseError( 00624 df_translate( 00625 'scripts.Dataface.HistoryTool.getDiffs.ERROR_HISTORY_TABLE_DOES_NOT_EXIST', 00626 "History table for '{$tablename}' does not exist, so we cannot obtain changes for records of that table.", 00627 array('tablename'=>$tablename) 00628 ), DATAFACE_E_ERROR); 00629 00630 $rec = df_get_record($htablename, array('history__id'=>$id)); 00631 return $rec; 00632 } 00633 00634 00643 function getHistoryLog(&$record, $lang=null, $limit=100){ 00644 $app =& Dataface_Application::getInstance(); 00645 $history_tablename = $record->_table->tablename.'__history'; 00646 if ( !Dataface_Table::tableExists($history_tablename) ) return array(); 00647 $keys = $record->strvals(array_keys($record->_table->keys())); 00648 $clauses = array(); 00649 foreach ( $keys as $key=>$val){ 00650 $clauses[] = "`{$key}`='".addslashes($val)."'"; 00651 } 00652 if ( isset($lang) ) $clauses[] = "`history__language` = '".addslashes($lang)."'"; 00653 $where = implode(' and ', $clauses); 00654 if ( isset($limit) ) $limit = "LIMIT $limit"; 00655 else $limit = ''; 00656 00657 $sql = "select `".implode('`,`', array_keys($this->meta_fields))."` from `{$history_tablename}` where {$where} order by `history__modified` desc {$limit}"; 00658 //echo $sql; 00659 $res = mysql_query($sql, $app->db()); 00660 if ( !$res ) trigger_error(mysql_error($app->db()), E_USER_ERROR); 00661 $out = array(); 00662 while ( $row = mysql_fetch_assoc($res) ) $out[] = $row; 00663 @mysql_free_result($res); 00664 return $out; 00665 00666 00667 00668 00669 } 00670 00671 00676 function findMatchingSnapshots($record, $query, $idsOnly=true){ 00677 $app =& Dataface_Application::getInstance(); 00678 $htablename = $record->_table->tablename.'__history'; 00679 if ( !Dataface_Table::tableExists($htablename) ) return array(); 00680 $keys = $record->strvals(array_keys($record->_table->keys())); 00681 foreach ($keys as $key=>$val){ 00682 $query[$key] = '='.$val; 00683 } 00684 if ( $idsOnly ){ 00685 $qbuilder = new Dataface_QueryBuilder($htablename, $query); 00686 $sql = $qbuilder->select(array('history__id'), $query); 00687 $res = mysql_query($sql, df_db()); 00688 $ids = array(); 00689 while ( $row = mysql_fetch_row($res) ) $ids[] = $row[0]; 00690 @mysql_free_result($res); 00691 return $ids; 00692 } else { 00693 return df_get_records_array($htablename, $query); 00694 } 00695 00696 } 00697 00698 00699 00700 00701 00702 }