" . getTitleFromFile($path) . "
"; } mysql_close($db); $hIndexFile = fopen($indexFilename, "w"); if ($hIndexFile == FALSE) { die("Error while updating index!: " . $indexFilename); } fwrite($hIndexFile, $INDEX_FILE_HEADER); fwrite($hIndexFile, $contents); fwrite($hIndexFile, $INDEX_FILE_FOOTER); fclose($hIndexFile); } // Attempts to delete a content file. Only the global admin or high-level // language administrator can successfully do this. All other users will // be denied. public static function deleteFile($loggedInUser, $file) { global $ACL_ACCESS_ADMIN_HIGH; global $CMS_CONTENT_FS_PATH; global $RM_PATH; require_once("languages.php"); // Make sure nobody is trying something funny here. Relative paths are // rejected outright. $slashPos = strpos($file, "/"); if (($slashPos == FALSE) || (strpos($file, "..") !== FALSE)) { die("Invalid file argument: $file"); } $langName = substr($file, 0, $slashPos); $lid = Language::lookupLID($langName); $accessLevel = $loggedInUser->getAccessLevel($lid); if (($accessLevel == $ACL_ACCESS_ADMIN_HIGH) || isAdminUser()) { // Do nothing. } else { die("Sorry, you do not have sufficient rights to delete this file."); } // Delete any changes in the PendingQueue that refer to this file. $db = getDBConnection(); $result = mysql_query("DELETE FROM PendingQueue WHERE path='" . $file . "'", $db); if (!$result) die("Error while deleting file!"); // Delete all the history associated with this file. $result = mysql_query("DELETE FROM History WHERE path='" . $file . "'", $db); if (!$result) die("Error while deleting file!"); // Delete the file from the filesystem. exec($RM_PATH . " -rf " . $CMS_CONTENT_FS_PATH . $file); // Regenerate the index so that the now-broken link is gone. ContentHandler::regenerateIndex($lid, $langName); } // Submit new content. Anonymous users, unprivileged registered users, and // low-level administrators' content will be queued. High-level // administrators will have their content posted instantly. public static function submitNew($loggedInUser, $ip, $title, $lid, $content){ global $ACL_ACCESS_NOBODY; global $ACL_ACCESS_NOBODY_VERIFIED; global $ACL_ACCESS_ADMIN_LOW; global $ACL_ACCESS_ADMIN_HIGH; global $CMS_CONTENT_PATH; $retVal = -1; $accessLevel = $ACL_ACCESS_NOBODY; if (isset($loggedInUser) && ($loggedInUser != null)) { $accessLevel = $loggedInUser->getAccessLevel($lid); } if ($accessLevel == $ACL_ACCESS_ADMIN_HIGH) { $file = Language::lookupName($lid) . '/' . $title . '.html'; if (ContentHandler::writeNewFile($loggedInUser,$lid,$file,stripslashes($content)) && ContentHandler::addHistory($file, $loggedInUser->getUID(), $lid, $content,ContentHandler::$QUEUE_ENTRY_NEW, $loggedInUser)) { $retVal = ContentHandler::$CONTENT_POSTED; } else $retVal = ContentHandler::$CONTENT_FAILURE; } else { $file = Language::lookupName($lid) . '/' . $title . '.html'; if (ContentHandler::addQueue($lid, $file, $content, ContentHandler::$QUEUE_ENTRY_NEW, $loggedInUser, $ip)) $retVal = ContentHandler::$CONTENT_QUEUED; else $retVal = ContentHandler::$CONTENT_FAILURE; } return $retVal; } // Submit a change to a content file. Anonymous and unprivileged registered // users will have their changes queued. Low- and high-level admins will // have their content posted instantly. public static function submitEdit($loggedInUser, $ip, $file, $content) { require_once("languages.php"); global $ACL_ACCESS_NOBODY; global $ACL_ACCESS_NOBODY_VERIFIED; global $ACL_ACCESS_ADMIN_LOW; global $ACL_ACCESS_ADMIN_HIGH; $retVal = -1; // if (substr($content, strlen($content) - 1, 1) != "\n") // $content .= "\n"; $diff = ContentHandler::makeDiff($file, stripslashes($content)); if ($diff == null) return ContentHandler::$CONTENT_NO_CHANGE; $langName = substr($file, 0, strpos($file, "/")); $lid = Language::lookupLID($langName); $accessLevel = $ACL_ACCESS_NOBODY; if (isset($loggedInUser) && ($loggedInUser != null)) { $accessLevel = $loggedInUser->getAccessLevel($lid); } // echo "Access level: $accessLevel
langid: $lid
"; if (($accessLevel == $ACL_ACCESS_ADMIN_LOW) || ($accessLevel == $ACL_ACCESS_ADMIN_HIGH)) { if (ContentHandler::applyDiff($file, $diff)) { if (ContentHandler::addHistory($file, $loggedInUser->getUID(), $lid, $diff, ContentHandler::$QUEUE_ENTRY_EDIT, $loggedInUser)) { $retVal = ContentHandler::$CONTENT_POSTED; } else { die("error adding history"); $retVal = ContentHandler::$CONTENT_FAILURE; } } else { die("error while applying diff."); $retVal = ContentHandler::$CONTENT_FAILURE; } } else { if (ContentHandler::addQueue($lid, $file, $diff, ContentHandler::$QUEUE_ENTRY_EDIT, $loggedInUser, $ip)) $retVal = ContentHandler::$CONTENT_QUEUED; else $retVal = ContentHandler::$CONTENT_FAILURE; } return $retVal; } // Approves a specific entry in the PendingQueue. public static function approveQueueEntry($pid, $loggedInUser){ global $CMS_CONTENT_PATH; settype($pid, "integer"); $retVal = ContentHandler::$CONTENT_FAILURE; $db = getDBConnection(); $result = mysql_query("SELECT UID,LID,path,type,data FROM PendingQueue WHERE PID=" . $pid, $db); if (!$result) die("Error while approving queue entry!"); $row = mysql_fetch_row($result); $uid = $row[ 0 ]; $lid = $row[ 1 ]; $path = $row[ 2 ]; $type = $row[ 3 ]; $data = addslashes($row[ 4 ]); settype($uid, "integer"); settype($lid, "integer"); settype($type, "integer"); $accessLevel = $loggedInUser->getAccessLevel($lid); if ($type == 0) { if ($accessLevel != ACLs::$ACL_ACCESS_ADMIN_HIGH) die("Error: You have insufficient privileges to approve new submissions!"); if (ContentHandler::writeNewFile($loggedInUser, $lid, $path, stripslashes($data)) && ContentHandler::addHistory($path,$uid,$lid,$data,$type,$loggedInUser) && ContentHandler::delQueue($pid)) { $retVal = ContentHandler::$CONTENT_POSTED; } else $retVal = ContentHandler::$CONTENT_FAILURE; } else if ($type == 1) { if ($accessLevel < ACLs::$ACL_ACCESS_ADMIN_LOW) die("Error: You have insufficient privileges to approve new submissions!"); $db = getDBConnection(); $result = mysql_query("SELECT UID,path,data FROM PendingQueue WHERE PID=" . $pid, $db); if (!$result) die("Error while retrieving from pending queue!: " . $pid); $row = mysql_fetch_row($result); $uid = $row[ 0 ]; $path = $row[ 1 ]; $diff = $row[ 2 ]; mysql_close($db); if (ContentHandler::applyDiff($path, $diff) && ContentHandler::addHistory($path, $uid, $lid, $diff, ContentHandler::$QUEUE_ENTRY_EDIT, $loggedInUser) && ContentHandler::delQueue($pid)) { $retVal = ContentHandler::$CONTENT_POSTED; } else $retVal = ContentHandler::$CONTENT_FAILURE; } else die("Internal error!"); return $retVal; } // Rejects a queue entry. // TODO: check the access level of the user!!! public static function rejectQueueEntry($pid) { if (ContentHandler::delQueue($pid)) return ContentHandler::$CONTENT_SUCCESS; else return ContentHandler::$CONTENT_FAILURE; } // Gets an unused PID for the PendingQueue. Current implementation can // be greatly improved. private static function getUnusedPID($db) { $needToClose = false; if ($db == null) { $db = getDBConnection(); $needToClose = true; } $result = mysql_query("SELECT MAX(PID) FROM PendingQueue", $db); if (!$result) { die('Error getting unused PID: ' . mysql_error()); } $retVal = mysql_result($result, 0); settype($retVal, "integer"); $retVal++; mysql_free_result($result); if ($needToClose) mysql_close($db); return $retVal; } // Obtains an unused HID for the History table. Current implementation can // be greatly improved. private static function getUnusedHID($db) { $needToClose = false; if ($db == null) { $db = getDBConnection(); $needToClose = true; } $result = mysql_query("SELECT MAX(HID) FROM History", $db); if (!$result) { die('Error getting unused HID: ' . mysql_error()); } $retVal = mysql_result($result, 0); settype($retVal, "integer"); $retVal++; mysql_free_result($result); if ($needToClose) mysql_close($db); return $retVal; } // Adds a content file to an index. Index will be updated to reflect the // new file. private static function addFileToIndex($loggedInUser, $langName, $relativePath, $title) { global $CMS_CONTENT_FS_PATH; global $CMS_CONTENT_URL_PATH; global $INDEX_FILE_HEADER; global $INDEX_FILE_FOOTER; $indexFilename = $CMS_CONTENT_FS_PATH . $langName . "/index.html"; $contents = file_get_contents($indexFilename); $contents = extractBetweenTags($contents, "", ""); $contents = $contents . "$title
"; $hIndexFile = fopen($indexFilename, "w"); if ($hIndexFile == FALSE) { die("Error while updating index!: " . $hIndexFile); } fwrite($hIndexFile, $INDEX_FILE_HEADER); fwrite($hIndexFile, $contents); fwrite($hIndexFile, $INDEX_FILE_FOOTER); fclose($hIndexFile); } // Creates a new content file on the filesystem. Caller must ensure that // the user has proper access to do this. private static function writeNewFile($loggedInUser, $lid, $file, $data) { global $CMS_CONTENT_FS_PATH; global $FILE_MODE; global $CONTENT_HEADER; global $CONTENT_FOOTER; $title = getTitleFromFile($file); #$title = normalizeString($title); $langName = normalizeString(Language::lookupName($lid)); #$relativePath = $langName . '/' . $title . '.html'; $filename = $CMS_CONTENT_FS_PATH . $file; if (!($hFile = fopen($filename, "w+"))) die("Error while creating new file " . $filename); $footer = str_replace("%EDIT_URL%", $file, $CONTENT_FOOTER); if (fwrite($hFile, $CONTENT_HEADER . htmlize($data) . $footer) === FALSE) die("Error while writing to file " . $filename); fclose($hFile); $oldumask = umask(0000); if (!chmod($filename, $FILE_MODE)) die("Error while chmod'ing file " . $filename); umask($oldumask); ContentHandler::addFileToIndex($loggedInUser, $langName, $file, $title); return true; } // Creates a diff from a filename of original content and the text of // something new. The text of that diff is returned. private static function makeDiff($originalFileName, $newContent) { global $CMS_CONTENT_FS_PATH; global $DIFF_PATH; $retVal = null; $tempFileOld = ContentHandler::extractContent($originalFileName); $tempFileNew = ContentHandler::tempFile($newContent); $tempFileDiff = ContentHandler::tempFile(""); $command = $DIFF_PATH . ' -u ' . $tempFileOld . ' ' . $tempFileNew . " > " . $tempFileDiff; $diff = array(); $diffRetVal = null; // echo "Executing: " . $command . "
"; exec($command, $diff, $diffRetVal); // if ($diffRetVal == 1) { // $retVal = ''; // for($i = 0; $i < count($diff); $i++) { // $retVal .= ($diff[ $i ] . "\n"); // } // } $hDiffFile = fopen($tempFileDiff, "r"); $retVal = fread($hDiffFile, filesize($tempFileDiff)); fclose($hDiffFile); if (unlink($tempFileOld) === false) die("Error while deleting temporary file: " . $tempFileOld); if (unlink($tempFileNew) === false) die("Error while deleting temporary file: " . $tempFileNew); if (unlink($tempFileDiff) === false) die("Error while deleting temporary file: " . $tempFileDiff); // echo 'Diff:
' . $retVal . '
'; return $retVal; } // Creates a temporary file to hold the specified data. That temporary // file's name is returned. private static function tempFile($data) { global $TEMP_DIR; $oldumask = umask(0077); $randString = mt_rand(); settype($randString, "string"); $filename = $TEMP_DIR . "litcms" . $randString; if (!($hFile = fopen($filename, "w+"))) die("Error while creating temporary file!"); fwrite($hFile, $data); fclose($hFile); umask($oldumask); return $filename; } // Applies a diff to a specified filename. Returns true if successful, // or false if otherwise (Uh-oh!! This probably means a collision occurred // which definitely shouldn't be happening!!!) private static function applyDiff($originalFileName, $diff) { global $PATCH_PATH; global $CMS_CONTENT_FS_PATH; global $CONTENT_HEADER; global $CONTENT_FOOTER; // TODO: make backup of file before overwriting! $contentTempFile = ContentHandler::extractContent($originalFileName); //ContentHandler::dumpFile($contentTempFile); $diffTempFile = ContentHandler::tempFile($diff); //ContentHandler::dumpFile($diffTempFile); //die("X"); $command = $PATCH_PATH . ' -f -l ' . $contentTempFile . ' < ' . $diffTempFile; $patchResult = array(); $patchRetVal = -1; exec($command, $patchResult, $patchRetVal); // if (unlink($diffTempFile) === false) // my_log("Error while deleting temporary file: " . $diffTempFile); if ($patchRetVal == 0) { $hFile = null; if (!($hFile = fopen($contentTempFile, "r"))) die("Error while reading temporary content file: " . $contentTempFile); $newContent = fread($hFile, filesize($contentTempFile)); fclose($hFile); // if (unlink($contentTempFile) === false) // my_log("Error while deleting temporary file: " . $contentTempFile); if (!($hFile = fopen($CMS_CONTENT_FS_PATH . $originalFileName, "w+"))) { my_log("Error while applying the following diff file to $originalFileName:\n\n$diff\n\n"); } else { $newContent = $CONTENT_HEADER . htmlize($newContent) . str_replace("%EDIT_URL%", $originalFileName, $CONTENT_FOOTER); fwrite($hFile, $newContent); fclose($hFile); $retVal = true; } } else { my_log("Error while applying the following diff file to $originalFileName:\n\n$diff\n\nPatch result: " . $patchResult); if (unlink($contentTempFile) === false) my_log("Error while deleting temporary file: " . $contentTempFile); $retVal = false; } return $retVal; } // Extracts the content from a content file (the header and footer data // needs to be stripped--that's what this does). The text of the content // is returned. public static function extractContent($file) { global $CMS_CONTENT_FS_PATH; $hFile = fopen($CMS_CONTENT_FS_PATH . $file, "r"); $content = fread($hFile, filesize($CMS_CONTENT_FS_PATH . $file)); fclose($hFile); $beginPos = strpos($content, ""); $endPos = strpos($content, ""); if (($beginPos === false) || ($endPos === false)) { die("Cannot read file " . $file); } else $beginPos += 19; $contentLen = $endPos - $beginPos; $content = dehtmlize(substr($content, $beginPos, $contentLen)); $tempFileName = ContentHandler::tempFile($content); return $tempFileName; } // Adds a row to the History table. private static function addHistory($path, $uid, $lid, $data, $type, $loggedInUser) { global $TEMP_DIR; if ($type == 1) { $data = str_replace($TEMP_DIR, "/", $data); } $retVal = false; $db = getDBConnection(); $unusedHID = ContentHandler::getUnusedHID($db); $approvedbyUID = $loggedInUser->getUID(); $query = "INSERT INTO History VALUES($unusedHID,'$path',NOW(),$uid,$lid,'$data',$type,$approvedbyUID)"; $result = mysql_query($query, $db); if ($result) $retVal = true; else my_log("Error while executing: " . $query); mysql_close($db); return $retVal; } // Adds an entry to the PendingQueue table. private static function addQueue($lid, $file, $content, $type, $loggedInUser, $ip) { $retVal = false; $db = getDBConnection(); $uid = -1; if ($loggedInUser != null) $uid = $loggedInUser->getUID(); $query = "INSERT INTO PendingQueue VALUES(" . ContentHandler::getUnusedPID($db) . "," . $uid . "," . $lid . ",NOW(),'" . $ip . "','" . $file . "',$type,'" . $content . "')"; $result = mysql_query($query, $db); if ($result) $retVal = true; else my_log("Error while executing: " . $query); mysql_close($db); return $retVal; } // Deletes an entry from the PendingQueue. Caller is responsible for // ensuring user has access to do this. private static function delQueue($pid) { settype($pid, "integer"); $retVal = false; $db = getDBConnection(); $result = mysql_query("DELETE FROM PendingQueue WHERE PID=" . $pid, $db); if (!$result || (mysql_affected_rows($db) != 1)) { $retVal = false; my_log("Error while deleting from pending queue!: " . $pid); } else $retVal = true; mysql_close($db); return $retVal; } // Creates a temporary file, modifies the argument variable to hold its // filename, and returns an open handle to that file. private static function makeTempFile(&$filename) { global $TEMP_DIR; $randString = mt_rand(); settype($randString, "string"); $filename = $TEMP_DIR . "litcms" . $randString; if (!($hFile = fopen($filename, "w+"))) die("Error while creating temporary file!"); return $hFile; } // Applies a patch to a content file without permanently modifying it; the // new content is returned as a string. public static function applyPatchTemporary($originalTempFile, $diff) { global $PATCH_PATH; $diff_filename = ""; $hDiffFile = ContentHandler::makeTempFile($diff_filename); fwrite($hDiffFile, $diff); fclose($hDiffFile); exec($PATCH_PATH . " " . $originalTempFile . " < " . $diff_filename); clearstatcache(); $hFile = fopen($originalTempFile, "r"); $retval = fread($hFile, filesize($originalTempFile)); fclose($hFile); unlink($diff_filename); return $retval; } // For debugging only. public static function dumpFile($filename) { $hFile = fopen($filename, "r"); if ($hFile === FALSE) { die("dumpFile: can't open $filename."); } $stuff = fread($hFile, filesize($filename)); fclose($hFile); print "

DUMPING [$filename]: [$stuff]

"; } } ?>