diff options
Diffstat (limited to 'src')
45 files changed, 10583 insertions, 0 deletions
diff --git a/src/SemanticScuttle/Service/bookmark2tagservice.php b/src/SemanticScuttle/Service/bookmark2tagservice.php new file mode 100644 index 0000000..918fb5b --- /dev/null +++ b/src/SemanticScuttle/Service/bookmark2tagservice.php @@ -0,0 +1,478 @@ +<?php +class Bookmark2TagService { + var $db; + var $tablename; + + function &getInstance(&$db) { + static $instance; + if (!isset($instance)) + $instance =& new Bookmark2TagService($db); + return $instance; + } + + function Bookmark2TagService(&$db) { + $this->db =& $db; + $this->tablename = $GLOBALS['tableprefix'] .'bookmarks2tags'; + } + + function isNotSystemTag($var) { + if (utf8_substr($var, 0, 7) == 'system:') + return false; + else + return true; + } + + function attachTags($bookmarkid, $tags, $fromApi = false, $extension = NULL, $replace = true, $fromImport = false) { + // Make sure that categories is an array of trimmed strings, and that if the categories are + // coming in from an API call to add a bookmark, that underscores are converted into strings. + + if (!is_array($tags)) { + $tags = trim($tags); + if ($tags != '') { + if (substr($tags, -1) == ',') { + $tags = substr($tags, 0, -1); + } + if ($fromApi) { + $tags = explode(' ', $tags); + } else { + $tags = explode(',', $tags); + } + } else { + $tags = null; + } + } + + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tags = $tagservice->normalize($tags); + + + $tags_count = is_array($tags)?count($tags):0; + + for ($i = 0; $i < $tags_count; $i++) { + $tags[$i] = trim(strtolower($tags[$i])); + if ($fromApi) { + include_once(dirname(__FILE__) .'/../functions.inc.php'); + $tags[$i] = convertTag($tags[$i], 'in'); + } + } + + if ($tags_count > 0) { + // Remove system tags + $tags = array_filter($tags, array($this, "isNotSystemTag")); + + // Eliminate any duplicate categories + $temp = array_unique($tags); + $tags = array_values($temp); + } else { + // Unfiled + $tags[] = 'system:unfiled'; + } + + // Media and file types + if (!is_null($extension)) { + include_once(dirname(__FILE__) .'/../functions.inc.php'); + + if ($keys = multi_array_search($extension, $GLOBALS['filetypes'])) { + $tags[] = 'system:filetype:'. $extension; + $tags[] = 'system:media:'. array_shift($keys); + } + } + + // Imported + if ($fromImport) { + $tags[] = 'system:imported'; + } + + $this->db->sql_transaction('begin'); + + if ($replace) { + if (!$this->deleteTagsForBookmark($bookmarkid)){ + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not attach tags (deleting old ones failed)', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + } + + $bs =& ServiceFactory::getServiceInstance('BookmarkService'); + $tts =& ServiceFactory::getServiceInstance('Tag2TagService'); + + // Create links between tags + foreach($tags as $key => $tag) { + if(strpos($tag, '=')) { + // case "=" + $pieces = explode('=', $tag); + $nbPieces = count($pieces); + if($nbPieces > 1) { + for($i = 0; $i < $nbPieces-1; $i++) { + $bookmark = $bs->getBookmark($bookmarkid); + $uId = $bookmark['uId']; + $tts->addLinkedTags($pieces[$i], $pieces[$i+1], '=', $uId); + } + $tags[$key] = $pieces[0]; // Attach just the last tag to the bookmark + } + } else { + // case ">" + $pieces = explode('>', $tag); + $nbPieces = count($pieces); + if($nbPieces > 1) { + for($i = 0; $i < $nbPieces-1; $i++) { + $bookmark = $bs->getBookmark($bookmarkid); + $uId = $bookmark['uId']; + $tts->addLinkedTags($pieces[$i], $pieces[$i+1], '>', $uId); + } + $tags[$key] = $pieces[$nbPieces-1]; // Attach just the last tag to the bookmark + } + } + + + } + + // Add the categories to the DB. + for ($i = 0; $i < count($tags); $i++) { + if ($tags[$i] != '') { + $values = array( + 'bId' => intval($bookmarkid), + 'tag' => $tags[$i] + ); + + if (!$this->hasTag($bookmarkid, $tags[$i])) { + $sql = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + if (!($dbresult =& $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not attach tags', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + } + } + } + $this->db->sql_transaction('commit'); + return true; + } + + function deleteTag($uId, $tag) { + $bs =& ServiceFactory::getServiceInstance('BookmarkService'); + + $query = 'DELETE FROM '. $this->getTableName(); + $query.= ' USING '. $this->getTableName() .', '. $bs->getTableName(); + $query.= ' WHERE '. $this->getTableName() .'.bId = '. $bs->getTableName() .'.bId'; + $query.= ' AND '. $bs->getTableName() .'.uId = '. $uId; + $query.= ' AND '. $this->getTableName() .'.tag = "'. $this->db->sql_escape($tag) .'"'; + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not delete tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + return true; + } + + function deleteTagsForBookmark($bookmarkid) { + if (!is_int($bookmarkid)) { + message_die(GENERAL_ERROR, 'Could not delete tags (invalid bookmarkid)', '', __LINE__, __FILE__, $query); + return false; + } + + $query = 'DELETE FROM '. $this->getTableName() .' WHERE bId = '. intval($bookmarkid); + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not delete tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + return true; + } + + /* Allow deletion in admin page */ + function deleteTagsForUser($uId) {
+ $qmask = 'DELETE FROM %s USING %s, %s WHERE %s.bId = %s.bId AND %s.uId = %d';
+ $query = sprintf($qmask,
+ $this->getTableName(),
+ $this->getTableName(),
+ $GLOBALS['tableprefix'].'bookmarks',
+ $this->getTableName(),
+ $GLOBALS['tableprefix'].'bookmarks',
+ $GLOBALS['tableprefix'].'bookmarks',
+ $uId);
+
+ if (!($dbresult =& $this->db->sql_query($query))) {
+ message_die(GENERAL_ERROR, 'Could not delete tags', '', __LINE__, __FILE__, $query, $this->db);
+ return false;
+ }
+
+ return true;
+ } + + function &getTagsForBookmark($bookmarkid) { + if (!is_numeric($bookmarkid)) { + message_die(GENERAL_ERROR, 'Could not get tags (invalid bookmarkid)', '', __LINE__, __FILE__, $query); + return false; + } + + $query = 'SELECT tag FROM '. $this->getTableName() .' WHERE bId = '. intval($bookmarkid) .' AND LEFT(tag, 7) <> "system:" ORDER BY id ASC'; + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $tags = array(); + while ($row =& $this->db->sql_fetchrow($dbresult)) { + $tags[] = $row['tag']; + } + $this->db->sql_freeresult($dbresult); + return $tags; + } + + function &getTags($userid = NULL) { + $userservice =& ServiceFactory::getServiceInstance('UserService'); + $logged_on_user = $userservice->getCurrentUserId(); + + $query = 'SELECT T.tag, COUNT(B.bId) AS bCount FROM '. $GLOBALS['tableprefix'] .'bookmarks AS B INNER JOIN '. $userservice->getTableName() .' AS U ON B.uId = U.'. $userservice->getFieldName('primary') .' INNER JOIN '. $GLOBALS['tableprefix'] .'bookmarks2tags AS T ON B.bId = T.bId'; + + $conditions = array(); + if (!is_null($userid)) { + $conditions['U.'. $userservice->getFieldName('primary')] = intval($userid); + if ($logged_on_user != $userid) + $conditions['B.bStatus'] = 0; + } else { + $conditions['B.bStatus'] = 0; + } + + $query .= ' WHERE '. $this->db->sql_build_array('SELECT', $conditions) .' AND LEFT(T.tag, 7) <> "system:" GROUP BY T.tag ORDER BY bCount DESC, tag'; + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $output = $this->db->sql_fetchrowset($dbresult); + $this->db->sql_freeresult($dbresult); + return $output; + } + + + // Returns the tags related to the specified tags; i.e. attached to the same bookmarks + function &getRelatedTags($tags, $for_user = NULL, $logged_on_user = NULL, $limit = 10) { + $conditions = array(); + // Only count the tags that are visible to the current user. + if ($for_user != $logged_on_user || is_null($for_user)) + $conditions['B.bStatus'] = 0; + + if (!is_null($for_user)) + $conditions['B.uId'] = $for_user; + + // Set up the tags, if need be. + if (is_numeric($tags)) + $tags = NULL; + if (!is_array($tags) and !is_null($tags)) + $tags = explode('+', trim($tags)); + + $tagcount = count($tags); + for ($i = 0; $i < $tagcount; $i++) { + $tags[$i] = trim($tags[$i]); + } + + // Set up the SQL query. + $query_1 = 'SELECT DISTINCTROW T0.tag, COUNT(B.bId) AS bCount FROM '. $GLOBALS['tableprefix'] .'bookmarks AS B, '. $this->getTableName() .' AS T0'; + $query_2 = ''; + $query_3 = ' WHERE B.bId = T0.bId '; + if (count($conditions) > 0) + $query_4 = ' AND '. $this->db->sql_build_array('SELECT', $conditions); + else + $query_4 = ''; + // Handle the parts of the query that depend on any tags that are present. + for ($i = 1; $i <= $tagcount; $i++) { + $query_2 .= ', '. $this->getTableName() .' AS T'. $i; + $query_4 .= ' AND T'. $i .'.bId = B.bId AND T'. $i .'.tag = "'. $this->db->sql_escape($tags[$i - 1]) .'" AND T0.tag <> "'. $this->db->sql_escape($tags[$i - 1]) .'"'; + } + $query_5 = ' AND LEFT(T0.tag, 7) <> "system:" GROUP BY T0.tag ORDER BY bCount DESC, T0.tag'; + $query = $query_1 . $query_2 . $query_3 . $query_4 . $query_5; + + if (! ($dbresult =& $this->db->sql_query_limit($query, $limit)) ){ + message_die(GENERAL_ERROR, 'Could not get related tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + $output = $this->db->sql_fetchrowset($dbresult); + $this->db->sql_freeresult($dbresult); + return $output; + } + + // Returns the most popular tags used for a particular bookmark hash + function &getRelatedTagsByHash($hash, $limit = 20) { + $userservice = & ServiceFactory :: getServiceInstance('UserService'); + $sId = $userservice->getCurrentUserId(); + // Logged in + if ($userservice->isLoggedOn()) { + $arrWatch = $userservice->getWatchList($sId); + // From public bookmarks or user's own + $privacy = ' AND ((B.bStatus = 0) OR (B.uId = '. $sId .')'; + // From shared bookmarks in watchlist + foreach ($arrWatch as $w) { + $privacy .= ' OR (B.uId = '. $w .' AND B.bStatus = 1)'; + } + $privacy .= ') '; + // Not logged in + } else { + $privacy = ' AND B.bStatus = 0 '; + } + + $query = 'SELECT T.tag, COUNT(T.tag) AS bCount FROM '.$GLOBALS['tableprefix'].'bookmarks AS B LEFT JOIN '.$GLOBALS['tableprefix'].'bookmarks2tags AS T ON B.bId = T.bId WHERE B.bHash = "'. $hash .'" '. $privacy .'AND LEFT(T.tag, 7) <> "system:" GROUP BY T.tag ORDER BY bCount DESC'; + + if (!($dbresult =& $this->db->sql_query_limit($query, $limit))) { + message_die(GENERAL_ERROR, 'Could not get related tags for this hash', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + $output = $this->db->sql_fetchrowset($dbresult); + $this->db->sql_freeresult($dbresult); + return $output; + } + + function &getAdminTags($limit = 30, $logged_on_user = NULL, $days = NULL) { + // look for admin ids + $userservice = & ServiceFactory :: getServiceInstance('UserService'); + $adminIds = $userservice->getAdminIds(); + + // ask for their tags + return $this->getPopularTags($adminIds, $limit, $logged_on_user, $days); + } + + function &getContactTags($user, $limit = 30, $logged_on_user = NULL, $days = NULL) { + // look for contact ids + $userservice = & ServiceFactory :: getServiceInstance('UserService'); + $contacts = $userservice->getWatchlist($user); + + // add the user (to show him/her also his/her tags) + if(!is_null($logged_on_user)) { + $contacts[] = $logged_on_user; + } + + // ask for their tags + return $this->getPopularTags($contacts, $limit, $logged_on_user, $days); + } + + // $users can be {NULL, an id, an array of id} + function &getPopularTags($user = NULL, $limit = 30, $logged_on_user = NULL, $days = NULL) { + // Only count the tags that are visible to the current user. + if (($user != $logged_on_user) || is_null($user) || ($user === false)) + $privacy = ' AND B.bStatus = 0'; + else + $privacy = ''; + + if (is_null($days) || !is_int($days)) + $span = ''; + else + $span = ' AND B.bDatetime > "'. date('Y-m-d H:i:s', time() - (86400 * $days)) .'"'; + + $query = 'SELECT T.tag, COUNT(T.bId) AS bCount FROM '. $this->getTableName() .' AS T, '. $GLOBALS['tableprefix'] .'bookmarks AS B WHERE '; + if (is_null($user) || ($user === false)) { + $query .= 'B.bId = T.bId AND B.bStatus = 0'; + } elseif(is_array($user)) { + $query .= ' (1 = 0'; //tricks + foreach($user as $u) { + $query .= ' OR B.uId = '. $this->db->sql_escape($u) .' AND B.bId = T.bId'; + } + $query .= ' )'; + } else { + $query .= 'B.uId = '. $this->db->sql_escape($user) .' AND B.bId = T.bId'. $privacy; + } + $query .= $span .' AND LEFT(T.tag, 7) <> "system:" GROUP BY T.tag ORDER BY bCount DESC, tag'; + + if (!($dbresult =& $this->db->sql_query_limit($query, $limit))) { + message_die(GENERAL_ERROR, 'Could not get popular tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $output = $this->db->sql_fetchrowset($dbresult); + $this->db->sql_freeresult($dbresult); + return $output; + } + + function hasTag($bookmarkid, $tag) { + $query = 'SELECT COUNT(*) AS tCount FROM '. $this->getTableName() .' WHERE bId = '. intval($bookmarkid) .' AND tag ="'. $this->db->sql_escape($tag) .'"'; + + if (! ($dbresult =& $this->db->sql_query($query)) ) { + message_die(GENERAL_ERROR, 'Could not find tag', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row =& $this->db->sql_fetchrow($dbresult)) { + if ($row['tCount'] > 0) { + $output = true; + } + } + $output = false; + $this->db->sql_freeresult($dbresult); + return $output; + } + + function renameTag($userid, $old, $new, $fromApi = false) { + $bookmarkservice =& ServiceFactory::getServiceInstance('BookmarkService'); + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + + if (is_null($userid) || is_null($old) || is_null($new)) + return false; + + // Find bookmarks with old tag + $bookmarksInfo =& $bookmarkservice->getBookmarks(0, NULL, $userid, $old); + $bookmarks =& $bookmarksInfo['bookmarks']; + + // Delete old tag + $this->deleteTag($userid, $old); + + // Attach new tags + $new = $tagservice->normalize($new); + + foreach(array_keys($bookmarks) as $key) { + $row =& $bookmarks[$key]; + $this->attachTags($row['bId'], $new, $fromApi, NULL, false); + } + + return true; + } + + function &tagCloud($tags = NULL, $steps = 5, $sizemin = 90, $sizemax = 225, $sortOrder = NULL) { + + if (is_null($tags) || count($tags) < 1) { + $output = false; + return $output; + } + + $min = $tags[count($tags) - 1]['bCount']; + $max = $tags[0]['bCount']; + + for ($i = 1; $i <= $steps; $i++) { + $delta = ($max - $min) / (2 * $steps - $i); + $limit[$i] = $i * $delta + $min; + } + $sizestep = ($sizemax - $sizemin) / $steps; + foreach ($tags as $row) { + $next = false; + for ($i = 1; $i <= $steps; $i++) { + if (!$next && $row['bCount'] <= $limit[$i]) { + $size = $sizestep * ($i - 1) + $sizemin; + $next = true; + } + } + $tempArray = array('size' => $size .'%'); + $row = array_merge($row, $tempArray); + $output[] = $row; + } + + if ($sortOrder == 'alphabet_asc') { + usort($output, create_function('$a,$b','return strcasecmp(utf8_deaccent($a["tag"]), utf8_deaccent($b["tag"]));')); + } + + return $output; + } + + function deleteAll() { + $query = 'TRUNCATE TABLE `'. $this->getTableName() .'`'; + $this->db->sql_query($query); + } + + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } +} +?> diff --git a/src/SemanticScuttle/Service/bookmarkservice.php b/src/SemanticScuttle/Service/bookmarkservice.php new file mode 100644 index 0000000..f119593 --- /dev/null +++ b/src/SemanticScuttle/Service/bookmarkservice.php @@ -0,0 +1,552 @@ +<?php +class BookmarkService { + var $db; + var $tablename; + + function & getInstance(& $db) { + static $instance; + if (!isset ($instance)) + $instance = & new BookmarkService($db); + return $instance; + } + + function BookmarkService(& $db) { + $this->db = & $db; + $this->tablename = $GLOBALS['tableprefix'] .'bookmarks'; + } + + function _getbookmark($fieldname, $value, $all = false) { + if (!$all) { + $userservice = & ServiceFactory :: getServiceInstance('UserService'); + $sId = $userservice->getCurrentUserId(); + $range = ' AND uId = '. $sId; + } else { + $range = ''; + } + + $query = 'SELECT * FROM '. $this->getTableName() .' WHERE '. $fieldname .' = "'. $this->db->sql_escape($value) .'"'. $range; + + if (!($dbresult = & $this->db->sql_query_limit($query, 1, 0))) { + message_die(GENERAL_ERROR, 'Could not get bookmark', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row =& $this->db->sql_fetchrow($dbresult)) { + $output = $row; + } else { + $output = false; + } + $this->db->sql_freeresult($dbresult); + return $output; + } + + function & getBookmark($bid, $include_tags = false) { + if (!is_numeric($bid)) + return; + + $sql = 'SELECT * FROM '. $this->getTableName() .' WHERE bId = '. $this->db->sql_escape($bid); + + if (!($dbresult = & $this->db->sql_query($sql))) + message_die(GENERAL_ERROR, 'Could not get vars', '', __LINE__, __FILE__, $sql, $this->db); + + if ($row = & $this->db->sql_fetchrow($dbresult)) { + if ($include_tags) { + $b2tservice = & ServiceFactory :: getServiceInstance('Bookmark2TagService'); + $row['tags'] = $b2tservice->getTagsForBookmark($bid); + } + $output = $row; + } else { + $output = false; + } + $this->db->sql_freeresult($dbresult); + return $output; + } + + function getBookmarkByAddress($address) { + $hash = md5($address); + return $this->getBookmarkByHash($hash); + } + + function getBookmarkByHash($hash) { + return $this->_getbookmark('bHash', $hash, true); + } + + /* Counts bookmarks for a user. $range = {'public', 'shared', 'private', 'all'}*/ + function countBookmarks($uId, $range = 'public') { + $sql = 'SELECT COUNT(*) FROM '. $GLOBALS['tableprefix'] .'bookmarks'; + $sql.= ' WHERE uId = '.$uId; + switch ($range) { + case 'all': + //no constraints + break; + case 'private': + $sql.= ' AND bStatus = 2'; + break; + case 'shared': + $sql.= ' AND bStatus = 1'; + break; + case 'public': + default: + $sql.= ' AND bStatus = 0'; + break; + } + + if (!($dbresult = & $this->db->sql_query($sql))) { + message_die(GENERAL_ERROR, 'Could not get vars', '', __LINE__, __FILE__, $sql, $this->db); + } + return $this->db->sql_fetchfield(0, 0); + } + + /** + * Check if a bookmark may be edited by the current user + * + * @param integer|array $bookmark Bookmark uId or bookmark array + * + * @return boolean True if allowed + */ + function editAllowed($bookmark) + { + if (!is_numeric($bookmark) && (!is_array($bookmark) + || !is_numeric($bookmark['bId'])) + ) { + return false; + } + + if (!is_array($bookmark) + && !($bookmark = $this->getBookmark($bookmark)) + ) { + return false; + } + + $userservice = & ServiceFactory::getServiceInstance('UserService'); + $user = $userservice->getCurrentUser(); + + //user has to be either admin, or owner + if ($GLOBALS['adminsCanModifyBookmarksFromOtherUsers'] + && $userservice->isAdmin($user) + ) { + return true; + } else { + return ($bookmark['uId'] == $user['uId']); + } + } + + function bookmarkExists($address = false, $uid = NULL) { + if (!$address) { + return; + } + + $address = $this->normalize($address); + + $crit = array ('bHash' => md5($address)); + if (isset ($uid)) { + $crit['uId'] = $uid; + } + + $sql = 'SELECT COUNT(*) FROM '. $GLOBALS['tableprefix'] .'bookmarks WHERE '. $this->db->sql_build_array('SELECT', $crit); + if (!($dbresult = & $this->db->sql_query($sql))) { + message_die(GENERAL_ERROR, 'Could not get vars', '', __LINE__, __FILE__, $sql, $this->db); + } + if($this->db->sql_fetchfield(0, 0) > 0) { + $output = true; + } else { + $output = false; + } + $this->db->sql_freeresult($dbresult); + return $output; + } + + // Adds a bookmark to the database. + // Note that date is expected to be a string that's interpretable by strtotime(). + function addBookmark($address, $title, $description, $privateNote, $status, $categories, $date = NULL, $fromApi = false, $fromImport = false, $sId = -1) { + if($sId == -1) { + $userservice = & ServiceFactory :: getServiceInstance('UserService'); + $sId = $userservice->getCurrentUserId(); + } + + $address = $this->normalize($address); + + // Get the client's IP address and the date; note that the date is in GMT. + if (getenv('HTTP_CLIENT_IP')) + $ip = getenv('HTTP_CLIENT_IP'); + else + if (getenv('REMOTE_ADDR')) + $ip = getenv('REMOTE_ADDR'); + else + $ip = getenv('HTTP_X_FORWARDED_FOR'); + + // Note that if date is NULL, then it's added with a date and time of now, and if it's present, + // it's expected to be a string that's interpretable by strtotime(). + if (is_null($date) || $date == '') + $time = time(); + else + $time = strtotime($date); + $datetime = gmdate('Y-m-d H:i:s', $time); + + // Set up the SQL insert statement and execute it. + $values = array('uId' => intval($sId), 'bIp' => $ip, 'bDatetime' => $datetime, 'bModified' => $datetime, 'bTitle' => $title, 'bAddress' => $address, 'bDescription' => $description, 'bPrivateNote' => $privateNote, 'bStatus' => intval($status), 'bHash' => md5($address)); + $sql = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not insert bookmark', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + // Get the resultant row ID for the bookmark. + $bId = $this->db->sql_nextid($dbresult); + if (!isset($bId) || !is_int($bId)) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not insert bookmark', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + + $uriparts = explode('.', $address); + $extension = end($uriparts); + unset($uriparts); + + $b2tservice = & ServiceFactory :: getServiceInstance('Bookmark2TagService'); + if (!$b2tservice->attachTags($bId, $categories, $fromApi, $extension, false, $fromImport)) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not insert bookmark', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + $this->db->sql_transaction('commit'); + // Everything worked out, so return the new bookmark's bId. + return $bId; + } + + function updateBookmark($bId, $address, $title, $description, $privateNote, $status, $categories, $date = NULL, $fromApi = false) { + if (!is_numeric($bId)) + return false; + + // Get the client's IP address and the date; note that the date is in GMT. + if (getenv('HTTP_CLIENT_IP')) + $ip = getenv('HTTP_CLIENT_IP'); + else + if (getenv('REMOTE_ADDR')) + $ip = getenv('REMOTE_ADDR'); + else + $ip = getenv('HTTP_X_FORWARDED_FOR'); + + $moddatetime = gmdate('Y-m-d H:i:s', time()); + + $address = $this->normalize($address); + + //check if a new address ($address) doesn't already exist for another bookmark from the same user + $bookmark = $this->getBookmark($bId); + if($bookmark['bAddress'] != $address && $this->bookmarkExists($address, $bookmark['uId'])) { + message_die(GENERAL_ERROR, 'Could not update bookmark (URL already existing = '.$address.')', '', __LINE__, __FILE__); + return false; + } + + // Set up the SQL update statement and execute it. + $updates = array('bModified' => $moddatetime, 'bTitle' => $title, 'bAddress' => $address, 'bDescription' => $description, 'bPrivateNote' => $privateNote, 'bStatus' => $status, 'bHash' => md5($address)); + + if (!is_null($date)) { + $datetime = gmdate('Y-m-d H:i:s', strtotime($date)); + $updates[] = array('bDateTime' => $datetime); + } + + $sql = 'UPDATE '. $GLOBALS['tableprefix'] .'bookmarks SET '. $this->db->sql_build_array('UPDATE', $updates) .' WHERE bId = '. intval($bId); + $this->db->sql_transaction('begin'); + + if (!($dbresult = & $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not update bookmark', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + + $uriparts = explode('.', $address); + $extension = end($uriparts); + unset($uriparts); + + $b2tservice = & ServiceFactory :: getServiceInstance('Bookmark2TagService'); + if (!$b2tservice->attachTags($bId, $categories, $fromApi, $extension)) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not update bookmark', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + + $this->db->sql_transaction('commit'); + // Everything worked out, so return true. + return true; + } + + function & getBookmarks($start = 0, $perpage = NULL, $user = NULL, $tags = NULL, $terms = NULL, $sortOrder = NULL, $watched = NULL, $startdate = NULL, $enddate = NULL, $hash = NULL) { + // Only get the bookmarks that are visible to the current user. Our rules: + // - if the $user is NULL, that means get bookmarks from ALL users, so we need to make + // sure to check the logged-in user's watchlist and get the contacts-only bookmarks from + // those users. If the user isn't logged-in, just get the public bookmarks. + // - if the $user is set and isn't the logged-in user, then get that user's bookmarks, and + // if that user is on the logged-in user's watchlist, get the public AND contacts-only + // bookmarks; otherwise, just get the public bookmarks. + // - if the $user is set and IS the logged-in user, then get all bookmarks. + + $userservice =& ServiceFactory::getServiceInstance('UserService'); + $b2tservice =& ServiceFactory::getServiceInstance('Bookmark2TagService'); + $tag2tagservice =& ServiceFactory::getServiceInstance('Tag2TagService'); + $sId = $userservice->getCurrentUserId(); + + if ($userservice->isLoggedOn()) { + // All public bookmarks, user's own bookmarks and any shared with user + $privacy = ' AND ((B.bStatus = 0) OR (B.uId = '. $sId .')'; + $watchnames = $userservice->getWatchNames($sId, true); + foreach($watchnames as $watchuser) { + $privacy .= ' OR (U.username = "'. $watchuser .'" AND B.bStatus = 1)'; + } + $privacy .= ')'; + } else { + // Just public bookmarks + $privacy = ' AND B.bStatus = 0'; + } + + // Set up the tags, if need be. + if (!is_array($tags) && !is_null($tags)) { + $tags = explode('+', trim($tags)); + } + + $tagcount = count($tags); + for ($i = 0; $i < $tagcount; $i ++) { + $tags[$i] = trim($tags[$i]); + } + + // Set up the SQL query. + $query_1 = 'SELECT DISTINCT '; + if (SQL_LAYER == 'mysql4') { + $query_1 .= 'SQL_CALC_FOUND_ROWS '; + } + $query_1 .= 'B.*, U.'. $userservice->getFieldName('username'); + + $query_2 = ' FROM '. $userservice->getTableName() .' AS U, '. $this->getTableName() .' AS B'; + + $query_3 = ' WHERE B.uId = U.'. $userservice->getFieldName('primary') . $privacy; + if (is_null($watched)) { + if (!is_null($user)) { + $query_3 .= ' AND B.uId = '. $user; + } + } else { + $arrWatch = $userservice->getWatchlist($user); + if (count($arrWatch) > 0) { + $query_3_1 = ''; + foreach($arrWatch as $row) { + $query_3_1 .= 'B.uId = '. intval($row) .' OR '; + } + $query_3_1 = substr($query_3_1, 0, -3); + } else { + $query_3_1 = 'B.uId = -1'; + } + $query_3 .= ' AND ('. $query_3_1 .') AND B.bStatus IN (0, 1)'; + } + + $query_5 = ''; + if($hash == null) { + $query_5.= ' GROUP BY B.bHash'; + } + + switch($sortOrder) { + case 'date_asc': + $query_5.= ' ORDER BY B.bModified ASC '; + break; + case 'title_desc': + $query_5.= ' ORDER BY B.bTitle DESC '; + break; + case 'title_asc': + $query_5.= ' ORDER BY B.bTitle ASC '; + break; + case 'url_desc': + $query_5.= ' ORDER BY B.bAddress DESC '; + break; + case 'url_asc': + $query_5.= ' ORDER BY B.bAddress ASC '; + break; + default: + $query_5.= ' ORDER BY B.bModified DESC '; + } + + // Handle the parts of the query that depend on any tags that are present. + $query_4 = ''; + for ($i = 0; $i < $tagcount; $i ++) { + $query_2 .= ', '. $b2tservice->getTableName() .' AS T'. $i; + $query_4 .= ' AND ('; + + $allLinkedTags = $tag2tagservice->getAllLinkedTags($this->db->sql_escape($tags[$i]), '>', $user); + + while (is_array($allLinkedTags) && count($allLinkedTags)>0) { + $query_4 .= ' T'. $i .'.tag = "'. array_pop($allLinkedTags) .'"'; + $query_4 .= ' OR'; + } + + $query_4 .= ' T'. $i .'.tag = "'. $this->db->sql_escape($tags[$i]) .'"'; + + $query_4 .= ') AND T'. $i .'.bId = B.bId'; + //die($query_4); + } + + // Search terms + if ($terms) { + // Multiple search terms okay + $aTerms = explode(' ', $terms); + $aTerms = array_map('trim', $aTerms); + + // Search terms in tags as well when none given + if (!count($tags)) { + $query_2 .= ' LEFT JOIN '. $b2tservice->getTableName() .' AS T ON B.bId = T.bId'; + $dotags = true; + } else { + $dotags = false; + } + + $query_4 = ''; + for ($i = 0; $i < count($aTerms); $i++) { + $query_4 .= ' AND (B.bTitle LIKE "%'. $this->db->sql_escape($aTerms[$i]) .'%"'; + $query_4 .= ' OR B.bDescription LIKE "%'. $this->db->sql_escape($aTerms[$i]) .'%"'; + $query_4 .= ' OR B.bPrivateNote LIKE "'. $this->db->sql_escape($aTerms[$i]) .'%"'; //warning : search in private notes of everybody but private notes won't appear if not allowed. + $query_4 .= ' OR U.username = "'. $this->db->sql_escape($aTerms[$i]) .'"'; //exact match for username + if ($dotags) { + $query_4 .= ' OR T.tag LIKE "'. $this->db->sql_escape($aTerms[$i]) .'%"'; + } + $query_4 .= ')'; + } + } + + // Start and end dates + if ($startdate) { + $query_4 .= ' AND B.bDatetime > "'. $startdate .'"'; + } + if ($enddate) { + $query_4 .= ' AND B.bDatetime < "'. $enddate .'"'; + } + + // Hash + if ($hash) { + $query_4 .= ' AND B.bHash = "'. $hash .'"'; + } + $query = $query_1 . $query_2 . $query_3 . $query_4 . $query_5; + + if (!($dbresult = & $this->db->sql_query_limit($query, intval($perpage), intval($start)))) { + message_die(GENERAL_ERROR, 'Could not get bookmarks', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if (SQL_LAYER == 'mysql4') { + $totalquery = 'SELECT FOUND_ROWS() AS total'; + } else { + if ($hash) { + $totalquery = 'SELECT COUNT(*) AS total'. $query_2 . $query_3 . $query_4; + } else { + $totalquery = 'SELECT COUNT(DISTINCT bAddress) AS total'. $query_2 . $query_3 . $query_4; + } + } + + if (!($totalresult = & $this->db->sql_query($totalquery)) || (!($row = & $this->db->sql_fetchrow($totalresult)))) { + message_die(GENERAL_ERROR, 'Could not get total bookmarks', '', __LINE__, __FILE__, $totalquery, $this->db); + return false; + } + + $total = $row['total']; + $this->db->sql_freeresult($totalresult); + + $bookmarks = array(); + while ($row = & $this->db->sql_fetchrow($dbresult)) { + $row['tags'] = $b2tservice->getTagsForBookmark(intval($row['bId'])); + $bookmarks[] = $row; + } + + $this->db->sql_freeresult($dbresult); + $output = array ('bookmarks' => $bookmarks, 'total' => $total); + return $output; + } + + function deleteBookmark($bookmarkid) { + $query = 'DELETE FROM '. $GLOBALS['tableprefix'] .'bookmarks WHERE bId = '. intval($bookmarkid); + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($query))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not delete bookmarks', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + + + $query = 'DELETE FROM '. $GLOBALS['tableprefix'] .'bookmarks2tags WHERE bId = '. intval($bookmarkid); + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($query))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not delete bookmarks', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $this->db->sql_transaction('commit'); + return true; + } + + function deleteBookmarksForUser($uId) {
+ $query = 'DELETE FROM '. $GLOBALS['tableprefix'] .'bookmarks WHERE uId = '. intval($uId);
+
+ if (!($dbresult = & $this->db->sql_query($query))) {
+ message_die(GENERAL_ERROR, 'Could not delete bookmarks', '', __LINE__, __FILE__, $query, $this->db);
+ return false;
+ }
+
+ return true;
+ } + + function countOthers($address) { + if (!$address) { + return false; + } + + $userservice = & ServiceFactory :: getServiceInstance('UserService'); + $sId = $userservice->getCurrentUserId(); + + if ($userservice->isLoggedOn()) { + // All public bookmarks, user's own bookmarks and any shared with user + $privacy = ' AND ((B.bStatus = 0) OR (B.uId = '. $sId .')'; + $watchnames = $userservice->getWatchNames($sId, true); + foreach($watchnames as $watchuser) { + $privacy .= ' OR (U.username = "'. $watchuser .'" AND B.bStatus = 1)'; + } + $privacy .= ')'; + } else { + // Just public bookmarks + $privacy = ' AND B.bStatus = 0'; + } + + $sql = 'SELECT COUNT(*) FROM '. $userservice->getTableName() .' AS U, '. $GLOBALS['tableprefix'] .'bookmarks AS B WHERE U.'. $userservice->getFieldName('primary') .' = B.uId AND B.bHash = "'. md5($address) .'"'. $privacy; + if (!($dbresult = & $this->db->sql_query($sql))) { + message_die(GENERAL_ERROR, 'Could not get vars', '', __LINE__, __FILE__, $sql, $this->db); + } + + $output = $this->db->sql_fetchfield(0, 0) - 1; + $this->db->sql_freeresult($dbresult); + return $output; + } + + function normalize($address) { + // If bookmark address doesn't contain ":", add "http://" to the start as a default protocol + if (strpos($address, ':') === false) { + $address = 'http://'. $address; + } + + // Delete final / + if (substr($address, -1) == '/') { + $address = substr($address, 0, count($address)-2); + } + + return $address; + } + + function deleteAll() { + $query = 'TRUNCATE TABLE `'. $this->getTableName() .'`'; + $this->db->sql_query($query); + } + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } + +} + + + +?> diff --git a/src/SemanticScuttle/Service/cacheservice.php b/src/SemanticScuttle/Service/cacheservice.php new file mode 100644 index 0000000..fe66d38 --- /dev/null +++ b/src/SemanticScuttle/Service/cacheservice.php @@ -0,0 +1,38 @@ +<?php +class CacheService { + var $basedir; + var $fileextension = '.cache'; + + function &getInstance() { + static $instance; + + if (!isset($instance)) + $instance =& new CacheService(); + + return $instance; + } + + function CacheService() { + $this->basedir = $GLOBALS['dir_cache']; + } + + function Start($hash, $time = 300) { + $cachefile = $this->basedir .'/'. $hash . $this->fileextension; + if (file_exists($cachefile) && time() < filemtime($cachefile) + $time) { + @readfile($cachefile); + echo "\n<!-- Cached: ". date('r', filemtime($cachefile)) ." -->\n"; + unset($cachefile); + exit; + } + ob_start("ob_gzhandler"); + } + + function End($hash) { + $cachefile = $this->basedir .'/'. $hash . $this->fileextension; + $handle = fopen($cachefile, 'w'); + fwrite($handle, ob_get_contents()); + fclose($handle); + ob_flush(); + } +} +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/Service/commondescriptionservice.php b/src/SemanticScuttle/Service/commondescriptionservice.php new file mode 100644 index 0000000..86e0c0f --- /dev/null +++ b/src/SemanticScuttle/Service/commondescriptionservice.php @@ -0,0 +1,167 @@ +<?php +class CommonDescriptionService { + var $db; + var $tablename; + + function &getInstance(&$db) { + static $instance; + if (!isset($instance)) + $instance =& new CommonDescriptionService($db); + return $instance; + } + + function CommonDescriptionService(&$db) { + $this->db =& $db; + $this->tablename = $GLOBALS['tableprefix'] .'commondescription'; + } + + function addTagDescription($tag, $desc, $uId, $time) { + // Check if no modification + $lastDesc = $this->getLastTagDescription($tag); + if($lastDesc['cdDescription'] == $desc) { + return true; + } + + // If modification + $datetime = gmdate('Y-m-d H:i:s', $time); + $values = array('tag'=>$tag, 'cdDescription'=>$desc, 'uId'=>$uId, 'cdDatetime'=>$datetime); + $sql = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + + if (!($dbresult =& $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not add tag description', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + + return true; + } + + function getLastTagDescription($tag) { + $query = "SELECT *"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag='".$tag."'"; + $query.= " ORDER BY cdDatetime DESC"; + + if (!($dbresult = & $this->db->sql_query_limit($query, 1, 0))) { + message_die(GENERAL_ERROR, 'Could not get tag description', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row =& $this->db->sql_fetchrow($dbresult)) { + return $row; + } else { + return false; + } + } + + function getAllTagsDescription($tag) { + $query = "SELECT *"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag='".$tag."'"; + $query.= " ORDER BY cdDatetime DESC"; + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get tag descriptions', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + return $this->db->sql_fetchrowset($dbresult); + + } + + function getDescriptionById($cdId) { + $query = "SELECT *"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE cdId='".$cdId."'"; + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get tag descriptions', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row =& $this->db->sql_fetchrow($dbresult)) { + return $row; + } else { + return false; + } + + } + + function addBookmarkDescription($bHash, $title, $desc, $uId, $time) { + // Check if no modification + $lastDesc = $this->getLastBookmarkDescription($bHash); + if($lastDesc['cdTitle'] == $title && $lastDesc['cdDescription'] == $desc) { + return true; + } + + // If modification + $datetime = gmdate('Y-m-d H:i:s', $time); + $values = array('bHash'=>$bHash, 'cdTitle'=>$title, 'cdDescription'=>$desc, 'uId'=>$uId, 'cdDatetime'=>$datetime); + $sql = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + + if (!($dbresult =& $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not add bookmark description', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + return true; + } + + function getLastBookmarkDescription($bHash) { + $query = "SELECT *"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE bHash='".$bHash."'"; + $query.= " ORDER BY cdDatetime DESC"; + + if (!($dbresult = & $this->db->sql_query_limit($query, 1, 0))) { + message_die(GENERAL_ERROR, 'Could not get bookmark description', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row =& $this->db->sql_fetchrow($dbresult)) { + return $row; + } else { + return false; + } + } + + function getAllBookmarksDescription($bHash) { + $query = "SELECT *"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE bHash='".$bHash."'"; + $query.= " ORDER BY cdDatetime DESC"; + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get bookmark descriptions', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + return $this->db->sql_fetchrowset($dbresult); + + } + + function deleteDescriptionsForUser($uId){ + $query = 'DELETE FROM '. $this->getTableName() . ' WHERE uId = '. intval($uId); + + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($query))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not delete user descriptions', '', + __LINE__, __FILE__, $query, $this->db); + return false; + } + + return true; + } + + + function deleteAll() { + $query = 'TRUNCATE TABLE `'. $this->getTableName() .'`'; + $this->db->sql_query($query); + } + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } +} +?> diff --git a/src/SemanticScuttle/Service/searchhistoryservice.php b/src/SemanticScuttle/Service/searchhistoryservice.php new file mode 100644 index 0000000..91457e8 --- /dev/null +++ b/src/SemanticScuttle/Service/searchhistoryservice.php @@ -0,0 +1,124 @@ +<?php +class SearchHistoryService { + var $db; + var $tablename; + var $sizeSearchHistory; + + function &getInstance(&$db) { + static $instance; + if (!isset($instance)) + $instance =& new SearchHistoryService($db); + return $instance; + } + + function SearchHistoryService(& $db) { + $this->db =& $db; + $this->tablename = $GLOBALS['tableprefix'] .'searchhistory'; + if(isset($GLOBALS['sizeSearchHistory'])) { + $this->sizeSearchHistory = $GLOBALS['sizeSearchHistory']; + } else { + $this->sizeSearchHistory = 10; + } + } + + function addSearch($terms, $range, $nbResults, $uId=0) { + if(strlen($terms) == 0) { + return false; + } + $datetime = gmdate('Y-m-d H:i:s', time()); + + //Insert values + $values = array('shTerms'=>$terms, 'shRange'=>$range, 'shDatetime'=>$datetime, 'shNbResults'=>$nbResults, 'uId'=>$uId); + $sql = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not insert search history', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + + if($this->sizeSearchHistory != -1 && + $this->countSearches() > $this->sizeSearchHistory) { + $this->deleteOldestSearch(); + } + } + + function getAllSearches($range = NULL, $uId = NULL, $nb = NULL, $start = NULL, $distinct = false, $withResults = false) { + $sql = 'SELECT DISTINCT(shTerms), shId, shRange, shNbResults, shDatetime, uId'; + $sql.= ' FROM '. $this->getTableName(); + $sql.= ' WHERE 1=1'; + if($range != NULL) { + $sql.= ' AND shRange = "'.$range.'"'; + } else { + $sql.= ' AND shRange = "all"'; + } + if($uId != NULL) { + $sql.= ' AND uId = '.$uId; + } + if($withResults = true) { + $sql.= ' AND shNbResults > 0'; + } + if($distinct) { + $sql.= ' GROUP BY shTerms'; + } + $sql.= ' ORDER BY shId DESC'; + + if (!($dbresult = & $this->db->sql_query_limit($sql, $nb, $start))) { + message_die(GENERAL_ERROR, 'Could not get searches', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + + $searches = array(); + while ($row = & $this->db->sql_fetchrow($dbresult)) { + $searches[] = $row; + } + $this->db->sql_freeresult($dbresult); + return $searches; + } + + function countSearches() { + $sql = 'SELECT COUNT(*) AS `total` FROM '. $this->getTableName(); + if (!($dbresult = & $this->db->sql_query($sql)) || (!($row = & $this->db->sql_fetchrow($dbresult)))) { + message_die(GENERAL_ERROR, 'Could not get total searches', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + $this->db->sql_freeresult($dbresult); + return $row['total']; + } + + /* This function allows to limit the number of saved searches + by deleting the oldest one */ + function deleteOldestSearch() { + $sql = 'DELETE FROM '.$this->getTableName(); + $sql.= ' ORDER BY shId ASC LIMIT 1'; // warning: here the limit is important + + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not delete bookmarks', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + } + + function deleteSearchHistoryForUser($uId) { + $query = 'DELETE FROM '. $this->getTableName() .' WHERE uId = '. intval($uId); + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not delete search history', '', + __LINE__, __FILE__, $query, $this->db); + return false; + } + + return true; + } + + function deleteAll() { + $query = 'TRUNCATE TABLE `'. $this->getTableName() .'`'; + $this->db->sql_query($query); + } + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } +} +?> diff --git a/src/SemanticScuttle/Service/servicefactory.php b/src/SemanticScuttle/Service/servicefactory.php new file mode 100644 index 0000000..b5215e3 --- /dev/null +++ b/src/SemanticScuttle/Service/servicefactory.php @@ -0,0 +1,38 @@ +<?php +/* Connect to the database and build services */ + +class ServiceFactory { + function ServiceFactory(&$db, $serviceoverrules = array()) { + } + + function &getServiceInstance($name, $servicedir = NULL) { + global $dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbpersist, $dbtype; + static $instances = array(); + static $db; + if (!isset($db)) { + require_once(dirname(__FILE__) .'/../includes/db/'. $dbtype .'.php'); + $db = new sql_db(); + $db->sql_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, $dbpersist); + if(!$db->db_connect_id) { + message_die(CRITICAL_ERROR, "Could not connect to the database", $db); + } + $db->sql_query("SET NAMES UTF8"); + } + + if (!isset($instances[$name])) { + if (isset($serviceoverrules[$name])) { + $name = $serviceoverrules[$name]; + } + if (!class_exists($name)) { + if (!isset($servicedir)) { + $servicedir = dirname(__FILE__) .'/'; + } + + require_once($servicedir . strtolower($name) . '.php'); + } + $instances[$name] = call_user_func(array($name, 'getInstance'), $db); + } + return $instances[$name]; + } +} +?> diff --git a/src/SemanticScuttle/Service/tag2tagservice.php b/src/SemanticScuttle/Service/tag2tagservice.php new file mode 100644 index 0000000..956fd49 --- /dev/null +++ b/src/SemanticScuttle/Service/tag2tagservice.php @@ -0,0 +1,377 @@ +<?php +class Tag2TagService { + var $db; + var $tablename; + + function &getInstance(&$db) { + static $instance; + if (!isset($instance)) + $instance =& new Tag2TagService($db); + return $instance; + } + + function Tag2TagService(&$db) { + $this->db =& $db; + $this->tablename = $GLOBALS['tableprefix'] .'tags2tags'; + } + + function addLinkedTags($tag1, $tag2, $relationType, $uId) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tag1 = $tagservice->normalize($tag1); + $tag2 = $tagservice->normalize($tag2); + + if($tag1 == $tag2 || strlen($tag1) == 0 || strlen($tag2) == 0 + || ($relationType != ">" && $relationType != "=") + || !is_numeric($uId) || $uId<=0 + || ($this->existsLinkedTags($tag1, $tag2, $relationType, $uId))) { + return false; + } + + $values = array('tag1' => $tag1, 'tag2' => $tag2, 'relationType'=> $relationType, 'uId'=> $uId); + $query = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + //die($query); + if (!($dbresult =& $this->db->sql_query($query))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not attach tag to tag', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + $this->db->sql_transaction('commit'); + + // Update stats and cache + $this->update($tag1, $tag2, $relationType, $uId); + + return true; + } + + // Return linked tags just for admin users + function getAdminLinkedTags($tag, $relationType, $inverseRelation = false, $stopList = array()) { + // look for admin ids + $userservice = & ServiceFactory :: getServiceInstance('UserService'); + $adminIds = $userservice->getAdminIds(); + + //ask for their linked tags + return $this->getLinkedTags($tag, $relationType, $adminIds, $inverseRelation, $stopList); + } + + // Return the target linked tags. If inverseRelation is true, return the source linked tags. + function getLinkedTags($tag, $relationType, $uId = null, $inverseRelation = false, $stopList = array()) { + // Set up the SQL query. + if($inverseRelation) { + $queriedTag = "tag1"; + $givenTag = "tag2"; + } else { + $queriedTag = "tag2"; + $givenTag = "tag1"; + } + + $query = "SELECT DISTINCT ". $queriedTag ." as 'tag'"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE 1=1"; + if($tag !=null) { + $query.= " AND ". $givenTag ." = '". $tag ."'"; + } + if($relationType) { + $query.= " AND relationType = '". $relationType ."'"; + } + if(is_array($uId)) { + $query.= " AND ( 1=0 "; //tricks always false + foreach($uId as $u) { + $query.= " OR uId = '".$u."'"; + } + $query.= " ) "; + } elseif($uId != null) { + $query.= " AND uId = '".$uId."'"; + } + //die($query); + if (! ($dbresult =& $this->db->sql_query($query)) ){ + message_die(GENERAL_ERROR, 'Could not get related tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $rowset = $this->db->sql_fetchrowset($dbresult); + $output = array(); + foreach($rowset as $row) { + if(!in_array($row['tag'], $stopList)) { + $output[] = $row['tag']; + } + } + + //bijective case for '=' + if($relationType == '=' && $inverseRelation == false) { + //$stopList[] = $tag; + $bijectiveOutput = $this->getLinkedTags($tag, $relationType, $uId, true, $stopList); + $output = array_merge($output, $bijectiveOutput); + //$output = array_unique($output); // remove duplication + } + + $this->db->sql_freeresult($dbresult); + return $output; + } + + /* + * Returns all linked tags (all descendants if relation is >, + * all synonyms if relation is = ) + * $stopList allows to avoid cycle (a > b > a) between tags + */ + function getAllLinkedTags($tag1, $relationType, $uId, $stopList=array()) { + if(in_array($tag1, $stopList) || $tag1 == '') { + return array(); + } + + // try to find data in cache + $tcs = & ServiceFactory::getServiceInstance('TagCacheService'); + if(count($stopList) == 0) { + $activatedCache = true; + } else { + $activatedCache = false; + } + + // look for existing links + $stopList[] = $tag1; + $linkedTags = $this->getLinkedTags($tag1, $relationType, $uId, false, $stopList); + if($relationType != '=') { + $linkedTags = array_merge($linkedTags, $this->getLinkedTags($tag1, '=', $uId, false, $stopList)); + } + + if(count($linkedTags) == 0) { + return array(); + + } else { + // use cache if possible + if($activatedCache) { + if($relationType == '>') { + $output = $tcs->getChildren($tag1, $uId); + } elseif($relationType == '=') { + $output = $tcs->getSynonyms($tag1, $uId); + } + if(count($output)>0) { + return $output; + } + } + + // else compute the links + $output = array(); + + foreach($linkedTags as $linkedTag) { + $allLinkedTags = $this->getAllLinkedTags($linkedTag, $relationType, $uId, $stopList); + $output[] = $linkedTag; + if(is_array($allLinkedTags)) { + $output = array_merge($output, $allLinkedTags); + } else { + $output[] = $allLinkedTags; + } + } + + // and save in cache + if($activatedCache == true && $uId>0) { + $tcs->updateTag($tag1, $relationType, $output, $uId); + } + + //$output = array_unique($output); // remove duplication + return $output; + + } + } + + function getOrphewTags($relationType, $uId = 0, $limit = null, $orderBy = null) { + $query = "SELECT DISTINCT tts.tag1 as tag"; + $query.= " FROM `". $this->getTableName() ."` tts"; + if($orderBy != null) { + $tsts =& ServiceFactory::getServiceInstance('TagStatService'); + $query.= ", ".$tsts->getTableName() ." tsts"; + } + $query.= " WHERE tts.tag1 <> ALL"; + $query.= " (SELECT DISTINCT tag2 FROM `". $this->getTableName() ."`"; + $query.= " WHERE relationType = '".$relationType."'"; + if($uId > 0) { + $query.= " AND uId = '".$uId."'"; + } + $query.= ")"; + if($uId > 0) { + $query.= " AND tts.uId = '".$uId."'"; + } + + switch($orderBy) { + case "nb": + $query.= " AND tts.tag1 = tsts.tag1"; + $query.= " AND tsts.relationType = '".$relationType."'"; + if($uId > 0) { + $query.= " AND tsts.uId = ".$uId; + } + $query.= " ORDER BY tsts.nb DESC"; + break; + case "depth": // by nb of descendants + $query.= " AND tts.tag1 = tsts.tag1"; + $query.= " AND tsts.relationType = '".$relationType."'"; + if($uId > 0) { + $query.= " AND tsts.uId = ".$uId; + } + $query.= " ORDER BY tsts.depth DESC"; + break; + case "nbupdate": + $query.= " AND tts.tag1 = tsts.tag1"; + $query.= " AND tsts.relationType = '".$relationType."'"; + if($uId > 0) { + $query.= " AND tsts.uId = ".$uId; + } + $query.= " ORDER BY tsts.nbupdate DESC"; + break; + } + + if($limit != null) { + $query.= " LIMIT 0,".$limit; + } + + if (! ($dbresult =& $this->db->sql_query($query)) ){ + message_die(GENERAL_ERROR, 'Could not get linked tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + $output = $this->db->sql_fetchrowset($dbresult); + $this->db->sql_freeresult($dbresult); + return $output; + } + + function getMenuTags($uId) { + if(strlen($GLOBALS['menuTag']) < 1) { + return array(); + } else { + // we don't use the getAllLinkedTags function in order to improve performance + $query = "SELECT tag2 as 'tag', COUNT(tag2) as 'count'"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag1 = '".$GLOBALS['menuTag']."'"; + $query.= " AND relationType = '>'"; + if($uId > 0) { + $query.= " AND uId = '".$uId."'"; + } + $query.= " GROUP BY tag2"; + $query.= " ORDER BY count DESC"; + $query.= " LIMIT 0, ".$GLOBALS['maxSizeMenuBlock']; + + if (! ($dbresult =& $this->db->sql_query($query)) ){ + message_die(GENERAL_ERROR, 'Could not get linked tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + $output = $this->db->sql_fetchrowset($dbresult); + $this->db->sql_freeresult($dbresult); + return $output; + } + } + + + function existsLinkedTags($tag1, $tag2, $relationType, $uId) { + + //$tag1 = mysql_real_escape_string($tag1); + //$tag2 = mysql_real_escape_string($tag2); + + $query = "SELECT tag1, tag2, relationType, uId FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND tag2 = '".$tag2."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + + //echo($query."<br>\n"); + + return $this->db->sql_numrows($this->db->sql_query($query)) > 0; + } + + function getLinks($uId) { + $query = "SELECT tag1, tag2, relationType, uId FROM `". $this->getTableName() ."`"; + $query.= " WHERE 1=1"; + if($uId > 0) { + $query.= " AND uId = '".$uId."'"; + } + + return $this->db->sql_fetchrowset($this->db->sql_query($query)); + } + + function removeLinkedTags($tag1, $tag2, $relationType, $uId) { + if(($tag1 != '' && $tag1 == $tag2) || + ($relationType != ">" && $relationType != "=" && $relationType != "") || + ($tag1 == '' && $tag2 == '')) { + return false; + } + $query = 'DELETE FROM '. $this->getTableName(); + $query.= ' WHERE 1=1'; + $query.= strlen($tag1)>0 ? ' AND tag1 = "'. $tag1 .'"' : ''; + $query.= strlen($tag2)>0 ? ' AND tag2 = "'. $tag2 .'"' : ''; + $query.= strlen($relationType)>0 ? ' AND relationType = "'. $relationType .'"' : ''; + $query.= strlen($uId)>0 ? ' AND uId = "'. $uId .'"' : ''; + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not remove tag relation', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + + // Update stats and cache + $this->update($tag1, $tag2, $relationType, $uId); + + $this->db->sql_freeresult($dbresult); + return true; + } + + function removeLinkedTagsForUser($uId) { + $query = 'DELETE FROM '. $this->getTableName(); + $query.= ' WHERE uId = "'. $uId .'"'; + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not remove tag relation', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + + // Update stats and cache + $this->update('', '', '', $uId); + + $this->db->sql_freeresult($dbresult); + return true; + } + + function renameTag($uId, $oldName, $newName) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $newName = $tagservice->normalize($newName); + + $query = 'UPDATE `'. $this->getTableName() .'`'; + $query.= ' SET tag1="'.$newName.'"'; + $query.= ' WHERE tag1="'.$oldName.'"'; + $query.= ' AND uId="'.$uId.'"'; + $this->db->sql_query($query); + + $query = 'UPDATE `'. $this->getTableName() .'`'; + $query.= ' SET tag2="'.$newName.'"'; + $query.= ' WHERE tag2="'.$oldName.'"'; + $query.= ' AND uId="'.$uId.'"'; + $this->db->sql_query($query); + + + // Update stats and cache + $this->update($oldName, NULL, '=', $uId); + $this->update($oldName, NULL, '>', $uId); + $this->update($newName, NULL, '=', $uId); + $this->update($newName, NULL, '>', $uId); + + return true; + + } + + function update($tag1, $tag2, $relationType, $uId) { + $tsts =& ServiceFactory::getServiceInstance('TagStatService'); + $tsts->updateStat($tag1, $relationType, $uId); + + $tcs = & ServiceFactory::getServiceInstance('TagCacheService'); + $tcs->deleteByUser($uId); + } + + function deleteAll() { + $query = 'TRUNCATE TABLE `'. $this->getTableName() .'`'; + $this->db->sql_query($query); + + $tsts =& ServiceFactory::getServiceInstance('TagStatService'); + $tsts->deleteAll(); + } + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } +} +?> diff --git a/src/SemanticScuttle/Service/tagcacheservice.php b/src/SemanticScuttle/Service/tagcacheservice.php new file mode 100644 index 0000000..ed2eefc --- /dev/null +++ b/src/SemanticScuttle/Service/tagcacheservice.php @@ -0,0 +1,349 @@ +<?php + +/* + * This class infers on relation between tags by storing all the including tags or synonymous tag. + * For example, if the user creates: tag1>tag2>tag3, the system can infer that tag is included into tag1. + * Instead of computing this relation several times, it is saved into this current table. + * For synonymy, this table stores also the group of synonymous tags. + * The table must be updated for each modification of the relations between tags. + */ + +class TagCacheService { + var $db; + var $tablename; + + function &getInstance(&$db) { + static $instance; + if (!isset($instance)) + $instance =& new TagCacheService($db); + return $instance; + } + + function TagCacheService(&$db) { + $this->db =& $db; + $this->tablename = $GLOBALS['tableprefix'] .'tagscache'; + } + + function getChildren($tag1, $uId) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tag1 = $tagservice->normalize($tag1); + + if($tag1 == '') return false; + + $query = "SELECT DISTINCT tag2 as 'tag'"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE relationType = '>'"; + $query.= " AND tag1 = '".$tag1."'"; + $query.= " AND uId = '".$uId."'"; + + //die($query); + if (! ($dbresult =& $this->db->sql_query($query)) ){ + message_die(GENERAL_ERROR, 'Could not get related tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + + $rowset = $this->db->sql_fetchrowset($dbresult); + $output = array(); + foreach($rowset as $row) { + $output[] = $row['tag']; + } + + $this->db->sql_freeresult($dbresult); + return $output; + } + + function addChild($tag1, $tag2, $uId) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tag1 = $tagservice->normalize($tag1); + $tag2 = $tagservice->normalize($tag2); + + if($tag1 == $tag2 || strlen($tag1) == 0 || strlen($tag2) == 0 + || ($this->existsChild($tag1, $tag2, $uId))) { + return false; + } + + $values = array('tag1' => $tag1, 'tag2' => $tag2, 'relationType'=> '>', 'uId'=> $uId); + $query = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + //die($query); + if (!($dbresult =& $this->db->sql_query($query))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not add tag cache inference', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + $this->db->sql_transaction('commit'); + } + + function removeChild($tag1, $tag2, $uId) { + if(($tag1 != '' && $tag1 == $tag2) || + ($tag1 == '' && $tag2 == '' && $uId == '')) { + return false; + } + + $query = 'DELETE FROM '. $this->getTableName(); + $query.= ' WHERE 1=1'; + $query.= strlen($tag1)>0 ? ' AND tag1 = "'. $tag1 .'"' : ''; + $query.= strlen($tag2)>0 ? ' AND tag2 = "'. $tag2 .'"' : ''; + $query.= ' AND relationType = ">"'; + $query.= strlen($uId)>0 ? ' AND uId = "'. $uId .'"' : ''; + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not remove tag cache inference', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + } + + function removeChildren($tag1, $uId) { + $this->removeChild($tag1, NULL, $uId); + } + + function existsChild($tag1, $tag2, $uId) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tag1 = $tagservice->normalize($tag1); + $tag2 = $tagservice->normalize($tag2); + + $query = "SELECT tag1, tag2, relationType, uId FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND tag2 = '".$tag2."'"; + $query.= " AND relationType = '>'"; + $query.= " AND uId = '".$uId."'"; + + //echo($query."<br>\n"); + + return $this->db->sql_numrows($this->db->sql_query($query)) > 0; + + } + + /* + * Synonyms of a same concept are a group. A group has one main synonym called key + * and a list of synonyms called values. + */ + function addSynonym($tag1, $tag2, $uId) { + + if($tag1 == $tag2 || strlen($tag1) == 0 || strlen($tag2) == 0 + || ($this->existsSynonym($tag1, $tag2, $uId))) { + return false; + } + + $case1 = '0'; // not in DB + if($this->_isSynonymKey($tag1, $uId)) { + $case1 = 'key'; + } elseif($this->_isSynonymValue($tag1, $uId)) { + $case1 = 'value'; + } + + $case2 = '0'; // not in DB + if($this->_isSynonymKey($tag2, $uId)) { + $case2 = 'key'; + } elseif($this->_isSynonymValue($tag2, $uId)) { + $case2 = 'value'; + } + $case = $case1.$case2; + + // all the possible cases + switch ($case) { + case 'keykey': + $values = $this->_getSynonymValues($tag2, $uId); + $this->removeSynonymGroup($tag2, $uId); + foreach($values as $value) { + $this->addSynonym($tag1, $value['tag'], $uId); + } + $this->addSynonym($tag1, $tag2, $uId); + break; + + case 'valuekey': + $key = $this->_getSynonymKey($tag1, $uId); + $this->addSynonym($key, $tag2, $uId); + break; + + case 'keyvalue': + $this->addSynonym($tag2, $tag1, $uId); + break; + case 'valuevalue': + $key1 = $this->_getSynonymKey($tag1, $uId); + $key2 = $this->_getSynonymKey($tag2, $uId); + $this->addSynonym($key1, $key2, $uId); + break; + case '0value': + $key = $this->_getSynonymKey($tag2, $uId); + $this->addSynonym($key, $tag1, $uId); + break; + case 'value0': + $this->addSynonym($tag2, $tag1, $uId); + break; + case '0key': + $this->addSynonym($tag2, $tag1, $uId); + break; + default: + $values = array('tag1' => $tag1, 'tag2' => $tag2, 'relationType'=> '=', 'uId'=> $uId); + $query = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + //die($query); + if (!($dbresult =& $this->db->sql_query($query))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not add tag cache synonymy', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + $this->db->sql_transaction('commit'); + break; + } + } + + function removeSynonymGroup($tag1, $uId) { + $query = 'DELETE FROM '. $this->getTableName(); + $query.= ' WHERE 1=1'; + $query.= ' AND tag1 = "'. $tag1 .'"'; + $query.= ' AND relationType = "="'; + $query.= ' AND uId = "'. $uId .'"'; + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not remove tag cache inference', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + } + + function _isSynonymKey($tag1, $uId) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tag1 = $tagservice->normalize($tag1); + + $query = "SELECT tag1 FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '='"; + $query.= " AND uId = '".$uId."'"; + + return $this->db->sql_numrows($this->db->sql_query($query)) > 0; + } + + function _isSynonymValue($tag2, $uId) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tag2 = $tagservice->normalize($tag2); + + $query = "SELECT tag2 FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag2 = '" .$tag2 ."'"; + $query.= " AND relationType = '='"; + $query.= " AND uId = '".$uId."'"; + + return $this->db->sql_numrows($this->db->sql_query($query)) > 0; + } + + function getSynonyms($tag1, $uId) { + $values = array(); + if($this->_isSynonymKey($tag1, $uId)) { + $values = $this->_getSynonymValues($tag1, $uId); + } elseif($this->_isSynonymValue($tag1, $uId)) { + $key = $this->_getSynonymKey($tag1, $uId); + $values = $this->_getSynonymValues($key, $uId, $tag1); + $values[] = $key; + } + return $values; + } + + function _getSynonymKey($tag2, $uId) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tag2 = $tagservice->normalize($tag2); + + if($this->_isSynonymKey($tag2)) return $tag2; + + if($tag2 == '') return false; + + $query = "SELECT DISTINCT tag1 as 'tag'"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE relationType = '='"; + $query.= " AND tag2 = '".$tag2."'"; + $query.= " AND uId = '".$uId."'"; + + //die($query); + if (! ($dbresult =& $this->db->sql_query($query)) ){ + message_die(GENERAL_ERROR, 'Could not get related tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $row = $this->db->sql_fetchrow($dbresult); + $this->db->sql_freeresult($dbresult); + return $row['tag']; + } + + /* + * Return values associated with a key. + * $tagExcepted allows to hide a value. + */ + function _getSynonymValues($tag1, $uId, $tagExcepted = NULL) { + $tagservice =& ServiceFactory::getServiceInstance('TagService'); + $tag1 = $tagservice->normalize($tag1); + $tagExcepted = $tagservice->normalize($tagExcepted); + + if($tag1 == '') return false; + + $query = "SELECT DISTINCT tag2 as 'tag'"; + $query.= " FROM `". $this->getTableName() ."`"; + $query.= " WHERE relationType = '='"; + $query.= " AND tag1 = '".$tag1."'"; + $query.= " AND uId = '".$uId."'"; + $query.= $tagExcepted!=''?" AND tag2!='".$tagExcepted."'":""; + + if (! ($dbresult =& $this->db->sql_query($query)) ){ + message_die(GENERAL_ERROR, 'Could not get related tags', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $rowset = $this->db->sql_fetchrowset($dbresult); + + $output = array(); + foreach($rowset as $row) { + $output[] = $row['tag']; + } + + $this->db->sql_freeresult($dbresult); + return $output; + } + + function existsSynonym($tag1, $tag2, $uId) { + if($this->_getSynonymKey($tag1, $uId) == $tag2 || $this->_getSynonymKey($tag2, $uId) == $tag1) { + return true; + } else { + return false; + } + } + + + function updateTag($tag1, $relationType, $otherTags, $uId) { + if($relationType == '=') { + if($this->getSynonyms($tag1, $uId)) { // remove previous data avoiding unconstistency + $this->removeSynonymGroup($tag1, $uId); + } + + foreach($otherTags as $tag2) { + $this->addSynonym($tag1, $tag2, $uId); + } + } elseif($relationType == '>') { + if(count($this->getChildren($tag1, $uId))>0) { // remove previous data avoiding unconstistency + $this->removeChildren($tag1); + } + + foreach($otherTags as $tag2) { + $this->addChild($tag1, $tag2, $uId); + } + } + } + + function deleteByUser($uId) { + $query = 'DELETE FROM '. $this->getTableName() .' WHERE uId = '. intval($uId); + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not delete user tags cache', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + return true; + + } + + function deleteAll() { + $query = 'TRUNCATE TABLE `'. $this->getTableName() .'`'; + $this->db->sql_query($query); + } + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } +} +?> diff --git a/src/SemanticScuttle/Service/tagservice.php b/src/SemanticScuttle/Service/tagservice.php new file mode 100644 index 0000000..fc44a99 --- /dev/null +++ b/src/SemanticScuttle/Service/tagservice.php @@ -0,0 +1,123 @@ +<?php +class TagService { + var $db; + var $tablename; + + function &getInstance(&$db) { + static $instance; + if (!isset($instance)) + $instance =& new TagService($db); + return $instance; + } + + function TagService(&$db) { + $this->db =& $db; + $this->tablename = $GLOBALS['tableprefix'] .'tags'; + } + + function getDescription($tag, $uId) { + $query = 'SELECT tag, uId, tDescription'; + $query.= ' FROM '.$this->getTableName(); + $query.= ' WHERE tag = "'.$tag.'"'; + $query.= ' AND uId = "'.$uId.'"'; + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get tag description', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row =& $this->db->sql_fetchrow($dbresult)) { + return $row; + } else { + return array('tDescription'=>''); + } + } + + function existsDescription($tag, $uId) { + $query = 'SELECT tag, uId, tDescription'; + $query.= ' FROM '.$this->getTableName(); + $query.= ' WHERE tag = "'.$tag.'"'; + $query.= ' AND uId = "'.$uId.'"'; + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get tag description', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row =& $this->db->sql_fetchrow($dbresult)) { + return true; + } else { + return false; + } + } + + function getAllDescriptions($tag) { + $query = 'SELECT tag, uId, tDescription'; + $query.= ' FROM '.$this->getTableName(); + $query.= ' WHERE tag = "'.$tag.'"'; + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get tag description', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + return $this->db->sql_fetchrowset($dbresult); + } + + function updateDescription($tag, $uId, $desc) { + if($this->existsDescription($tag, $uId)) { + $query = 'UPDATE '.$this->getTableName(); + $query.= ' SET tDescription="'.$this->db->sql_escape($desc).'"'; + $query.= ' WHERE tag="'.$tag.'" AND uId="'.$uId.'"'; + } else { + $values = array('tag'=>$tag, 'uId'=>$uId, 'tDescription'=>$desc); + $query = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + } + + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($query))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not delete bookmarks', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + $this->db->sql_transaction('commit'); + return true; + } + + function renameTag($uId, $oldName, $newName) { + $newname = $this->normalize($newname); + + $query = 'UPDATE `'. $this->getTableName() .'`'; + $query.= ' SET tag="'.$newName.'"'; + $query.= ' WHERE tag="'.$oldName.'"'; + $query.= ' AND uId="'.$uId.'"'; + $this->db->sql_query($query); + return true; + } + + /* normalize the input tags which could be a string or an array*/ + function normalize($tags) { + //clean tags from strange characters + $tags = str_replace(array('"', '\'', '/'), "_", $tags); + + //normalize + if(!is_array($tags)) { + $tags = strtolower(trim($tags)); + } else { + for($i=0; $i<count($tags); $i++) { + $tags[$i] = strtolower(trim($tags[$i])); + } + } + return $tags; + } + + function deleteAll() { + $query = 'TRUNCATE TABLE `'. $this->getTableName() .'`'; + $this->db->sql_query($query); + } + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } +} +?> diff --git a/src/SemanticScuttle/Service/tagstatservice.php b/src/SemanticScuttle/Service/tagstatservice.php new file mode 100644 index 0000000..9d3ca5d --- /dev/null +++ b/src/SemanticScuttle/Service/tagstatservice.php @@ -0,0 +1,193 @@ +<?php +class TagStatService { + var $db; + var $tablename; + + function &getInstance(&$db) { + static $instance; + if (!isset($instance)) + $instance =& new TagStatService($db); + return $instance; + } + + function TagStatService(&$db) { + $this->db =& $db; + $this->tablename = $GLOBALS['tableprefix'] .'tagsstats'; + } + + function getNbChildren($tag1, $relationType, $uId) { + $tts =& ServiceFactory::getServiceInstance('Tag2TagService'); + $query = "SELECT tag1, relationType, uId FROM `". $tts->getTableName() ."`"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + + return $this->db->sql_numrows($this->db->sql_query($query)); + } + + function getNbDescendants($tag1, $relationType, $uId) { + $query = "SELECT nb FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + + $dbresults =& $this->db->sql_query($query); + $row = $this->db->sql_fetchrow($dbresults); + if($row['nb'] == null) { + return 0; + } else { + return (int) $row['nb']; + } + } + + function getMaxDepth($tag1, $relationType, $uId) { + $query = "SELECT depth FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + + $dbresults =& $this->db->sql_query($query); + $row = $this->db->sql_fetchrow($dbresults); + if($row['depth'] == null) { + return 0; + } else { + return (int) $row['depth']; + }; + } + + function getNbUpdates($tag1, $relationType, $uId) { + $query = "SELECT nbupdate FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + + $dbresults =& $this->db->sql_query($query); + $row = $this->db->sql_fetchrow($dbresults); + if($row['nbupdate'] == null) { + return 0; + } else { + return (int) $row['nbupdate']; + } + } + + function existStat($tag1, $relationType, $uId) { + $query = "SELECT tag1, relationType, uId FROM `". $this->getTableName() ."`"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + + return $this->db->sql_numrows($this->db->sql_query($query))>0; + } + + function createStat($tag1, $relationType, $uId) { + $query = "INSERT INTO `". $this->getTableName() ."`"; + $query.= "(tag1, relationType, uId)"; + $query.= " VALUES ('".$tag1."','".$relationType."','".$uId."')"; + $this->db->sql_query($query); + } + + function updateStat($tag1, $relationType, $uId=null, $stoplist=array()) { + if(in_array($tag1, $stoplist)) { + return false; + } + + $tts =& ServiceFactory::getServiceInstance('Tag2TagService'); + $linkedTags = $tts->getLinkedTags($tag1, $relationType, $uId); + $nbDescendants = 0; + $maxDepth = 0; + foreach($linkedTags as $linkedTag) { + $nbDescendants+= 1 + $this->getNbDescendants($linkedTag, $relationType, $uId); + $maxDepth = max($maxDepth, 1 + $this->getMaxDepth($linkedTag, $relationType, $uId)); + } + $this->setNbDescendants($tag1, $relationType, $uId, $nbDescendants); + $this->setMaxDepth($tag1, $relationType, $uId, $maxDepth); + $this->increaseNbUpdate($tag1, $relationType, $uId); + + // propagation to the precedent tags + $linkedTags = $tts->getLinkedTags($tag1, $relationType, $uId, true); + $stoplist[] = $tag1; + foreach($linkedTags as $linkedTag) { + $this->updateStat($linkedTag, $relationType, $uId, $stoplist); + } + } + + function updateAllStat() { + $tts =& ServiceFactory::getServiceInstance('Tag2TagService'); + + $query = "SELECT tag1, uId FROM `". $tts->getTableName() ."`"; + $query.= " WHERE relationType = '>'"; + + //die($query); + + if (! ($dbresult =& $this->db->sql_query($query)) ){ + message_die(GENERAL_ERROR, 'Could not update stats', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $rowset = $this->db->sql_fetchrowset($dbresult); + foreach($rowset as $row) { + $this->updateStat($row['tag1'], '>', $row['uId']); + } + } + + function setNbDescendants($tag1, $relationType, $uId, $nb) { + if(!$this->existStat($tag1, $relationType, $uId)) { + $this->createStat($tag1, $relationType, $uId); + } + $query = "UPDATE `". $this->getTableName() ."`"; + $query.= " SET nb = ". $nb; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + $this->db->sql_query($query); + } + + function setMaxDepth($tag1, $relationType, $uId, $depth) { + if(!$this->existStat($tag1, $relationType, $uId)) { + $this->createStat($tag1, $relationType, $uId); + } + $query = "UPDATE `". $this->getTableName() ."`"; + $query.= " SET depth = ". $depth; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + $this->db->sql_query($query); + } + + function increaseNbUpdate($tag1, $relationType, $uId) { + if(!$this->existStat($tag1, $relationType, $uId)) { + $this->createStat($tag1, $relationType, $uId); + } + $query = "UPDATE `". $this->getTableName() ."`"; + $query.= " SET nbupdate = nbupdate + 1"; + $query.= " WHERE tag1 = '" .$tag1 ."'"; + $query.= " AND relationType = '". $relationType ."'"; + $query.= " AND uId = '".$uId."'"; + + //die($query); + + $this->db->sql_query($query); + } + + function deleteTagStatForUser($uId) { + $query = 'DELETE FROM '. $this->getTableName() .' WHERE uId = '. intval($uId); + + if (!($dbresult = & $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not delete tag stats', '', __LINE__, + __FILE__, $query, $this->db); + return false; + } + + return true; + } + + function deleteAll() { + $query = 'TRUNCATE TABLE `'. $this->getTableName() .'`'; + $this->db->sql_query($query); + } + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } +} +?> diff --git a/src/SemanticScuttle/Service/templateservice.php b/src/SemanticScuttle/Service/templateservice.php new file mode 100644 index 0000000..05e494c --- /dev/null +++ b/src/SemanticScuttle/Service/templateservice.php @@ -0,0 +1,46 @@ +<?php +class TemplateService { + var $basedir; + + function &getInstance() { + static $instance; + if (!isset($instance)) + $instance =& new TemplateService(); + return $instance; + } + + function TemplateService() { + $this->basedir = $GLOBALS['TEMPLATES_DIR']; + } + + function loadTemplate($template, $vars = NULL) { + if (substr($template, -4) != '.php') + $template .= '.php'; + $tpl =& new Template($this->basedir .'/'. $template, $vars, $this); + $tpl->parse(); + return $tpl; + } +} + +class Template { + var $vars = array(); + var $file = ''; + var $templateservice; + + function Template($file, $vars = NULL, &$templateservice) { + $this->vars = $vars; + $this->file = $file; + $this->templateservice = $templateservice; + } + + function parse() { + if (isset($this->vars)) + extract($this->vars); + include($this->file); + } + + function includeTemplate($name) { + return $this->templateservice->loadTemplate($name, $this->vars); + } +} +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/Service/userservice.php b/src/SemanticScuttle/Service/userservice.php new file mode 100644 index 0000000..407632b --- /dev/null +++ b/src/SemanticScuttle/Service/userservice.php @@ -0,0 +1,665 @@ +<?php +class UserService { + var $db; + var $fields = array( + 'primary' => 'uId', + 'username' => 'username', + 'password' => 'password'); + var $profileurl; + var $tablename; + var $sessionkey; + var $cookiekey; + var $cookietime = 1209600; // 2 weeks + + function &getInstance(&$db) { + static $instance; + if (!isset($instance)) + $instance =& new UserService($db); + return $instance; + } + + function UserService(& $db) { + $this->db =& $db; + $this->tablename = $GLOBALS['tableprefix'] .'users'; + $this->sessionkey = INSTALLATION_ID.'-currentuserid'; + $this->cookiekey = INSTALLATION_ID.'-login'; + $this->profileurl = createURL('profile', '%2$s'); + $this->updateSessionStability(); + } + + function _checkdns($host) { + if (function_exists('checkdnsrr')) { + return checkdnsrr($host); + } else { + return $this->_checkdnsrr($host); + } + } + + function _checkdnsrr($host, $type = "MX") { + if(!empty($host)) { + @exec("nslookup -type=$type $host", $output); + while(list($k, $line) = each($output)) { + if(eregi("^$host", $line)) { + return true; + } + } + return false; + } + } + + function _getuser($fieldname, $value) { + $query = 'SELECT * FROM '. $this->getTableName() .' WHERE '. $fieldname .' = "'. $this->db->sql_escape($value) .'"'; + + if (! ($dbresult =& $this->db->sql_query($query)) ) { + message_die(GENERAL_ERROR, 'Could not get user', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $row =& $this->db->sql_fetchrow($dbresult); + $this->db->sql_freeresult($dbresult); + if ($row) { + return $row; + } else { + return false; + } + } + + function & getUsers($nb=0) { + $query = 'SELECT * FROM '. $this->getTableName() .' ORDER BY `uId` DESC'; + if($nb>0) { + $query .= ' LIMIT 0, '.$nb; + } + if (! ($dbresult =& $this->db->sql_query($query)) ) { + message_die(GENERAL_ERROR, 'Could not get user', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + while ($row = & $this->db->sql_fetchrow($dbresult)) { + $users[] = $row; + } + $this->db->sql_freeresult($dbresult); + return $users; + } + + function & getObjectUsers($nb=0) { + $query = 'SELECT * FROM '. $this->getTableName() .' ORDER BY `uId` DESC'; + if($nb>0) { + $query .= ' LIMIT 0, '.$nb; + } + if (! ($dbresult =& $this->db->sql_query($query)) ) { + message_die(GENERAL_ERROR, 'Could not get user', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + while ($row = & $this->db->sql_fetchrow($dbresult)) { + $users[] = new User($row[$this->getFieldName('primary')], $row[$this->getFieldName('username')]); + } + $this->db->sql_freeresult($dbresult); + return $users; + } + + function _randompassword() { + $seed = (integer) md5(microtime()); + mt_srand($seed); + $password = mt_rand(1, 99999999); + $password = substr(md5($password), mt_rand(0, 19), mt_rand(6, 12)); + return $password; + } + + function _updateuser($uId, $fieldname, $value) { + $updates = array ($fieldname => $value); + $sql = 'UPDATE '. $this->getTableName() .' SET '. $this->db->sql_build_array('UPDATE', $updates) .' WHERE '. $this->getFieldName('primary') .'='. intval($uId); + + // Execute the statement. + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not update user', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + $this->db->sql_transaction('commit'); + + // Everything worked out, so return true. + return true; + } + + function getProfileUrl($id, $username) { + return sprintf($this->profileurl, urlencode($id), urlencode($username)); + } + + function getUserByUsername($username) { + return $this->_getuser($this->getFieldName('username'), $username); + } + + function getObjectUserByUsername($username) { + $user = $this->_getuser($this->getFieldName('username'), $username); + if($user != false) { + return new User($user[$this->getFieldName('primary')], $username); + } else { + return NULL; + } + } + + /* Takes an numerical "id" or a string "username" + and returns the numerical "id" if the user exists else returns NULL */ + function getIdFromUser($user) { + if (is_int($user)) { + return intval($user); + } else { + $objectUser = $this->getObjectUserByUsername($user); + if($objectUser != NULL) { + return $objectUser->getId(); + } + } + return NULL; + } + + function getUser($id) { + return $this->_getuser($this->getFieldName('primary'), $id); + } + + // Momentary useful in order to go to object code + function getObjectUser($id) { + $user = $this->_getuser($this->getFieldName('primary'), $id); + return new User($id, $user[$this->getFieldName('username')]); + } + + function isLoggedOn() { + return ($this->getCurrentUserId() !== false); + } + + function &getCurrentUser($refresh = FALSE, $newval = NULL) { + static $currentuser; + if (!is_null($newval)) { //internal use only: reset currentuser + $currentuser = $newval; + } else if ($refresh || !isset($currentuser)) { + if ($id = $this->getCurrentUserId()) { + $currentuser = $this->getUser($id); + } else { + $currentuser = null; + } + } + return $currentuser; + } + + // Momentary useful in order to go to object code + function getCurrentObjectUser($refresh = FALSE, $newval = NULL) { + static $currentObjectUser; + if (!is_null($newval)) { //internal use only: reset currentuser + $currentObjectUser = $newval; + } else if ($refresh || !isset($currentObjectUser)) { + if ($id = $this->getCurrentUserId()) { + $currentObjectUser = $this->getObjectUser($id); + } else { + $currentObjectUser = null; + } + } + return $currentObjectUser; + } + + function existsUserWithUsername($username) { + if($this->getUserByUsername($username) != '') { + return true; + } else { + return false; + } + } + + function existsUser($id) { + if($this->getUser($id) != '') { + return true; + } else { + return false; + } + } + + /** + * Checks if the given user is an administrator. + * Uses global admin_users property containing admin + * user names + * + * @param integer|array $user User ID or user row from DB + * + * @return boolean True if the user is admin + */ + function isAdmin($user) + { + if (is_numeric($user)) { + $user = $this->getUser($user); + } + + if (isset($GLOBALS['admin_users']) + && in_array($user['username'], $GLOBALS['admin_users']) + ) { + return true; + } else { + return false; + } + } + + /* return current user id based on session or cookie */ + function getCurrentUserId() { + if (isset($_SESSION[$this->getSessionKey()])) { + return $_SESSION[$this->getSessionKey()]; + } else if (isset($_COOKIE[$this->getCookieKey()])) { + $cook = split(':', $_COOKIE[$this->getCookieKey()]); + //cookie looks like this: 'id:md5(username+password)' + $query = 'SELECT * FROM '. $this->getTableName() . + ' WHERE MD5(CONCAT('.$this->getFieldName('username') . + ', '.$this->getFieldName('password') . + ')) = \''.$this->db->sql_escape($cook[1]).'\' AND '. + $this->getFieldName('primary'). ' = '. $this->db->sql_escape($cook[0]); + + if (! ($dbresult =& $this->db->sql_query($query)) ) { + message_die(GENERAL_ERROR, 'Could not get user', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row = $this->db->sql_fetchrow($dbresult)) { + $_SESSION[$this->getSessionKey()] = $row[$this->getFieldName('primary')]; + $this->db->sql_freeresult($dbresult); + return $_SESSION[$this->getSessionKey()]; + } + } + return false; + } + + function login($username, $password, $remember = FALSE) { + $password = $this->sanitisePassword($password); + $query = 'SELECT '. $this->getFieldName('primary') .' FROM '. $this->getTableName() .' WHERE '. $this->getFieldName('username') .' = "'. $this->db->sql_escape($username) .'" AND '. $this->getFieldName('password') .' = "'. $this->db->sql_escape($password) .'"'; + + if (! ($dbresult =& $this->db->sql_query($query)) ) { + message_die(GENERAL_ERROR, 'Could not get user', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + if ($row =& $this->db->sql_fetchrow($dbresult)) { + $id = $_SESSION[$this->getSessionKey()] = $row[$this->getFieldName('primary')]; + if ($remember) { + $cookie = $id .':'. md5($username.$password); + setcookie($this->cookiekey, $cookie, time() + $this->cookietime, '/'); + } + $this->db->sql_freeresult($dbresult); + return true; + } else { + return false; + } + } + + function logout() { + @setcookie($this->getCookiekey(), '', time() - 1, '/'); + unset($_COOKIE[$this->getCookiekey()]); + session_unset(); + $this->getCurrentUser(TRUE, false); + } + + function getWatchlist($uId) { + // Gets the list of user IDs being watched by the given user. + $query = 'SELECT watched FROM '. $GLOBALS['tableprefix'] .'watched WHERE uId = '. intval($uId); + + if (! ($dbresult =& $this->db->sql_query($query)) ) { + message_die(GENERAL_ERROR, 'Could not get watchlist', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $arrWatch = array(); + if ($this->db->sql_numrows($dbresult) == 0) { + $this->db->sql_freeresult($dbresult); + return $arrWatch; + } + while ($row =& $this->db->sql_fetchrow($dbresult)) { + $arrWatch[] = $row['watched']; + } + $this->db->sql_freeresult($dbresult); + return $arrWatch; + } + + function getWatchNames($uId, $watchedby = false) { + // Gets the list of user names being watched by the given user. + // - If $watchedby is false get the list of users that $uId watches + // - If $watchedby is true get the list of users that watch $uId + if ($watchedby) { + $table1 = 'b'; + $table2 = 'a'; + } else { + $table1 = 'a'; + $table2 = 'b'; + } + $query = 'SELECT '. $table1 .'.'. $this->getFieldName('username') .' FROM '. $GLOBALS['tableprefix'] .'watched AS W, '. $this->getTableName() .' AS a, '. $this->getTableName() .' AS b WHERE W.watched = a.'. $this->getFieldName('primary') .' AND W.uId = b.'. $this->getFieldName('primary') .' AND '. $table2 .'.'. $this->getFieldName('primary') .' = '. intval($uId) .' ORDER BY '. $table1 .'.'. $this->getFieldName('username'); + + if (!($dbresult =& $this->db->sql_query($query))) { + message_die(GENERAL_ERROR, 'Could not get watchlist', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $arrWatch = array(); + if ($this->db->sql_numrows($dbresult) == 0) { + $this->db->sql_freeresult($dbresult); + return $arrWatch; + } + while ($row =& $this->db->sql_fetchrow($dbresult)) { + $arrWatch[] = $row[$this->getFieldName('username')]; + } + $this->db->sql_freeresult($dbresult); + return $arrWatch; + } + + function getWatchStatus($watcheduser, $currentuser) { + // Returns true if the current user is watching the given user, and false otherwise. + $query = 'SELECT watched FROM '. $GLOBALS['tableprefix'] .'watched AS W INNER JOIN '. $this->getTableName() .' AS U ON U.'. $this->getFieldName('primary') .' = W.watched WHERE U.'. $this->getFieldName('primary') .' = '. intval($watcheduser) .' AND W.uId = '. intval($currentuser); + + if (! ($dbresult =& $this->db->sql_query($query)) ) { + message_die(GENERAL_ERROR, 'Could not get watchstatus', '', __LINE__, __FILE__, $query, $this->db); + return false; + } + + $arrWatch = array(); + if ($this->db->sql_numrows($dbresult) == 0) + return false; + else + return true; + } + + function setWatchStatus($subjectUserID) { + if (!is_numeric($subjectUserID)) + return false; + + $currentUserID = $this->getCurrentUserId(); + $watched = $this->getWatchStatus($subjectUserID, $currentUserID); + + if ($watched) { + $sql = 'DELETE FROM '. $GLOBALS['tableprefix'] .'watched WHERE uId = '. intval($currentUserID) .' AND watched = '. intval($subjectUserID); + if (!($dbresult =& $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not add user to watch list', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + } else { + $values = array( + 'uId' => intval($currentUserID), + 'watched' => intval($subjectUserID) + ); + $sql = 'INSERT INTO '. $GLOBALS['tableprefix'] .'watched '. $this->db->sql_build_array('INSERT', $values); + if (!($dbresult =& $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not add user to watch list', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + } + + $this->db->sql_transaction('commit'); + return true; + } + + function addUser($username, $password, $email) { + // Set up the SQL UPDATE statement. + $datetime = gmdate('Y-m-d H:i:s', time()); + $password = $this->sanitisePassword($password); + $values = array('username' => $username, 'password' => $password, 'email' => $email, 'uDatetime' => $datetime, 'uModified' => $datetime); + $sql = 'INSERT INTO '. $this->getTableName() .' '. $this->db->sql_build_array('INSERT', $values); + + // Execute the statement. + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not insert user', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + $this->db->sql_transaction('commit'); + + // Everything worked out, so return true. + return true; + } + + function updateUser($uId, $password, $name, $email, $homepage, $uContent) { + if (!is_numeric($uId)) + return false; + + // Set up the SQL UPDATE statement. + $moddatetime = gmdate('Y-m-d H:i:s', time()); + if ($password == '') + $updates = array ('uModified' => $moddatetime, 'name' => $name, 'email' => $email, 'homepage' => $homepage, 'uContent' => $uContent); + else + $updates = array ('uModified' => $moddatetime, 'password' => $this->sanitisePassword($password), 'name' => $name, 'email' => $email, 'homepage' => $homepage, 'uContent' => $uContent); + $sql = 'UPDATE '. $this->getTableName() .' SET '. $this->db->sql_build_array('UPDATE', $updates) .' WHERE '. $this->getFieldName('primary') .'='. intval($uId); + + // Execute the statement. + $this->db->sql_transaction('begin'); + if (!($dbresult = & $this->db->sql_query($sql))) { + $this->db->sql_transaction('rollback'); + message_die(GENERAL_ERROR, 'Could not update user', '', __LINE__, __FILE__, $sql, $this->db); + return false; + } + $this->db->sql_transaction('commit'); + + // Everything worked out, so return true. + return true; + } + + function getAllUsers ( ) {
+ $query = 'SELECT * FROM '. $this->getTableName();
+
+ if (! ($dbresult =& $this->db->sql_query($query)) ) {
+ message_die(GENERAL_ERROR, 'Could not get users', '', __LINE__, __FILE__, $query, $this->db);
+ return false;
+ }
+
+ $rows = array();
+
+ while ( $row = $this->db->sql_fetchrow($dbresult) ) {
+ $rows[] = $row;
+ }
+ $this->db->sql_freeresult($dbresult);
+ return $rows;
+ } + + // Returns an array with admin uIds + function getAdminIds() { + $admins = array(); + foreach($GLOBALS['admin_users'] as $adminName) { + if($this->getIdFromUser($adminName) != NULL) + $admins[] = $this->getIdFromUser($adminName); + } + return $admins; + }
+
+ function deleteUser($uId) {
+ $query = 'DELETE FROM '. $this->getTableName() .' WHERE uId = '. intval($uId);
+
+ if (!($dbresult = & $this->db->sql_query($query))) {
+ message_die(GENERAL_ERROR, 'Could not delete user', '', __LINE__, __FILE__, $query, $this->db);
+ return false;
+ }
+
+ return true;
+ }
+ + + function sanitisePassword($password) { + return sha1(trim($password)); + } + + function generatePassword($uId) { + if (!is_numeric($uId)) + return false; + + $password = $this->_randompassword(); + + if ($this->_updateuser($uId, $this->getFieldName('password'), $this->sanitisePassword($password))) + return $password; + else + return false; + } + + function isReserved($username) { + if (in_array($username, $GLOBALS['reservedusers'])) { + return true; + } else { + return false; + } + } + + function isValidUsername($username) { + if (strlen($username) < 4) { + return false; + }elseif (strlen($username) > 24) { + // too long usernames are cut by database and may cause bugs when compared + return false; + } elseif (preg_match('/(\W)/', $username) > 0) { + // forbidden non-alphanumeric characters + return false; + } + return true; + } + + + + function isValidEmail($email) { + if (eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,6})$", $email)) { + list($emailUser, $emailDomain) = split("@", $email); + + // Check if the email domain has a DNS record + //if ($this->_checkdns($emailDomain)) { + return true; + //} + } + return false; + } + + /** + * Sets a session variable. + * Updates it when it is already set. + * This is used to detect if cookies work. + * + * @return void + * + * @see isSessionStable() + */ + function updateSessionStability() { + //find out if we have cookies enabled + if (!isset($_SESSION['sessionStable'])) { + $_SESSION['sessionStable'] = 0; + } else { + $_SESSION['sessionStable'] = 1; + } + } + + /** + * Tells you if the session is fresh or old. + * If the session is fresh, it's the first page + * call with that session id. If the session is old, + * we know that cookies (or session persistance) works + * + * @return boolean True if the + * + * @see updateSessionStability() + */ + function isSessionStable() { + return $_SESSION['sessionStable'] == 1; + } + + // Properties + function getTableName() { return $this->tablename; } + function setTableName($value) { $this->tablename = $value; } + + function getFieldName($field) { return $this->fields[$field]; } + function setFieldName($field, $value) { $this->fields[$field] = $value; } + + function getSessionKey() { return $this->sessionkey; } + function setSessionKey($value) { $this->sessionkey = $value; } + + function getCookieKey() { return $this->cookiekey; } + function setCookieKey($value) { $this->cookiekey = $value; } +} + + +/* Defines a user. Rare fields are filled if required. */ +class User { + + var $id; + var $username; + var $name; + var $email; + var $homepage; + var $content; + var $datetime; + var $isAdmin; + + function User($id, $username) { + $this->id = $id; + $this->username = $username; + } + + function getId() { + return $this->id; + } + + function getUsername() { + return $this->username; + } + + function getName() { + // Look for value only if not already set + if(!isset($this->name)) { + $userservice =& ServiceFactory::getServiceInstance('UserService'); + $user = $userservice->getUser($this->id); + $this->name = $user['name']; + } + return $this->name; + } + + function getEmail() { + // Look for value only if not already set + if(!isset($this->email)) { + $userservice =& ServiceFactory::getServiceInstance('UserService'); + $user = $userservice->getUser($this->id); + $this->email = $user['email']; + } + return $this->email; + } + + function getHomepage() { + // Look for value only if not already set + if(!isset($this->homepage)) { + $userservice =& ServiceFactory::getServiceInstance('UserService'); + $user = $userservice->getUser($this->id); + $this->homepage = $user['homepage']; + } + return $this->homepage; + } + + function getContent() { + // Look for value only if not already set + if(!isset($this->content)) { + $userservice =& ServiceFactory::getServiceInstance('UserService'); + $user = $userservice->getUser($this->id); + $this->content = $user['uContent']; + } + return $this->content; + } + + function getDatetime() { + // Look for value only if not already set + if(!isset($this->content)) { + $userservice =& ServiceFactory::getServiceInstance('UserService'); + $user = $userservice->getUser($this->id); + $this->datetime = $user['uDatetime']; + } + return $this->datetime; + } + + function isAdmin() { + // Look for value only if not already set + if(!isset($this->isAdmin)) { + $userservice =& ServiceFactory::getServiceInstance('UserService'); + $this->isAdmin = $userservice->isAdmin($this->id); + } + return $this->isAdmin; + } + + function getNbBookmarks($range = 'public') { + $bookmarkservice =& ServiceFactory::getServiceInstance('BookmarkService'); + return $bookmarkservice->countBookmarks($this->getId(), $range); + } +} +?> diff --git a/src/SemanticScuttle/constants.php b/src/SemanticScuttle/constants.php new file mode 100644 index 0000000..4940af8 --- /dev/null +++ b/src/SemanticScuttle/constants.php @@ -0,0 +1,62 @@ +<?php +/* + * Define constants used in all the application. + * Some constants are based on variables from configuration file. + */ + +// Debug managament +if(isset($GLOBALS['debugMode'])) { + define('DEBUG_MODE', $GLOBALS['debugMode']); + define('DEBUG_EXTRA', $GLOBALS['debugMode']); // Constant used exclusively into db/ directory +} + +// Determine the base URL as ROOT +if (!isset($GLOBALS['root'])) { + $pieces = explode('/', $_SERVER['SCRIPT_NAME']); + + $rootTmp = '/'; + foreach($pieces as $piece) { + //we eliminate possible sscuttle subfolders (like gsearch for example) + if ($piece != '' && !strstr($piece, '.php') && $piece != 'gsearch') { + $rootTmp .= $piece .'/'; + } + } + if (($rootTmp != '/') && (substr($rootTmp, -1, 1) != '/')) { + $rootTmp .= '/'; + } + + define('ROOT', 'http://'. $_SERVER['HTTP_HOST'] . $rootTmp); +} else { + define('ROOT', $GLOBALS['root']); +} + +// Error codes +define('GENERAL_MESSAGE', 200); +define('GENERAL_ERROR', 202); +define('CRITICAL_MESSAGE', 203); +define('CRITICAL_ERROR', 204); + +// Page name +define('PAGE_INDEX', "index"); +define('PAGE_BOOKMARKS', "bookmarks"); +define('PAGE_WATCHLIST', "watchlist"); + + +// Miscellanous + +// INSTALLATION_ID is based on directory DB and used as prefix (in session and cookie) to prevent mutual login for different installations on the same host server +define('INSTALLATION_ID', md5($GLOBALS['dbname'].$GLOBALS['tableprefix'])); + +// Correct bugs with PATH_INFO (maybe for Apache 1 or CGI) -- for 1&1 host... +if (isset($_SERVER['PATH_INFO']) && isset($_SERVER['ORIG_PATH_INFO'])) { + if(strlen($_SERVER["PATH_INFO"])<strlen($_SERVER["ORIG_PATH_INFO"])) { + $_SERVER["PATH_INFO"] = $_SERVER["ORIG_PATH_INFO"]; + } + if(strcasecmp($_SERVER["PATH_INFO"], $_SERVER["SCRIPT_NAME "]) == 0) { + unset($_SERVER["PATH_INFO"]); + } + if(strpos($_SERVER["PATH_INFO"], '.php') !== false) { + unset($_SERVER["PATH_INFO"]); + } +} +?> diff --git a/src/SemanticScuttle/db/db2.php b/src/SemanticScuttle/db/db2.php new file mode 100644 index 0000000..b1abf1a --- /dev/null +++ b/src/SemanticScuttle/db/db2.php @@ -0,0 +1,417 @@ +<?php +/** +* +* @package dbal_db2 +* @version $Id: db2.php,v 1.2 2005/06/10 08:52:03 devalley Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if(!defined("SQL_LAYER")) +{ + +define("SQL_LAYER","db2"); + +/** +* @package dbal_db2 +* DB2 Database Abstraction Layer +*/ +class sql_db +{ + + var $db_connect_id; + var $query_result; + var $query_resultset; + var $query_numrows; + var $next_id; + var $row = array(); + var $rowset = array(); + var $row_index; + var $num_queries = 0; + + // + // Constructor + // + function sql_db($sqlserver, $sqluser, $sqlpassword, $database, $persistency = true) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->password = $sqlpassword; + $this->dbname = $database; + + $this->server = $sqlserver; + + if($this->persistency) + { + $this->db_connect_id = odbc_pconnect($this->server, "", ""); + } + else + { + $this->db_connect_id = odbc_connect($this->server, "", ""); + } + + if($this->db_connect_id) + { + @odbc_autocommit($this->db_connect_id, off); + + return $this->db_connect_id; + } + else + { + return false; + } + } + // + // Other base methods + // + function sql_close() + { + if($this->db_connect_id) + { + if($this->query_result) + { + @odbc_free_result($this->query_result); + } + $result = @odbc_close($this->db_connect_id); + return $result; + } + else + { + return false; + } + } + + + // + // Query method + // + function sql_query($query = "", $transaction = FALSE) + { + // + // Remove any pre-existing queries + // + unset($this->query_result); + unset($this->row); + if($query != "") + { + $this->num_queries++; + + if(!eregi("^INSERT ",$query)) + { + if(eregi("LIMIT", $query)) + { + preg_match("/^(.*)LIMIT ([0-9]+)[, ]*([0-9]+)*/s", $query, $limits); + + $query = $limits[1]; + if($limits[3]) + { + $row_offset = $limits[2]; + $num_rows = $limits[3]; + } + else + { + $row_offset = 0; + $num_rows = $limits[2]; + } + + $query .= " FETCH FIRST ".($row_offset+$num_rows)." ROWS ONLY OPTIMIZE FOR ".($row_offset+$num_rows)." ROWS"; + + $this->query_result = odbc_exec($this->db_connect_id, $query); + + $query_limit_offset = $row_offset; + $this->result_numrows[$this->query_result] = $num_rows; + } + else + { + $this->query_result = odbc_exec($this->db_connect_id, $query); + + $row_offset = 0; + $this->result_numrows[$this->query_result] = 5E6; + } + + $result_id = $this->query_result; + if($this->query_result && eregi("^SELECT", $query)) + { + + for($i = 1; $i < odbc_num_fields($result_id)+1; $i++) + { + $this->result_field_names[$result_id][] = odbc_field_name($result_id, $i); + } + + $i = $row_offset + 1; + $k = 0; + while(odbc_fetch_row($result_id, $i) && $k < $this->result_numrows[$result_id]) + { + + for($j = 1; $j < count($this->result_field_names[$result_id])+1; $j++) + { + $this->result_rowset[$result_id][$k][$this->result_field_names[$result_id][$j-1]] = odbc_result($result_id, $j); + } + $i++; + $k++; + } + + $this->result_numrows[$result_id] = $k; + $this->row_index[$result_id] = 0; + } + else + { + $this->result_numrows[$result_id] = @odbc_num_rows($result_id); + $this->row_index[$result_id] = 0; + } + } + else + { + if(eregi("^(INSERT|UPDATE) ", $query)) + { + $query = preg_replace("/\\\'/s", "''", $query); + } + + $this->query_result = odbc_exec($this->db_connect_id, $query); + + if($this->query_result) + { + $sql_id = "VALUES(IDENTITY_VAL_LOCAL())"; + + $id_result = odbc_exec($this->db_connect_id, $sql_id); + if($id_result) + { + $row_result = odbc_fetch_row($id_result); + if($row_result) + { + $this->next_id[$this->query_result] = odbc_result($id_result, 1); + } + } + } + + odbc_commit($this->db_connect_id); + + $this->query_limit_offset[$this->query_result] = 0; + $this->result_numrows[$this->query_result] = 0; + } + + return $this->query_result; + } + else + { + return false; + } + } + + // + // Other query methods + // + function sql_numrows($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + return $this->result_numrows[$query_id]; + } + else + { + return false; + } + } + function sql_affectedrows($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + return $this->result_numrows[$query_id]; + } + else + { + return false; + } + } + function sql_numfields($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = count($this->result_field_names[$query_id]); + return $result; + } + else + { + return false; + } + } + function sql_fieldname($offset, $query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = $this->result_field_names[$query_id][$offset]; + return $result; + } + else + { + return false; + } + } + function sql_fieldtype($offset, $query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = @odbc_field_type($query_id, $offset); + return $result; + } + else + { + return false; + } + } + function sql_fetchrow($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + if($this->row_index[$query_id] < $this->result_numrows[$query_id]) + { + $result = $this->result_rowset[$query_id][$this->row_index[$query_id]]; + $this->row_index[$query_id]++; + return $result; + } + else + { + return false; + } + } + else + { + return false; + } + } + function sql_fetchrowset($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $this->row_index[$query_id] = $this->result_numrows[$query_id]; + return $this->result_rowset[$query_id]; + } + else + { + return false; + } + } + function sql_fetchfield($field, $row = -1, $query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + if($row < $this->result_numrows[$query_id]) + { + if($row == -1) + { + $getrow = $this->row_index[$query_id]-1; + } + else + { + $getrow = $row; + } + + return $this->result_rowset[$query_id][$getrow][$this->result_field_names[$query_id][$field]]; + + } + else + { + return false; + } + } + else + { + return false; + } + } + function sql_rowseek($offset, $query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $this->row_index[$query_id] = 0; + return true; + } + else + { + return false; + } + } + function sql_nextid($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + return $this->next_id[$query_id]; + } + else + { + return false; + } + } + function sql_freeresult($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = @odbc_free_result($query_id); + return $result; + } + else + { + return false; + } + } + function sql_error($query_id = 0) + { +// $result['code'] = @odbc_error($this->db_connect_id); +// $result['message'] = @odbc_errormsg($this->db_connect_id); + + return ""; + } + +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/firebird.php b/src/SemanticScuttle/db/firebird.php new file mode 100644 index 0000000..58a7d07 --- /dev/null +++ b/src/SemanticScuttle/db/firebird.php @@ -0,0 +1,527 @@ +<?php +/** +* +* @package dbal_firebird +* @version $Id: firebird.php,v 1.2 2005/06/10 08:52:03 devalley Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('SQL_LAYER')) +{ + +define('SQL_LAYER', 'firebird'); + +/** +* @package dbal_firebird +* Firebird/Interbase Database Abstraction Layer +* Minimum Requirement is Firebird 1.5+/Interbase 7.1+ +*/ +class sql_db +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = 0; + var $open_queries = array(); + + var $last_query_text = ''; + + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->db_connect_id = ($this->persistency) ? @ibase_pconnect($this->server . ':' . $this->dbname, $this->user, $sqlpassword, false, false, 3) : @ibase_connect($this->server . ':' . $this->dbname, $this->user, $sqlpassword, false, false, 3); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + // + // Other base methods + // + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + @ibase_commit($this->db_connect_id); + } + + if (sizeof($this->open_queries)) + { + foreach ($this->open_queries as $i_query_id => $query_id) + { + @ibase_free_query($query_id); + } + } + + return @ibase_close($this->db_connect_id); + } + + function sql_return_on_error($fail = false) + { + $this->return_on_error = $fail; + } + + function sql_num_queries() + { + return $this->num_queries; + } + + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + $this->transaction = true; + break; + + case 'commit': + $result = @ibase_commit(); + $this->transaction = false; + + if (!$result) + { + @ibase_rollback(); + } + break; + + case 'rollback': + $result = @ibase_rollback(); + $this->transaction = false; + break; + + default: + $result = true; + } + + return $result; + } + + // Base query method + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + $this->last_query_text = $query; + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + + if (!$this->query_result) + { + $this->num_queries++; + + if (($this->query_result = @ibase_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + // TODO: have to debug the commit states in firebird + if (!$this->transaction) + { + @ibase_commit_ret(); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + } + } + else + { + return false; + } + + return ($this->query_result) ? $this->query_result : false; + } + + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if ($query != '') + { + $this->query_result = false; + + $query = 'SELECT FIRST ' . $total . ((!empty($offset)) ? ' SKIP ' . $offset : '') . substr($query, 6); + + return $this->sql_query($query, $cache_ttl); + } + else + { + return false; + } + } + + // Idea for this from Ikonboard + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = array(); + $values = array(); + if ($query == 'INSERT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_null($var)) + { + $values[] = 'NULL'; + } + elseif (is_string($var)) + { + $values[] = "'" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? intval($var) : $var; + } + } + + $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')'; + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + if (is_null($var)) + { + $values[] = "$key = NULL"; + } + elseif (is_string($var)) + { + $values[] = "$key = '" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; + } + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + // Other query methods + // + // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ... + // don't want this here by a middle Milestone + function sql_numrows($query_id = false) + { + return FALSE; + } + + function sql_affectedrows() + { + // TODO: hmm, maybe doing something similar as in mssql-odbc.php? + return ($this->query_result) ? true : false; + } + + function sql_fetchrow($query_id = false) + { + global $cache; + + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + $row = array(); + $cur_row = @ibase_fetch_object($query_id, IBASE_TEXT); + + if (!$cur_row) + { + return false; + } + + foreach (get_object_vars($cur_row) as $key => $value) + { + $row[strtolower($key)] = trim(str_replace("\\0", "\0", str_replace("\\n", "\n", $value))); + } + return ($query_id) ? $row : false; + } + + function sql_fetchrowset($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + unset($this->rowset[$query_id]); + unset($this->row[$query_id]); + + $result = array(); + while ($this->rowset[$query_id] = get_object_vars(@ibase_fetch_object($query_id, IBASE_TEXT))) + { + $result[] = $this->rowset[$query_id]; + } + + return $result; + } + else + { + return false; + } + } + + function sql_fetchfield($field, $rownum = -1, $query_id = 0) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($rownum > -1) + { + // erm... ok, my bad, we always use zero. :/ + for ($i = 0; $i <= $rownum; $i++) + { + $row = $this->sql_fetchrow($query_id); + } + + return $row[$field]; + } + else + { + if (empty($this->row[$query_id]) && empty($this->rowset[$query_id])) + { + if ($this->sql_fetchrow($query_id)) + { + $result = $this->row[$query_id][$field]; + } + } + else + { + if ($this->rowset[$query_id]) + { + $result = $this->rowset[$query_id][$field]; + } + else if ($this->row[$query_id]) + { + $result = $this->row[$query_id][$field]; + } + } + } + return $result; + } + else + { + return false; + } + } + + function sql_rowseek($rownum, $query_id = 0) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + for($i = 1; $i < $rownum; $i++) + { + if (!$this->sql_fetchrow($query_id)) + { + return false; + } + } + + return true; + } + + function sql_nextid() + { + if ($this->query_result && preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename)) + { + $query = "SELECT GEN_ID('" . $tablename[1] . "_gen', 0) AS new_id + FROM RDB\$DATABASE"; + if (!($temp_q_id = @ibase_query($this->db_connect_id, $query))) + { + return false; + } + + $temp_result = @ibase_fetch_object($temp_q_id); + $this->sql_freeresult($temp_q_id); + + return ($temp_result) ? $temp_result->last_value : false; + } + } + + function sql_freeresult($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (!$this->transaction && $query_id) + { + @ibase_commit(); + } + + return ($query_id) ? @ibase_free_result($query_id) : false; + } + + function sql_escape($msg) + { + return (@ini_get('magic_quotes_sybase') || strtolower(@ini_get('magic_quotes_sybase')) == 'on') ? str_replace('\\\'', '\'', addslashes($msg)) : str_replace('\'', '\'\'', stripslashes($msg)); + } + + function sql_error($sql = '') + { + if (!$this->return_on_error) + { + $this_page =(!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; + $this_page .= '&' .((!empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : $_ENV['QUERY_STRING']); + + $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @ibase_errmsg() . '<br /><br /><u>CALLING PAGE</u><br /><br />' . $this_page .(($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />'; + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + trigger_error($message, E_USER_ERROR); + } + + $result['message'] = @ibase_errmsg(); + $result['code'] = ''; + + return $result; + } + + function sql_report($mode, $query = '') + { + if (empty($_GET['explain'])) + { + return; + } + + global $cache, $starttime, $phpbb_root_path; + static $curtime, $query_hold, $html_hold; + static $sql_report = ''; + static $cache_num_queries = 0; + + if (!$query && !empty($query_hold)) + { + $query = $query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8869-1"><meta http-equiv="Content-Style-Type" content="text/css"><link rel="stylesheet" href="' . $phpbb_root_path . 'adm/subSilver.css" type="text/css"><style type="text/css">' . "\n"; + echo 'th { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic3.gif\') }' . "\n"; + echo 'td.cat { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic1.gif\') }' . "\n"; + echo '</style><title>' . $msg_title . '</title></head><body>'; + echo '<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td><a href="' . htmlspecialchars(preg_replace('/&explain=([^&]*)/', '', $_SERVER['REQUEST_URI'])) . '"><img src="' . $phpbb_root_path . 'adm/images/header_left.jpg" width="200" height="60" alt="phpBB Logo" title="phpBB Logo" border="0"/></a></td><td width="100%" background="' . $phpbb_root_path . 'adm/images/header_bg.jpg" height="60" align="right" nowrap="nowrap"><span class="maintitle">SQL Report</span> </td></tr></table><br clear="all"/><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td height="40" align="center" valign="middle"><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries} queries" . (($cache_num_queries) ? " + $cache_num_queries " . (($cache_num_queries == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></td></tr><tr><td align="center" nowrap="nowrap">Time spent on MySQL queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></td></tr></table><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td>'; + echo $sql_report; + echo '</td></tr></table><br /></body></html>'; + exit; + break; + + case 'start': + $query_hold = $query; + $html_hold = ''; + + $curtime = explode(' ', microtime()); + $curtime = $curtime[0] + $curtime[1]; + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @ibase_query($this->db_connect_id, $query); + while ($void = @ibase_fetch_object($result, IBASE_TEXT)) + { + // Take the time spent on parsing rows into account + } + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $time_cache = $endtime - $curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query results obtained from the cache</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table><p align="center">'; + + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p>'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + @ibase_freeresult($result); + $cache_num_queries++; + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query #' . $this->num_queries . '</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table> ' . $html_hold . '<p align="center">'; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | '; + } + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>'; + } + else + { + $error = $this->sql_error(); + $sql_report .= '<b style="color: red">FAILED</b> - ' . SQL_LAYER . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $sql_report .= '</p>'; + + $this->sql_time += $endtime - $curtime; + break; + } + } + +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/index.htm b/src/SemanticScuttle/db/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/src/SemanticScuttle/db/index.htm @@ -0,0 +1,10 @@ +<html> +<head> +<title></title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +</head> + +<body bgcolor="#FFFFFF" text="#000000"> + +</body> +</html> diff --git a/src/SemanticScuttle/db/mssql-odbc.php b/src/SemanticScuttle/db/mssql-odbc.php new file mode 100644 index 0000000..a2d3d02 --- /dev/null +++ b/src/SemanticScuttle/db/mssql-odbc.php @@ -0,0 +1,576 @@ +<?php +/** +* +* @package dbal_odbc_mssql +* @version $Id: mssql-odbc.php,v 1.2 2005/06/10 08:52:03 devalley Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('SQL_LAYER')) +{ + +define('SQL_LAYER', 'mssql-odbc'); + +/** +* @package dbal_odbc_mssql +* MSSQL ODBC Database Abstraction Layer for MSSQL +* Minimum Requirement is Version 2000+ +*/ +class sql_db +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = 0; + var $open_queries = array(); + + var $result_rowset = array(); + var $field_names = array(); + var $field_types = array(); + var $num_rows = array(); + var $current_row = array(); + + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->db_connect_id = ($this->persistency) ? @odbc_pconnect($this->server, $this->user, $sqlpassword) : @odbc_connect($this->server, $this->user, $sqlpassword); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + // + // Other base methods + // + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + @odbc_commit($this->db_connect_id); + } + + if (sizeof($this->result_rowset)) + { + unset($this->result_rowset); + unset($this->field_names); + unset($this->field_types); + unset($this->num_rows); + unset($this->current_row); + } + + if (sizeof($this->open_queries)) + { + foreach ($this->open_queries as $i_query_id => $query_id) + { + @odbc_free_result($query_id); + } + } + + return @odbc_close($this->db_connect_id); + } + + function sql_return_on_error($fail = false) + { + $this->return_on_error = $fail; + } + + function sql_num_queries() + { + return $this->num_queries; + } + + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + $result = @odbc_autocommit($this->db_connect_id, false); + $this->transaction = true; + break; + + case 'commit': + $result = @odbc_commit($this->db_connect_id); + @odbc_autocommit($this->db_connect_id, true); + $this->transaction = false; + + if (!$result) + { + @odbc_rollback($this->db_connect_id); + @odbc_autocommit($this->db_connect_id, true); + } + break; + + case 'rollback': + $result = @odbc_rollback($this->db_connect_id); + @odbc_autocommit($this->db_connect_id, true); + $this->transaction = false; + break; + + default: + $result = true; + } + + return $result; + } + + // Base query method + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + + if (!$this->query_result) + { + $this->num_queries++; + + if (($this->query_result = $this->_odbc_execute_query($query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + // odbc_free_result called within sql_save() + } + else if (strpos($query, 'SELECT') !== false && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return ($this->query_result) ? $this->query_result : false; + } + + function _odbc_execute_query($query) + { + $result = false; + + if (eregi("^SELECT ", $query)) + { + $result = @odbc_exec($this->db_connect_id, $query); + + if ($result) + { + if (empty($this->field_names[$result])) + { + for ($i = 1, $j = @odbc_num_fields($result) + 1; $i < $j; $i++) + { + $this->field_names[$result][] = @odbc_field_name($result, $i); + $this->field_types[$result][] = @odbc_field_type($result, $i); + } + } + + $this->current_row[$result] = 0; + $this->result_rowset[$result] = array(); + + $row_outer = (isset($row_offset)) ? $row_offset + 1 : 1; + $row_outer_max = (isset($num_rows)) ? $row_offset + $num_rows + 1 : 1E9; + $row_inner = 0; + + while (@odbc_fetch_row($result, $row_outer) && $row_outer < $row_outer_max) + { + for ($i = 0, $j = sizeof($this->field_names[$result]); $i < $j; $i++) + { + $this->result_rowset[$result][$row_inner][$this->field_names[$result][$i]] = stripslashes(@odbc_result($result, $i + 1)); + } + + $row_outer++; + $row_inner++; + } + + $this->num_rows[$result] = sizeof($this->result_rowset[$result]); + } + } + else if (eregi("^INSERT ", $query)) + { + $result = @odbc_exec($this->db_connect_id, $query); + + if ($result) + { + $result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY'); + if ($result_id) + { + if (@odbc_fetch_row($result_id)) + { + $this->next_id[$this->db_connect_id] = @odbc_result($result_id, 1); + $this->affected_rows[$this->db_connect_id] = @odbc_num_rows($result); + } + } + } + } + else + { + $result = @odbc_exec($this->db_connect_id, $query); + + if ($result) + { + $this->affected_rows[$this->db_connect_id] = @odbc_num_rows($result); + } + } + + return $result; + } + + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if ($query != '') + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = -1; + } + + $row_offset = ($total) ? $offset : ''; + $num_rows = ($total) ? $total : $offset; + + $query = 'SELECT TOP ' . ($row_offset + $num_rows) . ' ' . substr($query, 6); + + return $this->sql_query($query, $cache_ttl); + } + else + { + return false; + } + } + + // Idea for this from Ikonboard + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = array(); + $values = array(); + if ($query == 'INSERT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_null($var)) + { + $values[] = 'NULL'; + } + elseif (is_string($var)) + { + $values[] = "'" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? intval($var) : $var; + } + } + + $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')'; + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + if (is_null($var)) + { + $values[] = "$key = NULL"; + } + elseif (is_string($var)) + { + $values[] = "$key = '" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; + } + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + // Other query methods + // + // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ... + // don't want this here by a middle Milestone + function sql_numrows($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @$this->num_rows($query_id) : false; + } + + function sql_affectedrows() + { + return ($this->affected_rows[$this->db_connect_id]) ? $this->affected_rows[$this->db_connect_id] : false; + } + + function sql_fetchrow($query_id = false) + { + global $cache; + + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + return ($this->num_rows[$query_id] && $this->current_row[$query_id] < $this->num_rows[$query_id]) ? $this->result_rowset[$query_id][$this->current_row[$query_id]++] : false; + } + + function sql_fetchrowset($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($this->num_rows[$query_id]) ? $this->result_rowset[$query_id] : false; + } + + function sql_fetchfield($field, $rownum = -1, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($rownum < $this->num_rows[$query_id]) + { + $getrow = ($rownum == -1) ? $this->current_row[$query_id] - 1 : $rownum; + + return $this->result_rowset[$query_id][$getrow][$this->field_names[$query_id][$field]]; + } + } + + return false; + } + + function sql_rowseek($rownum, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($this->current_row[$query_id])) + { + $this->current_row[$query_id] = $rownum; + return true; + } + + return false; + } + + function sql_nextid() + { + return ($this->next_id[$this->db_connect_id]) ? $this->next_id[$this->db_connect_id] : false; + } + + function sql_freeresult($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + unset($this->num_rows[$query_id]); + unset($this->current_row[$query_id]); + unset($this->result_rowset[$query_id]); + unset($this->field_names[$query_id]); + unset($this->field_types[$query_id]); + + return @odbc_free_result($query_id); + } + + return false; + } + + function sql_escape($msg) + { + return str_replace("'", "''", str_replace('\\', '\\\\', $msg)); + } + + function sql_error($sql = '') + { + if (!$this->return_on_error) + { + $this_page = (isset($_SERVER['PHP_SELF']) && !empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; + $this_page .= '&' . ((isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : (isset($_ENV['QUERY_STRING']) ? $_ENV['QUERY_STRING'] : '')); + + $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @odbc_errormsg() . '<br /><br /><u>CALLING PAGE</u><br /><br />' . htmlspecialchars($this_page) . (($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />'; + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + trigger_error($message, E_USER_ERROR); + } + + $result = array( + 'message' => @odbc_errormsg(), + 'code' => @odbc_error() + ); + + return $result; + } + + function sql_report($mode, $query = '') + { + if (empty($_GET['explain'])) + { + return; + } + + global $cache, $starttime, $phpbb_root_path; + static $curtime, $query_hold, $html_hold; + static $sql_report = ''; + static $cache_num_queries = 0; + + if (!$query && !empty($query_hold)) + { + $query = $query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8869-1"><meta http-equiv="Content-Style-Type" content="text/css"><link rel="stylesheet" href="' . $phpbb_root_path . 'adm/subSilver.css" type="text/css"><style type="text/css">' . "\n"; + echo 'th { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic3.gif\') }' . "\n"; + echo 'td.cat { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic1.gif\') }' . "\n"; + echo '</style><title>' . $msg_title . '</title></head><body>'; + echo '<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td><a href="' . htmlspecialchars(preg_replace('/&explain=([^&]*)/', '', $_SERVER['REQUEST_URI'])) . '"><img src="' . $phpbb_root_path . 'adm/images/header_left.jpg" width="200" height="60" alt="phpBB Logo" title="phpBB Logo" border="0"/></a></td><td width="100%" background="' . $phpbb_root_path . 'adm/images/header_bg.jpg" height="60" align="right" nowrap="nowrap"><span class="maintitle">SQL Report</span> </td></tr></table><br clear="all"/><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td height="40" align="center" valign="middle"><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries} queries" . (($cache_num_queries) ? " + $cache_num_queries " . (($cache_num_queries == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></td></tr><tr><td align="center" nowrap="nowrap">Time spent on MySQL queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></td></tr></table><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td>'; + echo $sql_report; + echo '</td></tr></table><br /></body></html>'; + exit; + break; + + case 'start': + $query_hold = $query; + $html_hold = ''; + + $curtime = explode(' ', microtime()); + $curtime = $curtime[0] + $curtime[1]; + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = $this->_odbc_execute_query($query); + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $time_cache = $endtime - $curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query results obtained from the cache</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table><p align="center">'; + + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p>'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + @odbc_free_result($result); + $cache_num_queries++; + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query #' . $this->num_queries . '</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table> ' . $html_hold . '<p align="center">'; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | '; + } + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>'; + } + else + { + $error = $this->sql_error(); + $sql_report .= '<b style="color: red">FAILED</b> - ' . SQL_LAYER . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $sql_report .= '</p>'; + + $this->sql_time += $endtime - $curtime; + break; + } + } + +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/mssql.php b/src/SemanticScuttle/db/mssql.php new file mode 100644 index 0000000..2b17b9e --- /dev/null +++ b/src/SemanticScuttle/db/mssql.php @@ -0,0 +1,551 @@ +<?php +/** +* +* @package dbal_mssql +* @version $Id: mssql.php,v 1.2 2005/06/10 08:52:03 devalley Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('SQL_LAYER')) +{ + +define('SQL_LAYER', 'mssql'); + +/** +* @package dbal_mssql +* MSSQL Database Abstraction Layer +* Minimum Requirement is MSSQL 2000+ +*/ +class sql_db +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = 0; + var $open_queries = array(); + + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->db_connect_id = ($this->persistency) ? @mssql_pconnect($this->server, $this->user, $sqlpassword) : @mssql_connect($this->server, $this->user, $sqlpassword); + + if ($this->db_connect_id && $this->dbname != '') + { + if (!@mssql_select_db($this->dbname, $this->db_connect_id)) + { + @mssql_close($this->db_connect_id); + return false; + } + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + @mssql_query('COMMIT', $this->db_connect_id); + } + + if (sizeof($this->open_queries)) + { + foreach ($this->open_queries as $i_query_id => $query_id) + { + @mssql_free_result($query_id); + } + } + + return @mssql_close($this->db_connect_id); + } + + function sql_return_on_error($fail = false) + { + $this->return_on_error = $fail; + } + + function sql_num_queries() + { + return $this->num_queries; + } + + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + $result = @mssql_query('BEGIN TRANSACTION', $this->db_connect_id); + $this->transaction = true; + break; + + case 'commit': + $result = @mssql_query('commit', $this->db_connect_id); + $this->transaction = false; + + if (!$result) + { + @mssql_query('ROLLBACK', $this->db_connect_id); + } + break; + + case 'rollback': + $result = @mssql_query('ROLLBACK', $this->db_connect_id); + $this->transaction = false; + break; + + default: + $result = true; + } + + return $result; + } + + // Base query method + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + + if (!$this->query_result) + { + $this->num_queries++; + + if (($this->query_result = @mssql_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + // sql_freeresult called within sql_save() + } + else if (strpos($query, 'SELECT') !== false && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return ($this->query_result) ? $this->query_result : false; + } + + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if ($query != '') + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = -1; + } + + $row_offset = ($total) ? $offset : ''; + $num_rows = ($total) ? $total : $offset; + + $query = 'SELECT TOP ' . ($row_offset + $num_rows) . ' ' . substr($query, 6); + + return $this->sql_query($query, $cache_ttl); + } + else + { + return false; + } + } + + // Idea for this from Ikonboard + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = array(); + $values = array(); + if ($query == 'INSERT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_null($var)) + { + $values[] = 'NULL'; + } + elseif (is_string($var)) + { + $values[] = "'" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? intval($var) : $var; + } + } + + $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')'; + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + if (is_null($var)) + { + $values[] = "$key = NULL"; + } + elseif (is_string($var)) + { + $values[] = "$key = '" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; + } + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + // Other query methods + // + // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ... + // don't want this here by a middle Milestone + function sql_numrows($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + +// return (isset($this->limit_offset[$query_id])) ? @mssql_num_rows($query_id) - $this->limit_offset[$query_id] : @mssql_num_rows($query_id); + return ($query_id) ? @mssql_num_rows($query_id) : false; + } + + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mssql_rows_affected($this->db_connect_id) : false; + } + + function sql_fetchrow($query_id = false) + { + global $cache; + + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + $row = @mssql_fetch_array($query_id, MSSQL_ASSOC); + + if ($row) + { + foreach ($row as $key => $value) + { + $row[$key] = ($value === ' ') ? trim($value) : $value; + } + } + + return $row; + } + + function sql_fetchrowset($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + unset($this->rowset[$query_id]); + unset($this->row[$query_id]); + + $result = array(); + while ($this->rowset[$query_id] = $this->sql_fetchrow($query_id)) + { + $result[] = $this->rowset[$query_id]; + } + return $result; + } + + return false; + } + + function sql_fetchfield($field, $rownum = -1, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($rownum > -1) + { +// (!empty($this->limit_offset[$query_id])) ? @mssql_data_seek($query_id, ($this->limit_offset[$query_id] + $rownum)) : @mssql_data_seek($query_id, $rownum); + @mssql_data_seek($query_id, $rownum); + $row = @mssql_fetch_array($query_id, MSSQL_ASSOC); + $result = isset($row[$field]) ? $row[$field] : false; + } + else + { + if (empty($this->row[$query_id]) && empty($this->rowset[$query_id])) + { + if ($this->sql_fetchrow($query_id)) + { + $result = $this->row[$query_id][$field]; + } + } + else + { + if ($this->rowset[$query_id]) + { + $result = $this->rowset[$query_id][$field]; + } + elseif ($this->row[$query_id]) + { + $result = $this->row[$query_id][$field]; + } + } + } + + return $result; + } + + return false; + } + + function sql_rowseek($rownum, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($this->current_row[$query_id])) + { +// (!empty($this->limit_offset[$query_id])) ? @mssql_data_seek($query_id, ($this->limit_offset[$query_id] + $rownum)) : @mssql_data_seek($query_id, $rownum); + @mssql_data_seek($query_id, $rownum); + return true; + } + + return false; + } + + function sql_nextid() + { + $result_id = @mssql_query('SELECT @@IDENTITY', $this->db_connect_id); + if ($result_id) + { + if (@mssql_fetch_array($result_id, MSSQL_ASSOC)) + { + return @mssql_result($result_id, 1); + } + } + + return false; + } + + function sql_freeresult($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($this->open_queries[$query_id])) + { + unset($this->open_queries[$query_id]); + unset($this->result_rowset[$query_id]); + + return @mssql_free_result($query_id); + } + + return false; + } + + function sql_escape($msg) + { + return str_replace("'", "''", str_replace('\\', '\\\\', $msg)); + } + + function sql_error($sql = '') + { + if (!$this->return_on_error) + { + $this_page = (isset($_SERVER['PHP_SELF']) && !empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; + $this_page .= '&' . ((isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : (isset($_ENV['QUERY_STRING']) ? $_ENV['QUERY_STRING'] : '')); + + $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @mssql_get_last_message() . '<br /><br /><u>CALLING PAGE</u><br /><br />' . htmlspecialchars($this_page) . (($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />'; + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + trigger_error($message, E_USER_ERROR); + } + + $result = array( + 'message' => @mssql_get_last_message($this->db_connect_id), + 'code' => '' + ); + + return $result; + } + + function sql_report($mode, $query = '') + { + if (empty($_GET['explain'])) + { + return; + } + + global $cache, $starttime, $phpbb_root_path; + static $curtime, $query_hold, $html_hold; + static $sql_report = ''; + static $cache_num_queries = 0; + + if (!$query && !empty($query_hold)) + { + $query = $query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8869-1"><meta http-equiv="Content-Style-Type" content="text/css"><link rel="stylesheet" href="' . $phpbb_root_path . 'adm/subSilver.css" type="text/css"><style type="text/css">' . "\n"; + echo 'th { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic3.gif\') }' . "\n"; + echo 'td.cat { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic1.gif\') }' . "\n"; + echo '</style><title>' . $msg_title . '</title></head><body>'; + echo '<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td><a href="' . htmlspecialchars(preg_replace('/&explain=([^&]*)/', '', $_SERVER['REQUEST_URI'])) . '"><img src="' . $phpbb_root_path . 'adm/images/header_left.jpg" width="200" height="60" alt="phpBB Logo" title="phpBB Logo" border="0"/></a></td><td width="100%" background="' . $phpbb_root_path . 'adm/images/header_bg.jpg" height="60" align="right" nowrap="nowrap"><span class="maintitle">SQL Report</span> </td></tr></table><br clear="all"/><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td height="40" align="center" valign="middle"><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries} queries" . (($cache_num_queries) ? " + $cache_num_queries " . (($cache_num_queries == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></td></tr><tr><td align="center" nowrap="nowrap">Time spent on MySQL queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></td></tr></table><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td>'; + echo $sql_report; + echo '</td></tr></table><br /></body></html>'; + exit; + break; + + case 'start': + $query_hold = $query; + $html_hold = ''; + + $curtime = explode(' ', microtime()); + $curtime = $curtime[0] + $curtime[1]; + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mssql_query($query, $this->db_connect_id); + while ($void = @mssql_fetch_array($result, MSSQL_ASSOC)) + { + // Take the time spent on parsing rows into account + } + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $time_cache = $endtime - $curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query results obtained from the cache</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table><p align="center">'; + + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p>'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + @mssql_free_result($result); + $cache_num_queries++; + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query #' . $this->num_queries . '</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table> ' . $html_hold . '<p align="center">'; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | '; + } + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>'; + } + else + { + $error = $this->sql_error(); + $sql_report .= '<b style="color: red">FAILED</b> - ' . SQL_LAYER . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $sql_report .= '</p>'; + + $this->sql_time += $endtime - $curtime; + break; + } + } + +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/mysql.php b/src/SemanticScuttle/db/mysql.php new file mode 100644 index 0000000..a646e0d --- /dev/null +++ b/src/SemanticScuttle/db/mysql.php @@ -0,0 +1,552 @@ +<?php +/** +* +* @package dbal_mysql +* @version $Id: mysql.php,v 1.5 2006/02/10 01:30:19 scronide Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('SQL_LAYER')) +{ + +define('SQL_LAYER', 'mysql'); + +/** +* @package dbal_mysql +* MySQL Database Abstraction Layer +* Minimum Requirement is 3.23+/4.0+/4.1+ +*/ +class sql_db +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = 0; + var $open_queries = array(); + + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->db_connect_id = ($this->persistency) ? @mysql_pconnect($this->server, $this->user, $sqlpassword) : @mysql_connect($this->server, $this->user, $sqlpassword); + + if ($this->db_connect_id && $this->dbname != '') + { + if (@mysql_select_db($this->dbname)) + { + return $this->db_connect_id; + } + } + + return $this->sql_error(''); + } + + // + // Other base methods + // + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if (sizeof($this->open_queries)) + { + foreach ($this->open_queries as $i_query_id => $query_id) + { + @mysql_free_result($query_id); + } + } + + return @mysql_close($this->db_connect_id); + } + + function sql_return_on_error($fail = false) + { + $this->return_on_error = $fail; + } + + function sql_num_queries() + { + return $this->num_queries; + } + + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + $result = @mysql_query('BEGIN', $this->db_connect_id); + $this->transaction = true; + break; + + case 'commit': + $result = @mysql_query('COMMIT', $this->db_connect_id); + $this->transaction = false; + + if (!$result) + { + @mysql_query('ROLLBACK', $this->db_connect_id); + } + break; + + case 'rollback': + $result = @mysql_query('ROLLBACK', $this->db_connect_id); + $this->transaction = false; + break; + + default: + $result = true; + } + + return $result; + } + + // Base query method + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + + if (!$this->query_result) + { + $this->num_queries++; + + if (($this->query_result = @mysql_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + // mysql_free_result called within sql_save() + } + else if (strpos($query, 'SELECT') !== false && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return ($this->query_result) ? $this->query_result : false; + } + + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) { + if ($query != '') { + $this->query_result = false; + + // only limit the number of rows if $total is greater than 0 + if ($total > 0) + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } else { + return false; + } + } + + // Idea for this from Ikonboard + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = array(); + $values = array(); + if ($query == 'INSERT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_null($var)) + { + $values[] = 'NULL'; + } + elseif (is_string($var)) + { + $values[] = "'" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? intval($var) : $var; + } + } + + $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')'; + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + if (is_null($var)) + { + $values[] = "$key = NULL"; + } + elseif (is_string($var)) + { + $values[] = "$key = '" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; + } + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + // Other query methods + // + // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ... + // don't want this here by a middle Milestone + function sql_numrows($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @mysql_num_rows($query_id) : false; + } + + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mysql_affected_rows($this->db_connect_id) : false; + } + + function sql_fetchrow($query_id = false) + { + global $cache; + + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id) ? @mysql_fetch_assoc($query_id) : false; + } + + function sql_fetchrowset($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + unset($this->rowset[$query_id]); + unset($this->row[$query_id]); + + $result = array(); + while ($this->rowset[$query_id] = $this->sql_fetchrow($query_id)) + { + $result[] = $this->rowset[$query_id]; + } + return $result; + } + + return false; + } + + function sql_fetchfield($field, $rownum = -1, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($rownum > -1) + { + $result = @mysql_result($query_id, $rownum, $field); + } + else + { + if (empty($this->row[$query_id]) && empty($this->rowset[$query_id])) + { + if ($this->sql_fetchrow($query_id)) + { + $result = $this->row[$query_id][$field]; + } + } + else + { + if ($this->rowset[$query_id]) + { + $result = $this->rowset[$query_id][$field]; + } + elseif ($this->row[$query_id]) + { + $result = $this->row[$query_id][$field]; + } + } + } + return $result; + } + return false; + } + + function sql_rowseek($rownum, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @mysql_data_seek($query_id, $rownum) : false; + } + + function sql_nextid() + { + return ($this->db_connect_id) ? @mysql_insert_id($this->db_connect_id) : false; + } + + function sql_freeresult($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @mysql_free_result($query_id); + } + + return false; + } + + function sql_escape($msg) { + if (function_exists('mysql_real_escape_string')) { + return @mysql_real_escape_string($msg, $this->db_connect_id); + } else { + return mysql_escape_string($msg); + } + } + + function sql_error($sql = '') + { + if (!$this->return_on_error) + { + $this_page = (isset($_SERVER['PHP_SELF']) && !empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; + $this_page .= '&' . ((isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : (isset($_ENV['QUERY_STRING']) ? $_ENV['QUERY_STRING'] : '')); + + $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @mysql_error() . '<br /><br /><u>CALLING PAGE</u><br /><br />' . htmlspecialchars($this_page) . (($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />'; + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + trigger_error($message, E_USER_ERROR); + } + + $result = array( + 'message' => @mysql_error(), + 'code' => @mysql_errno() + ); + + return $result; + } + + function sql_report($mode, $query = '') + { + if (empty($_GET['explain'])) + { + return; + } + + global $db, $cache, $starttime, $phpbb_root_path; + static $curtime, $query_hold, $html_hold; + static $sql_report = ''; + static $cache_num_queries = 0; + + if (!$query && !empty($query_hold)) + { + $query = $query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $db->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8869-1"><meta http-equiv="Content-Style-Type" content="text/css"><link rel="stylesheet" href="' . $phpbb_root_path . 'adm/subSilver.css" type="text/css"><style type="text/css">' . "\n"; + echo 'th { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic3.gif\') }' . "\n"; + echo 'td.cat { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic1.gif\') }' . "\n"; + echo '</style><title>' . $msg_title . '</title></head><body>'; + echo '<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td><a href="' . htmlspecialchars(preg_replace('/&explain=([^&]*)/', '', $_SERVER['REQUEST_URI'])) . '"><img src="' . $phpbb_root_path . 'adm/images/header_left.jpg" width="200" height="60" alt="phpBB Logo" title="phpBB Logo" border="0"/></a></td><td width="100%" background="' . $phpbb_root_path . 'adm/images/header_bg.jpg" height="60" align="right" nowrap="nowrap"><span class="maintitle">SQL Report</span> </td></tr></table><br clear="all"/><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td height="40" align="center" valign="middle"><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries} queries" . (($cache_num_queries) ? " + $cache_num_queries " . (($cache_num_queries == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></td></tr><tr><td align="center" nowrap="nowrap">Time spent on MySQL queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></td></tr></table><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td>'; + echo $sql_report; + echo '</td></tr></table><br /></body></html>'; + exit; + break; + + case 'start': + $query_hold = $query; + $html_hold = ''; + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + elseif (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = FALSE; + + if ($result = mysql_query("EXPLAIN $explain_query", $this->db_connect_id)) + { + while ($row = mysql_fetch_assoc($result)) + { + if (!$html_table && sizeof($row)) + { + $html_table = TRUE; + $html_hold .= '<table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0" align="center"><tr>'; + + foreach (array_keys($row) as $val) + { + $html_hold .= '<th nowrap="nowrap">' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '</th>'; + } + $html_hold .= '</tr>'; + } + $html_hold .= '<tr>'; + + $class = 'row1'; + foreach (array_values($row) as $val) + { + $class = ($class == 'row1') ? 'row2' : 'row1'; + $html_hold .= '<td class="' . $class . '">' . (($val) ? $val : ' ') . '</td>'; + } + $html_hold .= '</tr>'; + } + } + + if ($html_table) + { + $html_hold .= '</table>'; + } + } + + $curtime = explode(' ', microtime()); + $curtime = $curtime[0] + $curtime[1]; + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = mysql_query($query, $this->db_connect_id); + while ($void = mysql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $time_cache = $endtime - $curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query results obtained from the cache</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table><p align="center">'; + + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p>'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + mysql_free_result($result); + $cache_num_queries++; + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query #' . $this->num_queries . '</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table> ' . $html_hold . '<p align="center">'; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | '; + } + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>'; + } + else + { + $error = $this->sql_error(); + $sql_report .= '<b style="color: red">FAILED</b> - MySQL Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $sql_report .= '</p>'; + + $this->sql_time += $endtime - $curtime; + break; + } + } +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/mysql4.php b/src/SemanticScuttle/db/mysql4.php new file mode 100644 index 0000000..0639518 --- /dev/null +++ b/src/SemanticScuttle/db/mysql4.php @@ -0,0 +1,552 @@ +<?php +/** +* +* @package dbal_mysql4 +* @version $Id: mysql4.php,v 1.4 2006/02/10 01:30:19 scronide Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('SQL_LAYER')) +{ + +define('SQL_LAYER', 'mysql4'); + +/** +* @package dbal_mysql4 +* MySQL4 Database Abstraction Layer +* Minimum Requirement is 4.0+/4.1+ +*/ +class sql_db +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = 0; + var $open_queries = array(); + + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->db_connect_id = ($this->persistency) ? @mysql_pconnect($this->server, $this->user, $sqlpassword) : @mysql_connect($this->server, $this->user, $sqlpassword); + + if ($this->db_connect_id && $this->dbname != '') + { + if (@mysql_select_db($this->dbname)) + { + return $this->db_connect_id; + } + } + + return $this->sql_error(''); + } + + // + // Other base methods + // + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if (sizeof($this->open_queries)) + { + foreach ($this->open_queries as $i_query_id => $query_id) + { + @mysql_free_result($query_id); + } + } + + return @mysql_close($this->db_connect_id); + } + + function sql_return_on_error($fail = false) + { + $this->return_on_error = $fail; + } + + function sql_num_queries() + { + return $this->num_queries; + } + + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + $result = @mysql_query('BEGIN', $this->db_connect_id); + $this->transaction = true; + break; + + case 'commit': + $result = @mysql_query('COMMIT', $this->db_connect_id); + $this->transaction = false; + + if (!$result) + { + @mysql_query('ROLLBACK', $this->db_connect_id); + } + break; + + case 'rollback': + $result = @mysql_query('ROLLBACK', $this->db_connect_id); + $this->transaction = false; + break; + + default: + $result = true; + } + + return $result; + } + + // Base query method + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + + if (!$this->query_result) + { + $this->num_queries++; + + if (($this->query_result = @mysql_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $cache->sql_save($query, $this->query_result, $cache_ttl); + // mysql_free_result called within sql_save() + } + else if (strpos($query, 'SELECT') !== false && $this->query_result) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return ($this->query_result) ? $this->query_result : false; + } + + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) { + if ($query != '') { + $this->query_result = false; + + // only limit the number of rows if $total is greater than 0 + if ($total > 0) + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } else { + return false; + } + } + + // Idea for this from Ikonboard + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = array(); + $values = array(); + if ($query == 'INSERT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_null($var)) + { + $values[] = 'NULL'; + } + elseif (is_string($var)) + { + $values[] = "'" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? intval($var) : $var; + } + } + + $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')'; + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + if (is_null($var)) + { + $values[] = "$key = NULL"; + } + elseif (is_string($var)) + { + $values[] = "$key = '" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; + } + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + // Other query methods + // + // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ... + // don't want this here by a middle Milestone + function sql_numrows($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @mysql_num_rows($query_id) : false; + } + + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mysql_affected_rows($this->db_connect_id) : false; + } + + function sql_fetchrow($query_id = false) + { + global $cache; + + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id) ? @mysql_fetch_assoc($query_id) : false; + } + + function sql_fetchrowset($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + unset($this->rowset[$query_id]); + unset($this->row[$query_id]); + + $result = array(); + while ($this->rowset[$query_id] = $this->sql_fetchrow($query_id)) + { + $result[] = $this->rowset[$query_id]; + } + return $result; + } + + return false; + } + + function sql_fetchfield($field, $rownum = -1, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($rownum > -1) + { + $result = @mysql_result($query_id, $rownum, $field); + } + else + { + if (empty($this->row[$query_id]) && empty($this->rowset[$query_id])) + { + if ($this->sql_fetchrow($query_id)) + { + $result = $this->row[$query_id][$field]; + } + } + else + { + if ($this->rowset[$query_id]) + { + $result = $this->rowset[$query_id][$field]; + } + elseif ($this->row[$query_id]) + { + $result = $this->row[$query_id][$field]; + } + } + } + return $result; + } + return false; + } + + function sql_rowseek($rownum, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @mysql_data_seek($query_id, $rownum) : false; + } + + function sql_nextid() + { + return ($this->db_connect_id) ? @mysql_insert_id($this->db_connect_id) : false; + } + + function sql_freeresult($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return @mysql_free_result($query_id); + } + + return false; + } + + function sql_escape($msg) { + if (function_exists('mysql_real_escape_string')) { + return @mysql_real_escape_string($msg, $this->db_connect_id); + } else { + return mysql_escape_string($msg); + } + } + + function sql_error($sql = '') + { + if (!$this->return_on_error) + { + $this_page = (isset($_SERVER['PHP_SELF']) && !empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; + $this_page .= '&' . ((isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : (isset($_ENV['QUERY_STRING']) ? $_ENV['QUERY_STRING'] : '')); + + $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @mysql_error() . '<br /><br /><u>CALLING PAGE</u><br /><br />' . htmlspecialchars($this_page) . (($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />'; + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + trigger_error($message, E_USER_ERROR); + } + + $result = array( + 'message' => @mysql_error(), + 'code' => @mysql_errno() + ); + + return $result; + } + + function sql_report($mode, $query = '') + { + if (empty($_GET['explain'])) + { + return; + } + + global $db, $cache, $starttime, $phpbb_root_path; + static $curtime, $query_hold, $html_hold; + static $sql_report = ''; + static $cache_num_queries = 0; + + if (!$query && !empty($query_hold)) + { + $query = $query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $db->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8869-1"><meta http-equiv="Content-Style-Type" content="text/css"><link rel="stylesheet" href="' . $phpbb_root_path . 'adm/subSilver.css" type="text/css"><style type="text/css">' . "\n"; + echo 'th { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic3.gif\') }' . "\n"; + echo 'td.cat { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic1.gif\') }' . "\n"; + echo '</style><title>' . $msg_title . '</title></head><body>'; + echo '<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td><a href="' . htmlspecialchars(preg_replace('/&explain=([^&]*)/', '', $_SERVER['REQUEST_URI'])) . '"><img src="' . $phpbb_root_path . 'adm/images/header_left.jpg" width="200" height="60" alt="phpBB Logo" title="phpBB Logo" border="0"/></a></td><td width="100%" background="' . $phpbb_root_path . 'adm/images/header_bg.jpg" height="60" align="right" nowrap="nowrap"><span class="maintitle">SQL Report</span> </td></tr></table><br clear="all"/><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td height="40" align="center" valign="middle"><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries} queries" . (($cache_num_queries) ? " + $cache_num_queries " . (($cache_num_queries == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></td></tr><tr><td align="center" nowrap="nowrap">Time spent on MySQL queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></td></tr></table><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td>'; + echo $sql_report; + echo '</td></tr></table><br /></body></html>'; + exit; + break; + + case 'start': + $query_hold = $query; + $html_hold = ''; + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + elseif (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = FALSE; + + if ($result = mysql_query("EXPLAIN $explain_query", $this->db_connect_id)) + { + while ($row = mysql_fetch_assoc($result)) + { + if (!$html_table && sizeof($row)) + { + $html_table = TRUE; + $html_hold .= '<table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0" align="center"><tr>'; + + foreach (array_keys($row) as $val) + { + $html_hold .= '<th nowrap="nowrap">' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '</th>'; + } + $html_hold .= '</tr>'; + } + $html_hold .= '<tr>'; + + $class = 'row1'; + foreach (array_values($row) as $val) + { + $class = ($class == 'row1') ? 'row2' : 'row1'; + $html_hold .= '<td class="' . $class . '">' . (($val) ? $val : ' ') . '</td>'; + } + $html_hold .= '</tr>'; + } + } + + if ($html_table) + { + $html_hold .= '</table>'; + } + } + + $curtime = explode(' ', microtime()); + $curtime = $curtime[0] + $curtime[1]; + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = mysql_query($query, $this->db_connect_id); + while ($void = mysql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $time_cache = $endtime - $curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query results obtained from the cache</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table><p align="center">'; + + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p>'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + mysql_free_result($result); + $cache_num_queries++; + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query #' . $this->num_queries . '</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table> ' . $html_hold . '<p align="center">'; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | '; + } + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>'; + } + else + { + $error = $this->sql_error(); + $sql_report .= '<b style="color: red">FAILED</b> - MySQL Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $sql_report .= '</p>'; + + $this->sql_time += $endtime - $curtime; + break; + } + } +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/mysqli.php b/src/SemanticScuttle/db/mysqli.php new file mode 100644 index 0000000..27814a7 --- /dev/null +++ b/src/SemanticScuttle/db/mysqli.php @@ -0,0 +1,562 @@ +<?php +/** +* +* @package dbal_mysqli +* @version $Id: mysqli.php,v 1.4 2006/02/10 01:30:19 scronide Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('SQL_LAYER')) +{ + +define('SQL_LAYER', 'mysqli'); + +/** +* @package dbal_mysqli +* MySQLi Database Abstraction Layer +* Minimum Requirement is MySQL 4.1+ and the mysqli-extension +*/ +class sql_db +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = 0; + var $open_queries = array(); + + var $indexed = 0; + + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->db_connect_id = ($this->persistency) ? @mysqli_pconnect($this->server, $this->user, $sqlpassword) : @mysqli_connect($this->server, $this->user, $sqlpassword); + + if ($this->db_connect_id && $this->dbname != '') + { + if (@mysqli_select_db($this->db_connect_id, $this->dbname)) + { + return $this->db_connect_id; + } + } + + return $this->sql_error(''); + } + + // + // Other base methods + // + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + @mysqli_commit($this->db_connect_id); + } + + return @mysqli_close($this->db_connect_id); + } + + function sql_return_on_error($fail = false) + { + $this->return_on_error = $fail; + } + + function sql_num_queries() + { + return $this->num_queries; + } + + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + $result = @mysqli_autocommit($this->db_connect_id, false); + $this->transaction = true; + break; + + case 'commit': + $result = @mysqli_commit($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + $this->transaction = false; + + if (!$result) + { + @mysqli_rollback($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + } + break; + + case 'rollback': + $result = @mysqli_rollback($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + $this->transaction = false; + break; + + default: + $result = true; + } + + return $result; + } + + // Base query method + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + + if (!$this->query_result) + { + $this->num_queries++; + + if (($this->query_result = @mysqli_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (is_object($this->query_result)) + { + $this->query_result->cur_index = $this->indexed++; + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return ($this->query_result) ? $this->query_result : false; + } + + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) { + if ($query != '') { + $this->query_result = false; + + // only limit the number of rows if $total is greater than 0 + if ($total > 0) + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } else { + return false; + } + } + + // Idea for this from Ikonboard + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = array(); + $values = array(); + if ($query == 'INSERT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_null($var)) + { + $values[] = 'NULL'; + } + elseif (is_string($var)) + { + $values[] = "'" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? intval($var) : $var; + } + } + + $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')'; + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + if (is_null($var)) + { + $values[] = "$key = NULL"; + } + elseif (is_string($var)) + { + $values[] = "$key = '" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; + } + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + // Other query methods + // + // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ... + // don't want this here by a middle Milestone + function sql_numrows($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @mysqli_num_rows($query_id) : false; + } + + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mysqli_affected_rows($this->db_connect_id) : false; + } + + function sql_fetchrow($query_id = false) + { + global $cache; + + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (!is_object($query_id) && isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id) ? @mysqli_fetch_assoc($query_id) : false; + } + + function sql_fetchrowset($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + $cur_index = (is_object($query_id)) ? $query_id->cur_index : $query_id; + + unset($this->rowset[$cur_index]); + unset($this->row[$cur_index]); + + $result = array(); + while ($this->rowset[$cur_index] = $this->sql_fetchrow($query_id)) + { + $result[] = $this->rowset[$cur_index]; + } + return $result; + } + + return false; + } + + function sql_fetchfield($field, $rownum = -1, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($rownum > -1) + { + @mysqli_data_seek($query_id, $rownum); + $row = @mysqli_fetch_assoc($query_id); + $result = isset($row[$field]) ? $row[$field] : false; + } + else + { + $cur_index = (is_object($query_id)) ? $query_id->cur_index : $query_id; + + if (empty($this->row[$cur_index]) && empty($this->rowset[$cur_index])) + { + if ($this->row[$cur_index] = $this->sql_fetchrow($query_id)) + { + $result = $this->row[$cur_index][$field]; + } + } + else + { + if ($this->rowset[$cur_index]) + { + $result = $this->rowset[$cur_index][$field]; + } + elseif ($this->row[$cur_index]) + { + $result = $this->row[$cur_index][$field]; + } + } + } + return $result; + } + return false; + } + + function sql_rowseek($rownum, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @mysqli_data_seek($query_id, $rownum) : false; + } + + function sql_nextid() + { + return ($this->db_connect_id) ? @mysqli_insert_id($this->db_connect_id) : false; + } + + function sql_freeresult($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + $cur_index = (is_object($query_id)) ? $query_id->cur_index : $query_id; + + unset($this->rowset[$cur_index]); + unset($this->row[$cur_index]); + + if (is_object($query_id)) + { + $this->indexed--; + return @mysqli_free_result($query_id); + } + else + { + return false; + } + } + + function sql_escape($msg) { + return mysqli_real_escape_string($this->db_connect_id, $msg); + } + + function sql_error($sql = '') + { + if (!$this->return_on_error) + { + $this_page = (isset($_SERVER['PHP_SELF']) && !empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; + $this_page .= '&' . ((isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : (isset($_ENV['QUERY_STRING']) ? $_ENV['QUERY_STRING'] : '')); + + $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @mysqli_error($this->db_connect_id) . '<br /><br /><u>CALLING PAGE</u><br /><br />' . htmlspecialchars($this_page) . (($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />'; + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + trigger_error($message, E_USER_ERROR); + } + + $result = array( + 'message' => @mysqli_error($this->db_connect_id), + 'code' => @mysqli_errno($this->db_connect_id) + ); + + return $result; + } + + function sql_report($mode, $query = '') + { + if (empty($_GET['explain'])) + { + return; + } + + global $db, $cache, $starttime, $phpbb_root_path; + static $curtime, $query_hold, $html_hold; + static $sql_report = ''; + static $cache_num_queries = 0; + + if (!$query && !empty($query_hold)) + { + $query = $query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $db->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8869-1"><meta http-equiv="Content-Style-Type" content="text/css"><link rel="stylesheet" href="' . $phpbb_root_path . 'adm/subSilver.css" type="text/css"><style type="text/css">' . "\n"; + echo 'th { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic3.gif\') }' . "\n"; + echo 'td.cat { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic1.gif\') }' . "\n"; + echo '</style><title>' . $msg_title . '</title></head><body>'; + echo '<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td><a href="' . htmlspecialchars(preg_replace('/&explain=([^&]*)/', '', $_SERVER['REQUEST_URI'])) . '"><img src="' . $phpbb_root_path . 'adm/images/header_left.jpg" width="200" height="60" alt="phpBB Logo" title="phpBB Logo" border="0"/></a></td><td width="100%" background="' . $phpbb_root_path . 'adm/images/header_bg.jpg" height="60" align="right" nowrap="nowrap"><span class="maintitle">SQL Report</span> </td></tr></table><br clear="all"/><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td height="40" align="center" valign="middle"><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries} queries" . (($cache_num_queries) ? " + $cache_num_queries " . (($cache_num_queries == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></td></tr><tr><td align="center" nowrap="nowrap">Time spent on MySQL queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></td></tr></table><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td>'; + echo $sql_report; + echo '</td></tr></table><br /></body></html>'; + exit; + break; + + case 'start': + $query_hold = $query; + $html_hold = ''; + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + elseif (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = FALSE; + + if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query")) + { + while ($row = @mysqli_fetch_assoc($result)) + { + if (!$html_table && sizeof($row)) + { + $html_table = TRUE; + $html_hold .= '<table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0" align="center"><tr>'; + + foreach (array_keys($row) as $val) + { + $html_hold .= '<th nowrap="nowrap">' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '</th>'; + } + $html_hold .= '</tr>'; + } + $html_hold .= '<tr>'; + + $class = 'row1'; + foreach (array_values($row) as $val) + { + $class = ($class == 'row1') ? 'row2' : 'row1'; + $html_hold .= '<td class="' . $class . '">' . (($val) ? $val : ' ') . '</td>'; + } + $html_hold .= '</tr>'; + } + } + + if ($html_table) + { + $html_hold .= '</table>'; + } + } + + $curtime = explode(' ', microtime()); + $curtime = $curtime[0] + $curtime[1]; + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mysqli_query($this->db_connect_id, $query); + while ($void = @mysqli_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $time_cache = $endtime - $curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query results obtained from the cache</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table><p align="center">'; + + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p>'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + @mysqli_free_result($result); + $cache_num_queries++; + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query #' . $this->num_queries . '</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table> ' . $html_hold . '<p align="center">'; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | '; + } + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>'; + } + else + { + $error = $this->sql_error(); + $sql_report .= '<b style="color: red">FAILED</b> - MySQL Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $sql_report .= '</p>'; + + $this->sql_time += $endtime - $curtime; + break; + } + } +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/oracle.php b/src/SemanticScuttle/db/oracle.php new file mode 100644 index 0000000..7ef10e5 --- /dev/null +++ b/src/SemanticScuttle/db/oracle.php @@ -0,0 +1,468 @@ +<?php +/** +* +* @package dbal_oracle +* @version $Id: oracle.php,v 1.2 2005/06/10 08:52:03 devalley Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if(!defined("SQL_LAYER")) +{ + +define("SQL_LAYER","oracle"); + +/** +* @package dbal_oracle +* Oracle Database Abstraction Layer +*/ +class sql_db +{ + + var $db_connect_id; + var $query_result; + var $in_transaction = 0; + var $row = array(); + var $rowset = array(); + var $num_queries = 0; + var $last_query_text = ""; + + // + // Constructor + // + function sql_db($sqlserver, $sqluser, $sqlpassword, $database="", $persistency = true) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->password = $sqlpassword; + $this->server = $sqlserver; + $this->dbname = $database; + + if($this->persistency) + { + $this->db_connect_id = @OCIPLogon($this->user, $this->password, $this->server); + } + else + { + $this->db_connect_id = @OCINLogon($this->user, $this->password, $this->server); + } + if($this->db_connect_id) + { + return $this->db_connect_id; + } + else + { + return false; + } + } + + // + // Other base methods + // + function sql_close() + { + if($this->db_connect_id) + { + // Commit outstanding transactions + if($this->in_transaction) + { + OCICommit($this->db_connect_id); + } + + if($this->query_result) + { + @OCIFreeStatement($this->query_result); + } + $result = @OCILogoff($this->db_connect_id); + return $result; + } + else + { + return false; + } + } + + // + // Base query method + // + function sql_query($query = "", $transaction = FALSE) + { + // Remove any pre-existing queries + unset($this->query_result); + + // Put us in transaction mode because with Oracle as soon as you make a query you're in a transaction + $this->in_transaction = TRUE; + + if($query != "") + { + $this->last_query = $query; + $this->num_queries++; + + if(eregi("LIMIT", $query)) + { + preg_match("/^(.*)LIMIT ([0-9]+)[, ]*([0-9]+)*/s", $query, $limits); + + $query = $limits[1]; + if($limits[3]) + { + $row_offset = $limits[2]; + $num_rows = $limits[3]; + } + else + { + $row_offset = 0; + $num_rows = $limits[2]; + } + } + + if(eregi("^(INSERT|UPDATE) ", $query)) + { + $query = preg_replace("/\\\'/s", "''", $query); + } + + $this->query_result = @OCIParse($this->db_connect_id, $query); + $success = @OCIExecute($this->query_result, OCI_DEFAULT); + } + if($success) + { + if($transaction == END_TRANSACTION) + { + OCICommit($this->db_connect_id); + $this->in_transaction = FALSE; + } + + unset($this->row[$this->query_result]); + unset($this->rowset[$this->query_result]); + $this->last_query_text[$this->query_result] = $query; + + return $this->query_result; + } + else + { + if($this->in_transaction) + { + OCIRollback($this->db_connect_id); + } + return false; + } + } + + // + // Other query methods + // + function sql_numrows($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = @OCIFetchStatement($query_id, $this->rowset); + // OCIFetchStatment kills our query result so we have to execute the statment again + // if we ever want to use the query_id again. + @OCIExecute($query_id, OCI_DEFAULT); + return $result; + } + else + { + return false; + } + } + function sql_affectedrows($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = @OCIRowCount($query_id); + return $result; + } + else + { + return false; + } + } + function sql_numfields($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = @OCINumCols($query_id); + return $result; + } + else + { + return false; + } + } + function sql_fieldname($offset, $query_id = 0) + { + // OCIColumnName uses a 1 based array so we have to up the offset by 1 in here to maintain + // full abstraction compatibitly + $offset += 1; + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = strtolower(@OCIColumnName($query_id, $offset)); + return $result; + } + else + { + return false; + } + } + function sql_fieldtype($offset, $query_id = 0) + { + // This situation is the same as fieldname + $offset += 1; + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = @OCIColumnType($query_id, $offset); + return $result; + } + else + { + return false; + } + } + function sql_fetchrow($query_id = 0, $debug = FALSE) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result_row = ""; + $result = @OCIFetchInto($query_id, $result_row, OCI_ASSOC+OCI_RETURN_NULLS); + if($debug) + { + echo "Query was: ".$this->last_query . "<br>"; + echo "Result: $result<br>"; + echo "Query ID: $query_id<br>"; + echo "<pre>"; + var_dump($result_row); + echo "</pre>"; + } + if($result_row == "") + { + return false; + } + + for($i = 0; $i < count($result_row); $i++) + { + list($key, $val) = each($result_row); + $return_arr[strtolower($key)] = $val; + } + $this->row[$query_id] = $return_arr; + + return $this->row[$query_id]; + } + else + { + return false; + } + } + // This function probably isn't as efficant is it could be but any other way I do it + // I end up losing 1 row... + function sql_fetchrowset($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $rows = @OCIFetchStatement($query_id, $results); + @OCIExecute($query_id, OCI_DEFAULT); + for($i = 0; $i <= $rows; $i++) + { + @OCIFetchInto($query_id, $tmp_result, OCI_ASSOC+OCI_RETURN_NULLS); + + for($j = 0; $j < count($tmp_result); $j++) + { + list($key, $val) = each($tmp_result); + $return_arr[strtolower($key)] = $val; + } + $result[] = $return_arr; + } + return $result; + } + else + { + return false; + } + } + function sql_fetchfield($field, $rownum = -1, $query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + if($rownum > -1) + { + // Reset the internal rownum pointer. + @OCIExecute($query_id, OCI_DEFAULT); + for($i = 0; $i < $rownum; $i++) + { + // Move the interal pointer to the row we want + @OCIFetch($query_id); + } + // Get the field data. + $result = @OCIResult($query_id, strtoupper($field)); + } + else + { + // The internal pointer should be where we want it + // so we just grab the field out of the current row. + $result = @OCIResult($query_id, strtoupper($field)); + } + return $result; + } + else + { + return false; + } + } + function sql_rowseek($rownum, $query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + @OCIExecute($query_id, OCI_DEFAULT); + for($i = 0; $i < $rownum; $i++) + { + @OCIFetch($query_id); + } + $result = @OCIFetch($query_id); + return $result; + } + else + { + return false; + } + } + function sql_nextid($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id && $this->last_query_text[$query_id] != "") + { + if( eregi("^(INSERT{1}|^INSERT INTO{1})[[:space:]][\"]?([a-zA-Z0-9\_\-]+)[\"]?", $this->last_query_text[$query_id], $tablename)) + { + $query = "SELECT ".$tablename[2]."_id_seq.currval FROM DUAL"; + $stmt = @OCIParse($this->db_connect_id, $query); + @OCIExecute($stmt,OCI_DEFAULT ); + $temp_result = @OCIFetchInto($stmt, $temp_result, OCI_ASSOC+OCI_RETURN_NULLS); + if($temp_result) + { + return $temp_result['CURRVAL']; + } + else + { + return false; + } + } + else + { + return false; + } + } + else + { + return false; + } + } + + function sql_nextid($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id && $this->last_query_text[$query_id] != "") + { + if( eregi("^(INSERT{1}|^INSERT INTO{1})[[:space:]][\"]?([a-zA-Z0-9\_\-]+)[\"]?", $this->last_query_text[$query_id], $tablename)) + { + $query = "SELECT ".$tablename[2]."_id_seq.CURRVAL FROM DUAL"; + $temp_q_id = @OCIParse($this->db_connect_id, $query); + @OCIExecute($temp_q_id, OCI_DEFAULT); + @OCIFetchInto($temp_q_id, $temp_result, OCI_ASSOC+OCI_RETURN_NULLS); + + if($temp_result) + { + return $temp_result['CURRVAL']; + } + else + { + return false; + } + } + else + { + return false; + } + } + else + { + return false; + } + } + + + + function sql_freeresult($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + if($query_id) + { + $result = @OCIFreeStatement($query_id); + return $result; + } + else + { + return false; + } + } + function sql_error($query_id = 0) + { + if(!$query_id) + { + $query_id = $this->query_result; + } + $result = @OCIError($query_id); + return $result; + } + +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/postgres.php b/src/SemanticScuttle/db/postgres.php new file mode 100644 index 0000000..b5bad20 --- /dev/null +++ b/src/SemanticScuttle/db/postgres.php @@ -0,0 +1,597 @@ +<?php +/** +* +* @package dbal_postgres +* @version $Id: postgres.php,v 1.2 2005/06/10 08:52:03 devalley Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined('SQL_LAYER')) +{ + +define('SQL_LAYER', 'postgresql'); + +/** +* @package dbal_postgres +* PostgreSQL Database Abstraction Layer +* Minimum Requirement is Version 7.3+ +*/ +class sql_db +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = 0; + var $open_queries = array(); + + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false) + { + $this->connect_string = ''; + + if ($sqluser) + { + $this->connect_string .= "user=$sqluser "; + } + + if ($sqlpassword) + { + $this->connect_string .= "password=$sqlpassword "; + } + + if ($sqlserver) + { + if (ereg(":", $sqlserver)) + { + list($sqlserver, $sqlport) = split(":", $sqlserver); + $this->connect_string .= "host=$sqlserver port=$sqlport "; + } + else + { + if ($sqlserver != "localhost") + { + $this->connect_string .= "host=$sqlserver "; + } + + if ($port) + { + $this->connect_string .= "port=$port "; + } + } + } + + if ($database) + { + $this->dbname = $database; + $this->connect_string .= "dbname=$database"; + } + + $this->persistency = $persistency; + + $this->db_connect_id = ($this->persistency) ? @pg_pconnect($this->connect_string) : @pg_connect($this->connect_string); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + // + // Other base methods + // + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + @pg_exec($this->db_connect_id, 'COMMIT'); + } + + return @pg_close($this->db_connect_id); + } + + function sql_return_on_error($fail = false) + { + $this->return_on_error = $fail; + } + + function sql_num_queries() + { + return $this->num_queries; + } + + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + $result = @pg_exec($this->db_connect_id, 'BEGIN'); + $this->transaction = true; + break; + + case 'commit': + $result = @pg_exec($this->db_connect_id, 'COMMIT'); + $this->transaction = false; + + if (!$result) + { + @pg_exec($this->db_connect_id, 'ROLLBACK'); + } + break; + + case 'rollback': + $result = @pg_exec($this->db_connect_id, 'ROLLBACK'); + $this->transaction = false; + break; + + default: + $result = true; + } + + return $result; + } + + // Base query method + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('start', $query); + } + + $this->query_result = ($cache_ttl && method_exists($cache, 'sql_load')) ? $cache->sql_load($query) : false; + + if (!$this->query_result) + { + $this->num_queries++; + $this->last_query_text = $query; + + if (($this->query_result = @pg_exec($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG_EXTRA')) + { + $this->sql_report('stop', $query); + } + + if ($cache_ttl && method_exists($cache, 'sql_save')) + { + $cache->sql_save($query, $this->query_result, $cache_ttl); + } + } + else if (defined('DEBUG_EXTRA')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return ($this->query_result) ? $this->query_result : false; + } + + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if ($query != '') + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = -1; + } + + $query .= "\n LIMIT $total OFFSET $offset"; + + return $this->sql_query($query, $cache_ttl); + } + else + { + return false; + } + } + + // Idea for this from Ikonboard + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = array(); + $values = array(); + if ($query == 'INSERT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_null($var)) + { + $values[] = 'NULL'; + } + elseif (is_string($var)) + { + $values[] = "'" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? intval($var) : $var; + } + } + + $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')'; + } + else if ($query == 'UPDATE' || $query == 'SELECT') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + if (is_null($var)) + { + $values[] = "$key = NULL"; + } + elseif (is_string($var)) + { + $values[] = "$key = '" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; + } + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + // Other query methods + // + // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ... + // don't want this here by a middle Milestone + function sql_numrows($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @pg_numrows($query_id) : false; + } + + function sql_affectedrows($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @pg_cmdtuples($query_id) : false; + } + + function sql_fetchrow($query_id = false) + { + global $cache; + + if (!$query_id) + { + $query_id = $this->query_result; + } + + if (!isset($this->rownum[$query_id])) + { + $this->rownum[$query_id] = 0; + } + + if (isset($cache->sql_rowset[$query_id])) + { + return $cache->sql_fetchrow($query_id); + } + + $result = @pg_fetch_array($query_id, NULL, PGSQL_ASSOC); + + if ($result) + { + $this->rownum[$query_id]++; + } + + return $result; + } + + function sql_fetchrowset($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + $result = array(); + + if ($query_id) + { + unset($this->rowset[$query_id]); + unset($this->row[$query_id]); + + $result = array(); + while ($this->rowset[$query_id] = $this->sql_fetchrow($query_id)) + { + $result[] = $this->rowset[$query_id]; + } + return $result; + } + + return false; + } + + function sql_fetchfield($field, $rownum = -1, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($rownum > -1) + { + if (@function_exists('pg_result_seek')) + { + @pg_result_seek($query_id, $rownum); + $row = @pg_fetch_assoc($query_id); + $result = isset($row[$field]) ? $row[$field] : false; + } + else + { + $this->sql_rowseek($offset, $query_id); + $row = $this->sql_fetchrow($query_id); + $result = isset($row[$field]) ? $row[$field] : false; + } + } + else + { + if (empty($this->row[$query_id]) && empty($this->rowset[$query_id])) + { + if ($this->sql_fetchrow($query_id)) + { + $result = $this->row[$query_id][$field]; + } + } + else + { + if ($this->rowset[$query_id]) + { + $result = $this->rowset[$query_id][$field]; + } + elseif ($this->row[$query_id]) + { + $result = $this->row[$query_id][$field]; + } + } + } + return $result; + } + return false; + } + + function sql_rowseek($offset, $query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($offset > -1) + { + if (@function_exists('pg_result_seek')) + { + @pg_result_seek($query_id, $rownum); + } + else + { + for ($i = $this->rownum[$query_id]; $i < $offset; $i++) + { + $this->sql_fetchrow($query_id); + } + } + return true; + } + else + { + return false; + } + } + + return false; + } + + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id && $this->last_query_text != '') + { + if (preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $this->last_query_text, $tablename)) + { + $query = "SELECT currval('" . $tablename[1] . "_id_seq') AS last_value"; + $temp_q_id = @pg_exec($this->db_connect_id, $query); + if (!$temp_q_id) + { + return false; + } + + $temp_result = @pg_fetch_array($temp_q_id, NULL, PGSQL_ASSOC); + + return ($temp_result) ? $temp_result['last_value'] : false; + } + } + + return false; + } + + function sql_freeresult($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return (is_resource($query_id)) ? @pg_freeresult($query_id) : false; + } + + function sql_escape($msg) + { + return str_replace("'", "''", str_replace('\\', '\\\\', $msg)); + } + + function sql_error($sql = '') + { + if (!$this->return_on_error) + { + $this_page = (isset($_SERVER['PHP_SELF']) && !empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; + $this_page .= '&' . ((isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : (isset($_ENV['QUERY_STRING']) ? $_ENV['QUERY_STRING'] : '')); + + $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @pg_errormessage() . '<br /><br /><u>CALLING PAGE</u><br /><br />' . htmlspecialchars($this_page) . (($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />'; + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + trigger_error($message, E_USER_ERROR); + } + + $result = array( + 'message' => @pg_errormessage(), + 'code' => '' + ); + + return $result; + } + + function sql_report($mode, $query = '') + { + if (empty($_GET['explain'])) + { + return; + } + + global $cache, $starttime, $phpbb_root_path; + static $curtime, $query_hold, $html_hold; + static $sql_report = ''; + static $cache_num_queries = 0; + + if (!$query && !empty($query_hold)) + { + $query = $query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=iso-8869-1"><meta http-equiv="Content-Style-Type" content="text/css"><link rel="stylesheet" href="' . $phpbb_root_path . 'adm/subSilver.css" type="text/css"><style type="text/css">' . "\n"; + echo 'th { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic3.gif\') }' . "\n"; + echo 'td.cat { background-image: url(\'' . $phpbb_root_path . 'adm/images/cellpic1.gif\') }' . "\n"; + echo '</style><title>' . $msg_title . '</title></head><body>'; + echo '<table width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td><a href="' . htmlspecialchars(preg_replace('/&explain=([^&]*)/', '', $_SERVER['REQUEST_URI'])) . '"><img src="' . $phpbb_root_path . 'adm/images/header_left.jpg" width="200" height="60" alt="phpBB Logo" title="phpBB Logo" border="0"/></a></td><td width="100%" background="' . $phpbb_root_path . 'adm/images/header_bg.jpg" height="60" align="right" nowrap="nowrap"><span class="maintitle">SQL Report</span> </td></tr></table><br clear="all"/><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td height="40" align="center" valign="middle"><b>Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries} queries" . (($cache_num_queries) ? " + $cache_num_queries " . (($cache_num_queries == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '</b></td></tr><tr><td align="center" nowrap="nowrap">Time spent on MySQL queries: <b>' . round($this->sql_time, 5) . 's</b> | Time spent on PHP: <b>' . round($totaltime - $this->sql_time, 5) . 's</b></td></tr></table><table width="95%" cellspacing="1" cellpadding="4" border="0" align="center"><tr><td>'; + echo $sql_report; + echo '</td></tr></table><br /></body></html>'; + exit; + break; + + case 'start': + $query_hold = $query; + $html_hold = ''; + + $curtime = explode(' ', microtime()); + $curtime = $curtime[0] + $curtime[1]; + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @pg_exec($this->db_connect_id, $query); + while ($void = @pg_fetch_array($result, NULL, PGSQL_ASSOC)) + { + // Take the time spent on parsing rows into account + } + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $time_cache = $endtime - $curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query results obtained from the cache</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table><p align="center">'; + + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: <b style="color: ' . $color . '">' . sprintf('%.5f', ($time_cache)) . 's</b> | Elapsed [db]: <b>' . sprintf('%.5f', $time_db) . 's</b></p>'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + @pg_freeresult($result); + $cache_num_queries++; + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $sql_report .= '<hr width="100%"/><br /><table class="bg" width="100%" cellspacing="1" cellpadding="4" border="0"><tr><th>Query #' . $this->num_queries . '</th></tr><tr><td class="row1"><textarea style="font-family:\'Courier New\',monospace;width:100%" rows="5">' . preg_replace('/\t(AND|OR)(\W)/', "\$1\$2", htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n", $query))) . '</textarea></td></tr></table> ' . $html_hold . '<p align="center">'; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $sql_report .= "Affected rows: <b>" . $this->sql_affectedrows($this->query_result) . '</b> | '; + } + $sql_report .= 'Before: ' . sprintf('%.5f', $curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: <b>' . sprintf('%.5f', $endtime - $curtime) . 's</b>'; + } + else + { + $error = $this->sql_error(); + $sql_report .= '<b style="color: red">FAILED</b> - ' . SQL_LAYER . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $sql_report .= '</p>'; + + $this->sql_time += $endtime - $curtime; + break; + } + } + +} // class ... db_sql + +} // if ... defined + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/db/sqlite.php b/src/SemanticScuttle/db/sqlite.php new file mode 100644 index 0000000..1591396 --- /dev/null +++ b/src/SemanticScuttle/db/sqlite.php @@ -0,0 +1,387 @@ +<?php +/** +* +* @package dbal_sqlite +* @version $Id: sqlite.php,v 1.2 2005/06/10 08:52:03 devalley Exp $ +* @copyright (c) 2005 phpBB Group +* @license http://opensource.org/licenses/gpl-license.php GNU Public License +* +*/ + +/** +* @ignore +*/ +if (!defined("SQL_LAYER")) +{ + +define("SQL_LAYER","sqlite"); + +/** +* @package dbal_sqlite +* Sqlite Database Abstraction Layer +*/ +class sql_db +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_report = ''; + var $sql_time = 0; + var $num_queries = 0; + var $open_queries = array(); + + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port, $persistency = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->db_connect_id = ($this->persistency) ? @sqlite_popen($this->server, 0, $error) : @sqlite_open($this->server, 0, $error); + + return ($this->db_connect_id) ? true : $error; + } + + // Other base methods + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + return @sqlite_close($this->db_connect_id); + } + + function sql_return_on_error($fail = false) + { + $this->return_on_error = $fail; + } + + function sql_num_queries() + { + return $this->num_queries; + } + + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + $this->transaction = true; + $result = @sqlite_query('BEGIN', $this->db_connect_id); + break; + + case 'commit': + $this->transaction = false; + $result = @sqlite_query('COMMIT', $this->db_connect_id); + break; + + case 'rollback': + $this->transaction = false; + $result = @sqlite_query('ROLLBACK', $this->db_connect_id); + break; + + default: + $result = true; + } + + return $result; + } + + // Base query method + function sql_query($query = '', $expire_time = 0) + { + if ($query != '') + { + global $cache; + + $query = preg_replace('#FROM \((.*?)\)(,|[\n\t ]+?WHERE) #s', 'FROM \1\2 ', $query); + + if (!$expire_time || !$cache->sql_load($query, $expire_time)) + { + if ($expire_time) + { + $cache_result = true; + } + + $this->query_result = false; + $this->num_queries++; + + if (!empty($_GET['explain'])) + { + global $starttime; + + $curtime = explode(' ', microtime()); + $curtime = $curtime[0] + $curtime[1] - $starttime; + } + + if (!($this->query_result = @sqlite_query($query, $this->db_connect_id))) + { + $this->sql_error($query); + } + + if (!empty($_GET['explain'])) + { + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1] - $starttime; + + $this->sql_report .= "<pre>Query:\t" . htmlspecialchars(preg_replace('/[\s]*[\n\r\t]+[\n\r\s\t]*/', "\n\t", $query)) . "\n\n"; + + if ($this->query_result) + { + $this->sql_report .= "Time before: $curtime\nTime after: $endtime\nElapsed time: <b>" . ($endtime - $curtime) . "</b>\n</pre>"; + } + else + { + $error = $this->sql_error(); + $this->sql_report .= '<b>FAILED</b> - SQLite ' . $error['code'] . ': ' . htmlspecialchars($error['message']) . '<br><br><pre>'; + } + + $this->sql_time += $endtime - $curtime; + + if (preg_match('#^SELECT#', $query)) + { + $html_table = FALSE; + if ($result = @sqlite_query("EXPLAIN $query", $this->db_connect_id)) + { + while ($row = @sqlite_fetch_array($result, @sqlite_ASSOC)) + { + if (!$html_table && sizeof($row)) + { + $html_table = TRUE; + $this->sql_report .= "<table width=100% border=1 cellpadding=2 cellspacing=1>\n"; + $this->sql_report .= "<tr>\n<td><b>" . implode("</b></td>\n<td><b>", array_keys($row)) . "</b></td>\n</tr>\n"; + } + $this->sql_report .= "<tr>\n<td>" . implode(" </td>\n<td>", array_values($row)) . " </td>\n</tr>\n"; + } + } + + if ($html_table) + { + $this->sql_report .= '</table><br>'; + } + } + + $this->sql_report .= "<hr>\n"; + } + + if (preg_match('#^SELECT#', $query)) + { + $this->open_queries[] = $this->query_result; + } + } + + if (!empty($cache_result)) + { + $cache->sql_save($query, $this->query_result); + } + } + else + { + return false; + } + + return ($this->query_result) ? $this->query_result : false; + } + + function sql_query_limit($query, $total, $offset = 0, $expire_time = 0) + { + if ($query != '') + { + $this->query_result = false; + + $query .= ' LIMIT ' . ((!empty($offset)) ? $total . ' OFFSET ' . $offset : $total); + + return $this->sql_query($query, $expire_time); + } + else + { + return false; + } + } + + // Idea for this from Ikonboard + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = array(); + $values = array(); + if ($query == 'INSERT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_null($var)) + { + $values[] = 'NULL'; + } + elseif (is_string($var)) + { + $values[] = "'" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? intval($var) : $var; + } + } + + $query = ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')'; + } + else if ($query == 'UPDATE') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + if (is_null($var)) + { + $values[] = "$key = NULL"; + } + elseif (is_string($var)) + { + $values[] = "$key = '" . $this->sql_escape($var) . "'"; + } + else + { + $values[] = (is_bool($var)) ? "$key = " . intval($var) : "$key = $var"; + } + } + $query = implode(', ', $values); + } + + return $query; + } + + // Other query methods + // + // NOTE :: Want to remove _ALL_ reliance on sql_numrows from core code ... + // don't want this here by a middle Milestone + function sql_numrows($query_id = false) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @sqlite_num_rows($query_id) : false; + } + + function sql_affectedrows() + { + return ($this->db_connect_id) ? @sqlite_changes($this->db_connect_id) : false; + } + + function sql_fetchrow($query_id = 0) + { + global $cache; + + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id) ? @sqlite_fetch_array($query_id, @sqlite_ASSOC) : false; + } + + function sql_fetchrowset($query_id = 0) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + unset($this->rowset[$query_id]); + unset($this->row[$query_id]); + while ($this->rowset[$query_id] = @sqlite_fetch_array($query_id, @sqlite_ASSOC)) + { + $result[] = $this->rowset[$query_id]; + } + return $result; + } + else + { + return false; + } + } + + function sql_fetchfield($field, $rownum = -1, $query_id = 0) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + return ($rownum > -1) ? ((@sqlite_seek($query_id, $rownum)) ? @sqlite_column($query_id, $field) : false) : @sqlite_column($query_id, $field); + } + } + + function sql_rowseek($rownum, $query_id = 0) + { + if (!$query_id) + { + $query_id = $this->query_result; + } + + return ($query_id) ? @sqlite_seek($query_id, $rownum) : false; + } + + function sql_nextid() + { + return ($this->db_connect_id) ? @sqlite_last_insert_rowid($this->db_connect_id) : false; + } + + function sql_freeresult($query_id = false) + { + return true; + } + + function sql_escape($msg) + { + return @sqlite_escape_string(stripslashes($msg)); + } + + function sql_error($sql = '') + { + if (!$this->return_on_error) + { + $this_page = (!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF']; + $this_page .= '&' . ((!empty($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : $_ENV['QUERY_STRING']); + + $message = '<u>SQL ERROR</u> [ ' . SQL_LAYER . ' ]<br /><br />' . @sqlite_error_string(@sqlite_last_error($this->db_connect_id)) . '<br /><br /><u>CALLING PAGE</u><br /><br />' . htmlspecialchars($this_page) . (($sql != '') ? '<br /><br /><u>SQL</u><br /><br />' . $sql : '') . '<br />'; + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + trigger_error($message, E_USER_ERROR); + } + + $result = array( + 'message' => @sqlite_error_string(@sqlite_last_error($this->db_connect_id)), + 'code' => @sqlite_last_error($this->db_connect_id) + ); + + return $result; + } + +} // class sql_db + +} // if ... define + +?>
\ No newline at end of file diff --git a/src/SemanticScuttle/functions.inc.php b/src/SemanticScuttle/functions.inc.php new file mode 100644 index 0000000..08d5f33 --- /dev/null +++ b/src/SemanticScuttle/functions.inc.php @@ -0,0 +1,207 @@ +<?php +/* Define functions used into the application */ + + +// Converts tags: +// - direction = out: convert spaces to underscores; +// - direction = in: convert underscores to spaces. +function convertTag($tag, $direction = 'out') { + if ($direction == 'out') { + $tag = str_replace(' ', '_', $tag); + } else { + $tag = str_replace('_', ' ', $tag); + } + return $tag; +} + +function filter($data, $type = NULL) { + if (is_string($data)) { + $data = trim($data); + $data = stripslashes($data); + switch ($type) { + case 'url': + $data = rawurlencode($data); + break; + default: + $data = htmlspecialchars($data); + break; + } + } else if (is_array($data)) { + foreach(array_keys($data) as $key) { + $row =& $data[$key]; + $row = filter($row, $type); + } + } + return $data; +} + +function getPerPageCount($userObject = null) { + global $defaultPerPage, $defaultPerPageForAdmins; + + if(isset($defaultPerPageForAdmins) && $userObject != null && $userObject->isAdmin()) { + return $defaultPerPageForAdmins; + } else { + return $defaultPerPage; + } +} + +function getSortOrder($override = NULL) { + global $defaultOrderBy; + + if (isset($_GET['sort'])) { + return $_GET['sort']; + } else if (isset($override)) { + return $override; + } else { + return $defaultOrderBy; + } +} + +function multi_array_search($needle, $haystack) { + if (is_array($haystack)) { + foreach(array_keys($haystack) as $key) { + $value =& $haystack[$key]; + $result = multi_array_search($needle, $value); + if (is_array($result)) { + $return = $result; + array_unshift($return, $key); + return $return; + } elseif ($result == true) { + $return[] = $key; + return $return; + } + } + return false; + } else { + if ($needle === $haystack) { + return true; + } else { + return false; + } + } +} + +function createURL($page = '', $ending = '') { + global $cleanurls; + if (!$cleanurls && $page != '') { + $page .= '.php'; + } + if(strlen($ending)>0) { + return ROOT . $page .'/'. $ending; + } else { + return ROOT . $page; + } +} + +/* Shorten a string like a URL for example by cutting the middle of it */ +function shortenString($string, $maxSize=75) { + $output = ''; + if(strlen($string) > $maxSize) { + $output = substr($string, 0, $maxSize/2).'...'.substr($string, -$maxSize/2); + } else { + $output = $string; + } + return $output; +} + +/* Check url format and check online if the url is a valid page (Not a 404 error for example) */ +function checkUrl($url, $checkOnline = true) { + //check format + if(!preg_match("#(ht|f)tp(s?)\://\S+\.\S+#i",$url)) { + return false; + } + + if($checkOnline) { + //look if the page doesn't return a void or 40X or 50X HTTP code error + $h = @get_headers($url); + if(is_array($h) && strpos($h[0], '40') === false && strpos($h[0], '50') === false) { + return true; + } else { + return false; + } + } else { + return true; + } +} + +/* Returns a concatenated String + * including all the tags from the array $arrayTags (excepted of the $exceptedTag) + * separated by the $separator. + * */ +function aggregateTags($arrayTags, $separator = ' + ', $exceptedTag = '') { + $output = ''; + + for($i = 0; $i<count($arrayTags); $i++) { + if($arrayTags[$i] != $exceptedTag) { + $output.= $arrayTags[$i] . $separator; + } + } + return substr($output, 0, strlen($output) - strlen($separator) ); +} + +function message_die($msg_code, $msg_text = '', $msg_title = '', $err_line = '', $err_file = '', $sql = '', $db = NULL) { + if(defined('HAS_DIED')) + die(T_('message_die() was called multiple times.')); + define('HAS_DIED', 1); + + $sql_store = $sql; + + // Get SQL error if we are debugging. Do this as soon as possible to prevent + // subsequent queries from overwriting the status of sql_error() + if (DEBUG_MODE && ($msg_code == GENERAL_ERROR || $msg_code == CRITICAL_ERROR)) { + $sql_error = is_null($db) ? '' : $db->sql_error(); + $debug_text = ''; + + if ($sql_error['message'] != '') + $debug_text .= '<br /><br />'. T_('SQL Error') .' : '. $sql_error['code'] .' '. $sql_error['message']; + + if ($sql_store != '') + $debug_text .= '<br /><br />'. $sql_store; + + if ($err_line != '' && $err_file != '') + $debug_text .= '</br /><br />'. T_('Line') .' : '. $err_line .'<br />'. T_('File') .' :'. $err_file; + } + + switch($msg_code) { + case GENERAL_MESSAGE: + if ($msg_title == '') + $msg_title = T_('Information'); + break; + + case CRITICAL_MESSAGE: + if ($msg_title == '') + $msg_title = T_('Critical Information'); + break; + + case GENERAL_ERROR: + if ($msg_text == '') + $msg_text = T_('An error occured'); + + if ($msg_title == '') + $msg_title = T_('General Error'); + break; + + case CRITICAL_ERROR: + // Critical errors mean we cannot rely on _ANY_ DB information being + // available so we're going to dump out a simple echo'd statement + + if ($msg_text == '') + $msg_text = T_('An critical error occured'); + + if ($msg_title == '') + $msg_title = T_('Critical Error'); + break; + } + + // Add on DEBUG_MODE info if we've enabled debug mode and this is an error. This + // prevents debug info being output for general messages should DEBUG_MODE be + // set TRUE by accident (preventing confusion for the end user!) + if (DEBUG_MODE && ($msg_code == GENERAL_ERROR || $msg_code == CRITICAL_ERROR)) { + if ($debug_text != '') + $msg_text = $msg_text . '<br /><br /><strong>'. T_('DEBUG MODE') .'</strong>'. $debug_text; + } + + echo "<html>\n<body>\n". $msg_title ."\n<br /><br />\n". $msg_text ."</body>\n</html>"; + exit; +} +?> diff --git a/src/SemanticScuttle/header.inc.php b/src/SemanticScuttle/header.inc.php new file mode 100644 index 0000000..024cb06 --- /dev/null +++ b/src/SemanticScuttle/header.inc.php @@ -0,0 +1,55 @@ +<?php +if(!file_exists(dirname(__FILE__) .'/config.inc.php')) { + die('Please copy "config.inc.php.dist" to "config.inc.php"'); +} + +// 1 // First requirements part (before debug management) +require_once(dirname(__FILE__) .'/config.default.inc.php'); +require_once(dirname(__FILE__) .'/config.inc.php'); +require_once(dirname(__FILE__) .'/constants.inc.php'); // some constants are based on variables from config file + + +// Debug Management using constants +if(DEBUG_MODE) { + ini_set('display_errors', '1'); + ini_set('mysql.trace_mode', '1'); + error_reporting(E_ALL); +} else { + ini_set('display_errors', '0'); + ini_set('mysql.trace_mode', '0'); + error_reporting(0); +} + +// 2 // Second requirements part which could display bugs (must come after debug management) +require_once(dirname(__FILE__) .'/services/servicefactory.php'); +require_once(dirname(__FILE__) .'/functions.inc.php'); + + +// 3 // Third requirements part which import functions from includes/ directory + +// UTF-8 functions +require_once(dirname(__FILE__) .'/includes/utf8.php'); + +// Translation +require_once(dirname(__FILE__) .'/includes/php-gettext/gettext.inc'); +$domain = 'messages'; +T_setlocale(LC_MESSAGES, $locale); +T_bindtextdomain($domain, dirname(__FILE__) .'/locales'); +T_bind_textdomain_codeset($domain, 'UTF-8'); +T_textdomain($domain); + +// 4 // Session +session_start(); + +// 5 // Create mandatory services and objects +$userservice =& ServiceFactory::getServiceInstance('UserService'); +$currentUser = $userservice->getCurrentObjectUser(); + +$templateservice =& ServiceFactory::getServiceInstance('TemplateService'); +$tplVars = array(); +$tplVars['currentUser'] = $currentUser; +$tplVars['userservice'] = $userservice; + +// 6 // Force UTF-8 behaviour for server (cannot be move into top.inc.php which is not included into every file) +header('Content-Type: text/html; charset=utf-8'); +?> diff --git a/src/SemanticScuttle/search.inc.php b/src/SemanticScuttle/search.inc.php new file mode 100644 index 0000000..ce57aea --- /dev/null +++ b/src/SemanticScuttle/search.inc.php @@ -0,0 +1,54 @@ +<?php + + +/* Managing all possible inputs */ +$select_watchlist = isset($select_watchlist)?$select_watchlist:''; +$select_all = isset($select_all)?$select_all:''; + +$selected = ' selected="selected"'; +?> + + +<form id="search" action="<?php echo createURL('search'); ?>" method="post"> + <table> + <tr> + <?php + $currentUser = $currentUsername = null; + if ($userservice->isLoggedOn()) { + $currentUser = $userservice->getCurrentObjectUser(); + $currentUsername = $currentUser->getUsername(); + } + if ($userservice->isLoggedOn() || isset($user)) { + ?> + + <td><input type="text" name="terms" size="30" value="<?php $terms=!isset($terms)?T_('Search...'):$terms; echo filter($terms); ?>" onfocus="if (this.value == '<?php echo T_('Search...') ?>') this.value = '';" onblur="if (this.value == '') this.value = '<?php echo T_('Search...') ?>';"/></td> + <td><?php echo T_('in') ?></td> + <td> + <select name="range"> + <?php + if ($range == 'user' && $user!=$currentUsername) { + ?> + <option value="<?php echo $user ?>"><?php echo T_("this user's bookmarks"); ?></option> + <?php + } + if ($userservice->isLoggedOn()) { + ?> + <option value="<?php echo $currentUsername; ?>"><?php echo T_('my bookmarks'); ?></option> + <option value="watchlist" <?php echo ($range == 'watchlist')?$selected:''?> ><?php echo T_('my watchlist'); ?></option> + <?php + } + ?> + <option value="all" <?php echo ($range == 'all' || $range == '')?$selected:'' ?> ><?php echo T_('all bookmarks'); ?></option> + </select> + </td> + <?php + } else { + ?> + <td><input type="hidden" name="range" value="all" /></td> + <?php + } + ?> + <td><input type="submit" value="<?php echo T_('Search' /* Submit button */); ?>" /></td> + </tr> + </table> +</form> diff --git a/src/php-gettext/AUTHORS b/src/php-gettext/AUTHORS new file mode 100644 index 0000000..da6ade7 --- /dev/null +++ b/src/php-gettext/AUTHORS @@ -0,0 +1,3 @@ +Danilo Segan <danilo@kvota.net> +Nico Kaiser <nico@siriux.net> (contributed most changes between 1.0.2 and 1.0.3, bugfix for 1.0.5) +Steven Armstrong <sa@c-area.ch> (gettext.inc, leading to 1.0.6) diff --git a/src/php-gettext/COPYING b/src/php-gettext/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/src/php-gettext/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/src/php-gettext/ChangeLog b/src/php-gettext/ChangeLog new file mode 100644 index 0000000..5e0949d --- /dev/null +++ b/src/php-gettext/ChangeLog @@ -0,0 +1,144 @@ +2006-02-07 Danilo Šegan <danilo@gnome.org> + + * examples/pigs_dropin.php: comment-out bind_textdomain_codeset + + * gettext.inc (T_bind_textdomain_codeset): bind_textdomain_codeset + is available only in PHP 4.2.0+ (thanks to Jens A. Tkotz). + + * Makefile: Include gettext.inc in DIST_FILES, VERSION up to + 1.0.7. + +2006-02-03 Danilo Šegan <danilo@gnome.org> + + Added setlocale() emulation as well. + + * examples/pigs_dropin.php: Use T_setlocale() and locale_emulation(). + * examples/pigs_fallback.php: Use T_setlocale() and locale_emulation(). + + * gettext.inc: Added globals $EMULATEGETTEXT and $CURRENTLOCALE. + (locale_emulation): Whether emulation is active. + (_check_locale): Rewrite. + (_setlocale): Added emulated setlocale function. + (T_setlocale): Wrapper around _setlocale. + (_get_reader): Use variables and _setlocale. + +2006-02-02 Danilo Šegan <danilo@gnome.org> + + Fix bug #12192. + + * examples/locale/sr_CS/LC_MESSAGES/messages.po: Correct grammar. + * examples/locale/sr_CS/LC_MESSAGES/messages.mo: Rebuild. + +2006-02-02 Danilo Šegan <danilo@gnome.org> + + Fix bug #15419. + + * streams.php: Support for PHP 5.1.1 fread() which reads most 8kb. + (Fix by Piotr Szotkowski <shot@hot.pl>) + +2006-02-02 Danilo Šegan <danilo@gnome.org> + + Merge Steven Armstrong's changes, supporting standard gettext + interfaces: + + * examples/*: Restructured examples. + * gettext.inc: Added. + * AUTHORS: Added Steven. + * Makefile (VERSION): Up to 1.0.6. + +2006-01-28 Nico Kaiser <nico@siriux.net> + + * gettext.php (select_string): Fix "true" <-> 1 difference of PHP + +2005-07-29 Danilo Šegan <danilo@gnome.org> + + * Makefile (VERSION): Up to 1.0.5. + +2005-07-29 Danilo Šegan <danilo@gnome.org> + + Fixes bug #13850. + + * gettext.php (gettext_reader): check $Reader->error as well. + +2005-07-29 Danilo Šegan <danilo@gnome.org> + + * Makefile (VERSION): Up to 1.0.4. + +2005-07-29 Danilo Šegan <danilo@gnome.org> + + Fixes bug #13771. + + * gettext.php (gettext_reader->get_plural_forms): Plural forms + header extraction regex change. Reported by Edgar Gonzales. + +2005-02-28 Danilo Šegan <dsegan@gmx.net> + + * AUTHORS: Added Nico to the list. + + * Makefile (VERSION): Up to 1.0.3. + + * README: Updated. + +2005-02-28 Danilo Šegan <dsegan@gmx.net> + + * gettext.php: Added pre-loading, code documentation, and many + code clean-ups by Nico Kaiser <nico@siriux.net>. + +2005-02-28 Danilo Šegan <dsegan@gmx.net> + + * streams.php (FileReader.read): Handle read($bytes = 0). + + * examples/pigs.php: Prefix gettext function names with T or T_. + + * examples/update: Use the same keywords T_ and T_ngettext. + + * streams.php: Added CachedFileReader. + +2003-11-11 Danilo Šegan <dsegan@gmx.net> + + * gettext.php: Added hashing to find_string. + +2003-11-01 Danilo Šegan <dsegan@gmx.net> + + * Makefile (DIST_FILES): Replaced LICENSE with COPYING. + (VERSION): Up to 1.0.2. + + * AUTHORS: Minor edits. + + * README: Minor edits. + + * COPYING: Removed LICENSE, added this file. + + * gettext.php: Added copyright notice and disclaimer. + * streams.php: Same. + * examples/pigs.php: Same. + +2003-10-23 Danilo Šegan <dsegan@gmx.net> + + * Makefile: Upped version to 1.0.1. + + * gettext.php (gettext_reader): Remove a call to set_total_plurals. + (set_total_plurals): Removed unused function for some better days. + +2003-10-23 Danilo Šegan <dsegan@gmx.net> + + * Makefile: Added, version 1.0.0. + + * examples/*: Added an example of usage. + + * README: Described all the crap. + +2003-10-22 Danilo Šegan <dsegan@gmx.net> + + * gettext.php: Plural forms implemented too. + + * streams.php: Added FileReader for direct access to files (no + need to keep file in memory). + + * gettext.php: It works, except for plural forms. + + * streams.php: Created abstract class StreamReader. + Added StringReader class. + + * gettext.php: Started writing gettext_reader. + diff --git a/src/php-gettext/Makefile b/src/php-gettext/Makefile new file mode 100644 index 0000000..2dba911 --- /dev/null +++ b/src/php-gettext/Makefile @@ -0,0 +1,32 @@ +PACKAGE = php-gettext-$(VERSION) +VERSION = 1.0.7 + +DIST_FILES = \ + gettext.php \ + gettext.inc \ + streams.php \ + AUTHORS \ + ChangeLog \ + README \ + COPYING \ + Makefile \ + examples/index.php \ + examples/pigs_dropin.php \ + examples/pigs_fallback.php \ + examples/locale/sr_CS/LC_MESSAGES/messages.po \ + examples/locale/sr_CS/LC_MESSAGES/messages.mo \ + examples/locale/de_CH/LC_MESSAGES/messages.po \ + examples/locale/de_CH/LC_MESSAGES/messages.mo \ + examples/update + +dist: + if [ -d $(PACKAGE) ]; then \ + rm -rf $(PACKAGE); \ + fi; \ + mkdir $(PACKAGE); \ + if [ -d $(PACKAGE) ]; then \ + cp -rp --parents $(DIST_FILES) $(PACKAGE); \ + tar cvzf $(PACKAGE).tar.gz $(PACKAGE); \ + rm -rf $(PACKAGE); \ + fi; + diff --git a/src/php-gettext/README b/src/php-gettext/README new file mode 100644 index 0000000..c7525e2 --- /dev/null +++ b/src/php-gettext/README @@ -0,0 +1,189 @@ +PHP-gettext 1.0 + +Copyright 2003, 2006 -- Danilo "angry with PHP[1]" Segan +Licensed under GPLv2 (or any later version, see COPYING) + +[1] PHP is actually cyrillic, and translates roughly to + "works-doesn't-work" (UTF-8: Ради-Не-Ради) + + +Introduction + + How many times did you look for a good translation tool, and + found out that gettext is best for the job? Many times. + + How many times did you try to use gettext in PHP, but failed + miserably, because either your hosting provider didn't support + it, or the server didn't have adequate locale? Many times. + + Well, this is a solution to your needs. It allows using gettext + tools for managing translations, yet it doesn't require gettext + library at all. It parses generated MO files directly, and thus + might be a bit slower than the (maybe provided) gettext library. + + PHP-gettext is a simple reader for GNU gettext MO files. Those + are binary containers for translations, produced by GNU msgfmt. + +Why? + + I got used to having gettext work even without gettext + library. It's there in my favourite language Python, so I was + surprised that I couldn't find it in PHP. I even Googled for it, + but to no avail. + + So, I said, what the heck, I'm going to write it for this + disguisting language of PHP, because I'm often constrained to it. + +Features + + o Support for simple translations + Just define a simple alias for translate() function (suggested + use of _() or gettext(); see provided example). + + o Support for ngettext calls (plural forms, see a note under bugs) + You may also use plural forms. Translations in MO files need to + provide this, and they must also provide "plural-forms" header. + Please see 'info gettext' for more details. + + o Support for reading straight files, or strings (!!!) + Since I can imagine many different backends for reading in the MO + file data, I used imaginary abstract class StreamReader to do all + the input (check streams.php). For your convenience, I've already + provided two classes for reading files: FileReader and + StringReader (CachedFileReader is a combination of the two: it + loads entire file contents into a string, and then works on that). + See example below for usage. You can for instance use StringReader + when you read in data from a database, or you can create your own + derivative of StreamReader for anything you like. + + +Bugs + + Plural-forms field in MO header (translation for empty string, + i.e. "") is treated according to PHP syntactic rules (it's + eval()ed). Since these should actually follow C syntax, there are + some problems. + + For instance, I'm used to using this: + Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : \ + n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2; + but it fails with PHP (it sets $plural=2 instead of 0 for $n==1). + + The fix is usually simple, but I'm lazy to go into the details of + PHP operator precedence, and maybe try to fix it. In here, I had + to put everything after the first ':' in parenthesis: + Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : \ + (n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2); + That works, and I'm satisfied. + + Besides this one, there are probably a bunch of other bugs, since + I hate PHP (did I mention it already? no? strange), and don't + know it very well. So, feel free to fix any of those and report + them back to me at <danilo@kvota.net>. + +Usage + + Put files streams.php and gettext.php somewhere you can load them + from, and require 'em in where you want to use them. + + Then, create one 'stream reader' (a class that provides functions + like read(), seekto(), currentpos() and length()) which will + provide data for the 'gettext_reader', with eg. + $streamer = new FileStream('data.mo'); + + Then, use that as a parameter to gettext_reader constructor: + $wohoo = new gettext_reader($streamer); + + If you want to disable pre-loading of entire message catalog in + memory (if, for example, you have a multi-thousand message catalog + which you'll use only occasionally), use "false" for second + parameter to gettext_reader constructor: + $wohoo = new gettext_reader($streamer, false); + + From now on, you have all the benefits of gettext data at your + disposal, so may run: + print $wohoo->translate("This is a test"); + print $wohoo->ngettext("%d bird", "%d birds", $birds); + + You might need to pass parameter "-k" to xgettext to make it + extract all the strings. In above example, try with + xgettext -ktranslate -kngettext:1,2 file.php + what should create messages.po which contains two messages for + translation. + + I suggest creating simple aliases for these functions (see + example/pigs.php for how do I do it, which means it's probably a + bad way). + + +Usage with gettext.inc (standard gettext interfaces emulation) + + Check example in examples/pig_dropin.php, basically you include + gettext.inc and use all the standard gettext interfaces as + documented on: + + http://www.php.net/gettext + + The only catch is that you can check return value of setlocale() + to see if your locale is system supported or not. + + +Example + + See in examples/ subdirectory. There are a couple of files. + pigs.php is an example, serbian.po is a translation to Serbian + language, and serbian.mo is generated with + msgfmt -o serbian.mo serbian.po + There is also simple "update" script that can be used to generate + POT file and to update the translation using msgmerge. + +Interesting TODO: + + o Try to parse "plural-forms" header field, and to follow C syntax + rules. This won't be easy. + +Boring TODO: + + o Learn PHP and fix bugs, slowness and other stuff resulting from + my lack of knowledge (but *maybe*, it's not my knowledge that is + bad, but PHP itself ;-). + + (This is mostly done thanks to Nico Kaiser.) + + o Try to use hash tables in MO files: with pre-loading, would it + be useful at all? + +Never-asked-questions: + + o Why did you mark this as version 1.0 when this is the first code + release? + + Well, it's quite simple. I consider that the first released thing + should be labeled "version 1" (first, right?). Zero is there to + indicate that there's zero improvement and/or change compared to + "version 1". + + I plan to use version numbers 1.0.* for small bugfixes, and to + release 1.1 as "first stable release of version 1". + + This may trick someone that this is actually useful software, but + as with any other free software, I take NO RESPONSIBILITY for + creating such a masterpiece that will smoke crack, trash your + hard disk, and make lasers in your CD device dance to the tune of + Mozart's 40th Symphony (there is one like that, right?). + + o Can I...? + + Yes, you can. This is free software (as in freedom, free speech), + and you might do whatever you wish with it, provided you do not + limit freedom of others (GPL). + + I'm considering licensing this under LGPL, but I *do* want + *every* PHP-gettext user to contribute and respect ideas of free + software, so don't count on it happening anytime soon. + + I'm sorry that I'm taking away your freedom of taking others' + freedom away, but I believe that's neglible as compared to what + freedoms you could take away. ;-) + + Uhm, whatever. diff --git a/src/php-gettext/bin/gettexts.bat b/src/php-gettext/bin/gettexts.bat new file mode 100644 index 0000000..3ee4252 --- /dev/null +++ b/src/php-gettext/bin/gettexts.bat @@ -0,0 +1,20 @@ +@echo off +xgettext -kT_ngettext:1,2 -kT_ -L PHP -o ..\..\..\locales\messages.po ..\..\..\*.php ..\..\..\services\*.php ..\..\..\templates\*.php +if /i "%1" == "-p" goto stats +if exist "..\..\..\locales\%1.po" goto merge +echo "Usage: $0 [-p|<basename>]" +goto end + +:stats +msgfmt --statistics ..\..\..\locales\messages.po +goto end + +:merge +msgmerge -o ..\..\..\locales\tmp%1.po ..\..\..\locales\%1.po ..\..\..\locales\messages.po +if exist "..\..\..\locales\%1.po" rename ..\..\..\locales\%1.po %1.po.bak +rename ..\..\..\locales\tmp%1.po %1.po +if exist "..\..\..\locales\%1.po.bak" del ..\..\..\locales\%1.po.bak +msgfmt --statistics "..\..\..\locales\%1.po" + +:end +echo Finished diff --git a/src/php-gettext/bin/gettexts.sh b/src/php-gettext/bin/gettexts.sh new file mode 100755 index 0000000..267f670 --- /dev/null +++ b/src/php-gettext/bin/gettexts.sh @@ -0,0 +1,12 @@ +#!/bin/sh +xgettext -kT_ngettext:1,2 -kT_ -L PHP -o ../../../locales/messages.po ../../../*.php ../../../services/*.php ../../../templates/*.php + +if [ -f "../../../locales/$1/LC_MESSAGES/messages.po" ] +then +msgmerge -o ../../../locales/$1/LC_MESSAGES/messages.po ../../../locales/$1/LC_MESSAGES/messages.po ../../../locales/messages.po + +msgfmt --statistics "../../../locales/$1/LC_MESSAGES/messages.po" -o "../../../locales/$1/LC_MESSAGES/messages.mo" +else +echo "gettexts.sh LANGUAGE_CODE" +echo "example: 'gettexts fr_FR' to get text for French" +fi diff --git a/src/php-gettext/examples/index.php b/src/php-gettext/examples/index.php new file mode 100644 index 0000000..263cd3d --- /dev/null +++ b/src/php-gettext/examples/index.php @@ -0,0 +1,27 @@ +<html> +<head> +<title>PHP-gettext examples</title> +</head> +<body> +<h1>PHP-gettext</h1> + +<h2>Introduction</h2> +<p>PHP-gettext provides a simple gettext replacement that works independently from the system's gettext abilities. +It can read MO files and use them for translating strings.</p> +<p>This version has the ability to cache all strings and translations to speed up the string lookup. +While the cache is enabled by default, it can be switched off with the second parameter in the constructor (e.g. when using very large MO files +that you don't want to keep in memory)</p> + + +<h2>Examples</h2> +<ul> + <li><a href="pigs_dropin.php">PHP-gettext as a dropin replacement</a></li> + <li><a href="pigs_fallback.php">PHP-gettext as a fallback solution</a></li> +</ul> + +<hr /> +<p>Copyright (c) 2003-2006 Danilo Segan</p> +<p>Copyright (c) 2005-2006 Steven Armstrong</p> + +</body> +</html> diff --git a/src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.mo b/src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.mo Binary files differnew file mode 100644 index 0000000..9193037 --- /dev/null +++ b/src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.mo diff --git a/src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.po b/src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.po new file mode 100644 index 0000000..6e4886b --- /dev/null +++ b/src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.po @@ -0,0 +1,30 @@ +# Sample translation for PHP-gettext 1.0 +# Copyright (c) 2003 Danilo Segan <danilo@kvota.net> +# +msgid "" +msgstr "" +"Project-Id-Version: pigs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2003-10-23 04:50+0200\n" +"PO-Revision-Date: 2003-11-01 23:40+0100\n" +"Last-Translator: Danilo Segan <danilo@kvota.net>\n" +"Language-Team: Serbian (sr) <danilo@kvota.net>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +#"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#: pigs.php:19 +msgid "" +"This is how the story goes.\n" +"\n" +msgstr "" +"Und so geht die Geschichte.\n" +"\n" + +#: pigs.php:21 +#, php-format +msgid "%d pig went to the market\n" +msgid_plural "%d pigs went to the market\n" +msgstr[0] "%d Schwein ging zum Markt\n" +msgstr[1] "%d Schweine gingen zum Markt\n" diff --git a/src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mo b/src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mo Binary files differnew file mode 100644 index 0000000..6ffccfd --- /dev/null +++ b/src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mo diff --git a/src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.po b/src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.po new file mode 100644 index 0000000..7e620cc --- /dev/null +++ b/src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.po @@ -0,0 +1,29 @@ +# Sample translation for PHP-gettext 1.0 +# Copyright (c) 2003,2006 Danilo Segan <danilo@kvota.net> +# +msgid "" +msgstr "" +"Project-Id-Version: pigs\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2003-10-23 04:50+0200\n" +"PO-Revision-Date: 2006-02-02 21:06+0100\n" +"Last-Translator: Danilo Segan <danilo@kvota.net>\n" +"Language-Team: Serbian (sr) <danilo@kvota.net>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : (n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: pigs.php:19 +msgid "" +"This is how the story goes.\n" +"\n" +msgstr "Овако иде прича.\n\n" + +#: pigs.php:21 +#, php-format +msgid "%d pig went to the market\n" +msgid_plural "%d pigs went to the market\n" +msgstr[0] "%d мало прасе је отишло на пијац\n" +msgstr[1] "%d мала прасета су отишла на пијац\n" +msgstr[2] "%d малих прасића је отишло на пијац\n" diff --git a/src/php-gettext/examples/pigs_dropin.php b/src/php-gettext/examples/pigs_dropin.php new file mode 100644 index 0000000..edd2b0d --- /dev/null +++ b/src/php-gettext/examples/pigs_dropin.php @@ -0,0 +1,87 @@ +<?php +/* + Copyright (c) 2003,2004,2005 Danilo Segan <danilo@kvota.net>. + Copyright (c) 2005,2006 Steven Armstrong <sa@c-area.ch> + + This file is part of PHP-gettext. + + PHP-gettext is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PHP-gettext is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +// define constants +define(PROJECT_DIR, realpath('./')); +define(LOCALE_DIR, PROJECT_DIR .'/locale'); +define(DEFAULT_LOCALE, 'en_US'); + +require_once('../gettext.inc'); + +$supported_locales = array('en_US', 'sr_CS', 'de_CH'); +$encoding = 'UTF-8'; + +$locale = (isset($_GET['lang']))? $_GET['lang'] : DEFAULT_LOCALE; + +// gettext setup +T_setlocale(LC_MESSAGES, $locale); +// Set the text domain as 'messages' +$domain = 'messages'; +bindtextdomain($domain, LOCALE_DIR); +// bind_textdomain_codeset is supported only in PHP 4.2.0+ +if (function_exists('bind_textdomain_codeset')) + bind_textdomain_codeset($domain, $encoding); +textdomain($domain); + +header("Content-type: text/html; charset=$encoding"); +?> +<html> +<head> +<title>PHP-gettext dropin example</title> +</head> +<body> +<h1>PHP-gettext as a dropin replacement</h1> +<p>Example showing how to use PHP-gettext as a dropin replacement for the native gettext library.</p> +<?php +print "<p>"; +foreach($supported_locales as $l) { + print "[<a href=\"?lang=$l\">$l</a>] "; +} +print "</p>\n"; + +if (!locale_emulation()) { + print "<p>locale '$locale' is supported by your system, using native gettext implementation.</p>\n"; +} +else { + print "<p>locale '$locale' is _not_ supported on your system, using the default locale '". DEFAULT_LOCALE ."'.</p>\n"; +} +?> + +<hr /> + +<?php +// using PHP-gettext +print "<pre>"; +print _("This is how the story goes.\n\n"); +for ($number=6; $number>=0; $number--) { + print sprintf(T_ngettext("%d pig went to the market\n", + "%d pigs went to the market\n", $number), + $number ); +} +print "</pre>\n"; +?> + +<hr /> +<p>« <a href="./">back</a></p> +</body> +</html> diff --git a/src/php-gettext/examples/pigs_fallback.php b/src/php-gettext/examples/pigs_fallback.php new file mode 100644 index 0000000..b50f752 --- /dev/null +++ b/src/php-gettext/examples/pigs_fallback.php @@ -0,0 +1,86 @@ +<?php +/* + Copyright (c) 2003,2004,2005 Danilo Segan <danilo@kvota.net>. + Copyright (c) 2005,2006 Steven Armstrong <sa@c-area.ch> + + This file is part of PHP-gettext. + + PHP-gettext is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PHP-gettext is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +// define constants +define(PROJECT_DIR, realpath('./')); +define(LOCALE_DIR, PROJECT_DIR .'/locale'); +define(DEFAULT_LOCALE, 'en_US'); + +require_once('../gettext.inc'); + +$supported_locales = array('en_US', 'sr_CS', 'de_CH'); +$encoding = 'UTF-8'; + +$locale = (isset($_GET['lang']))? $_GET['lang'] : DEFAULT_LOCALE; + +// gettext setup +T_setlocale(LC_MESSAGES, $locale); +// Set the text domain as 'messages' +$domain = 'messages'; +T_bindtextdomain($domain, LOCALE_DIR); +T_bind_textdomain_codeset($domain, $encoding); +T_textdomain($domain); + +header("Content-type: text/html; charset=$encoding"); +?> +<html> +<head> +<title>PHP-gettext fallback example</title> +</head> +<body> +<h1>PHP-gettext as a fallback solution</h1> +<p>Example showing how to use PHP-gettext as a fallback solution if the native gettext library is not available or the system does not support the requested locale.</p> + +<?php +print "<p>"; +foreach($supported_locales as $l) { + print "[<a href=\"?lang=$l\">$l</a>] "; +} +print "</p>\n"; + +if (!locale_emulation()) { + print "<p>locale '$locale' is supported by your system, using native gettext implementation.</p>\n"; +} +else { + print "<p>locale '$locale' is <strong>not</strong> supported on your system, using custom gettext implementation.</p>\n"; +} +?> + +<hr /> + +<?php +// using PHP-gettext +print "<pre>"; +print T_("This is how the story goes.\n\n"); +for ($number=6; $number>=0; $number--) { + print sprintf( T_ngettext("%d pig went to the market\n", + "%d pigs went to the market\n", $number), + $number ); +} +print "</pre>\n"; +?> + +<hr /> +<p>« <a href="./">back</a></p> +</body> +</html> diff --git a/src/php-gettext/examples/update b/src/php-gettext/examples/update new file mode 100644 index 0000000..c8d8b61 --- /dev/null +++ b/src/php-gettext/examples/update @@ -0,0 +1,14 @@ +#!/bin/sh +TEMPLATE=pigs.pot +xgettext -kT_ngettext:1,2 -kT_ -L PHP -o $TEMPLATE pigs.php +if [ x$1 == 'x-p' ]; then + msgfmt --statistics $TEMPLATE +else + if [ -f $1.po ]; then + msgmerge -o .tmp$1.po $1.po $TEMPLATE + mv .tmp$1.po $1.po + msgfmt --statistics $1.po + else + echo "Usage: $0 [-p|<basename>]" + fi +fi diff --git a/src/php-gettext/gettext.inc b/src/php-gettext/gettext.inc new file mode 100644 index 0000000..a67811f --- /dev/null +++ b/src/php-gettext/gettext.inc @@ -0,0 +1,318 @@ +<?php +/* + Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch> + + Drop in replacement for native gettext. + + This file is part of PHP-gettext. + + PHP-gettext is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PHP-gettext is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +/* +LC_CTYPE 0 +LC_NUMERIC 1 +LC_TIME 2 +LC_COLLATE 3 +LC_MONETARY 4 +LC_MESSAGES 5 +LC_ALL 6 +*/ + +require('streams.php'); +require('gettext.php'); + + +// Variables + +global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE; +$text_domains = array(); +$default_domain = 'messages'; +$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL'); +$EMULATEGETTEXT = 0; +$CURRENTLOCALE = ''; + + +// Utility functions + +/** + * Utility function to get a StreamReader for the given text domain. + */ +function _get_reader($domain=null, $category=5, $enable_cache=true) { + global $text_domains, $default_domain, $LC_CATEGORIES; + if (!isset($domain)) $domain = $default_domain; + if (!isset($text_domains[$domain]->l10n)) { + // get the current locale + $locale = _setlocale(LC_MESSAGES, 0); + $p = isset($text_domains[$domain]->path) ? $text_domains[$domain]->path : './'; + $path = $p . "$locale/". $LC_CATEGORIES[$category] ."/$domain.mo"; + if (file_exists($path)) { + $input = new FileReader($path); + } + else { + $input = null; + } + $text_domains[$domain]->l10n = new gettext_reader($input, $enable_cache); + } + return $text_domains[$domain]->l10n; +} + +/** + * Returns whether we are using our emulated gettext API or PHP built-in one. + */ +function locale_emulation() { + global $EMULATEGETTEXT; + return $EMULATEGETTEXT; +} + +/** + * Checks if the current locale is supported on this system. + */ +function _check_locale() { + global $EMULATEGETTEXT; + return !$EMULATEGETTEXT; +} + +/** + * Get the codeset for the given domain. + */ +function _get_codeset($domain=null) { + global $text_domains, $default_domain, $LC_CATEGORIES; + if (!isset($domain)) $domain = $default_domain; + return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding'); +} + +/** + * Convert the given string to the encoding set by bind_textdomain_codeset. + */ +function _encode($text) { + $source_encoding = mb_detect_encoding($text); + $target_encoding = _get_codeset(); + if ($source_encoding != $target_encoding) { + return mb_convert_encoding($text, $target_encoding, $source_encoding); + } + else { + return $text; + } +} + + + + +// Custom implementation of the standard gettext related functions + +/** + * Sets a requested locale, if needed emulates it. + */ +function _setlocale($category, $locale) { + global $CURRENTLOCALE, $EMULATEGETTEXT; + if ($locale === 0) { // use === to differentiate between string "0" + if ($CURRENTLOCALE != '') + return $CURRENTLOCALE; + else + // obey LANG variable, maybe extend to support all of LC_* vars + // even if we tried to read locale without setting it first + return _setlocale($category, $CURRENTLOCALE); + } else { + $ret = 0; + if (function_exists('setlocale')) // I don't know if this ever happens ;) + $ret = @setlocale($category, $locale); //the @ hides warning messages on few installations + if (($ret and $locale == '') or ($ret == $locale)) { + $EMULATEGETTEXT = 0; + $CURRENTLOCALE = $ret; + } else { + if ($locale == '') // emulate variable support + $CURRENTLOCALE = getenv('LANG'); + else + $CURRENTLOCALE = $locale; + $EMULATEGETTEXT = 1; + } + return $CURRENTLOCALE; + } +} + +/** + * Sets the path for a domain. + */ +function _bindtextdomain($domain, $path) { + global $text_domains; + // ensure $path ends with a slash + if ($path[strlen($path) - 1] != '/') $path .= '/'; + elseif ($path[strlen($path) - 1] != '\\') $path .= '\\'; + $text_domains[$domain]->path = $path; +} + +/** + * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned. + */ +function _bind_textdomain_codeset($domain, $codeset) { + global $text_domains; + $text_domains[$domain]->codeset = $codeset; +} + +/** + * Sets the default domain. + */ +function _textdomain($domain) { + global $default_domain; + $default_domain = $domain; +} + +/** + * Lookup a message in the current domain. + */ +function _gettext($msgid) { + $l10n = _get_reader(); + //return $l10n->translate($msgid); + return _encode($l10n->translate($msgid)); +} +/** + * Alias for gettext. + */ +function __($msgid) { + return _gettext($msgid); +} +/** + * Plural version of gettext. + */ +function _ngettext($single, $plural, $number) { + $l10n = _get_reader(); + //return $l10n->ngettext($single, $plural, $number); + return _encode($l10n->ngettext($single, $plural, $number)); +} + +/** + * Override the current domain. + */ +function _dgettext($domain, $msgid) { + $l10n = _get_reader($domain); + //return $l10n->translate($msgid); + return _encode($l10n->translate($msgid)); +} +/** + * Plural version of dgettext. + */ +function _dngettext($domain, $single, $plural, $number) { + $l10n = _get_reader($domain); + //return $l10n->ngettext($single, $plural, $number); + return _encode($l10n->ngettext($single, $plural, $number)); +} + +/** + * Overrides the domain and category for a single lookup. + */ +function _dcgettext($domain, $msgid, $category) { + $l10n = _get_reader($domain, $category); + //return $l10n->translate($msgid); + return _encode($l10n->translate($msgid)); +} +/** + * Plural version of dcgettext. + */ +function _dcngettext($domain, $single, $plural, $number, $category) { + $l10n = _get_reader($domain, $category); + //return $l10n->ngettext($single, $plural, $number); + return _encode($l10n->ngettext($single, $plural, $number)); +} + + + +// Wrappers to use if the standard gettext functions are available, but the current locale is not supported by the system. +// Use the standard impl if the current locale is supported, use the custom impl otherwise. + +function T_setlocale($category, $locale) { + return _setlocale($category, $locale); +} + +function T_bindtextdomain($domain, $path) { + if (_check_locale()) return bindtextdomain($domain, $path); + else return _bindtextdomain($domain, $path); +} +function T_bind_textdomain_codeset($domain, $codeset) { + // bind_textdomain_codeset is available only in PHP 4.2.0+ + if (_check_locale() and function_exists('bind_textdomain_codeset')) return bind_textdomain_codeset($domain, $codeset); + else return _bind_textdomain_codeset($domain, $codeset); +} +function T_textdomain($domain) { + if (_check_locale()) return textdomain($domain); + else return _textdomain($domain); +} +function T_gettext($msgid) { + if (_check_locale()) return gettext($msgid); + else return _gettext($msgid); +} +function T_($msgid) { + if (_check_locale()) return _($msgid); + return __($msgid); +} +function T_ngettext($single, $plural, $number) { + if (_check_locale()) return ngettext($single, $plural, $number); + else return _ngettext($single, $plural, $number); +} +function T_dgettext($domain, $msgid) { + if (_check_locale()) return dgettext($domain, $msgid); + else return _dgettext($domain, $msgid); +} +function T_dngettext($domain, $single, $plural, $number) { + if (_check_locale()) return dngettext($domain, $single, $plural, $number); + else return _dngettext($domain, $single, $plural, $number); +} +function T_dcgettext($domain, $msgid, $category) { + if (_check_locale()) return dcgettext($domain, $msgid, $category); + else return _dcgettext($domain, $msgid, $category); +} +function T_dcngettext($domain, $single, $plural, $number, $category) { + if (_check_locale()) return dcngettext($domain, $single, $plural, $number, $category); + else return _dcngettext($domain, $single, $plural, $number, $category); +} + + + +// Wrappers used as a drop in replacement for the standard gettext functions + +if (!function_exists('gettext')) { + function bindtextdomain($domain, $path) { + return _bindtextdomain($domain, $path); + } + function bind_textdomain_codeset($domain, $codeset) { + return _bind_textdomain_codeset($domain, $codeset); + } + function textdomain($domain) { + return _textdomain($domain); + } + function gettext($msgid) { + return _gettext($msgid); + } + function _($msgid) { + return __($msgid); + } + function ngettext($single, $plural, $number) { + return _ngettext($single, $plural, $number); + } + function dgettext($domain, $msgid) { + return _dgettext($domain, $msgid); + } + function dngettext($domain, $single, $plural, $number) { + return _dngettext($domain, $single, $plural, $number); + } + function dcgettext($domain, $msgid, $category) { + return _dcgettext($domain, $msgid, $category); + } + function dcngettext($domain, $single, $plural, $number, $category) { + return _dcngettext($domain, $single, $plural, $number, $category); + } +} + +?> diff --git a/src/php-gettext/gettext.php b/src/php-gettext/gettext.php new file mode 100644 index 0000000..ad94a98 --- /dev/null +++ b/src/php-gettext/gettext.php @@ -0,0 +1,358 @@ +<?php +/* + Copyright (c) 2003 Danilo Segan <danilo@kvota.net>. + Copyright (c) 2005 Nico Kaiser <nico@siriux.net> + + This file is part of PHP-gettext. + + PHP-gettext is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PHP-gettext is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +/** + * Provides a simple gettext replacement that works independently from + * the system's gettext abilities. + * It can read MO files and use them for translating strings. + * The files are passed to gettext_reader as a Stream (see streams.php) + * + * This version has the ability to cache all strings and translations to + * speed up the string lookup. + * While the cache is enabled by default, it can be switched off with the + * second parameter in the constructor (e.g. whenusing very large MO files + * that you don't want to keep in memory) + */ +class gettext_reader { + //public: + var $error = 0; // public variable that holds error code (0 if no error) + + //private: + var $BYTEORDER = 0; // 0: low endian, 1: big endian + var $STREAM = NULL; + var $short_circuit = false; + var $enable_cache = false; + var $originals = NULL; // offset of original table + var $translations = NULL; // offset of translation table + var $pluralheader = NULL; // cache header field for plural forms + var $total = 0; // total string count + var $table_originals = NULL; // table for original strings (offsets) + var $table_translations = NULL; // table for translated strings (offsets) + var $cache_translations = NULL; // original -> translation mapping + + + /* Methods */ + + + /** + * Reads a 32bit Integer from the Stream + * + * @access private + * @return Integer from the Stream + */ + function readint() { + if ($this->BYTEORDER == 0) { + // low endian + return array_shift(unpack('V', $this->STREAM->read(4))); + } else { + // big endian + return array_shift(unpack('N', $this->STREAM->read(4))); + } + } + + /** + * Reads an array of Integers from the Stream + * + * @param int count How many elements should be read + * @return Array of Integers + */ + function readintarray($count) { + if ($this->BYTEORDER == 0) { + // low endian + return unpack('V'.$count, $this->STREAM->read(4 * $count)); + } else { + // big endian + return unpack('N'.$count, $this->STREAM->read(4 * $count)); + } + } + + /** + * Constructor + * + * @param object Reader the StreamReader object + * @param boolean enable_cache Enable or disable caching of strings (default on) + */ + function gettext_reader($Reader, $enable_cache = true) { + // If there isn't a StreamReader, turn on short circuit mode. + if (! $Reader || isset($Reader->error) ) { + $this->short_circuit = true; + return; + } + + // Caching can be turned off + $this->enable_cache = $enable_cache; + + // $MAGIC1 = (int)0x950412de; //bug in PHP 5 + $MAGIC1 = (int) - 1794895138; + // $MAGIC2 = (int)0xde120495; //bug + $MAGIC2 = (int) - 569244523; + + $this->STREAM = $Reader; + $magic = $this->readint(); + if ($magic == $MAGIC1) { + $this->BYTEORDER = 0; + } elseif ($magic == $MAGIC2) { + $this->BYTEORDER = 1; + } else { + $this->error = 1; // not MO file + return false; + } + + // FIXME: Do we care about revision? We should. + $revision = $this->readint(); + + $this->total = $this->readint(); + $this->originals = $this->readint(); + $this->translations = $this->readint(); + } + + /** + * Loads the translation tables from the MO file into the cache + * If caching is enabled, also loads all strings into a cache + * to speed up translation lookups + * + * @access private + */ + function load_tables() { + if (is_array($this->cache_translations) && + is_array($this->table_originals) && + is_array($this->table_translations)) + return; + + /* get original and translations tables */ + $this->STREAM->seekto($this->originals); + $this->table_originals = $this->readintarray($this->total * 2); + $this->STREAM->seekto($this->translations); + $this->table_translations = $this->readintarray($this->total * 2); + + if ($this->enable_cache) { + $this->cache_translations = array (); + /* read all strings in the cache */ + for ($i = 0; $i < $this->total; $i++) { + $this->STREAM->seekto($this->table_originals[$i * 2 + 2]); + $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); + $this->STREAM->seekto($this->table_translations[$i * 2 + 2]); + $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); + $this->cache_translations[$original] = $translation; + } + } + } + + /** + * Returns a string from the "originals" table + * + * @access private + * @param int num Offset number of original string + * @return string Requested string if found, otherwise '' + */ + function get_original_string($num) { + $length = $this->table_originals[$num * 2 + 1]; + $offset = $this->table_originals[$num * 2 + 2]; + if (! $length) + return ''; + $this->STREAM->seekto($offset); + $data = $this->STREAM->read($length); + return (string)$data; + } + + /** + * Returns a string from the "translations" table + * + * @access private + * @param int num Offset number of original string + * @return string Requested string if found, otherwise '' + */ + function get_translation_string($num) { + $length = $this->table_translations[$num * 2 + 1]; + $offset = $this->table_translations[$num * 2 + 2]; + if (! $length) + return ''; + $this->STREAM->seekto($offset); + $data = $this->STREAM->read($length); + return (string)$data; + } + + /** + * Binary search for string + * + * @access private + * @param string string + * @param int start (internally used in recursive function) + * @param int end (internally used in recursive function) + * @return int string number (offset in originals table) + */ + function find_string($string, $start = -1, $end = -1) { + if (($start == -1) or ($end == -1)) { + // find_string is called with only one parameter, set start end end + $start = 0; + $end = $this->total; + } + if (abs($start - $end) <= 1) { + // We're done, now we either found the string, or it doesn't exist + $txt = $this->get_original_string($start); + if ($string == $txt) + return $start; + else + return -1; + } else if ($start > $end) { + // start > end -> turn around and start over + return $this->find_string($string, $end, $start); + } else { + // Divide table in two parts + $half = (int)(($start + $end) / 2); + $cmp = strcmp($string, $this->get_original_string($half)); + if ($cmp == 0) + // string is exactly in the middle => return it + return $half; + else if ($cmp < 0) + // The string is in the upper half + return $this->find_string($string, $start, $half); + else + // The string is in the lower half + return $this->find_string($string, $half, $end); + } + } + + /** + * Translates a string + * + * @access public + * @param string string to be translated + * @return string translated string (or original, if not found) + */ + function translate($string) { + if ($this->short_circuit) + return $string; + $this->load_tables(); + + if ($this->enable_cache) { + // Caching enabled, get translated string from cache + if (array_key_exists($string, $this->cache_translations)) + return $this->cache_translations[$string]; + else + return $string; + } else { + // Caching not enabled, try to find string + $num = $this->find_string($string); + if ($num == -1) + return $string; + else + return $this->get_translation_string($num); + } + } + + /** + * Get possible plural forms from MO header + * + * @access private + * @return string plural form header + */ + function get_plural_forms() { + // lets assume message number 0 is header + // this is true, right? + $this->load_tables(); + + // cache header field for plural forms + if (! is_string($this->pluralheader)) { + if ($this->enable_cache) { + $header = $this->cache_translations[""]; + } else { + $header = $this->get_translation_string(0); + } + if (eregi("plural-forms: ([^\n]*)\n", $header, $regs)) + $expr = $regs[1]; + else + $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; + $this->pluralheader = $expr; + } + return $this->pluralheader; + } + + /** + * Detects which plural form to take + * + * @access private + * @param n count + * @return int array index of the right plural form + */ + function select_string($n) { + $string = $this->get_plural_forms(); + $string = str_replace('nplurals',"\$total",$string); + $string = str_replace("n",$n,$string); + $string = str_replace('plural',"\$plural",$string); + + $total = 0; + $plural = 0; + + eval("$string"); + if ($plural >= $total) $plural = $total - 1; + return $plural; + } + + /** + * Plural version of gettext + * + * @access public + * @param string single + * @param string plural + * @param string number + * @return translated plural form + */ + function ngettext($single, $plural, $number) { + if ($this->short_circuit) { + if ($number != 1) + return $plural; + else + return $single; + } + + // find out the appropriate form + $select = $this->select_string($number); + + // this should contains all strings separated by NULLs + $key = $single.chr(0).$plural; + + + if ($this->enable_cache) { + if (! array_key_exists($key, $this->cache_translations)) { + return ($number != 1) ? $plural : $single; + } else { + $result = $this->cache_translations[$key]; + $list = explode(chr(0), $result); + return $list[$select]; + } + } else { + $num = $this->find_string($key); + if ($num == -1) { + return ($number != 1) ? $plural : $single; + } else { + $result = $this->get_translation_string($num); + $list = explode(chr(0), $result); + return $list[$select]; + } + } + } + +} + +?> diff --git a/src/php-gettext/streams.php b/src/php-gettext/streams.php new file mode 100644 index 0000000..4237de1 --- /dev/null +++ b/src/php-gettext/streams.php @@ -0,0 +1,167 @@ +<?php +/* + Copyright (c) 2003, 2005 Danilo Segan <danilo@kvota.net>. + + This file is part of PHP-gettext. + + PHP-gettext is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PHP-gettext is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + + +// Simple class to wrap file streams, string streams, etc. +// seek is essential, and it should be byte stream +class StreamReader { + // should return a string [FIXME: perhaps return array of bytes?] + function read($bytes) { + return false; + } + + // should return new position + function seekto($position) { + return false; + } + + // returns current position + function currentpos() { + return false; + } + + // returns length of entire stream (limit for seekto()s) + function length() { + return false; + } +} + +class StringReader { + var $_pos; + var $_str; + + function StringReader($str='') { + $this->_str = $str; + $this->_pos = 0; + } + + function read($bytes) { + $data = substr($this->_str, $this->_pos, $bytes); + $this->_pos += $bytes; + if (strlen($this->_str)<$this->_pos) + $this->_pos = strlen($this->_str); + + return $data; + } + + function seekto($pos) { + $this->_pos = $pos; + if (strlen($this->_str)<$this->_pos) + $this->_pos = strlen($this->_str); + return $this->_pos; + } + + function currentpos() { + return $this->_pos; + } + + function length() { + return strlen($this->_str); + } + +} + + +class FileReader { + var $_pos; + var $_fd; + var $_length; + + function FileReader($filename) { + if (file_exists($filename)) { + + $this->_length=filesize($filename); + $this->_pos = 0; + $this->_fd = fopen($filename,'rb'); + if (!$this->_fd) { + $this->error = 3; // Cannot read file, probably permissions + return false; + } + } else { + $this->error = 2; // File doesn't exist + return false; + } + } + + function read($bytes) { + if ($bytes) { + fseek($this->_fd, $this->_pos); + + // PHP 5.1.1 does not read more than 8192 bytes in one fread() + // the discussions at PHP Bugs suggest it's the intended behaviour + $data = ''; + while ($bytes > 0) { + $chunk = fread($this->_fd, $bytes); + $data .= $chunk; + $bytes -= strlen($chunk); + } + $this->_pos = ftell($this->_fd); + + return $data; + } else return ''; + } + + function seekto($pos) { + fseek($this->_fd, $pos); + $this->_pos = ftell($this->_fd); + return $this->_pos; + } + + function currentpos() { + return $this->_pos; + } + + function length() { + return $this->_length; + } + + function close() { + fclose($this->_fd); + } + +} + +// Preloads entire file in memory first, then creates a StringReader +// over it (it assumes knowledge of StringReader internals) +class CachedFileReader extends StringReader { + function CachedFileReader($filename) { + if (file_exists($filename)) { + + $length=filesize($filename); + $fd = fopen($filename,'rb'); + + if (!$fd) { + $this->error = 3; // Cannot read file, probably permissions + return false; + } + $this->_str = fread($fd, $length); + fclose($fd); + + } else { + $this->error = 2; // File doesn't exist + return false; + } + } +} + + +?> |