Commit e18feedf authored by webchick's avatar webchick
Browse files

#564394 by Berdir and Crell: Removed database BC layer. nah nah nah nah... hey...

#564394 by Berdir and Crell: Removed database BC layer. nah nah nah nah... hey hey hey... gooood byeeee...
parent 6a1217af
......@@ -881,7 +881,7 @@ function _drupal_decode_exception($exception) {
// The first element in the stack is the call, the second element gives us the caller.
// We skip calls that occurred in one of the classes of the database layer
// or in one of its global functions.
$db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
$db_functions = array('db_query', 'db_query_range', 'update_sql');
while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
in_array($caller['function'], $db_functions))) {
......
......@@ -23,9 +23,10 @@
* inherits much of its syntax and semantics.
*
* Most Drupal database SELECT queries are performed by a call to db_query() or
* db_query_range(). Module authors should also consider using pager_query() for
* queries that return results that need to be presented on multiple pages, and
* tablesort_sql() for generating appropriate queries for sortable tables.
* db_query_range(). Module authors should also consider using the PagerDefault
* Extender for queries that return results that need to be presented on
* multiple pages, and the Tablesort Extender for generating appropriate queries
* for sortable tables.
*
* For example, one might wish to return a list of the most recent 10 nodes
* authored by a given user. Instead of directly issuing the SQL query
......@@ -943,7 +944,7 @@ public function popTransaction() {
* A database query result resource, or NULL if the query was not executed
* correctly.
*/
abstract public function queryRange($query, array $args, $from, $count, array $options = array());
abstract public function queryRange($query, $from, $count, array $args = array(), array $options = array());
/**
* Generate a temporary table name.
......@@ -977,7 +978,7 @@ protected function generateTemporaryTableName() {
* @return
* The name of the temporary table.
*/
abstract function queryTemporary($query, array $args, array $options = array());
abstract function queryTemporary($query, array $args = array(), array $options = array());
/**
* Returns the type of database driver.
......@@ -1812,12 +1813,10 @@ public function fetchAssoc() {
* @return
* A prepared statement object, already executed.
*/
function db_query($query, $args = array(), $options = array()) {
if (!is_array($args)) {
$args = func_get_args();
array_shift($args);
function db_query($query, array $args = array(), array $options = array()) {
if (empty($options['target'])) {
$options['target'] = 'default';
}
list($query, $args, $options) = _db_query_process_args($query, $args, $options);
return Database::getConnection($options['target'])->query($query, $args, $options);
}
......@@ -1830,30 +1829,26 @@ function db_query($query, $args = array(), $options = array()) {
* The prepared statement query to run. Although it will accept both
* named and unnamed placeholders, named placeholders are strongly preferred
* as they are more self-documenting.
* @param $from
* The first record from the result set to return.
* @param $limit
* The number of records to return from the result set.
* @param $args
* An array of values to substitute into the query. If the query uses named
* placeholders, this is an associative array in any order. If the query uses
* unnamed placeholders (?), this is an indexed array and the order must match
* the order of placeholders in the query string.
* @param $from
* The first record from the result set to return.
* @param $limit
* The number of records to return from the result set.
* @param $options
* An array of options to control how the query operates.
* @return
* A prepared statement object, already executed.
*/
function db_query_range($query, $args, $from = 0, $count = 0, $options = array()) {
if (!is_array($args)) {
$args = func_get_args();
array_shift($args);
$count = array_pop($args);
$from = array_pop($args);
function db_query_range($query, $from, $count, array $args = array(), array $options = array()) {
if (empty($options['target'])) {
$options['target'] = 'default';
}
list($query, $args, $options) = _db_query_process_args($query, $args, $options);
return Database::getConnection($options['target'])->queryRange($query, $args, $from, $count, $options);
return Database::getConnection($options['target'])->queryRange($query, $from, $count, $args, $options);
}
/**
......@@ -1874,12 +1869,10 @@ function db_query_range($query, $args, $from = 0, $count = 0, $options = array()
* @return
* The name of the temporary table.
*/
function db_query_temporary($query, $args, $options = array()) {
if (!is_array($args)) {
$args = func_get_args();
array_shift($args);
function db_query_temporary($query, array $args = array(), array $options = array()) {
if (empty($options['target'])) {
$options['target'] = 'default';
}
list($query, $args, $options) = _db_query_process_args($query, $args, $options);
return Database::getConnection($options['target'])->queryTemporary($query, $args, $options);
}
......@@ -2064,26 +2057,6 @@ function update_sql($sql) {
return array('success' => $result !== FALSE, 'query' => check_plain($sql));
}
/**
* Wraps the given table.field entry with a DISTINCT(). The wrapper is added to
* the SELECT list entry of the given query and the resulting query is returned.
* This function only applies the wrapper if a DISTINCT doesn't already exist in
* the query.
*
* @todo Remove this.
* @param $table
* Table containing the field to set as DISTINCT
* @param $field
* Field to set as DISTINCT
* @param $query
* Query to apply the wrapper to
* @return
* SQL query with the DISTINCT wrapper surrounding the given table.field.
*/
function db_distinct_field($table, $field, $query) {
return Database::getConnection()->distinctField($table, $field, $query);
}
/**
* Retrieve the name of the currently active database driver, such as
* "mysql" or "pgsql".
......@@ -2475,24 +2448,6 @@ function db_ignore_slave() {
}
}
/**
* @ingroup database-legacy
*
* These functions are no longer necessary, as the DatabaseStatementInterface interface
* offers this and much more functionality. They are kept temporarily for backward
* compatibility during conversion and should be removed as soon as possible.
*
* @{
*/
function db_fetch_object(DatabaseStatementInterface $statement) {
return $statement->fetch(PDO::FETCH_OBJ);
}
function db_result(DatabaseStatementInterface $statement) {
return $statement->fetchField();
}
/**
* Redirect the user to the installation script if Drupal has not been
* installed yet (i.e., if no $databases array has been defined in the
......@@ -2504,152 +2459,4 @@ function _db_check_install_needed() {
include_once DRUPAL_ROOT . '/includes/install.inc';
install_goto('install.php');
}
}
/**
* Backward-compatibility utility.
*
* This function should be removed after all queries have been converted
* to the new API. It is temporary only.
*
* @todo Remove this once the query conversion is complete.
*/
function _db_query_process_args($query, $args, $options) {
if (!is_array($options)) {
$options = array();
}
if (empty($options['target'])) {
$options['target'] = 'default';
}
// Temporary backward-compatibility hacks. Remove later.
$old_query = $query;
$query = str_replace(array('%n', '%d', '%f', '%b', "'%s'", '%s'), '?', $old_query);
if ($old_query !== $query) {
$args = array_values($args); // The old system allowed named arrays, but PDO doesn't if you use ?.
}
return array($query, $args, $options);
}
/**
* Helper function for db_rewrite_sql.
*
* Collects JOIN and WHERE statements via hook_db_rewrite_sql()
* Decides whether to select primary_key or DISTINCT(primary_key)
*
* @todo Remove this function when all code has been converted to query_alter.
* @param $query
* Query to be rewritten.
* @param $primary_table
* Name or alias of the table which has the primary key field for this query.
* Typical table names would be: {block}, {comment}, {forum}, {node},
* {menu}, {taxonomy_term_data} or {taxonomy_vocabulary}. However, in most cases the usual
* table alias (b, c, f, n, m, t or v) is used instead of the table name.
* @param $primary_field
* Name of the primary field.
* @param $args
* Array of additional arguments.
* @return
* An array: join statements, where statements, field or DISTINCT(field).
*/
function _db_rewrite_sql($query = '', $primary_table = 'n', $primary_field = 'nid', $args = array()) {
$where = array();
$join = array();
$distinct = FALSE;
foreach (module_implements('db_rewrite_sql') as $module) {
$result = module_invoke($module, 'db_rewrite_sql', $query, $primary_table, $primary_field, $args);
if (isset($result) && is_array($result)) {
if (isset($result['where'])) {
$where[] = $result['where'];
}
if (isset($result['join'])) {
$join[] = $result['join'];
}
if (isset($result['distinct']) && $result['distinct']) {
$distinct = TRUE;
}
}
elseif (isset($result)) {
$where[] = $result;
}
}
$where = empty($where) ? '' : '(' . implode(') AND (', $where) . ')';
$join = empty($join) ? '' : implode(' ', $join);
return array($join, $where, $distinct);
}
/**
* Rewrites node, taxonomy and comment queries. Use it for listing queries. Do not
* use FROM table1, table2 syntax, use JOIN instead.
*
* @todo Remove this function when all code has been converted to query_alter.
* @param $query
* Query to be rewritten.
* @param $primary_table
* Name or alias of the table which has the primary key field for this query.
* Typical table names would be: {block}, {comment}, {forum}, {node},
* {menu}, {taxonomy_term_data} or {taxonomy_vocabulary}. However, it is more common to use the
* the usual table aliases: b, c, f, n, m, t or v.
* @param $primary_field
* Name of the primary field.
* @param $args
* An array of arguments, passed to the implementations of hook_db_rewrite_sql.
* @return
* The original query with JOIN and WHERE statements inserted from
* hook_db_rewrite_sql implementations. nid is rewritten if needed.
*/
function db_rewrite_sql($query, $primary_table = 'n', $primary_field = 'nid', $args = array()) {
list($join, $where, $distinct) = _db_rewrite_sql($query, $primary_table, $primary_field, $args);
if ($distinct) {
$query = db_distinct_field($primary_table, $primary_field, $query);
}
if (!empty($where) || !empty($join)) {
$pattern = '{
# Beginning of the string
^
((?P<anonymous_view>
# Everything within this set of parentheses is named "anonymous view"
(?:
[^()]++ # anything not parentheses
|
\( (?P>anonymous_view) \) # an open parenthesis, more "anonymous view" and finally a close parenthesis.
)*
)[^()]+WHERE)
}x';
preg_match($pattern, $query, $matches);
if ($where) {
$n = strlen($matches[1]);
$second_part = substr($query, $n);
$first_part = substr($matches[1], 0, $n - 5) . " $join WHERE $where AND ( ";
foreach (array('GROUP', 'ORDER', 'LIMIT') as $needle) {
$pos = strrpos($second_part, $needle);
if ($pos !== FALSE) {
// All needles are five characters long.
$pos += 5;
break;
}
}
if ($pos === FALSE) {
$query = $first_part . $second_part . ')';
}
else {
$query = $first_part . substr($second_part, 0, -$pos) . ')' . substr($second_part, -$pos);
}
}
else {
$query = $matches[1] . " $join " . substr($query, strlen($matches[1]));
}
}
return $query;
}
/**
* @} End of "ingroup database-legacy".
*/
}
\ No newline at end of file
......@@ -46,11 +46,11 @@ public function __construct(array $connection_options = array()) {
$this->exec("SET sql_mode='ANSI,TRADITIONAL'");
}
public function queryRange($query, array $args, $from, $count, array $options = array()) {
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
return $this->query($query . ' LIMIT ' . $from . ', ' . $count, $args, $options);
}
public function queryTemporary($query, array $args, array $options = array()) {
public function queryTemporary($query, array $args = array(), array $options = array()) {
$tablename = $this->generateTemporaryTableName();
$this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY SELECT', $query), $args, $options);
return $tablename;
......@@ -68,15 +68,6 @@ public function mapConditionOperator($operator) {
// We don't want to override any of the defaults.
return NULL;
}
/**
* @todo Remove this as soon as db_rewrite_sql() has been exterminated.
*/
public function distinctField($table, $field, $query) {
$field_to_select = 'DISTINCT(' . $table . '.' . $field . ')';
// (?<!text) is a negative look-behind (no need to rewrite queries that already use DISTINCT).
return preg_replace('/(SELECT.*)(?:' . $table . '\.|\s)(?<!DISTINCT\()(?<!DISTINCT\(' . $table . '\.)' . $field . '(.*FROM )/AUsi', '\1 ' . $field_to_select . '\2', $query);
}
}
......
......@@ -99,11 +99,11 @@ public function query($query, array $args = array(), $options = array()) {
}
}
public function queryRange($query, array $args, $from, $count, array $options = array()) {
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
return $this->query($query . ' LIMIT ' . $count . ' OFFSET ' . $from, $args, $options);
}
public function queryTemporary($query, array $args, array $options = array()) {
public function queryTemporary($query, array $args = array(), array $options = array()) {
$tablename = $this->generateTemporaryTableName();
$this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options);
return $tablename;
......@@ -126,15 +126,6 @@ public function mapConditionOperator($operator) {
return isset($specials[$operator]) ? $specials[$operator] : NULL;
}
/**
* @todo Remove this as soon as db_rewrite_sql() has been exterminated.
*/
public function distinctField($table, $field, $query) {
$field_to_select = 'DISTINCT(' . $table . '.' . $field . ')';
// (?<!text) is a negative look-behind (no need to rewrite queries that already use DISTINCT).
return preg_replace('/(SELECT.*)(?:' . $table . '\.|\s)(?<!DISTINCT\()(?<!DISTINCT\(' . $table . '\.)' . $field . '(.*FROM )/AUsi', '\1 ' . $field_to_select . '\2', $query);
}
}
/**
......
......@@ -1084,7 +1084,7 @@ public function execute() {
$args = $this->getArguments();
if (!empty($this->range)) {
return $this->connection->queryRange((string)$this, $args, $this->range['start'], $this->range['length'], $this->queryOptions);
return $this->connection->queryRange((string)$this, $this->range['start'], $this->range['length'], $args, $this->queryOptions);
}
return $this->connection->query((string)$this, $args, $this->queryOptions);
}
......
......@@ -135,11 +135,11 @@ public function PDOPrepare($query, array $options = array()) {
return parent::prepare($query, $options);
}
public function queryRange($query, array $args, $from, $count, array $options = array()) {
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
return $this->query($query . ' LIMIT ' . $from . ', ' . $count, $args, $options);
}
public function queryTemporary($query, array $args, array $options = array()) {
public function queryTemporary($query, array $args = array(), array $options = array()) {
$tablename = $this->generateTemporaryTableName();
$this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options);
return $tablename;
......@@ -164,15 +164,6 @@ public function prepareQuery($query, $cache = TRUE) {
// DatabaseStatement_sqlite::execute() and cannot be cached.
return $this->prepare($this->prefixTables($query));
}
/**
* @todo Remove this as soon as db_rewrite_sql() has been exterminated.
*/
public function distinctField($table, $field, $query) {
$field_to_select = 'DISTINCT(' . $table . '.' . $field . ')';
// (?<!text) is a negative look-behind (no need to rewrite queries that already use DISTINCT).
return preg_replace('/(SELECT.*)(?:' . $table . '\.|\s)(?<!DISTINCT\()(?<!DISTINCT\(' . $table . '\.)' . $field . '(.*FROM )/AUsi', '\1 ' . $field_to_select . '\2', $query);
}
}
/**
......
......@@ -170,76 +170,6 @@ public function element($element) {
}
}
/**
* Perform a paged database query.
*
* Use this function when doing select queries you wish to be able to page. The
* pager uses LIMIT-based queries to fetch only the records required to render a
* certain page. However, it has to learn the total number of records returned
* by the query to compute the number of pages (the number of records / records
* per page). This is done by inserting "COUNT(*)" in the original query. For
* example, the query "SELECT nid, type FROM node WHERE status = '1' ORDER BY
* sticky DESC, created DESC" would be rewritten to read "SELECT COUNT(*) FROM
* node WHERE status = '1' ORDER BY sticky DESC, created DESC". Rewriting the
* query is accomplished using a regular expression.
*
* Unfortunately, the rewrite rule does not always work as intended for queries
* that already have a "COUNT(*)" or a "GROUP BY" clause, and possibly for
* other complex queries. In those cases, you can optionally pass a query that
* will be used to count the records.
*
* For example, if you want to page the query "SELECT COUNT(*), TYPE FROM node
* GROUP BY TYPE", pager_query() would invoke the incorrect query "SELECT
* COUNT(*) FROM node GROUP BY TYPE". So instead, you should pass "SELECT
* COUNT(DISTINCT(TYPE)) FROM node" as the optional $count_query parameter.
*
* @param $query
* The SQL query that needs paging.
* @param $limit
* The number of query results to display per page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
* @param $count_query
* An SQL query used to count matching records.
* @param ...
* A variable number of arguments which are substituted into the query (and
* the count query) using printf() syntax. Instead of a variable number of
* query arguments, you may also pass a single array containing the query
* arguments.
* @return
* A database query result resource, or FALSE if the query was not executed
* correctly.
*
* @ingroup database
*/
function pager_query($query, $limit = 10, $element = 0, $count_query = NULL) {
global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
$page = isset($_GET['page']) ? $_GET['page'] : '';
// Substitute in query arguments.
$args = func_get_args();
$args = array_slice($args, 4);
// Alternative syntax for '...'
if (isset($args[0]) && is_array($args[0])) {
$args = $args[0];
}
// Construct a count query if none was given.
if (!isset($count_query)) {
$count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY .*/'), array('SELECT COUNT(*) FROM ', ''), $query);
}
// Convert comma-separated $page to an array, used by other functions.
$pager_page_array = explode(',', $page);
// We calculate the total of pages as ceil(items / limit).
$pager_total_items[$element] = db_query($count_query, $args)->fetchField();
$pager_total[$element] = ceil($pager_total_items[$element] / $limit);
$pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
$pager_limits[$element] = $limit;
return db_query_range($query, $args, $pager_page_array[$element] * $limit, $limit);
}
/**
* Compose a query string to append to pager requests.
*
......
......@@ -145,37 +145,6 @@ function tablesort_init($header) {
return $ts;
}
/**
* Create an SQL sort clause.
*
* This function produces the ORDER BY clause to insert in your SQL queries,
* assuring that the returned database table rows match the sort order chosen
* by the user.
*
* @param $header
* An array of column headers in the format described in theme_table().
* @param $before
* An SQL string to insert after ORDER BY and before the table sorting code.
* Useful for sorting by important attributes like "sticky" first.
* @return
* An SQL string to append to the end of a query.
*
* @ingroup database
*/
function tablesort_sql($header, $before = '') {
$ts = tablesort_init($header);
if ($ts['sql']) {
// Based on code from db_escape_table(), but this can also contain a dot.
$field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']);
// Sort order can only be ASC or DESC.
$sort = drupal_strtoupper($ts['sort']);
$sort = in_array($sort, array('ASC', 'DESC')) ? $sort : '';
return " ORDER BY $before $field $sort";
}
}
/**
* Format a column header.
*
......
......@@ -131,10 +131,10 @@ function db_change_column(&$ret, $table, $column, $column_new, $type, $attribute
function update_fix_compatibility() {
$ret = array();
$incompatible = array();
$query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
while ($result = db_fetch_object($query)) {
if (update_check_incompatibility($result->name, $result->type)) {
$incompatible[] = $result->name;
$result = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')");
foreach ($result as $row) {
if (update_check_incompatibility($row->name, $row->type)) {
$incompatible[] = $row->name;
}
}
if (!empty($incompatible)) {
......
......@@ -756,8 +756,8 @@ function install_system_module(&$install_state) {
*/
function install_verify_completed_task() {
try {
if ($result = db_query("SELECT value FROM {variable} WHERE name = '%s'", 'install_task')) {
$task = unserialize(db_result($result));
if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) {
$task = unserialize($result->fetchField());
}
}
// Do not trigger an error if the database query fails, since the database
......
......@@ -377,7 +377,7 @@ function aggregator_block_view($delta = '') {
case 'feed':
if ($feed = db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE block <> 0 AND fid = :fid', array(':fid' => $id))->fetchObject()) {
$block['subject'] = check_plain($feed->title);
$result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", array(':fid' => $id), 0, $feed->block);
$result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", 0, $feed->block, array(':fid' => $id));
$read_more = theme('more_link', url('aggregator/sources/' . $feed->fid), t("View this feed's recent news."));
}
break;
......@@ -385,7 +385,7 @@ function aggregator_block_view($delta = '') {
case 'category':
if ($category = db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchObject()) {
$block['subject'] = check_plain($category->title);
$result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = :cid ORDER BY i.timestamp DESC, i.iid DESC', array(':cid' => $category->cid), 0, $category->block);
$result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = :cid ORDER BY i.timestamp DESC, i.iid DESC', 0, $category->block, array(':cid' => $category->cid));
$read_more = theme('more_link', url('aggregator/categories/' . $category->cid), t("View this category's recent news."));
}
break;
......
......@@ -90,10 +90,10 @@ function aggregator_feed_items_load($type, $data = NULL) {
$result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC', 0, $range_limit);
break;
case 'source':
$result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC', array(':fid' => $data->fid), 0, $range_limit);
$result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC', 0, $range_limit, array(':fid' => $data->fid));
break;
case 'category':
$result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', array(':cid' => $data['cid']), 0, $range_limit);
$result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', 0, $range_limit, array(':cid' => $data['cid']));
break;
}
......@@ -304,7 +304,7 @@ function aggregator_page_sources() {
// Most recent items:
$summary_items = array();
if (variable_get('aggregator_summary_items', 3)) {
$items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = :fid ORDER BY i.timestamp DESC', array(':fid' => $feed->fid), 0, variable_get('aggregator_summary_items', 3));
$items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = :fid ORDER BY i.timestamp DESC', 0, variable_get('aggregator_summary_items', 3), array(':fid' => $feed->fid));
foreach ($items as $item) {
$summary_items[] = theme('aggregator_summary_item', $item);