From b83f4a721a50e79271f9518d1d30202d4684cb08 Mon Sep 17 00:00:00 2001
From: Demian Katz <demian.katz@villanova.edu>
Date: Wed, 18 Jul 2012 14:53:44 -0400
Subject: [PATCH] Implemented browse.

---
 module/VuFind/config/module.config.php        |   1 +
 .../VuFind/Controller/BrowseController.php    | 672 ++++++++++++++++++
 module/VuFind/src/VuFind/Db/Table/Tags.php    | 114 +--
 3 files changed, 733 insertions(+), 54 deletions(-)
 create mode 100644 module/VuFind/src/VuFind/Controller/BrowseController.php

diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index 6e21b58347d..6425b12c887 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -67,6 +67,7 @@ $config = array(
             'alphabrowse' => 'VuFind\Controller\AlphabrowseController',
             'author' => 'VuFind\Controller\AuthorController',
             'authority' => 'VuFind\Controller\AuthorityController',
+            'browse' => 'VuFind\Controller\BrowseController',
             'cover' => 'VuFind\Controller\CoverController',
             'error' => 'VuFind\Controller\ErrorController',
             'index' => 'VuFind\Controller\IndexController',
diff --git a/module/VuFind/src/VuFind/Controller/BrowseController.php b/module/VuFind/src/VuFind/Controller/BrowseController.php
new file mode 100644
index 00000000000..e9b92619bb2
--- /dev/null
+++ b/module/VuFind/src/VuFind/Controller/BrowseController.php
@@ -0,0 +1,672 @@
+<?php
+/**
+ * Browse Module Controller
+ *
+ * PHP Version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA    02111-1307    USA
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Chris Hallberg <challber@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/alphabetical_heading_browse Wiki
+ */
+namespace VuFind\Controller;
+use VuFind\Config\Reader as ConfigReader, VuFind\Db\Table\Tags as TagsTable,
+    VuFind\Search\Solr\Params as SolrParams,
+    VuFind\Search\Solr\Results as SolrResults;
+
+/**
+ * BrowseController Class
+ *
+ * Controls the alphabetical browsing feature
+ *
+ * @category VuFind2
+ * @package  Controller
+ * @author   Chris Hallberg <challber@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/alphabetical_heading_browse Wiki
+ */
+class BrowseController extends AbstractBase
+{
+
+    protected $config, $currentAction = null, $disabledFacets;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->config = ConfigReader::getConfig();
+
+        $this->disabledFacets = array();
+        foreach ($this->config->Browse as $key => $setting) {
+            if ($setting == false) {
+                $this->disabledFacets[] = $key;
+            }
+        }
+    }
+
+    /**
+     * Set the name of the current action.
+     *
+     * @param string $name Name of the current action
+     *
+     * @return void
+     */
+    protected function setCurrentAction($name)
+    {
+        $this->currentAction = $name;
+    }
+
+    /**
+     * Get the name of the current action.
+     *
+     * @return string
+     */
+    protected function getCurrentAction()
+    {
+        return $this->currentAction;
+    }
+
+    /**
+     * Create a new ViewModel.
+     *
+     * @param array $params Parameters to pass to ViewModel constructor.
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    protected function createViewModel($params = null)
+    {
+        $view = parent::createViewModel($params);
+
+        // Set the current action.
+        $currentAction = $this->getCurrentAction();
+        if (!empty($currentAction)) {
+            $view->currentAction = $currentAction;
+        }
+
+        // Initialize the array of top-level browse options.
+        $browseOptions = array();
+
+        // First option: tags -- is it enabled in config.ini?  If no setting is
+        // found, assume it is active.
+        if (!isset($this->config->Browse->tag)
+            || $this->config->Browse->tag == true
+        ) {
+            $browseOptions[] = $this->buildBrowseOption('Tag', 'Tag');
+            $view->tagEnabled = true;
+        }
+
+        // Read configuration settings for LC / Dewey call number display; default
+        // to LC only if no settings exist in config.ini.
+        if (!isset($this->config->Browse->dewey)
+            && !isset($this->config->Browse->lcc)
+        ) {
+            $lcc = true;
+            $dewey = false;
+        } else {
+            $lcc = (isset($this->config->Browse->lcc)
+                && $this->config->Browse->lcc);
+            $dewey = (isset($this->config->Browse->dewey)
+                && $this->config->Browse->dewey);
+        }
+
+        // Add the call number options as needed -- note that if both options exist,
+        // we need to use special text to disambiguate them.
+        if ($dewey) {
+            $browseOptions[] = $this->buildBrowseOption(
+                'Dewey', ($lcc ? 'browse_dewey' : 'Call Number')
+            );
+            $view->deweyEnabled = true;
+        }
+        if ($lcc) {
+            $browseOptions[] = $this->buildBrowseOption(
+                'LCC', ($dewey ? 'browse_lcc' : 'Call Number')
+            );
+            $view->lccEnabled = true;
+        }
+
+        // Loop through remaining browse options.  All may be individually disabled
+        // in config.ini, but if no settings are found, they are assumed to be on.
+        $remainingOptions = array(
+            'Author', 'Topic', 'Genre', 'Region', 'Era'
+        );
+        foreach ($remainingOptions as $current) {
+            $option = strToLower($current);
+            if (!isset($this->config->Browse->$option)
+                || $this->config->Browse->$option == true
+            ) {
+                $browseOptions[] = $this->buildBrowseOption($current, $current);
+                $option .= 'Enabled';
+                $view->$option = true;
+            }
+        }
+
+        // CARRY
+        if ($findby = $this->params()->fromQuery('findby')) {
+            $view->findby = $findby;
+        }
+        if ($query = $this->params()->fromQuery('query')) {
+            $view->query = $query;
+        }
+        if ($category = $this->params()->fromQuery('category')) {
+            $view->category = $category;
+        }
+        $view->browseOptions = $browseOptions;
+
+        return $view;
+    }
+
+    /**
+     * Build an array containing options describing a top-level Browse option.
+     *
+     * @param string $action      The name of the Action for this option
+     * @param string $description A description of this Browse option
+     *
+     * @return array              The Browse option array
+     */
+    protected function buildBrowseOption($action, $description)
+    {
+        return array('action' => $action, 'description' => $description);
+    }
+
+    /**
+     * Gathers data for the view of the AlphaBrowser and does some initialization
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    public function homeAction()
+    {
+        $this->setCurrentAction('Home');
+        return $this->createViewModel();
+    }
+
+    /**
+     * Perform the search
+     *
+     * @param \Zend\View\Model\ViewModel $view View model to modify
+     *
+     * @return \Zend\View\Model\ViewModel
+     */
+    protected function performSearch($view)
+    {
+        // Remove disabled facets
+        $facets = $view->categoryList;
+        foreach ($this->disabledFacets as $facet) {
+            unset($facets[$facet]);
+        }
+        $view->categoryList = $facets;
+
+        // SEARCH (Tag does its own search)
+        if ($this->params()->fromQuery('query')
+            && $this->getCurrentAction() != 'Tag'
+        ) {
+            $results = $this->getFacetList(
+                $this->params()->fromQuery('facet_field'),
+                $this->params()->fromQuery('query_field'),
+                'count', $this->params()->fromQuery('query')
+            );
+            $resultList = array();
+            foreach ($results as $result) {
+                $resultList[] = array(
+                    'result' => $result['displayText'],
+                    'count' => $result['count']
+                );
+            }
+            // Don't make a second filter if it would be the same facet
+            $view->paramTitle
+                = ($this->params()->fromQuery('query_field') != $this->getCategory())
+                ? 'filter[]=' . $this->params()->fromQuery('query_field') . ':'
+                    . urlencode($this->params()->fromQuery('query')) . '&'
+                : '';
+            switch($this->getCurrentAction()) {
+            case 'LCC':
+                $view->paramTitle .= 'filter[]=callnumber-subject:';
+                break;
+            case 'Dewey':
+                $view->paramTitle .= 'filter[]=dewey-ones:';
+                break;
+            default:
+                $view->paramTitle .= 'filter[]='.$this->getCategory().':';
+            }
+            $view->paramTitle = str_replace(
+                '+AND+',
+                '&filter[]=',
+                $view->paramTitle
+            );
+            $view->resultList = $resultList;
+        }
+
+        $view->setTemplate('browse/home');
+        return $view;
+    }
+
+    /**
+     * Browse tags
+     *
+     * @return void
+     */
+    public function tagAction()
+    {
+        $this->setCurrentAction('Tag');
+        $view = $this->createViewModel();
+
+        $view->categoryList = array(
+            'alphabetical' => 'By Alphabetical',
+            'popularity'   => 'By Popularity',
+            'recent'       => 'By Recent'
+        );
+
+        if ($this->params()->fromQuery('findby')) {
+            $params = $this->getRequest()->getQuery()->toArray();
+            $tagTable = new TagsTable();
+            // Special case -- display alphabet selection if necessary:
+            if ($params['findby'] == 'alphabetical') {
+                $legalLetters = $this->getAlphabetList();
+                $view->secondaryList = $legalLetters;
+                // Only display tag list when a valid letter is selected:
+                if (isset($params['query'])) {
+                    // Note -- this does not need to be escaped because
+                    // $params['query'] has already been validated against
+                    // the _getAlphabetList() method below!
+                    $tags = $tagTable->matchText($params['query']);
+                    $tagList = array();
+                    foreach ($tags as $tag) {
+                        $count = $tagTable->getCount($tag['id']);
+                        if ($count > 0) {
+                            $tagList[] = array(
+                                'result' => $tag['tag'],
+                                'count' => $count
+                            );
+                        }
+                    }
+                    $view->resultList = array_slice(
+                        $tagList, 0, $this->config->Browse->result_limit
+                    );
+                }
+            } else {
+                // Default case: always display tag list for non-alphabetical modes:
+                $tagList = $tagTable->getTagList(
+                    $params['findby'],
+                    $this->config->Browse->result_limit
+                );
+                $resultList = array();
+                foreach ($tagList as $i=>$tag) {
+                    $resultList[$i] = array(
+                        'result' => $tag['tag'],
+                        'count'    => $tag['cnt']
+                    );
+                }
+                $view->resultList = $resultList;
+            }
+            $view->paramTitle = 'lookfor=';
+            $view->searchParams = array();
+        }
+
+        return $this->performSearch($view);
+    }
+
+    /**
+     * Browse LCC
+     *
+     * @return void
+     */
+    public function lccAction()
+    {
+        $this->setCurrentAction('LCC');
+        $view = $this->createViewModel();
+        list($view->filter, $view->secondaryList) = $this->getSecondaryList('lcc');
+        $view->secondaryParams = array(
+            'query_field' => 'callnumber-first',
+            'facet_field' => 'callnumber-subject'
+        );
+        $view->searchParams = array();
+        return $this->performSearch($view);
+    }
+
+    /**
+     * Browse Dewey
+     *
+     * @return void
+     */
+    public function deweyAction()
+    {
+        $this->setCurrentAction('Dewey');
+        $view = $this->createViewModel();
+        list($view->filter, $hundredsList) = $this->getSecondaryList('dewey');
+        $categoryList = array();
+        foreach ($hundredsList as $dewey) {
+            $categoryList[$dewey['value']] = $dewey['displayText']
+                . ' (' . $dewey['count'] . ')';
+        }
+        $view->categoryList = $categoryList;
+        if ($this->params()->fromQuery('findby')) {
+            $secondaryList = $this->quoteValues(
+                $this->getFacetList(
+                    'dewey-tens',
+                    'dewey-hundreds',
+                    'count',
+                    $this->params()->fromQuery('findby')
+                )
+            );
+            foreach ($secondaryList as $index=>$item) {
+                $secondaryList[$index]['value'] .=
+                    ' AND dewey-hundreds:'
+                    . $this->params()->fromQuery('findby');
+            }
+            $view->secondaryList = $secondaryList;
+            $view->secondaryParams = array(
+                'query_field' => 'dewey-tens',
+                'facet_field' => 'dewey-ones'
+            );
+        }
+        return $this->performSearch($view);
+    }
+
+    /**
+     * Generic action function that handles all the common parts of the below actions
+     *
+     * @param string $currentAction name of the current action. profound stuff.
+     * @param array  $categoryList  category options
+     * @param string $facetPrefix   if this is true and we're looking
+     * alphabetically, add a facet_prefix to the URL
+     *
+     * @return void
+     */
+    protected function performBrowse($currentAction, $categoryList, $facetPrefix)
+    {
+        $this->setCurrentAction($currentAction);
+        $view = $this->createViewModel();
+        $view->categoryList = $categoryList;
+
+        $findby = $this->params()->fromQuery('findby');
+        if ($findby) {
+            $view->secondaryParams = array(
+                'query_field' => $this->getCategory($findby),
+                'facet_field' => $this->getCategory($currentAction)
+            );
+            $view->facetPrefix = $facetPrefix && $findby == 'alphabetical';
+            list($view->filter, $view->secondaryList)
+                = $this->getSecondaryList($findby);
+        }
+
+        return $this->performSearch($view);
+    }
+
+    /**
+     * Browse Author
+     *
+     * @return void
+     */
+    public function authorAction()
+    {
+        $categoryList = array(
+            'alphabetical' => 'By Alphabetical',
+            'lcc'          => 'By Call Number',
+            'topic'        => 'By Topic',
+            'genre'        => 'By Genre',
+            'region'       => 'By Region',
+            'era'          => 'By Era'
+        );
+
+        return $this->performBrowse('Author', $categoryList, false);
+    }
+
+    /**
+     * Browse Topic
+     *
+     * @return void
+     */
+    public function topicAction()
+    {
+        $categoryList = array(
+            'alphabetical' => 'By Alphabetical',
+            'genre'        => 'By Genre',
+            'region'       => 'By Region',
+            'era'          => 'By Era'
+        );
+
+        return $this->performBrowse('Topic', $categoryList, true);
+    }
+
+    /**
+     * Browse Genre
+     *
+     * @return void
+     */
+    public function genreAction()
+    {
+        $categoryList = array(
+            'alphabetical' => 'By Alphabetical',
+            'topic'        => 'By Topic',
+            'region'       => 'By Region',
+            'era'          => 'By Era'
+        );
+
+        return $this->performBrowse('Genre', $categoryList, true);
+    }
+
+    /**
+     * Browse Region
+     *
+     * @return void
+     */
+    public function regionAction()
+    {
+        $categoryList = array(
+            'alphabetical' => 'By Alphabetical',
+            'topic'        => 'By Topic',
+            'genre'        => 'By Genre',
+            'era'          => 'By Era'
+        );
+
+        return $this->performBrowse('Region', $categoryList, true);
+    }
+
+    /**
+     * Browse Era
+     *
+     * @return void
+     */
+    public function eraAction()
+    {
+        $categoryList = array(
+            'alphabetical' => 'By Alphabetical',
+            'topic'        => 'By Topic',
+            'genre'        => 'By Genre',
+            'region'       => 'By Region'
+        );
+
+        return $this->performBrowse('Era', $categoryList, true);
+    }
+
+    /**
+     * Get array with two values: a filter name and a secondary list based on facets
+     *
+     * @param string $facet the facet we need the contents of
+     *
+     * @return array
+     */
+    protected function getSecondaryList($facet)
+    {
+        $category = $this->getCategory();
+        switch($facet) {
+        case 'alphabetical':
+            return $this->getAlphabetList();
+        case 'dewey':
+            return array(
+                'dewey-tens', $this->quoteValues(
+                    $this->getFacetList('dewey-hundreds', $category, 'index')
+                )
+            );
+        case 'lcc':
+            return array(
+                'callnumber-first', $this->quoteValues(
+                    $this->getFacetList('callnumber-first', $category, 'index')
+                )
+            );
+        case 'topic':
+            return array(
+                'topic_facet', $this->quoteValues(
+                    $this->getFacetList('topic_facet', $category)
+                )
+            );
+        case 'genre':
+            return array(
+                'genre_facet', $this->quoteValues(
+                    $this->getFacetList('genre_facet', $category)
+                )
+            );
+        case 'region':
+            return array(
+                'geographic_facet', $this->quoteValues(
+                    $this->getFacetList('geographic_facet', $category)
+                )
+            );
+        case 'era':
+            return array(
+                'era_facet', $this->quoteValues(
+                    $this->getFacetList('era_facet', $category)
+                )
+            );
+        }
+    }
+
+    /**
+     * Get a list of items from a facet.
+     *
+     * @param string $facet    which facet we're searching in
+     * @param string $category which subfacet the search applies to
+     * @param string $sort     how are we ranking these? || 'index'
+     * @param string $query    is there a specific query? No = wildcard
+     *
+     * @return array, indexed by value with text of displayText and count
+     */
+    protected function getFacetList($facet, $category = null,
+        $sort = 'count', $query = '[* TO *]'
+    ) {
+        $params = new SolrParams();
+        $params->addFacet($facet);
+        if ($category != null) {
+            $query = $category . ':' . $query;
+        } else {
+            $query = $facet . ':' . $query;
+        }
+        $params->setOverrideQuery($query);
+        $searchObject = new SolrResults($params);
+        // Get limit from config
+        $params->setFacetLimit($this->config->Browse->result_limit);
+        $params->setLimit(0);
+        // Facet prefix
+        if ($this->params()->fromQuery('facet_prefix')) {
+            $params->setFacetPrefix($this->params()->fromQuery('facet_prefix'));
+        }
+        $params->setFacetSort($sort);
+        $result = $searchObject->getFacetList();
+        //var_dump($result[$facet]['list']);
+        if (isset($result[$facet])) {
+            return $result[$facet]['list'];
+        } else {
+            return array();
+        }
+    }
+
+    /**
+     * Helper class that adds quotes around the values of an array
+     *
+     * @param array $array object array where each item has a value param
+     *
+     * @return array, indexed by value with text of displayText and count
+     */
+    protected function quoteValues($array)
+    {
+        foreach ($array as $i=>$result) {
+            $result['value'] = '"'.$result['value'].'"';
+            $array[$i] = $result;
+        }
+        return $array;
+    }
+
+    /**
+     * Get the facet search term for an action
+     *
+     * @param string $action action to be translated
+     *
+     * @return string
+     */
+    protected function getCategory($action = null)
+    {
+        if ($action == null) {
+            $action = $this->getCurrentAction();
+        }
+        switch(strToLower($action)) {
+        case 'alphabetical':
+            return $this->getCategory();
+        case 'dewey':
+            return 'dewey-hundreds';
+        case 'lcc':
+            return 'callnumber-first';
+        case 'author':
+            return 'authorStr';
+        case 'topic':
+            return 'topic_facet';
+        case 'genre':
+            return 'genre_facet';
+        case 'region':
+            return 'geographic_facet';
+        case 'era':
+            return 'era_facet';
+        }
+        return $action;
+    }
+
+    /**
+     * Get a list of letters to display in alphabetical mode.
+     *
+     * @return array
+     */
+    protected function getAlphabetList()
+    {
+        // ALPHABET TO ['value','displayText']
+        $alphabet = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
+        foreach ($alphabet as $index=>$letter) {
+            $alphabet[$index] = array(
+                'value'       => $letter,
+                'displayText' => $letter
+            );
+        }
+        if ($this->getCurrentAction() == 'Tag') {
+            return $alphabet;
+        }
+        // ADD ASTERISK FOR THOSE THAT NEED IT
+        foreach ($alphabet as $index=>$letter) {
+            $letter['value'] .= '*';
+            $alphabet[$index] = $letter;
+        }
+        if ($this->getCurrentAction() != 'Era') {
+            return $alphabet;
+        }
+        // PUT NUMBERS FIRST FOR YEARS
+        array_unshift($alphabet, $alphabet[count($alphabet)-10]);
+        unset($alphabet[count($alphabet)-10]);
+        for ($i=0;$i<9;$i++) {
+            array_unshift($alphabet, array_pop($alphabet));
+        }
+        return $alphabet;
+    }
+}
\ No newline at end of file
diff --git a/module/VuFind/src/VuFind/Db/Table/Tags.php b/module/VuFind/src/VuFind/Db/Table/Tags.php
index 9bed8868034..99b63ea140d 100644
--- a/module/VuFind/src/VuFind/Db/Table/Tags.php
+++ b/module/VuFind/src/VuFind/Db/Table/Tags.php
@@ -26,6 +26,7 @@
  * @link     http://vufind.org   Main Site
  */
 namespace VuFind\Db\Table;
