From: Michael J. Rubinsky Date: Mon, 28 Jun 2010 22:40:19 +0000 (-0400) Subject: Capitalization X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=d5ef3a47fc354c68fdabe87573d7a1c3a0595712;p=horde.git Capitalization --- diff --git a/jonah/lib/Driver/Sql.php b/jonah/lib/Driver/Sql.php new file mode 100644 index 000000000..273d1f4d3 --- /dev/null +++ b/jonah/lib/Driver/Sql.php @@ -0,0 +1,1058 @@ + + * 'phptype' The database type (e.g. 'pgsql', 'mysql', etc.). + * 'charset' The database's internal charset. + * + * Required by some database implementations:
+ *      'hostspec'      The hostname of the database server.
+ *      'protocol'      The communication protocol ('tcp', 'unix', etc.).
+ *      'database'      The name of the database.
+ *      'username'      The username with which to connect to the database.
+ *      'password'      The password associated with 'username'.
+ *      'options'       Additional options to pass to the database.
+ *      'tty'           The TTY on which to connect to the database.
+ *      'port'          The port on which to connect to the database.
+ * + * The table structure can be created by the scripts/db/jonah_news.sql + * script. The needed tables are jonah_channels and jonah_stories. + * + * Copyright 2002-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file LICENSE for license information (BSD). If you did not + * did not receive this file, see http://cvs.horde.org/co.php/jonah/LICENSE. + * + * @author Marko Djukic + * @author Chuck Hagenbuch + * @author Jan Schneider + * @author Ben Klang + * @package Jonah + */ +class Jonah_Driver_Sql extends Jonah_Driver +{ + /** + * Handle for the current database connection. + * + * @TODO: Refactor to use Horde_Db + * @var DB + */ + protected $_db; + + /** + * Boolean indicating whether or not we're connected to the SQL server. + * + * @var boolean + */ + protected $_connected = false; + + /** + * Saves a channel to the backend. + * + * @param array $info The channel to add. + * Must contain a combination of the following + * entries: + *
+     * 'channel_id'       If empty a new channel is being added, otherwise one
+     *                    is being edited.
+     * 'channel_name'     The headline.
+     * 'channel_desc'     A description of this channel.
+     * 'channel_type'     Whether internal or external.
+     * 'channel_interval' If external then interval at which to refresh.
+     * 'channel_link'     The link to the source.
+     * 'channel_url'      The url from where to fetch the story list.
+     * 'channel_image'    A channel image.
+     * 
+ * + * @return int|PEAR_Error The channel ID on success, PEAR_Error on + * failure. + */ + public function saveChannel(&$info) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + if (empty($info['channel_id'])) { + $info['channel_id'] = $this->_db->nextId('jonah_channels'); + if (is_a($info['channel_id'], 'PEAR_Error')) { + Horde::logMessage($info['channel_id'], 'ERR'); + return $info['channel_id']; + } + $sql = 'INSERT INTO jonah_channels' . + ' (channel_id, channel_name, channel_type, channel_desc, channel_interval, channel_url, channel_link, channel_page_link, channel_story_url, channel_img)' . + ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + $values = array(); + } else { + $sql = 'UPDATE jonah_channels' . + ' SET channel_id = ?, channel_name = ?, channel_type = ?, channel_desc = ?, channel_interval = ?, channel_url = ?, channel_link = ?, channel_page_link = ?, channel_story_url = ?, channel_img = ?' . + ' WHERE channel_id = ?'; + $values = array((int)$info['channel_id']); + } + + array_unshift($values, + (int)$info['channel_id'], + Horde_String::convertCharset($info['channel_name'], Horde_Nls::getCharset(), $this->_params['charset']), + (int)$info['channel_type'], + isset($info['channel_desc']) ? $info['channel_desc'] : null, + isset($info['channel_interval']) ? (int)$info['channel_interval'] : null, + isset($info['channel_url']) ? $info['channel_url'] : null, + isset($info['channel_link']) ? $info['channel_link'] : null, + isset($info['channel_page_link']) ? $info['channel_page_link'] : null, + isset($info['channel_story_url']) ? $info['channel_story_url'] : null, + isset($info['channel_img']) ? $info['channel_img'] : null); + Horde::logMessage('SQL Query by Jonah_Driver_sql::saveChannel(): ' . $sql, 'DEBUG'); + $result = $this->_db->query($sql, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + return $result; + } + + return $info['channel_id']; + } + + /** + * Get a list of stored channels. + * + * @param integer $type The type of channel to filter for. Possible + * values are either JONAH_INTERNAL_CHANNEL + * to fetch only a list of internal channels, + * or JONAH_EXTERNAL_CHANNEL for only external. + * If null both channel types are returned. + * + * @return mixed An array of channels or PEAR_Error on error. + */ + public function getChannels($type = null) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $wsql = ''; + if (!is_null($type)) { + if (!is_array($type)) { + $type = array($type); + } + for ($i = 0, $i_max = count($type); $i < $i_max; ++$i) { + $type[$i] = 'channel_type = ' . (int)$type[$i]; + } + $wsql = 'WHERE ' . implode(' OR ', $type); + } + + $sql = sprintf('SELECT channel_id, channel_name, channel_type, channel_updated FROM jonah_channels %s ORDER BY channel_name', $wsql); + + Horde::logMessage('SQL Query by Jonah_Driver_sql::getChannels(): ' . $sql, 'DEBUG'); + $result = $this->_db->getAll($sql, DB_FETCHMODE_ASSOC); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + return $result; + } + for ($i = 0; $i < count($result); $i++) { + $result[$i]['channel_name'] = Horde_String::convertCharset($result[$i]['channel_name'], $this->_params['charset']); + } + + return $result; + } + + /** + */ + protected function _getChannel($channel_id) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'SELECT * FROM jonah_channels WHERE channel_id = ' . (int)$channel_id; + + Horde::logMessage('SQL Query by Jonah_Driver_sql::_getChannel(): ' . $sql, 'DEBUG'); + $result = $this->_db->getRow($sql, DB_FETCHMODE_ASSOC); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + return $result; + } elseif (empty($result)) { + return PEAR::raiseError(sprintf(_("Channel id \"%s\" not found."), $channel_id)); + } + + $result['channel_name'] = Horde_String::convertCharset($result['channel_name'], $this->_params['charset']); + if ($result['channel_type'] == Jonah::COMPOSITE_CHANNEL) { + $channels = explode(':', $result['channel_url']); + if (count($channels)) { + $sql = 'SELECT MAX(channel_updated) FROM jonah_channels WHERE channel_id IN (' . implode(',', $channels) . ')'; + Horde::logMessage('SQL Query by Jonah_Driver_sql::_getChannel(): ' . $sql, 'DEBUG'); + $updated = $this->_db->getOne($sql); + if (is_a($updated, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + } else { + $result['channel_updated'] = $updated; + $this->_timestampChannel($channel_id, $updated); + } + } + } + + return $result; + } + + /** + */ + protected function _timestampChannel($channel_id, $timestamp) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = sprintf('UPDATE jonah_channels SET channel_updated = %s WHERE channel_id = %s', + (int)$timestamp, + (int)$channel_id); + Horde::logMessage('SQL Query by Jonah_Driver_sql::_timestampChannel(): ' . $sql, 'DEBUG'); + $result = $this->_db->query($sql); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + } + return $result; + } + + /** + */ + protected function _readStory($story_id) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'UPDATE jonah_stories SET story_read = story_read + 1 WHERE story_id = ' . (int)$story_id; + Horde::logMessage('SQL Query by Jonah_Driver_sql::_readStory(): ' . $sql, 'DEBUG'); + $result = $this->_db->query($sql); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + } + return $result; + } + + /** + */ + protected function _deleteChannel($channel_id) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'DELETE FROM jonah_channels WHERE channel_id = ?'; + $values = array($channel_id); + + Horde::logMessage('SQL Query by Jonah_Driver_sql::deleteChannel(): ' . $sql, 'DEBUG'); + $result = $this->_db->query($sql, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + } + + return $result; + } + + /** + * @param array &$info + */ + protected function _saveStory(&$info) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + if (empty($info['story_id'])) { + $info['story_id'] = $this->_db->nextId('jonah_stories'); + if (is_a($info['story_id'], 'PEAR_Error')) { + Horde::logMessage($info['story_id'], 'ERR'); + return $info['story_id']; + } + $channel = $this->getChannel($info['channel_id']); + $permalink = $this->getStoryLink($channel, $info); + $sql = 'INSERT INTO jonah_stories (story_id, channel_id, story_title, story_desc, story_body_type, story_body, story_url, story_published, story_updated, story_read, story_permalink) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + $values = array($permalink); + } else { + $sql = 'UPDATE jonah_stories SET story_id = ?, channel_id = ?, story_title = ?, story_desc = ?, story_body_type = ?, story_body = ?, story_url = ?, story_published = ?, story_updated = ?, story_read = ? WHERE story_id = ?'; + $values = array((int)$info['story_id']); + } + + if (empty($info['story_read'])) { + $info['story_read'] = 0; + } + + /* Deal with any tags */ + if (!empty($info['story_tags'])) { + $tags = explode(',', $info['story_tags']); + } else { + $tags = array(); + } + $this->writeTags($info['story_id'], $info['channel_id'], $tags); + + array_unshift($values, + (int)$info['story_id'], + (int)$info['channel_id'], + Horde_String::convertCharset($info['story_title'], Horde_Nls::getCharset(), $this->_params['charset']), + Horde_String::convertCharset($info['story_desc'], Horde_Nls::getCharset(), $this->_params['charset']), + $info['story_body_type'], + isset($info['story_body']) ? Horde_String::convertCharset($info['story_body'], Horde_Nls::getCharset(), $this->_params['charset']) : null, + isset($info['story_url']) ? $info['story_url'] : null, + isset($info['story_published']) ? (int)$info['story_published'] : null, + time(), + (int)$info['story_read']); + + Horde::logMessage('SQL Query by Jonah_Driver_sql::_saveStory(): ' . $sql, 'DEBUG'); + $result = $this->_db->query($sql, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + return $result; + } + + $this->_timestampChannel($info['channel_id'], time()); + return true; + } + + /** + * Converts the text fields of a story from the backend charset to the + * output charset. + * + * @param array $story A story hash. + * + * @return array The converted hash. + */ + protected function _convertFromBackend($story) + { + $story['story_title'] = Horde_String::convertCharset($story['story_title'], $this->_params['charset'], Horde_Nls::getCharset()); + $story['story_desc'] = Horde_String::convertCharset($story['story_desc'], $this->_params['charset'], Horde_Nls::getCharset()); + if (isset($story['story_body'])) { + $story['story_body'] = Horde_String::convertCharset($story['story_body'], $this->_params['charset'], Horde_Nls::getCharset()); + } + if (isset($story['story_tags'])) { + $story['story_tags'] = Horde_String::convertCharset($story['story_tags'], $this->_params['charset'], Horde_Nls::getCharset()); + } + return $story; + } + + /** + * Look up a channel ID by its name + * + * @param string $channel + * + * @return int Channel ID + */ + public function getChannelId($channel) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'SELECT channel_id FROM jonah_channels WHERE channel_slug = ?'; + $values = array($channel); + $result = $this->_db->getOne($sql, $values); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + return $result; + } + + /** + * Returns the total number of stories in the specified + * channel + * + * @param int $channel_id The Channel ID + * + * @return mixed The count || PEAR_Error + */ + public function getStoryCount($channel_id) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'SELECT count(*) FROM jonah_stories WHERE channel_id = ?'; + $result = $this->_db->getOne($sql, $channel_id); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + return (int)$result; + } + + /** + * Returns a list of stories from the storage backend filtered by + * arbitrary criteria. + * NOTE: $criteria['channel_id'] MUST be set for this method to work. + * + * @param string $channel + * @param array $criteria + * + * @return array + * + * @see Jonah_Driver#getStories + */ + protected function _getStories($criteria) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'SELECT DISTINCT(tags.story_id) AS id, ' . + 'stories.story_author AS author, ' . + 'stories.story_title AS title, ' . + 'stories.story_desc AS description, ' . + 'stories.story_body_type AS body_type, ' . + 'stories.story_body AS body, ' . + 'stories.story_url AS url, ' . + 'stories.story_permalink AS permalink, ' . + 'stories.story_published AS published, ' . + 'stories.story_updated AS updated, ' . + 'stories.story_read AS readcount ' . + 'FROM jonah_stories_tags AS tags ' . + 'LEFT JOIN jonah_stories AS stories ON ' . + 'tags.story_id = stories.story_id ' . + 'WHERE stories.channel_id=?'; + $values = array($criteria['channel_id']); + + // Apply date filtering + if (isset($criteria['updated-min'])) { + $sql .= ' AND story_updated >= ?'; + $values[] = $criteria['updated-min']->timestamp(); + } + if (isset($criteria['updated-max'])) { + $sql .= ' AND story_updated <= ?'; + $values[] = $criteria['updated-max']->timestamp(); + } + if (isset($criteria['published-min'])) { + $sql .= ' AND story_published >= ?'; + $values[] = $criteria['published-min']->timestamp(); + } + if (isset($criteria['published-max'])) { + $sql .= ' AND story_published <= ?'; + $values[] = $criteria['published-max']->timestamp(); + } + + // Apply tag filtering + if (isset($criteria['tags'])) { + $sql .= ' AND ('; + $multiple = false; + foreach ($criteria['tags'] as $tag) { + if ($multiple) { + $sql .= ' OR '; + } + $sql .= 'tags.tag_id = ?'; + $values[] = $criteria['tagIDs'][$tag]; + $multiple = true; + } + $sql .= ')'; + } + + if (isset($criteria['alltags'])) { + $sql .= ' AND ('; + $multiple = false; + foreach ($criteria['alltags'] as $tag) { + if ($multiple) { + $sql .= ' AND '; + } + $sql .= 'tags.tag_id = ?'; + $values[] = $criteria['tagIDs'][$tag]; + $multiple = true; + } + $sql .= ')'; + } + + // Filter by story author + if (isset($criteria['author'])) { + $sql .= ' AND stories.story_author = ?'; + $values[] = $criteria['author']; + } + + // Filter stories by keyword + if (isset($criteria['keywords'])) { + foreach ($criteria['keywords'] as $keyword) { + $sql .= ' AND stories.story_body LIKE ?'; + $values[] = '%' . $keyword . '%'; + } + } + if (isset($criteria['notkeywords'])) { + foreach ($criteria['notkeywords'] as $keyword) { + $sql .= ' AND stories.story_body NOT LIKE ?'; + $values[] = '%' . $keyword . '%'; + } + } + + Horde::logMessage('SQL Query by Jonah_Driver_sql::_getStories(): ' . $sql, 'DEBUG'); + $results = $this->_db->getAll($sql, $values, DB_FETCHMODE_ASSOC); + if (is_a($results, 'PEAR_Error')) { + return $results; + } + + return $results; + } + + protected function _getIdBySlug($slug) + { + return $slug; + } + + /** + * Returns the most recent or all stories from a channel. + * This method is deprecated. + * + * @param integer $channel_id The news channel to get stories from. + * @param integer $max The maximum number of stories to get. + * @param integer $from The number of the story to start with. + * @param integer $date The timestamp of the date to start with. + * @param boolean $unreleased Whether to return not yet released stories. + * @param integer $order How to order the results for internal + * channels. Possible values are the + * JONAH_ORDER_* constants. + * + * @return array The specified number (or less, if there are fewer) of + * stories from the given channel. + */ + protected function _legacyGetStories($channel_id, $max, $from = 0, $date = null, + $unreleased = false, $order = Jonah::ORDER_PUBLISHED) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'SELECT * FROM jonah_stories WHERE channel_id = ?'; + $values = array((int)$channel_id); + + if ($unreleased) { + if ($date !== null) { + $sql .= ' AND story_published <= ?'; + $values[] = $date; + } + } else { + if ($date === null) { + $date = time(); + } else { + $date = max($date, time()); + } + $sql .= ' AND story_published <= ?'; + $values[] = $date; + } + + switch ($order) { + case Jonah::ORDER_PUBLISHED: + $sql .= ' ORDER BY story_published DESC'; + break; + case Jonah::ORDER_READ: + $sql .= ' ORDER BY story_read DESC'; + break; + case Jonah::ORDER_COMMENTS: + //@TODO + break; + } + + if (!is_null($max)) { + $sql = $this->_db->modifyLimitQuery($sql, (int)$from, (int)$max, $values); + } + + Horde::logMessage('SQL Query by Jonah_Driver_sql::_legacyGetStories(): ' . $sql, 'DEBUG'); + $result = $this->_db->getAll($sql, $values, DB_FETCHMODE_ASSOC); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + for ($i = 0; $i < count($result); $i++) { + $result[$i] = $this->_convertFromBackend($result[$i]); + if (empty($result[$i]['story_permalink'])) { + $this->_addPermalink($result[$i]); + } + $tags = $this->readTags($result[$i]['story_id']); + if (is_a($tags, 'PEAR_Error')) { + return $tags; + } + $result[$i]['story_tags'] = $tags; + } + return $result; + } + + /** + */ + protected function _getStory($story_id, $read = false) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'SELECT * FROM jonah_stories WHERE story_id = ?'; + $values = array((int)$story_id); + + Horde::logMessage('SQL Query by Jonah_Driver_sql::_getStory(): ' . $sql, 'DEBUG'); + $result = $this->_db->getRow($sql, $values, DB_FETCHMODE_ASSOC); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + return $result; + } elseif (empty($result)) { + return PEAR::raiseError(sprintf(_("Story id \"%s\" not found."), $story_id)); + } + $result['story_tags'] = $this->readTags($story_id); + $result = $this->_convertFromBackend($result); + if (empty($result['story_permalink'])) { + $this->_addPermalink($result); + } + if ($read) { + $this->_readStory($story_id); + } + + return $result; + } + + /** + */ + protected function _getStoryByUrl($channel_id, $story_url) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'SELECT * FROM jonah_stories' . + ' WHERE channel_id = ? AND story_url = ?'; + $values = array((int)$channel_id, $story_url); + + Horde::logMessage('SQL Query by Jonah_Driver_sql::_getStoryByUrl(): ' . $sql, 'DEBUG'); + $result = $this->_db->getRow($sql, $values, DB_FETCHMODE_ASSOC); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + return $result; + } elseif (empty($result)) { + return PEAR::raiseError(sprintf(_("Story URL \"%s\" not found."), $story_url)); + } + $result = $this->_convertFromBackend($result); + if (empty($result['story_permalink'])) { + $this->_addPermalink($result); + } + + return $result; + } + + /** + * Adds a missing permalink to a story. + * + * @param array $story A story hash. + */ + protected function _addPermalink(&$story) + { + $channel = $this->getChannel($story['channel_id']); + if (is_a($channel, 'PEAR_Error')) { + return; + } + $sql = 'UPDATE jonah_stories SET story_permalink = ? WHERE story_id = ?'; + $values = array($this->getStoryLink($channel, $story), $story['story_id']); + Horde::logMessage('SQL Query by Jonah_Driver_sql::_addPermalink(): ' . $sql, 'DEBUG'); + $result = $this->_db->query($sql, $values); + if (!is_a($result, 'PEAR_Error')) { + $story['story_permalink'] = $values[0]; + } + } + + /** + * Gets the latest released story from a given internal channel + * + * @param int $channel_id The channel id. + * + * @return int The story id. + */ + public function getLatestStoryId($channel_id) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'SELECT story_id FROM jonah_stories' . + ' WHERE channel_id = ? AND story_published <= ?' . + ' ORDER BY story_updated DESC'; + $values = array((int)$channel_id, time()); + + Horde::logMessage('SQL Query by Jonah_Driver_sql::getLatestStoryId(): ' . $sql, 'DEBUG'); + $result = $this->_db->getRow($sql, $values, DB_FETCHMODE_ASSOC); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, 'ERR'); + return $result; + } elseif (empty($result)) { + return PEAR::raiseError(sprintf(_("Channel \"%s\" not found."), $channel_id)); + } + + return $result['story_id']; + } + + /** + */ + public function deleteStory($channel_id, $story_id) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + $sql = 'DELETE FROM jonah_stories' . + ' WHERE channel_id = ? AND story_id = ?'; + $values = array((int)$channel_id, (int)$story_id); + + Horde::logMessage('SQL Query by Jonah_Driver_sql::deleteStory(): ' . $sql, 'DEBUG'); + $result = $this->_db->query($sql, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result->getMessage(), 'ERR'); + return $result; + } + + $sql = 'DELETE FROM jonah_stories_tags ' . + 'WHERE channel_id = ? AND story_id = ?'; + $result = $this->_db->query($sql, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result->getMessage(), 'ERR'); + return $result; + } + + return true; + } + + /** + * Write out the tags for a specific resource. + * + * @param int $resource_id The story we are tagging. + * @param int $channel_id The channel id for the story we are tagging + * @param array $tags An array of tags. + * + * @return mixed True | PEAR_Error + */ + public function writeTags($resource_id, $channel_id, $tags) + { + global $conf; + + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + // First, make sure all tag names exist in the DB. + $tagkeys = array(); + $insert = $this->_db->prepare('INSERT INTO jonah_tags (tag_id, tag_name) VALUES(?, ?)'); + $query = $this->_db->prepare('SELECT tag_id FROM jonah_tags WHERE tag_name = ?'); + foreach ($tags as $tag) { + $tag = Horde_String::lower(trim($tag)); + $results = $this->_db->execute($query, $this->_db->escapeSimple($tag)); + if (is_a($results, 'PEAR_Error')) { + return $results; + } elseif ($results->numRows() == 0) { + $id = $this->_db->nextId('jonah_tags'); + $result = $this->_db->execute($insert, array($id, $tag)); + $tagkeys[] = $id; + } else { + $row = $results->fetchRow(DB_FETCHMODE_ASSOC); + $tagkeys[] = $row['tag_id']; + } + } + + // Free our resources. + $this->_db->freePrepared($insert, true); + $this->_db->freePrepared($query, true); + + $sql = 'DELETE FROM jonah_stories_tags WHERE story_id = ' . (int)$resource_id; + $query = $this->_db->prepare('INSERT INTO jonah_stories_tags (story_id, channel_id, tag_id) VALUES(?, ?, ?)'); + + Horde::logMessage('SQL query by Jonah_Driver_sql::writeTags: ' . $sql, + 'DEBUG'); + + $this->_db->query($sql); + foreach ($tagkeys as $key) { + $this->_db->execute($query, array($resource_id, $channel_id, $key)); + } + $this->_db->freePrepared($query, true); + + /* @TODO We should clear at least any of our cached counts */ + return true; + } + + /** + * Retrieve the tags for a specified resource. + * + * @param int $resource_id The resource to get tags for. + * + * @return mixed An array of tags | PEAR_Error + */ + public function readTags($resource_id) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + $sql = 'SELECT jonah_tags.tag_id, tag_name FROM jonah_tags INNER JOIN jonah_stories_tags ON jonah_stories_tags.tag_id = jonah_tags.tag_id WHERE jonah_stories_tags.story_id = ?'; + + Horde::logMessage('SQL query by Jonah_Driver_sql::readTags ' . $sql, + 'DEBUG'); + + $tags = $this->_db->getAssoc($sql, false, array($resource_id), false); + return $tags; + } + + /** + * Retrieve the list of used tag_names, tag_ids and the total number + * of resources that are linked to that tag. + * + * @param array $tags An optional array of tag_ids. If omitted, all tags + * will be included. + * + * @param array $channel_id An optional array of channel_ids. + * + * @return mixed An array containing tag_name, and total | PEAR_Error + */ + public function listTagInfo($tags = array(), $channel_id = null) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + + if (!is_array($channel_id) && is_numeric($channel_id)) { + $channel_id = array($channel_id); + } + $cache = $GLOBALS['injector']->getInstance('Horde_Cache'); + $cache_key = 'jonah_tags_' . md5(serialize($tags) . md5(serialize($channel_id))); + $cache_value = $cache->get($cache_key, $GLOBALS['conf']['cache']['default_lifetime']); + if ($cache_value) { + return unserialize($cache_value); + } + + $haveWhere = false; + $sql = 'SELECT tn.tag_id, tag_name, COUNT(tag_name) total FROM jonah_tags as tn INNER JOIN jonah_stories_tags as t ON t.tag_id = tn.tag_id'; + if (count($tags)) { + $sql .= ' WHERE tn.tag_id IN (' . implode(',', $tags) . ')'; + $haveWhere = true; + } + if (!is_null($channel_id)) { + if (!$haveWhere) { + $sql .= ' WHERE'; + } else { + $sql .= ' AND'; + } + $channels = array(); + foreach ($channel_id as $cid) { + $c = $this->_getChannel($cid); + if ($c['channel_type'] == Jonah::COMPOSITE_CHANNEL) { + $channels = array_merge($channels, explode(':', $c['channel_url'])); + } + } + $channel_id = array_merge($channel_id, $channels); + $sql .= ' t.channel_id IN (' . implode(', ', $channel_id) . ')'; + } + $sql .= ' GROUP BY tn.tag_id, tag_name ORDER BY total DESC;'; + $results = $this->_db->getAssoc($sql,true, array(), DB_FETCHMODE_ASSOC, false); + $cache->set($cache_key, serialize($results)); + return $results; + } + + /** + * Search for resources matching the specified criteria + * + * @param array $ids An array of tag_ids to search for. Note that + * these are AND'd together. + * @param integer $max The maximum number of stories to get. If + * null, all stories will be returned. + * @param integer $from The number of the story to start with. + * @param array $channel Limit the result set to resources + * present in these channels + * @param integer $order How to order the results for internal + * channels. Possible values are the + * JONAH_ORDER_* constants. + * + * @return mixed Array of stories| PEAR_Error + */ + public function searchTagsById($ids, $max = 10, $from = 0, $channel_id = array(), + $order = Jonah::ORDER_PUBLISHED) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + if (!is_array($ids) || !count($ids)) { + $stories[] = array(); + } else { + $stories = array(); + $sql = 'SELECT DISTINCT s.story_id, s.channel_id FROM jonah_stories' + . ' as s, jonah_stories_tags as t'; + for ($i = 0; $i < count($ids); $i++) { + $sql .= ', jonah_stories_tags as t' . $i; + } + $sql .= ' WHERE s.story_id = t.story_id'; + for ($i = 0 ; $i < count($ids); $i++) { + $sql .= ' AND t' . $i . '.tag_id = ' . $ids[$i] . ' AND t' + . $i . '.story_id = t.story_id'; + } + + /* Limit to particular channels if requested */ + if (count($channel_id) > 0) { + // Have to find out if we are a composite channel or not. + $channels = array(); + foreach ($channel_id as $cid) { + $c = $this->_getChannel($cid); + if ($c['channel_type'] == Jonah::COMPOSITE_CHANNEL) { + $temp = explode(':', $c['channel_url']); + // Save a map of channels that are from composites. + foreach ($temp as $t) { + $cchannels[$t] = $cid; + } + $channels = array_merge($channels, $temp); + } + } + $channels = array_merge($channel_id, $channels); + $timestamp = time(); + $sql .= ' AND t.channel_id IN (' . implode(', ', $channels) + . ') AND s.story_published IS NOT NULL AND ' + . 's.story_published < ' . $timestamp; + } + + switch ($order) { + case Jonah::ORDER_PUBLISHED: + $sql .= ' ORDER BY story_published DESC'; + break; + case Jonah::ORDER_READ: + $sql .= ' ORDER BY story_read DESC'; + break; + case Jonah::ORDER_COMMENTS: + //@TODO + break; + } + + /* Instantiate the channel object outside the loop if we + * are only limiting to one channel. */ + if (count($channel_id) == 1) { + $channel = $this->getChannel($channel_id[0]); + } + Horde::logMessage('SQL query by Jonah_Driver_sql::searchTags: ' . $sql, 'DEBUG'); + $results = $this->_db->limitQuery($sql, $from, $max); + if (is_a($results, 'PEAR_Error')) { + return $results; + } + + for ($i = 0; $i < $results->numRows(); $i++) { + $row = $results->fetchRow(); + $story = $this->_getStory($row[0], false); + if (count($channel_id > 1)) { + // Make sure we get the correct channel info for composites + if (!empty($cchannels[$story['channel_id']])) { + $channel = $this->getChannel($cchannels[$story['channel_id']]); + } else { + $channel = $this->getChannel($story['channel_id']); + } + } + + /* Format story link. */ + $story['story_link'] = $this->getStoryLink($channel, $story); + $story = array_merge($story, $channel); + /* Format dates. */ + $date_format = $GLOBALS['prefs']->getValue('date_format'); + $story['story_updated_date'] = strftime($date_format, $story['story_updated']); + if (!empty($story['story_published'])) { + $story['story_published_date'] = strftime($date_format, $story['story_published']); + } + + $stories[] = $story; + } + } + + return $stories; + } + + /** + * Search for articles matching specific tag name(s). + * + * @see Jonah_Driver_sql::searchTagsById() + */ + public function searchTags($names, $max = 10, $from = 0, $channel_id = array(), + $order = Jonah::ORDER_PUBLISHED) + { + $ids = $this->getTagIds($names); + if (is_a($ids, 'PEAR_Error')) { + return $ids; + } + return $this->searchTagsById(array_values($ids), $max, $from, $channel_id, $order); + } + + + /** + * Return a set of tag names given the tag_ids. + * + * @param array $ids An array of tag_ids to get names for. + * + * @return mixed An array of tag names | PEAR_Error. + */ + public function getTagNames($ids) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + $sql = 'SELECT t.tag_name FROM jonah_tags as t WHERE t.tag_id IN('; + $needComma = false; + foreach ($ids as $id) { + $sql .= ($needComma ? ',' : '') . '\'' . $id . '\''; + $needComma = true; + } + $sql .= ')'; + $tags = $this->_db->getCol($sql); + return $tags; + } + + /** + * Return a set of tag_ids, given the tag name + * + * @param array $names An array of names to search for + * + * @return mixed An array of tag_name => tag_ids | PEAR_Error + */ + public function getTagIds($names) + { + if (is_a(($result = $this->_connect()), 'PEAR_Error')) { + return $result; + } + $sql = 'SELECT t.tag_name, t.tag_id FROM jonah_tags as t WHERE t.tag_name IN('; + $needComma = false; + foreach ($names as $name) { + $sql .= ($needComma ? ',' : '') . '\'' . $name . '\''; + $needComma = true; + } + $sql .= ')'; + $tags = $this->_db->getAssoc($sql); + return $tags; + } + + /** + * Attempts to open a persistent connection to the SQL server. + * + * @return boolean True on success; PEAR_Error on failure. + */ + protected function _connect() + { + if ($this->_connected) { + return true; + } + + Horde::assertDriverConfig($this->_params, 'news', + array('phptype', 'charset'), + 'jonah news SQL'); + + if (!isset($this->_params['database'])) { + $this->_params['database'] = ''; + } + if (!isset($this->_params['username'])) { + $this->_params['username'] = ''; + } + if (!isset($this->_params['hostspec'])) { + $this->_params['hostspec'] = ''; + } + + /* Connect to the SQL server using the supplied parameters. */ + $this->_db = &DB::connect($this->_params, + array('persistent' => !empty($this->_params['persistent']))); + if (is_a($this->_db, 'PEAR_Error')) { + return $this->_db; + } + + // Set DB portability options. + switch ($this->_db->phptype) { + case 'mssql': + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); + break; + default: + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + } + + $this->_connected = true; + return true; + } + +} diff --git a/jonah/lib/Driver/sql.php b/jonah/lib/Driver/sql.php deleted file mode 100644 index 273d1f4d3..000000000 --- a/jonah/lib/Driver/sql.php +++ /dev/null @@ -1,1058 +0,0 @@ - - * 'phptype' The database type (e.g. 'pgsql', 'mysql', etc.). - * 'charset' The database's internal charset. - * - * Required by some database implementations:
- *      'hostspec'      The hostname of the database server.
- *      'protocol'      The communication protocol ('tcp', 'unix', etc.).
- *      'database'      The name of the database.
- *      'username'      The username with which to connect to the database.
- *      'password'      The password associated with 'username'.
- *      'options'       Additional options to pass to the database.
- *      'tty'           The TTY on which to connect to the database.
- *      'port'          The port on which to connect to the database.
- * - * The table structure can be created by the scripts/db/jonah_news.sql - * script. The needed tables are jonah_channels and jonah_stories. - * - * Copyright 2002-2009 The Horde Project (http://www.horde.org/) - * - * See the enclosed file LICENSE for license information (BSD). If you did not - * did not receive this file, see http://cvs.horde.org/co.php/jonah/LICENSE. - * - * @author Marko Djukic - * @author Chuck Hagenbuch - * @author Jan Schneider - * @author Ben Klang - * @package Jonah - */ -class Jonah_Driver_Sql extends Jonah_Driver -{ - /** - * Handle for the current database connection. - * - * @TODO: Refactor to use Horde_Db - * @var DB - */ - protected $_db; - - /** - * Boolean indicating whether or not we're connected to the SQL server. - * - * @var boolean - */ - protected $_connected = false; - - /** - * Saves a channel to the backend. - * - * @param array $info The channel to add. - * Must contain a combination of the following - * entries: - *
-     * 'channel_id'       If empty a new channel is being added, otherwise one
-     *                    is being edited.
-     * 'channel_name'     The headline.
-     * 'channel_desc'     A description of this channel.
-     * 'channel_type'     Whether internal or external.
-     * 'channel_interval' If external then interval at which to refresh.
-     * 'channel_link'     The link to the source.
-     * 'channel_url'      The url from where to fetch the story list.
-     * 'channel_image'    A channel image.
-     * 
- * - * @return int|PEAR_Error The channel ID on success, PEAR_Error on - * failure. - */ - public function saveChannel(&$info) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - if (empty($info['channel_id'])) { - $info['channel_id'] = $this->_db->nextId('jonah_channels'); - if (is_a($info['channel_id'], 'PEAR_Error')) { - Horde::logMessage($info['channel_id'], 'ERR'); - return $info['channel_id']; - } - $sql = 'INSERT INTO jonah_channels' . - ' (channel_id, channel_name, channel_type, channel_desc, channel_interval, channel_url, channel_link, channel_page_link, channel_story_url, channel_img)' . - ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; - $values = array(); - } else { - $sql = 'UPDATE jonah_channels' . - ' SET channel_id = ?, channel_name = ?, channel_type = ?, channel_desc = ?, channel_interval = ?, channel_url = ?, channel_link = ?, channel_page_link = ?, channel_story_url = ?, channel_img = ?' . - ' WHERE channel_id = ?'; - $values = array((int)$info['channel_id']); - } - - array_unshift($values, - (int)$info['channel_id'], - Horde_String::convertCharset($info['channel_name'], Horde_Nls::getCharset(), $this->_params['charset']), - (int)$info['channel_type'], - isset($info['channel_desc']) ? $info['channel_desc'] : null, - isset($info['channel_interval']) ? (int)$info['channel_interval'] : null, - isset($info['channel_url']) ? $info['channel_url'] : null, - isset($info['channel_link']) ? $info['channel_link'] : null, - isset($info['channel_page_link']) ? $info['channel_page_link'] : null, - isset($info['channel_story_url']) ? $info['channel_story_url'] : null, - isset($info['channel_img']) ? $info['channel_img'] : null); - Horde::logMessage('SQL Query by Jonah_Driver_sql::saveChannel(): ' . $sql, 'DEBUG'); - $result = $this->_db->query($sql, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - return $result; - } - - return $info['channel_id']; - } - - /** - * Get a list of stored channels. - * - * @param integer $type The type of channel to filter for. Possible - * values are either JONAH_INTERNAL_CHANNEL - * to fetch only a list of internal channels, - * or JONAH_EXTERNAL_CHANNEL for only external. - * If null both channel types are returned. - * - * @return mixed An array of channels or PEAR_Error on error. - */ - public function getChannels($type = null) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $wsql = ''; - if (!is_null($type)) { - if (!is_array($type)) { - $type = array($type); - } - for ($i = 0, $i_max = count($type); $i < $i_max; ++$i) { - $type[$i] = 'channel_type = ' . (int)$type[$i]; - } - $wsql = 'WHERE ' . implode(' OR ', $type); - } - - $sql = sprintf('SELECT channel_id, channel_name, channel_type, channel_updated FROM jonah_channels %s ORDER BY channel_name', $wsql); - - Horde::logMessage('SQL Query by Jonah_Driver_sql::getChannels(): ' . $sql, 'DEBUG'); - $result = $this->_db->getAll($sql, DB_FETCHMODE_ASSOC); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - return $result; - } - for ($i = 0; $i < count($result); $i++) { - $result[$i]['channel_name'] = Horde_String::convertCharset($result[$i]['channel_name'], $this->_params['charset']); - } - - return $result; - } - - /** - */ - protected function _getChannel($channel_id) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'SELECT * FROM jonah_channels WHERE channel_id = ' . (int)$channel_id; - - Horde::logMessage('SQL Query by Jonah_Driver_sql::_getChannel(): ' . $sql, 'DEBUG'); - $result = $this->_db->getRow($sql, DB_FETCHMODE_ASSOC); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - return $result; - } elseif (empty($result)) { - return PEAR::raiseError(sprintf(_("Channel id \"%s\" not found."), $channel_id)); - } - - $result['channel_name'] = Horde_String::convertCharset($result['channel_name'], $this->_params['charset']); - if ($result['channel_type'] == Jonah::COMPOSITE_CHANNEL) { - $channels = explode(':', $result['channel_url']); - if (count($channels)) { - $sql = 'SELECT MAX(channel_updated) FROM jonah_channels WHERE channel_id IN (' . implode(',', $channels) . ')'; - Horde::logMessage('SQL Query by Jonah_Driver_sql::_getChannel(): ' . $sql, 'DEBUG'); - $updated = $this->_db->getOne($sql); - if (is_a($updated, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - } else { - $result['channel_updated'] = $updated; - $this->_timestampChannel($channel_id, $updated); - } - } - } - - return $result; - } - - /** - */ - protected function _timestampChannel($channel_id, $timestamp) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = sprintf('UPDATE jonah_channels SET channel_updated = %s WHERE channel_id = %s', - (int)$timestamp, - (int)$channel_id); - Horde::logMessage('SQL Query by Jonah_Driver_sql::_timestampChannel(): ' . $sql, 'DEBUG'); - $result = $this->_db->query($sql); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - } - return $result; - } - - /** - */ - protected function _readStory($story_id) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'UPDATE jonah_stories SET story_read = story_read + 1 WHERE story_id = ' . (int)$story_id; - Horde::logMessage('SQL Query by Jonah_Driver_sql::_readStory(): ' . $sql, 'DEBUG'); - $result = $this->_db->query($sql); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - } - return $result; - } - - /** - */ - protected function _deleteChannel($channel_id) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'DELETE FROM jonah_channels WHERE channel_id = ?'; - $values = array($channel_id); - - Horde::logMessage('SQL Query by Jonah_Driver_sql::deleteChannel(): ' . $sql, 'DEBUG'); - $result = $this->_db->query($sql, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - } - - return $result; - } - - /** - * @param array &$info - */ - protected function _saveStory(&$info) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - if (empty($info['story_id'])) { - $info['story_id'] = $this->_db->nextId('jonah_stories'); - if (is_a($info['story_id'], 'PEAR_Error')) { - Horde::logMessage($info['story_id'], 'ERR'); - return $info['story_id']; - } - $channel = $this->getChannel($info['channel_id']); - $permalink = $this->getStoryLink($channel, $info); - $sql = 'INSERT INTO jonah_stories (story_id, channel_id, story_title, story_desc, story_body_type, story_body, story_url, story_published, story_updated, story_read, story_permalink) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; - $values = array($permalink); - } else { - $sql = 'UPDATE jonah_stories SET story_id = ?, channel_id = ?, story_title = ?, story_desc = ?, story_body_type = ?, story_body = ?, story_url = ?, story_published = ?, story_updated = ?, story_read = ? WHERE story_id = ?'; - $values = array((int)$info['story_id']); - } - - if (empty($info['story_read'])) { - $info['story_read'] = 0; - } - - /* Deal with any tags */ - if (!empty($info['story_tags'])) { - $tags = explode(',', $info['story_tags']); - } else { - $tags = array(); - } - $this->writeTags($info['story_id'], $info['channel_id'], $tags); - - array_unshift($values, - (int)$info['story_id'], - (int)$info['channel_id'], - Horde_String::convertCharset($info['story_title'], Horde_Nls::getCharset(), $this->_params['charset']), - Horde_String::convertCharset($info['story_desc'], Horde_Nls::getCharset(), $this->_params['charset']), - $info['story_body_type'], - isset($info['story_body']) ? Horde_String::convertCharset($info['story_body'], Horde_Nls::getCharset(), $this->_params['charset']) : null, - isset($info['story_url']) ? $info['story_url'] : null, - isset($info['story_published']) ? (int)$info['story_published'] : null, - time(), - (int)$info['story_read']); - - Horde::logMessage('SQL Query by Jonah_Driver_sql::_saveStory(): ' . $sql, 'DEBUG'); - $result = $this->_db->query($sql, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - return $result; - } - - $this->_timestampChannel($info['channel_id'], time()); - return true; - } - - /** - * Converts the text fields of a story from the backend charset to the - * output charset. - * - * @param array $story A story hash. - * - * @return array The converted hash. - */ - protected function _convertFromBackend($story) - { - $story['story_title'] = Horde_String::convertCharset($story['story_title'], $this->_params['charset'], Horde_Nls::getCharset()); - $story['story_desc'] = Horde_String::convertCharset($story['story_desc'], $this->_params['charset'], Horde_Nls::getCharset()); - if (isset($story['story_body'])) { - $story['story_body'] = Horde_String::convertCharset($story['story_body'], $this->_params['charset'], Horde_Nls::getCharset()); - } - if (isset($story['story_tags'])) { - $story['story_tags'] = Horde_String::convertCharset($story['story_tags'], $this->_params['charset'], Horde_Nls::getCharset()); - } - return $story; - } - - /** - * Look up a channel ID by its name - * - * @param string $channel - * - * @return int Channel ID - */ - public function getChannelId($channel) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'SELECT channel_id FROM jonah_channels WHERE channel_slug = ?'; - $values = array($channel); - $result = $this->_db->getOne($sql, $values); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - - return $result; - } - - /** - * Returns the total number of stories in the specified - * channel - * - * @param int $channel_id The Channel ID - * - * @return mixed The count || PEAR_Error - */ - public function getStoryCount($channel_id) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'SELECT count(*) FROM jonah_stories WHERE channel_id = ?'; - $result = $this->_db->getOne($sql, $channel_id); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - - return (int)$result; - } - - /** - * Returns a list of stories from the storage backend filtered by - * arbitrary criteria. - * NOTE: $criteria['channel_id'] MUST be set for this method to work. - * - * @param string $channel - * @param array $criteria - * - * @return array - * - * @see Jonah_Driver#getStories - */ - protected function _getStories($criteria) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'SELECT DISTINCT(tags.story_id) AS id, ' . - 'stories.story_author AS author, ' . - 'stories.story_title AS title, ' . - 'stories.story_desc AS description, ' . - 'stories.story_body_type AS body_type, ' . - 'stories.story_body AS body, ' . - 'stories.story_url AS url, ' . - 'stories.story_permalink AS permalink, ' . - 'stories.story_published AS published, ' . - 'stories.story_updated AS updated, ' . - 'stories.story_read AS readcount ' . - 'FROM jonah_stories_tags AS tags ' . - 'LEFT JOIN jonah_stories AS stories ON ' . - 'tags.story_id = stories.story_id ' . - 'WHERE stories.channel_id=?'; - $values = array($criteria['channel_id']); - - // Apply date filtering - if (isset($criteria['updated-min'])) { - $sql .= ' AND story_updated >= ?'; - $values[] = $criteria['updated-min']->timestamp(); - } - if (isset($criteria['updated-max'])) { - $sql .= ' AND story_updated <= ?'; - $values[] = $criteria['updated-max']->timestamp(); - } - if (isset($criteria['published-min'])) { - $sql .= ' AND story_published >= ?'; - $values[] = $criteria['published-min']->timestamp(); - } - if (isset($criteria['published-max'])) { - $sql .= ' AND story_published <= ?'; - $values[] = $criteria['published-max']->timestamp(); - } - - // Apply tag filtering - if (isset($criteria['tags'])) { - $sql .= ' AND ('; - $multiple = false; - foreach ($criteria['tags'] as $tag) { - if ($multiple) { - $sql .= ' OR '; - } - $sql .= 'tags.tag_id = ?'; - $values[] = $criteria['tagIDs'][$tag]; - $multiple = true; - } - $sql .= ')'; - } - - if (isset($criteria['alltags'])) { - $sql .= ' AND ('; - $multiple = false; - foreach ($criteria['alltags'] as $tag) { - if ($multiple) { - $sql .= ' AND '; - } - $sql .= 'tags.tag_id = ?'; - $values[] = $criteria['tagIDs'][$tag]; - $multiple = true; - } - $sql .= ')'; - } - - // Filter by story author - if (isset($criteria['author'])) { - $sql .= ' AND stories.story_author = ?'; - $values[] = $criteria['author']; - } - - // Filter stories by keyword - if (isset($criteria['keywords'])) { - foreach ($criteria['keywords'] as $keyword) { - $sql .= ' AND stories.story_body LIKE ?'; - $values[] = '%' . $keyword . '%'; - } - } - if (isset($criteria['notkeywords'])) { - foreach ($criteria['notkeywords'] as $keyword) { - $sql .= ' AND stories.story_body NOT LIKE ?'; - $values[] = '%' . $keyword . '%'; - } - } - - Horde::logMessage('SQL Query by Jonah_Driver_sql::_getStories(): ' . $sql, 'DEBUG'); - $results = $this->_db->getAll($sql, $values, DB_FETCHMODE_ASSOC); - if (is_a($results, 'PEAR_Error')) { - return $results; - } - - return $results; - } - - protected function _getIdBySlug($slug) - { - return $slug; - } - - /** - * Returns the most recent or all stories from a channel. - * This method is deprecated. - * - * @param integer $channel_id The news channel to get stories from. - * @param integer $max The maximum number of stories to get. - * @param integer $from The number of the story to start with. - * @param integer $date The timestamp of the date to start with. - * @param boolean $unreleased Whether to return not yet released stories. - * @param integer $order How to order the results for internal - * channels. Possible values are the - * JONAH_ORDER_* constants. - * - * @return array The specified number (or less, if there are fewer) of - * stories from the given channel. - */ - protected function _legacyGetStories($channel_id, $max, $from = 0, $date = null, - $unreleased = false, $order = Jonah::ORDER_PUBLISHED) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'SELECT * FROM jonah_stories WHERE channel_id = ?'; - $values = array((int)$channel_id); - - if ($unreleased) { - if ($date !== null) { - $sql .= ' AND story_published <= ?'; - $values[] = $date; - } - } else { - if ($date === null) { - $date = time(); - } else { - $date = max($date, time()); - } - $sql .= ' AND story_published <= ?'; - $values[] = $date; - } - - switch ($order) { - case Jonah::ORDER_PUBLISHED: - $sql .= ' ORDER BY story_published DESC'; - break; - case Jonah::ORDER_READ: - $sql .= ' ORDER BY story_read DESC'; - break; - case Jonah::ORDER_COMMENTS: - //@TODO - break; - } - - if (!is_null($max)) { - $sql = $this->_db->modifyLimitQuery($sql, (int)$from, (int)$max, $values); - } - - Horde::logMessage('SQL Query by Jonah_Driver_sql::_legacyGetStories(): ' . $sql, 'DEBUG'); - $result = $this->_db->getAll($sql, $values, DB_FETCHMODE_ASSOC); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - for ($i = 0; $i < count($result); $i++) { - $result[$i] = $this->_convertFromBackend($result[$i]); - if (empty($result[$i]['story_permalink'])) { - $this->_addPermalink($result[$i]); - } - $tags = $this->readTags($result[$i]['story_id']); - if (is_a($tags, 'PEAR_Error')) { - return $tags; - } - $result[$i]['story_tags'] = $tags; - } - return $result; - } - - /** - */ - protected function _getStory($story_id, $read = false) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'SELECT * FROM jonah_stories WHERE story_id = ?'; - $values = array((int)$story_id); - - Horde::logMessage('SQL Query by Jonah_Driver_sql::_getStory(): ' . $sql, 'DEBUG'); - $result = $this->_db->getRow($sql, $values, DB_FETCHMODE_ASSOC); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - return $result; - } elseif (empty($result)) { - return PEAR::raiseError(sprintf(_("Story id \"%s\" not found."), $story_id)); - } - $result['story_tags'] = $this->readTags($story_id); - $result = $this->_convertFromBackend($result); - if (empty($result['story_permalink'])) { - $this->_addPermalink($result); - } - if ($read) { - $this->_readStory($story_id); - } - - return $result; - } - - /** - */ - protected function _getStoryByUrl($channel_id, $story_url) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'SELECT * FROM jonah_stories' . - ' WHERE channel_id = ? AND story_url = ?'; - $values = array((int)$channel_id, $story_url); - - Horde::logMessage('SQL Query by Jonah_Driver_sql::_getStoryByUrl(): ' . $sql, 'DEBUG'); - $result = $this->_db->getRow($sql, $values, DB_FETCHMODE_ASSOC); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - return $result; - } elseif (empty($result)) { - return PEAR::raiseError(sprintf(_("Story URL \"%s\" not found."), $story_url)); - } - $result = $this->_convertFromBackend($result); - if (empty($result['story_permalink'])) { - $this->_addPermalink($result); - } - - return $result; - } - - /** - * Adds a missing permalink to a story. - * - * @param array $story A story hash. - */ - protected function _addPermalink(&$story) - { - $channel = $this->getChannel($story['channel_id']); - if (is_a($channel, 'PEAR_Error')) { - return; - } - $sql = 'UPDATE jonah_stories SET story_permalink = ? WHERE story_id = ?'; - $values = array($this->getStoryLink($channel, $story), $story['story_id']); - Horde::logMessage('SQL Query by Jonah_Driver_sql::_addPermalink(): ' . $sql, 'DEBUG'); - $result = $this->_db->query($sql, $values); - if (!is_a($result, 'PEAR_Error')) { - $story['story_permalink'] = $values[0]; - } - } - - /** - * Gets the latest released story from a given internal channel - * - * @param int $channel_id The channel id. - * - * @return int The story id. - */ - public function getLatestStoryId($channel_id) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'SELECT story_id FROM jonah_stories' . - ' WHERE channel_id = ? AND story_published <= ?' . - ' ORDER BY story_updated DESC'; - $values = array((int)$channel_id, time()); - - Horde::logMessage('SQL Query by Jonah_Driver_sql::getLatestStoryId(): ' . $sql, 'DEBUG'); - $result = $this->_db->getRow($sql, $values, DB_FETCHMODE_ASSOC); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, 'ERR'); - return $result; - } elseif (empty($result)) { - return PEAR::raiseError(sprintf(_("Channel \"%s\" not found."), $channel_id)); - } - - return $result['story_id']; - } - - /** - */ - public function deleteStory($channel_id, $story_id) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - $sql = 'DELETE FROM jonah_stories' . - ' WHERE channel_id = ? AND story_id = ?'; - $values = array((int)$channel_id, (int)$story_id); - - Horde::logMessage('SQL Query by Jonah_Driver_sql::deleteStory(): ' . $sql, 'DEBUG'); - $result = $this->_db->query($sql, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result->getMessage(), 'ERR'); - return $result; - } - - $sql = 'DELETE FROM jonah_stories_tags ' . - 'WHERE channel_id = ? AND story_id = ?'; - $result = $this->_db->query($sql, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result->getMessage(), 'ERR'); - return $result; - } - - return true; - } - - /** - * Write out the tags for a specific resource. - * - * @param int $resource_id The story we are tagging. - * @param int $channel_id The channel id for the story we are tagging - * @param array $tags An array of tags. - * - * @return mixed True | PEAR_Error - */ - public function writeTags($resource_id, $channel_id, $tags) - { - global $conf; - - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - // First, make sure all tag names exist in the DB. - $tagkeys = array(); - $insert = $this->_db->prepare('INSERT INTO jonah_tags (tag_id, tag_name) VALUES(?, ?)'); - $query = $this->_db->prepare('SELECT tag_id FROM jonah_tags WHERE tag_name = ?'); - foreach ($tags as $tag) { - $tag = Horde_String::lower(trim($tag)); - $results = $this->_db->execute($query, $this->_db->escapeSimple($tag)); - if (is_a($results, 'PEAR_Error')) { - return $results; - } elseif ($results->numRows() == 0) { - $id = $this->_db->nextId('jonah_tags'); - $result = $this->_db->execute($insert, array($id, $tag)); - $tagkeys[] = $id; - } else { - $row = $results->fetchRow(DB_FETCHMODE_ASSOC); - $tagkeys[] = $row['tag_id']; - } - } - - // Free our resources. - $this->_db->freePrepared($insert, true); - $this->_db->freePrepared($query, true); - - $sql = 'DELETE FROM jonah_stories_tags WHERE story_id = ' . (int)$resource_id; - $query = $this->_db->prepare('INSERT INTO jonah_stories_tags (story_id, channel_id, tag_id) VALUES(?, ?, ?)'); - - Horde::logMessage('SQL query by Jonah_Driver_sql::writeTags: ' . $sql, - 'DEBUG'); - - $this->_db->query($sql); - foreach ($tagkeys as $key) { - $this->_db->execute($query, array($resource_id, $channel_id, $key)); - } - $this->_db->freePrepared($query, true); - - /* @TODO We should clear at least any of our cached counts */ - return true; - } - - /** - * Retrieve the tags for a specified resource. - * - * @param int $resource_id The resource to get tags for. - * - * @return mixed An array of tags | PEAR_Error - */ - public function readTags($resource_id) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - $sql = 'SELECT jonah_tags.tag_id, tag_name FROM jonah_tags INNER JOIN jonah_stories_tags ON jonah_stories_tags.tag_id = jonah_tags.tag_id WHERE jonah_stories_tags.story_id = ?'; - - Horde::logMessage('SQL query by Jonah_Driver_sql::readTags ' . $sql, - 'DEBUG'); - - $tags = $this->_db->getAssoc($sql, false, array($resource_id), false); - return $tags; - } - - /** - * Retrieve the list of used tag_names, tag_ids and the total number - * of resources that are linked to that tag. - * - * @param array $tags An optional array of tag_ids. If omitted, all tags - * will be included. - * - * @param array $channel_id An optional array of channel_ids. - * - * @return mixed An array containing tag_name, and total | PEAR_Error - */ - public function listTagInfo($tags = array(), $channel_id = null) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - - if (!is_array($channel_id) && is_numeric($channel_id)) { - $channel_id = array($channel_id); - } - $cache = $GLOBALS['injector']->getInstance('Horde_Cache'); - $cache_key = 'jonah_tags_' . md5(serialize($tags) . md5(serialize($channel_id))); - $cache_value = $cache->get($cache_key, $GLOBALS['conf']['cache']['default_lifetime']); - if ($cache_value) { - return unserialize($cache_value); - } - - $haveWhere = false; - $sql = 'SELECT tn.tag_id, tag_name, COUNT(tag_name) total FROM jonah_tags as tn INNER JOIN jonah_stories_tags as t ON t.tag_id = tn.tag_id'; - if (count($tags)) { - $sql .= ' WHERE tn.tag_id IN (' . implode(',', $tags) . ')'; - $haveWhere = true; - } - if (!is_null($channel_id)) { - if (!$haveWhere) { - $sql .= ' WHERE'; - } else { - $sql .= ' AND'; - } - $channels = array(); - foreach ($channel_id as $cid) { - $c = $this->_getChannel($cid); - if ($c['channel_type'] == Jonah::COMPOSITE_CHANNEL) { - $channels = array_merge($channels, explode(':', $c['channel_url'])); - } - } - $channel_id = array_merge($channel_id, $channels); - $sql .= ' t.channel_id IN (' . implode(', ', $channel_id) . ')'; - } - $sql .= ' GROUP BY tn.tag_id, tag_name ORDER BY total DESC;'; - $results = $this->_db->getAssoc($sql,true, array(), DB_FETCHMODE_ASSOC, false); - $cache->set($cache_key, serialize($results)); - return $results; - } - - /** - * Search for resources matching the specified criteria - * - * @param array $ids An array of tag_ids to search for. Note that - * these are AND'd together. - * @param integer $max The maximum number of stories to get. If - * null, all stories will be returned. - * @param integer $from The number of the story to start with. - * @param array $channel Limit the result set to resources - * present in these channels - * @param integer $order How to order the results for internal - * channels. Possible values are the - * JONAH_ORDER_* constants. - * - * @return mixed Array of stories| PEAR_Error - */ - public function searchTagsById($ids, $max = 10, $from = 0, $channel_id = array(), - $order = Jonah::ORDER_PUBLISHED) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - if (!is_array($ids) || !count($ids)) { - $stories[] = array(); - } else { - $stories = array(); - $sql = 'SELECT DISTINCT s.story_id, s.channel_id FROM jonah_stories' - . ' as s, jonah_stories_tags as t'; - for ($i = 0; $i < count($ids); $i++) { - $sql .= ', jonah_stories_tags as t' . $i; - } - $sql .= ' WHERE s.story_id = t.story_id'; - for ($i = 0 ; $i < count($ids); $i++) { - $sql .= ' AND t' . $i . '.tag_id = ' . $ids[$i] . ' AND t' - . $i . '.story_id = t.story_id'; - } - - /* Limit to particular channels if requested */ - if (count($channel_id) > 0) { - // Have to find out if we are a composite channel or not. - $channels = array(); - foreach ($channel_id as $cid) { - $c = $this->_getChannel($cid); - if ($c['channel_type'] == Jonah::COMPOSITE_CHANNEL) { - $temp = explode(':', $c['channel_url']); - // Save a map of channels that are from composites. - foreach ($temp as $t) { - $cchannels[$t] = $cid; - } - $channels = array_merge($channels, $temp); - } - } - $channels = array_merge($channel_id, $channels); - $timestamp = time(); - $sql .= ' AND t.channel_id IN (' . implode(', ', $channels) - . ') AND s.story_published IS NOT NULL AND ' - . 's.story_published < ' . $timestamp; - } - - switch ($order) { - case Jonah::ORDER_PUBLISHED: - $sql .= ' ORDER BY story_published DESC'; - break; - case Jonah::ORDER_READ: - $sql .= ' ORDER BY story_read DESC'; - break; - case Jonah::ORDER_COMMENTS: - //@TODO - break; - } - - /* Instantiate the channel object outside the loop if we - * are only limiting to one channel. */ - if (count($channel_id) == 1) { - $channel = $this->getChannel($channel_id[0]); - } - Horde::logMessage('SQL query by Jonah_Driver_sql::searchTags: ' . $sql, 'DEBUG'); - $results = $this->_db->limitQuery($sql, $from, $max); - if (is_a($results, 'PEAR_Error')) { - return $results; - } - - for ($i = 0; $i < $results->numRows(); $i++) { - $row = $results->fetchRow(); - $story = $this->_getStory($row[0], false); - if (count($channel_id > 1)) { - // Make sure we get the correct channel info for composites - if (!empty($cchannels[$story['channel_id']])) { - $channel = $this->getChannel($cchannels[$story['channel_id']]); - } else { - $channel = $this->getChannel($story['channel_id']); - } - } - - /* Format story link. */ - $story['story_link'] = $this->getStoryLink($channel, $story); - $story = array_merge($story, $channel); - /* Format dates. */ - $date_format = $GLOBALS['prefs']->getValue('date_format'); - $story['story_updated_date'] = strftime($date_format, $story['story_updated']); - if (!empty($story['story_published'])) { - $story['story_published_date'] = strftime($date_format, $story['story_published']); - } - - $stories[] = $story; - } - } - - return $stories; - } - - /** - * Search for articles matching specific tag name(s). - * - * @see Jonah_Driver_sql::searchTagsById() - */ - public function searchTags($names, $max = 10, $from = 0, $channel_id = array(), - $order = Jonah::ORDER_PUBLISHED) - { - $ids = $this->getTagIds($names); - if (is_a($ids, 'PEAR_Error')) { - return $ids; - } - return $this->searchTagsById(array_values($ids), $max, $from, $channel_id, $order); - } - - - /** - * Return a set of tag names given the tag_ids. - * - * @param array $ids An array of tag_ids to get names for. - * - * @return mixed An array of tag names | PEAR_Error. - */ - public function getTagNames($ids) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - $sql = 'SELECT t.tag_name FROM jonah_tags as t WHERE t.tag_id IN('; - $needComma = false; - foreach ($ids as $id) { - $sql .= ($needComma ? ',' : '') . '\'' . $id . '\''; - $needComma = true; - } - $sql .= ')'; - $tags = $this->_db->getCol($sql); - return $tags; - } - - /** - * Return a set of tag_ids, given the tag name - * - * @param array $names An array of names to search for - * - * @return mixed An array of tag_name => tag_ids | PEAR_Error - */ - public function getTagIds($names) - { - if (is_a(($result = $this->_connect()), 'PEAR_Error')) { - return $result; - } - $sql = 'SELECT t.tag_name, t.tag_id FROM jonah_tags as t WHERE t.tag_name IN('; - $needComma = false; - foreach ($names as $name) { - $sql .= ($needComma ? ',' : '') . '\'' . $name . '\''; - $needComma = true; - } - $sql .= ')'; - $tags = $this->_db->getAssoc($sql); - return $tags; - } - - /** - * Attempts to open a persistent connection to the SQL server. - * - * @return boolean True on success; PEAR_Error on failure. - */ - protected function _connect() - { - if ($this->_connected) { - return true; - } - - Horde::assertDriverConfig($this->_params, 'news', - array('phptype', 'charset'), - 'jonah news SQL'); - - if (!isset($this->_params['database'])) { - $this->_params['database'] = ''; - } - if (!isset($this->_params['username'])) { - $this->_params['username'] = ''; - } - if (!isset($this->_params['hostspec'])) { - $this->_params['hostspec'] = ''; - } - - /* Connect to the SQL server using the supplied parameters. */ - $this->_db = &DB::connect($this->_params, - array('persistent' => !empty($this->_params['persistent']))); - if (is_a($this->_db, 'PEAR_Error')) { - return $this->_db; - } - - // Set DB portability options. - switch ($this->_db->phptype) { - case 'mssql': - $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); - break; - default: - $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); - } - - $this->_connected = true; - return true; - } - -}