summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/SemanticScuttle/Service/bookmark2tagservice.php478
-rw-r--r--src/SemanticScuttle/Service/bookmarkservice.php552
-rw-r--r--src/SemanticScuttle/Service/cacheservice.php38
-rw-r--r--src/SemanticScuttle/Service/commondescriptionservice.php167
-rw-r--r--src/SemanticScuttle/Service/searchhistoryservice.php124
-rw-r--r--src/SemanticScuttle/Service/servicefactory.php38
-rw-r--r--src/SemanticScuttle/Service/tag2tagservice.php377
-rw-r--r--src/SemanticScuttle/Service/tagcacheservice.php349
-rw-r--r--src/SemanticScuttle/Service/tagservice.php123
-rw-r--r--src/SemanticScuttle/Service/tagstatservice.php193
-rw-r--r--src/SemanticScuttle/Service/templateservice.php46
-rw-r--r--src/SemanticScuttle/Service/userservice.php665
-rw-r--r--src/SemanticScuttle/constants.php62
-rw-r--r--src/SemanticScuttle/db/db2.php417
-rw-r--r--src/SemanticScuttle/db/firebird.php527
-rw-r--r--src/SemanticScuttle/db/index.htm10
-rw-r--r--src/SemanticScuttle/db/mssql-odbc.php576
-rw-r--r--src/SemanticScuttle/db/mssql.php551
-rw-r--r--src/SemanticScuttle/db/mysql.php552
-rw-r--r--src/SemanticScuttle/db/mysql4.php552
-rw-r--r--src/SemanticScuttle/db/mysqli.php562
-rw-r--r--src/SemanticScuttle/db/oracle.php468
-rw-r--r--src/SemanticScuttle/db/postgres.php597
-rw-r--r--src/SemanticScuttle/db/sqlite.php387
-rw-r--r--src/SemanticScuttle/functions.inc.php207
-rw-r--r--src/SemanticScuttle/header.inc.php55
-rw-r--r--src/SemanticScuttle/search.inc.php54
-rw-r--r--src/php-gettext/AUTHORS3
-rw-r--r--src/php-gettext/COPYING340
-rw-r--r--src/php-gettext/ChangeLog144
-rw-r--r--src/php-gettext/Makefile32
-rw-r--r--src/php-gettext/README189
-rw-r--r--src/php-gettext/bin/gettexts.bat20
-rwxr-xr-xsrc/php-gettext/bin/gettexts.sh12
-rw-r--r--src/php-gettext/examples/index.php27
-rw-r--r--src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.mobin0 -> 585 bytes
-rw-r--r--src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.po30
-rw-r--r--src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mobin0 -> 834 bytes
-rw-r--r--src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.po29
-rw-r--r--src/php-gettext/examples/pigs_dropin.php87
-rw-r--r--src/php-gettext/examples/pigs_fallback.php86
-rw-r--r--src/php-gettext/examples/update14
-rw-r--r--src/php-gettext/gettext.inc318
-rw-r--r--src/php-gettext/gettext.php358
-rw-r--r--src/php-gettext/streams.php167
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> &nbsp; &nbsp; &nbsp;</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> &nbsp; &nbsp; &nbsp;</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> &nbsp; &nbsp; &nbsp;</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> &nbsp; &nbsp; &nbsp;</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)) : '&nbsp;') . '</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 : '&nbsp;') . '</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> &nbsp; &nbsp; &nbsp;</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)) : '&nbsp;') . '</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 : '&nbsp;') . '</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> &nbsp; &nbsp; &nbsp;</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)) : '&nbsp;') . '</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 : '&nbsp;') . '</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> &nbsp; &nbsp; &nbsp;</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("&nbsp;</td>\n<td>", array_values($row)) . "&nbsp;</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
new file mode 100644
index 0000000..9193037
--- /dev/null
+++ b/src/php-gettext/examples/locale/de_CH/LC_MESSAGES/messages.mo
Binary files differ
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
new file mode 100644
index 0000000..6ffccfd
--- /dev/null
+++ b/src/php-gettext/examples/locale/sr_CS/LC_MESSAGES/messages.mo
Binary files differ
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>&laquo; <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>&laquo; <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;
+ }
+ }
+}
+
+
+?>