From 9ba1914524df9ea3e9ff33a875c5193f49e5f959 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Sat, 20 Jun 2015 13:37:49 +0000 Subject: [PATCH] Added an alternate implementation for storing "transaction" identifiers on disk instead of inside the $_SESSION variable. SVN:trunk[3598] --- application/transaction.class.inc.php | 192 +++++++++++++++++++++++++- core/config.class.inc.php | 8 ++ 2 files changed, 197 insertions(+), 3 deletions(-) diff --git a/application/transaction.class.inc.php b/application/transaction.class.inc.php index 1b8b831df..e882f622e 100644 --- a/application/transaction.class.inc.php +++ b/application/transaction.class.inc.php @@ -1,5 +1,5 @@ Get('transaction_storage'); + if (!class_exists($sClass, false)) + { + IssueLog::Error("Incorrect value '".MetaModel::GetConfig()->Get('transaction_storage')."' for 'transaction_storage', the class '$sClass' does not exists. Using privUITransactionSession instead for storing sessions."); + $sClass = 'privUITransactionSession'; + } + + return (string)$sClass::GetNewTransactionId(); + } + + /** + * Check whether a transaction is valid or not and (optionally) remove the valid transaction from + * the session so that another call to IsTransactionValid for the same transaction id + * will return false + * @param int $id Identifier of the transaction, as returned by GetNewTransactionId + * @param bool $bRemoveTransaction True if the transaction must be removed + * @return bool True if the transaction is valid, false otherwise + */ + public static function IsTransactionValid($id, $bRemoveTransaction = true) + { + $sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage'); + if (!class_exists($sClass, false)) + { + $sClass = 'privUITransactionSession'; + } + + return $sClass::IsTransactionValid($id, $bRemoveTransaction); + } + + /** + * Removes the transaction specified by its id + * @param int $id The Identifier (as returned by GetNewTranscationId) of the transaction to be removed. + * @return void + */ + public static function RemoveTransaction($id) + { + $sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage'); + if (!class_exists($sClass, false)) + { + $sClass = 'privUITransactionSession'; + } + + $sClass::RemoveTransaction($id); + } +} + +/** + * The original (and by default) mechanism for storing transaction information + * as an array in the $_SESSION variable + * + */ +class privUITransactionSession { /** * Create a new transaction id, store it in the session and return its id @@ -99,4 +160,129 @@ class privUITransaction } } } -?> + +/** + * An alternate implementation for storing the transactions as temporary files + * Useful when using an in-memory storage for the session which do not + * guarantee mutual exclusion for writing + */ +class privUITransactionFile +{ + /** + * Create a new transaction id, store it in the session and return its id + * @param void + * @return int The identifier of the new transaction + */ + public static function GetNewTransactionId() + { + if (!is_dir(APPROOT.'data/transactions')) + { + if (!is_writable(APPROOT.'data')) + { + throw new Exception('The directory "'.APPROOT.'data" must be writable to the application.'); + } + if (!@mkdir(APPROOT.'data/transactions')) + { + throw new Exception('Failed to create the directory "'.APPROOT.'data/transactions". Ajust the rights on the parent directory or let an administrator create the transactions directory and give the web sever enough rights to write into it.'); + } + } + if (!is_writable(APPROOT.'data/transactions')) + { + throw new Exception('The directory "'.APPROOT.'data/transactions" must be writable to the application.'); + } + self::CleanupOldTransactions(); + $id = basename(tempnam(APPROOT.'data/transactions', substr(UserRights::GetUser(), 0, 10).'-')); + IssueLog::Info('GetNewTransactionId: Created transaction: '.$id); + + return (string)$id; + } + + /** + * Check whether a transaction is valid or not and (optionally) remove the valid transaction from + * the session so that another call to IsTransactionValid for the same transaction id + * will return false + * @param int $id Identifier of the transaction, as returned by GetNewTransactionId + * @param bool $bRemoveTransaction True if the transaction must be removed + * @return bool True if the transaction is valid, false otherwise + */ + public static function IsTransactionValid($id, $bRemoveTransaction = true) + { + $bResult = file_exists(APPROOT.'data/transactions/'.$id); + if ($bResult) + { + if ($bRemoveTransaction) + { + $bResult = @unlink(APPROOT.'data/transactions/'.$id); + if (!$bSuccess) + { + IssueLog::Error('IsTransactionValid: FAILED to remove transaction '.$id); + } + else + { + IssueLog::Info('IsTransactionValid: Removed transaction: '.$id); + } + } + } + else + { + IssueLog::Info("IsTransactionValid: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions())); + } + return $bResult; + } + + /** + * Removes the transaction specified by its id + * @param int $id The Identifier (as returned by GetNewTransactionId) of the transaction to be removed. + * @return void + */ + public static function RemoveTransaction($id) + { + $bSuccess = true; + if(!file_exists(APPROOT.'data/transactions/'.$id)) + { + $bSuccess = false; + IssueLog::Info("RemoveTransaction: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions())); + } + $bSuccess = @unlink(APPROOT.'data/transactions/'.$id); + if (!$bSuccess) + { + IssueLog::Error('RemoveTransaction: FAILED to remove transaction '.$id); + } + return $bSuccess; + } + + /** + * Cleanup old transactions which have been pending since more than 24 hours + */ + protected static function CleanupOldTransactions() + { + $iLimit = time() - 24*3600; + $aTransactions = glob(APPROOT.'data/transactions/*-*'); + foreach($aTransactions as $sFileName) + { + if (filectime($sFileName) < $iLimit) + { + @unlink($sFileName); + } + } + } + + /** + * For debugging purposes: gets the pending transactions of the current user + * as an array, with the date of the creation of the transaction file + */ + protected static function GetPendingTransactions() + { + clearstatcache(); + $aResult = array(); + $aTransactions = glob(APPROOT.'data/transactions/'.UserRights::GetUser().'-*'); + foreach($aTransactions as $sFileName) + { + $aResult[] = date('Y-m-d H:i:s', filectime($sFileName)).' - '.basename($sFileName); + } + sort($aResult); + return $aResult; + } + +} + diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 77575a612..e9fdc3e30 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -809,6 +809,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'transaction_storage' => array( + 'type' => 'string', + 'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).', + 'default' => 'Session', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), ); public function IsProperty($sPropCode)