+use Zend\Db\Sql\Expression;
 
 /**
  * Table Definition for tags
@@ -79,13 +80,11 @@ class Tags extends Gateway
      */
     public function matchText($text)
     {
-        /* TODO
-        $select = $this->select();
-        $select->where('lower(tag) LIKE lower(?)', $text . '%');
-        $select->order('tag');
-        $result = $this->fetchAll($select);
-        return $result->toArray();
-         */
+        $callback = function ($select) use ($text) {
+            $select->where->literal('lower(tag) like lower(?)', array($text . '%'));
+            $select->order('tag');
+        };
+        return $this->select($callback);
     }
 
     /**
@@ -118,18 +117,20 @@ class Tags extends Gateway
      */
     public function getCount($tag_id)
     {
-        /* TODO
-        $resourceTagTable = new VuFind_Model_Db_ResourceTags();
-        $select = $resourceTagTable
-            ->select()
-            ->from(
-                array('resource_tags'),
-                array('cnt' => 'COUNT(*)')
-            )
-            ->where('tag_id = ?', $tag_id);
-        $count = $resourceTagTable->fetchRow($select);
-        return $count['cnt'];
-         */
+        $resourceTagTable = new ResourceTags();
+        $callback = function ($select) use ($tag_id) {
+            $select->columns(
+                array(
+                    'cnt' => new Expression(
+                        'COUNT(DISTINCT(?))', array('resource_id'),
+                        array(Expression::TYPE_IDENTIFIER)
+                    )
+                )
+            );
+            $select->where->equalTo('tag_id', $tag_id);
+        };
+        $count = $resourceTagTable->select($callback)->current();
+        return isset($count['cnt']) ? $count['cnt'] : 0;
     }
 
     /**
@@ -190,52 +191,57 @@ class Tags extends Gateway
      *
      * @param string $sort        Sort/search parameter
      * @param int    $limit       Maximum number of tags
-     * @param string $extra_where Additional select parameters
      *
      * @return array Tag details.
      */
