<?php
/**
* This package is meant to manage archives of successive revisions of content articles stored in a database.
* It enables version control of the articles in similar way to what the CVS program provides.
* It is capable of storing in a separate database table all the versions of content articles that are stored in other tables.
* This version control package makes it possible to retrieve any of the past versions of a content article,
* the date of each revision and the identification of the author that submitted each revision.
* The base class archives content articles that may contain a variable number of fields of arbitrary length.
* It also computes an MD5 hash of the submitted content to verify whether the content has changed between revisions
* and avoids creating a new revision if nothing was changed.
* The class can retrieve one or more revisions of a content article and output the differences between two articles using
* the diff utility program
* This package comes with an optional sub-class that is capable of storing and retrieving the content articles in a
* compressed format using the gzip library, so it can use less database table space.
* This package version requires MySQLQueryContainer package to access to the database.
*
* @author $Author: Cornelius Bolten $
**/
/**
* This package is meant to manage archives of successive revisions of content articles stored in a database.
* It enables version control of the articles in similar way to what the CVS program provides.
* It is capable of storing in a separate database table all the versions of content articles that are stored in other tables.
* This version control package makes it possible to retrieve any of the past versions of a content article,
* the date of each revision and the identification of the author that submitted each revision.
* The base class archives content articles that may contain a variable number of fields of arbitrary length.
* It also computes an MD5 hash of the submitted content to verify whether the content has changed between revisions
* and avoids creating a new revision if nothing was changed.
* The class can retrieve one or more revisions of a content article and output the differences between two articles using
* the diff utility program
* This package comes with an optional sub-class that is capable of storing and retrieving the content articles in a
* compressed format using the gzip library, so it can use less database table space.
* This package version requires MySQLQueryContainer package to access to the database.
*
* Usage is free for non-commercial work.
* For feedback, bug reports or enhancements please contact the author at
* hide@address.com Thanks a lot!
*
* The Initial Developer of the Original Code is Cornelius Bolten.
* Portions created by Cornelius Bolten are Copyright (C) 2004 by Cornelius Bolten.
* All Rights Reserved.
*
* benefits:
* - does automatically verisioning
* - can retrieve all old versions of a content-object
* - bulids hash to check if a new version is needed
* when checking in
* - does GZcompression if enabled
*
* required:
* - this version expected MySQLQueryContainer-Class
* by Cornelius Bolten.
* this package can be retrieved via http://www.phpclasses.org/browse/package/1152.html
*
*
* @author $Author: Cornelius Bolten $
* @version $Revision: 1.14 $
* @package VersionControl
* @example example.VersionControl.php example on how to get it work!
* @link http://www.phpclasses.org/browse/package/1502.html latest available version @ phpclasses.org
**/
class VersionControl {
/**
* @access private
* @var object dbHandle
*/
var $dbHandle;
/**
* @access private
* @var string dbHandleName
*/
var $dbHandleName;
/**
* @access private
* @var string vc_tablename
*/
var $vc_tablename;
/**
* @access private
* @var string vc_error
*/
var $vc_error;
/**
* @access private
* @var boolean hasChanged
*/
var $hasChanged;
/**
* @access private
* @var boolean alreadExists
*/
var $alreadExists;
/**
* @access private
* @var string DiffToolBinaryPath
*/
var $DiffToolBinaryPath;
/**
* editor of last checked-out contents
* @access public
* @var string contentEditor
*/
var $contentEditor;
/**
* date of last checked-out contents
* @access public
* @var string contentDate
*/
var $contentDate;
/**
* version of last checked-out contents
* @access public
* @var string contentVersion
*/
var $contentVersion;
/**
* constuctor
*
* @access public
* @param Object valid database-object
* @param String valid database-connection-name
* @param String TableName of VersionTable
*/
function versionControl($dbHandle, $dbHandleName, $vc_tablename) {
$this->dbHandle = $dbHandle;
$this->dbHandleName = $dbHandleName;
$this->vc_tablename = $vc_tablename;
$this->vc_error = array();
$this->hasChanged = false;
$this->alreadyExists= false;
}
/**
* function returns last occured error
*
* @access public
* @return String error-message
*/
function getErrorMessage() {
print_r($this->vc_error);
unset($this->vc_error);
}
/**
* function encodes data-record
*
* @access private
* @param array serialized Array of data
* return string encoded array
*/
function encode($serialized) {
$encArray = addslashes($serialized);
return ($encArray);
}
/**
* function decodes data-record
*
* @access private
* @param string encoded array
* return serialized array
*/
function decode($encArray) {
$serialized = stripslashes($encArray);
return ($serialized);
}
/**
* function returns id of requested content (latest version)
*
* @access private
* @param integer origin content-article-id
* @param string table name where content-article comes from originally
* @return int latest version of content-article
*/
function getLatestVersionID($id, $table) {
$query = 'SELECT max(vc_content_version)
FROM `'.$this->vc_tablename.'`
WHERE `vc_content_id` = \''.$id.'\'
AND `vc_content_table` = \''.$table.'\'';
$version= $this->dbHandle->executeSelect($this->dbHandleName, $query);
if(strlen($version[0][0])<=0) {
// no record found with this id/table combination
$this->vc_error[] = "getLatestVersion() -> no record found for $id::$table";
return false;
} else {
$latest_content_version = $version[0][0];
$query = 'SELECT `vc_id`,`vc_content_version`
FROM `'.$this->vc_tablename.'`
WHERE `vc_content_version` = \''.$latest_content_version.'\'
AND `vc_content_id` = \''.$id.'\'
AND `vc_content_table` = \''.$table.'\'';
$id = $this->dbHandle->executeSelect($this->dbHandleName, $query);
if(!$id) {
// definitely error in SQL-Statement.
$this->vc_error[] = "getLatestVersion() -> error in SQL-Statement max($latest_content_version) doesn't exist in VersionTable";
return false;
} else {
return $id[0][1];
}
}
} // end getLatestVersionID
/**
* function returns latest version of a content-object.
*
* @access public
* @param integer content-article-id
* @param string table name where content-article comes from originally
* @return string latest version of content-article
*/
function getLatestVersion($id,$table) {
if($id && $table) {
$latest_id = $this->getLatestVersionID($id,$table);
if($latest_id) {
$query = 'SELECT `vc_content_data`, `vc_content_editor`, `vc_content_date`, `vc_content_version`, `vc_content_changes`
FROM `'.$this->vc_tablename.'`
WHERE `vc_id` = \''.$latest_id.'\'';
$content= $this->dbHandle->executeSelect($this->dbHandleName, $query);
if(!$content) {
// definitely error in SQL-Statement.
$this->vc_error[] = "getLatestVersion() -> ".$this->dbHandle->sqlError;
return false;
} else {
if(isset($oldContent)) {
$this->contentEditor = $oldContent[0]["vc_content_editor"];
$this->contentDate = $oldContent[0]["vc_content_date"];
$this->contentVersion = $oldContent[0]["vc_content_version"];
$this->contentChanges = stripslashes($oldContent[0]["vc_content_changes"]);
}
return unserialize( $this->decode($content[0]["vc_content_data"]) );
}
} else {
$this->vc_error[] = "getLatestVersion() -> ".$this->dbHandle->sqlError;
return false;
}
} else {
$this->vc_error[] = "getLatestVersion() -> no id or table-name given";
return false;
}
} // end getLatestVersion()
/**
* function adds a new entry to VersionControl-Table.
*
* @access public
* @param integer content-article-id
* @param string table name where content-article comes from originally
* @param array content-data to save, each field as array-value
* @param integer editor-id who edited this text
* @param string changes that have been made
* @return integer if saved, return true
*/
function saveVersion($id, $table, $data, $editor,$changes="") {
$latest_id = $this->getLatestVersion($id,$table);
if($latest_id) {
$result_data = $this->encode(serialize($data));
$result_hash = md5($result_data);
if($this->checkHashChanged($id,$table,$result_hash) && !$this->checkHashExists($id,$table,$result_hash)) {
$vc_date = time();
$next_id = $this->contentVersion+1;
$query = 'INSERT INTO `'.$this->vc_tablename.'` (`vc_content_id`,`vc_content_table`,`vc_content_version`,`vc_content_editor`,`vc_content_date`,`vc_content_data`,`vc_content_hash`,`vc_content_changes`)
VALUES (\''.$id.'\', \''.$table.'\', \''.$next_id.'\', \''.$editor.'\', \''.$vc_date.'\',\''.$result_data.'\',\''.$result_hash.'\',\''.$changes.'\')';
echo $query."<br>";
$result = $this->dbHandle->executeInsert($this->dbHandleName, $query);
if($result) {
return true;
} else {
$this->vc_error[] = "saveVersion() -> ".$this->dbHandle->sqlError;
return false;
}
} else {
$this->vc_error[] = "saveVersion() -> did not add new Version. record wasn't changed or already exists in VersionControl (as old version).";
return false;
}
} else {
$this->vc_error[] = "saveVersion() -> record will be initiated now via initVersion()";
return $this->initVersion($id, $table, $data, $editor, $changes);
}
} // end saveVersion
/**
* function adds a new entry to VersionControl-Table with version No.1
*
* @access private
* @param integer content-article-id
* @param string table name where content-article comes from originally
* @param string content-data to save
* @param integer editor-id who edited this text
* @return integer if saved, return true
*/
function initVersion($id, $table, $data, $editor, $changes) {
$result_data = $this->encode(serialize($data));
$changes = addslashes($changes);
$result_hash = md5($result_data);
$vc_date = time();
$next_id = '1';
$query = 'INSERT INTO `'.$this->vc_tablename.'` (`vc_content_id`,`vc_content_table`,`vc_content_version`,`vc_content_editor`,`vc_content_date`,`vc_content_data`,`vc_content_hash`,`vc_content_changes`)
VALUES (\''.$id.'\', \''.$table.'\', \''.$next_id.'\', \''.$editor.'\', \''.$vc_date.'\', \''.$result_data.'\', \''.$result_hash.'\',\''.$changes.'\')';
$result = $this->dbHandle->executeInsert($this->dbHandleName, $query);
if($result) {
return true;
} else {
$this->vc_error[] = "initVersion() -> ".$this->dbHandle->sqlError;
}
} // end initVersion
/**
* function returns an old version of a stored Record.
*
* @access public
* @param integer content-article-id
* @param string table name where content-article comes from originally
* @param string requested version of content
* @return array record in version $version
*/
function getOldVersion($id, $table, $version) {
$query = 'SELECT `vc_content_data`, `vc_content_editor`, `vc_content_date`, `vc_content_version`, `vc_content_changes`
FROM `'.$this->vc_tablename.'`
WHERE `vc_content_id` = \''.$id.'\'
AND `vc_content_table` = \''.$table.'\'
AND `vc_content_version`= \''.$version.'\'';
$oldContent= $this->dbHandle->executeSelect($this->dbHandleName, $query);
if(!$oldContent) {
// no record found with this id/table combination
$this->vc_error[] = "getOldVersion() -> version $version not found for $id::$table";
return false;
} else {
$old_content_version = unserialize( $this->decode($oldContent[0]["vc_content_data"]) );
$this->contentEditor = $oldContent[0]["vc_content_editor"];
$this->contentDate = $oldContent[0]["vc_content_date"];
$this->contentVersion = $oldContent[0]["vc_content_version"];
$this->contentChanges = stripslashes($oldContent[0]["vc_content_changes"]);
return $old_content_version;
}
} // end getOldVersion
/**
* function returns a list of all available version-ids of a stored Record
*
* @access public
* @param integer content id
* @param string table name where content-article comes from originally
* @return array list of versions
*/
function getAllVersions($id, $table) {
// build query
$query = 'SELECT `vc_content_version`
FROM `'.$this->vc_tablename.'`
WHERE `vc_content_id` = \''.$id.'\'
AND `vc_content_table` = \''.$table.'\'';
$versions = $this->dbHandle->executeSelect($this->dbHandleName, $query);
if($versions) {
foreach($versions as $version) {
$vList[] = $version["vc_content_version"];
}
return $vList;
} else {
$this->vc_error[] = "getAllVersions() -> no versions available ($id::$table)";
}
}
/**
* function checks weather a records hash has changed (compared to latest old version)
*
* @access private
* @param integer content id
* @param string table name where content-article comes from originally
* @param string hash of record
* @return bool has changed
*/
function checkHashChanged($id,$table,$hash) {
// get latest content_version
$LatestVersion = $this->getLatestVersionID($id, $table);
// build hash-query
$query = 'SELECT `vc_content_hash`
FROM `'.$this->vc_tablename.'`
WHERE `vc_content_id` = \''.$id.'\'
AND `vc_content_table` = \''.$table.'\'
AND `vc_content_version`= \''.$LatestVersion.'\'';
// get hash from database
$hashData = $this->dbHandle->executeSelect($this->dbHandleName, $query);
if($hashData) {
$latestHashKey = $hashData[0]["vc_content_hash"];
if($latestHashKey == $hash) {
$this->vc_error[] = "checkHashChanged() -> same hash. no change to submitted version";
return false;
} else {
$this->hasChanged = true;
return true;
}
} else {
$this->vc_error[] = "checkHashChanged() -> no hash found. ($id, $table)";
return true;
}
}
/**
* function checks weather a hash alread exists
*
* @access private
* @param integer content id
* @param string table name where content-article comes from originally
* @param string hash of record
* @return bool hash exists
*/
function checkHashExists($id,$table,$hash) {
// build hash-query
$query = 'SELECT count(*)
FROM `'.$this->vc_tablename.'`
WHERE `vc_content_id` = \''.$id.'\'
AND `vc_content_table` = \''.$table.'\'
AND `vc_content_hash` = \''.$hash.'\'';
$hashData = $this->dbHandle->executeSelect($this->dbHandleName, $query);
if($hashData) {
$count_result = $hashData[0][0];
if($count_result >= 1) {
$this->alreadExists = true;
$this->vc_error[] = "checkHashExists() -> hash found in db ($id::$table::$hash)";
return true;
} else {
$this->vc_error[] = "checkHashExists() -> hash not found ($id::$table::$hash)";
return false;
}
} else {
$this->vc_error[] = "checkHashExists() -> query-error ($id::$table::$hash::$query)";
return false;
}
}
/**
* function sets path to DiffTool-Binary on the running system.
* default is "/usr/bin/diff"
*
* @access public
* @param string path to DiffTool-Binary
*/
function setDiffPath($pathToDiffBinary = "/usr/bin/diff") {
$this->DiffToolBinaryPath = $pathToDiffBinary;
}
/**
* function diff shows differences between versions of a text
*
* @access public
* @param integer content id
* @param string table name where content-article comes from originally
* @param string version1 to check
* @param string version2 to check
* @return string differences as diff-output
*/
function diff($id, $table, $version1, $version2, $dataField) {
if(!$this->DiffToolBinaryPath) {
$this->vc_error[] = "diff() -> DiffToolBinaryPath not set. please call setDiffPath(pathToDiffTool-Binary as string) first.";
return false;
}
// create files with version-content
$file1 = tempnam("/tmp", "libVC");
$file2 = tempnam("/tmp", "libVC");
$fh1 = fopen($file1, "w");
$fh2 = fopen($file2, "w");
// retrieve versions
$c1 = $this->getOldVersion($id,$table,$version1);
$c2 = $this->getOldVersion($id,$table,$version2);
// write temporary files
fwrite($fh1, $c1[$dataField]."\n");
fwrite($fh2, $c2[$dataField]."\n");
// close files
fclose($fh1);
fclose($fh2);
// diff files..
$handle = popen($this->DiffToolBinaryPath." $file1 $file2", 'r');
$read = fread($handle, 2096);
pclose($handle);
// delete files
unlink($file1);
unlink($file2);
return $read;
}
} // end class
?>