Index: includes/pager.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/pager.inc,v retrieving revision 1.64 diff -u -p -r1.64 pager.inc --- includes/pager.inc 12 Oct 2008 04:30:05 -0000 1.64 +++ includes/pager.inc 13 Oct 2008 05:06:06 -0000 @@ -6,6 +6,140 @@ * Functions to aid in presenting database results as a set of pages. */ + +/** + * Query extender for pager queries. + */ +class PagerFull implements QueryExtenderInterface { + + /** + * The highest element we've autogenerated so far. + * + * @var int + */ + static protected $maxElement = 0; + + /** + * The number of elements per page to allow. + * + * @var int + */ + protected $limit = 10; + + /** + * The unique ID of this pager on this page. + * + * @var int + */ + protected $element = NULL; + + /** + * The count query that will be used for this pager. + * + * @var Query + */ + protected $countQuery = FALSE; + + public function execute($query) { + global $pager_page_array, $pager_total, $pager_total_items; + + // A NULL limit is the "kill switch" for pager queries. + if (empty($this->limit)) { + return; + } + + $count_query = $this->getCountQuery($query); + $this->ensureElement(); + + $page = isset($_GET['page']) ? $_GET['page'] : ''; + + // 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[$this->element] = $count_query->execute()->fetchField(); + $pager_total[$this->element] = ceil($pager_total_items[$this->element] / $this->limit); + $pager_page_array[$this->element] = max(0, min((int)$pager_page_array[$this->element], ((int)$pager_total[$this->element]) - 1)); + $query->range($pager_page_array[$this->element] * $this->limit, $this->limit); + } + + /** + * Ensure that there is an element associated with this query. + * + * After running this query, access $this->element to get the element for this + * query. + */ + protected function ensureElement() { + if (!empty($this->element)) { + return; + } + + $this->element = self::$maxElement++; + } + + /** + * Retrieve the count query for this pager. + * + * The count query may be specified manually or, by default, taken from the + * query we are extending. + * + * @param $query + * The query we are extending. + * @return + * A count SelectQuery. + */ + protected function getCountQuery($query) { + if ($this->countQuery) { + return $this->countQuery(); + } + else { + return $query->countQuery(); + } + } + + /** + * Specify the maximum number of elements per page for this query. + * + * The default if not specified is 10 items per page. + * + * @param $limit + * An integer specifying the number of elements per page. If passed NULL, + * the paging functionality will be disabled. + */ + public function limit($limit = NULL) { + $this->limit = $limit; + } + + /** + * Specify the element ID for this pager query. + * + * The element is used to differentiate different pager queries on the same + * page so that they may be operated independently. If you do not specify an + * element, every pager query on the page will get a unique element. If for + * whatever reason you want to explicitly define an element for a given query, + * you may do so here. + * + * @param $element + */ + public function element($element) { + $this->element = $element; + } + + /** + * Specify the count query object to use for this pager. + * + * You will rarely need to specify a count query directly. If not specified, + * one is generated off of the pager query itself. + * + * @param SelectQuery $query + * The count query object. It must return a single row with a single column, + * which is the total number of records. + */ + public function countQuery(SelectQuery $query) { + $this->countQuery = $query; + } +} + /** * Perform a paged database query. * Index: includes/database/select.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/select.inc,v retrieving revision 1.4 diff -u -p -r1.4 select.inc --- includes/database/select.inc 15 Sep 2008 05:00:48 -0000 1.4 +++ includes/database/select.inc 13 Oct 2008 05:06:06 -0000 @@ -9,7 +9,7 @@ /** * Query builder for SELECT statements. */ -class SelectQuery extends Query implements QueryConditionInterface, QueryAlterableInterface { +class SelectQuery extends Query implements QueryConditionInterface, QueryAlterableInterface, QueryExtendableInterface { /** * The fields to SELECT. @@ -82,6 +82,13 @@ class SelectQuery extends Query implemen protected $distinct = FALSE; /** + * A sequential array of extender objects on this query. + * + * @var array + */ + protected $extenders = array(); + + /** * The range limiters for this query. * * @var array @@ -282,8 +289,15 @@ class SelectQuery extends Query implemen } public function execute() { + + // Allow other modules to modify this query as needed. drupal_alter('query', $this); + // Allow any registered extenders to modify this query as needed. + foreach ($this->extenders as $extender) { + $extender->execute($this); + } + $this->where->compile($this->connection); $this->having->compile($this->connection); $args = $this->where->arguments() + $this->having->arguments(); @@ -606,6 +620,10 @@ class SelectQuery extends Query implemen $expressions =& $count->getExpressions(); $expressions = array(); + // Zero out the existing extenders, as most of them break on count queries. + $extenders =& $count->getExtenders(); + $extenders = array(); + // Ordering a count query is a waste of cycles, and breaks on some // databases anyway. $orders = &$count->getOrderBy(); @@ -689,9 +707,184 @@ class SelectQuery extends Query implemen $this->where = clone($this->where); $this->having = clone($this->having); + + $extenders = $this->extenders; + $this->extenders = array(); + foreach ($extenders as $key => $extender) { + $this->extenders[$key] = clone($extender); + } + } + + public function extend($key, QueryExtenderInterface $extender) { + $this->extenders[$key] = $extender; + return $extender; + } + + public function extender($key) { + return isset($this->extenders[$key]) ? $this->extenders[$key] : NULL; + } + + public function &getExtenders() { + return $this->extenders; + } + +} + +/** + * Interface for a query extender object. + * + */ +interface QueryExtenderInterface { + + /** + * Modify a query immediately before it is executed. + * + * This method operates after all query_alter hooks are done. + * + * @param $query + * The query we are altering, so that we can manipulate it as needed. + */ + public function execute($query); + +} + +/** + * Interface for a query object that can be extended. + * + * Note that there are other requirements for extendable queries that cannot + * be encapsulated in an Interface definition. Specifically: + * + * - The execute() method of the query must call the execute() method of each + * registered extender object prior to compiling the query, but after any + * alter hooks have run. + * + */ +interface QueryExtendableInterface { + + /** + * Associate an extender object with this query. + * + * @param $key + * A unique string used to label this extender, so that we can reference + * it later if needed. + * @param QueryExtenderInterface $extender + * The extender object. + * @return + * The extender object. That allows the extender object to be created in + * the extend() call itself, like so: + * + * $extender = $query->extend('label', new QueryExtenderClass()); + */ + public function extend($key, QueryExtenderInterface $extender); + + /** + * Retrieve an extender object. + * + * @param $key + * The key defined in the extend() method for the extender we want to retrieve. + * @return + * The extender object, or NULL if not found. + */ + public function extender($key); + + /** + * Retrieve an array of extender objects. + * + * This method returns by reference and must be called by reference. That way, + * the internal array of extenders may be retrieved and modified if necessary. + * + * @return + * An associative array of extender objects, keyed by their keys specified + * in extend(). + */ + public function &getExtenders(); +} + + +class TableSort implements QueryExtenderInterface { + + /** + * The array of fields that can be sorted by. + * + * @var unknown_type + */ + protected $header = array(); + + public function execute($query) { + $ts = $this->init(); + 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 : ''; + + $query->orderBy($field, $sort); + } } + + public function setHeader(Array $header) { + $this->header = $header; + + } + + protected function init() { + $ts = $this->order(); + $ts['sort'] = $this->getSort(); + $ts['query_string'] = $this->getQueryString(); + return $ts; + } + + protected function getSort() { + if (isset($_GET['sort'])) { + return ($_GET['sort'] == 'desc') ? 'desc' : 'asc'; + } + // User has not specified a sort. Use default if specified; otherwise use "asc". + else { + foreach ($this->header as $header) { + if (is_array($header) && array_key_exists('sort', $header)) { + return $header['sort']; + } + } + } + return 'asc'; + } + + protected function getQueryString() { + return drupal_query_string_encode($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE))); + } + + protected function order() { + $order = isset($_GET['order']) ? $_GET['order'] : ''; + foreach ($this->header as $header) { + if (isset($header['data']) && $order == $header['data']) { + return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : ''); + } + + if (isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) { + $default = array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : ''); + } + } + + if (isset($default)) { + return $default; + } + else { + // The first column specified is initial 'order by' field unless otherwise specified + if (is_array($this->header[0])) { + $this->header[0] += array('data' => NULL, 'field' => NULL); + return array('name' => $this->header[0]['data'], 'sql' => $this->header[0]['field']); + } + else { + return array('name' => $this->header[0]); + } + } + } + } + /** * @} End of "ingroup database". */ Index: modules/comment/comment.admin.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.admin.inc,v retrieving revision 1.10 diff -u -p -r1.10 comment.admin.inc --- modules/comment/comment.admin.inc 16 Jul 2008 21:59:26 -0000 1.10 +++ modules/comment/comment.admin.inc 13 Oct 2008 05:06:06 -0000 @@ -68,11 +68,30 @@ function comment_admin_overview($type = array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'), array('data' => t('Operations')), )); - $result = pager_query('SELECT c.subject, c.nid, c.cid, c.comment, c.timestamp, c.status, c.name, c.homepage, u.name AS registered_name, u.uid, n.title as node_title FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid INNER JOIN {node} n ON n.nid = c.nid WHERE c.status = %d' . tablesort_sql($form['header']['#value']), 50, 0, NULL, $status); + + $select = db_select('comments', 'c'); + $select->join('users', 'u', 'u.uid = c.uid'); + $select->join('node', 'n', 'n.nid = c.nid'); + foreach (array('subject', 'nid', 'cid', 'comment', 'timestamp', 'status', 'name', 'homepage') as $field) { + $select->addField('c', $field, $field); + } + $select->addField('c', 'name', 'registered_name'); + $select->addField('u', 'uid', 'uid'); + $select->addField('n', 'title', 'node_title'); + $select->condition('c.status', $status); + + $pager = $select->extend('pager', new PagerFull()); + $pager->limit(50); + + $tablesort = $select->extend('sort', new TableSort()); + $tablesort->setHeader($form['header']['#value']); + + $result = $select->execute(); + // Build a table listing the appropriate comments. $destination = drupal_get_destination(); - while ($comment = db_fetch_object($result)) { + foreach ($result as $comment) { $comments[$comment->cid] = ''; $comment->name = $comment->uid ? $comment->registered_name : $comment->name; $form['subject'][$comment->cid] = array( Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.987 diff -u -p -r1.987 node.module --- modules/node/node.module 12 Oct 2008 19:00:55 -0000 1.987 +++ modules/node/node.module 13 Oct 2008 05:06:07 -0000 @@ -1821,11 +1821,26 @@ function node_feed($nids = FALSE, $chann * Menu callback; Generate a listing of promoted nodes. */ function node_page_default() { - $result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10)); + + $select = db_select('node', 'n'); + $select->addField('n', 'nid', 'nid'); + $select->addField('n', 'sticky', 'sticky'); + $select->addField('n', 'created', 'created'); + $select->orderBy('sticky', 'DESC'); + $select->orderBy('created', 'DESC'); + $select->addTag('node_access'); + $select + ->condition('promote', 1) + ->condition('status', 1); + + $pager = $select->extend('pager', new PagerFull()); + $pager->limit(variable_get('default_nodes_main', 10)); + + $result = $select->execute(); $output = ''; $num_rows = FALSE; - while ($node = db_fetch_object($result)) { + foreach ($result as $node) { $output .= node_view(node_load($node->nid), 1); $num_rows = TRUE; }