-    public function getTagList($sort, $limit = 100, $extra_where = '')
+    public function getTagList($sort, $limit = 100)
     {
-        /* TODO
-        $tagList = array();
-        $select = $this->select();
-        $select->from(
-            array('tags'),
-            array('tags.tag', 'COUNT(resource_tags.id) AS cnt')
-        );
-        $select->join(
-            array('resource_tags'),
-            'tags.id = resource_tags.tag_id',
-            array()
-        );
-        if (strlen($extra_where) > 0) {
-            $select->where($extra_where);
-        }
-        $select->group('tags.tag');
-        switch ($sort) {
-        case 'alphabetical':
-            $select->order(array('tags.tag', 'cnt DESC'));
-            break;
-        case 'popularity':
-            $select->order(array('cnt DESC', 'tags.tag'));
-            break;
-        case 'recent':
-            $select->order(
-                array('max(resource_tags.posted) DESC', 'cnt DESC', 'tags.tag')
+        $callback = function($select) use ($sort, $limit) {
+            $select->columns(
+                array(
+                    'tag',
+                    'cnt' => new Expression(
+                        'COUNT(DISTINCT(?))', array('resource_tags.resource_id'),
+                        array(Expression::TYPE_IDENTIFIER)
+                    ),
+                    'posted' => new Expression(
+                        'MAX(?)', array('resource_tags.posted'),
+                        array(Expression::TYPE_IDENTIFIER)
+                    )
+                )
             );
-            break;
-        }
-        // Limit the size of our results based on the ini browse limit setting
-        $select->limit($limit);
-        $tags = $this->fetchAll($select);
-        foreach ($tags as $t) {
+            $select->join(
+                'resource_tags', 'tags.id = resource_tags.tag_id', array()
+            );
+            if (strlen($extra_where) > 0) {
+                $select->where($extra_where);
+            }
+            $select->group('tags.tag');
+            switch ($sort) {
+            case 'alphabetical':
+                $select->order(array('tags.tag', 'cnt DESC'));
+                break;
+            case 'popularity':
+                $select->order(array('cnt DESC', 'tags.tag'));
+                break;
+            case 'recent':
+                $select->order(
+                    array('posted DESC', 'cnt DESC', 'tags.tag')
+                );
+                break;
+            }
+            // Limit the size of our results based on the ini browse limit setting
+            //$select->limit($limit);
+        };
+
+        $tagList = array();
+        foreach ($this->select($callback) as $t) {
             $tagList[] = array(
                 'tag' => $t->tag,
                 'cnt' => $t->cnt
             );
         }
         return $tagList;
-         */
     }
 
     /**
-- 
GitLab