public function tags()
    {
        $this->allowMethods(array('GET'));
        if (!$this->permissions->canAccess($this->objectLibraryID)) {
            $this->e403();
        }
        $tags = array();
        $totalResults = 0;
        $name = $this->objectName;
        $fixedValues = array();
        // Set of tags matching name
        if ($name && $this->subset != 'tags') {
            $tagIDs = Zotero_Tags::getIDs($this->objectLibraryID, $name);
            if (!$tagIDs) {
                $this->e404();
            }
            $title = "Tags matching ‘" . $name . "’";
        } else {
            if ($this->scopeObject) {
                // If id, redirect to key URL
                if ($this->scopeObjectID) {
                    if (!in_array($this->scopeObject, array("collections", "items"))) {
                        $this->e400();
                    }
                    $className = 'Zotero_' . ucwords($this->scopeObject);
                    $obj = call_user_func(array($className, 'get'), $this->objectLibraryID, $this->scopeObjectID);
                    if (!$obj) {
                        $this->e404("Scope " . substr($this->scopeObject, 0, -1) . " not found");
                    }
                    $base = call_user_func(array('Zotero_API', 'get' . substr(ucwords($this->scopeObject), 0, -1) . 'URI'), $obj);
                    $qs = !empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '';
                    header("Location: " . $base . "/tags" . $qs);
                    exit;
                }
                switch ($this->scopeObject) {
                    case 'collections':
                        $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey);
                        if (!$collection) {
                            $this->e404();
                        }
                        $title = "Tags in Collection ‘" . $collection->name . "’";
                        $counts = $collection->getTagItemCounts();
                        $tagIDs = array();
                        if ($counts) {
                            foreach ($counts as $tagID => $count) {
                                $tagIDs[] = $tagID;
                                $fixedValues[$tagID] = array('numItems' => $count);
                            }
                        }
                        break;
                    case 'items':
                        $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey);
                        if (!$item) {
                            $this->e404();
                        }
                        $title = "Tags of '" . $item->getDisplayTitle() . "'";
                        $tagIDs = $item->getTags(true);
                        break;
                    default:
                        throw new Exception("Invalid tags scope object '{$this->scopeObject}'");
                }
            } else {
                $title = "Tags";
                $results = Zotero_Tags::getAllAdvanced($this->objectLibraryID, $this->queryParams);
                $tags = $results['objects'];
                $totalResults = $results['total'];
            }
        }
        if (!empty($tagIDs)) {
            foreach ($tagIDs as $tagID) {
                $tags[] = Zotero_Tags::get($this->objectLibraryID, $tagID);
            }
            // Fake sorting and limiting
            $totalResults = sizeOf($tags);
            $key = $this->queryParams['order'];
            // 'title' order means 'name' for tags
            if ($key == 'title') {
                $key = 'name';
            }
            $dir = $this->queryParams['sort'];
            $cmp = create_function('$a, $b', '$dir = "' . $dir . '" == "asc" ? 1 : -1;
				if ($a->' . $key . ' == $b->' . $key . ') {
					return 0;
				}
				else {
					return ($a->' . $key . ' > $b->' . $key . ') ? $dir : ($dir * -1);}');
            usort($tags, $cmp);
            $tags = array_slice($tags, $this->queryParams['start'], $this->queryParams['limit']);
        }
        $this->responseXML = Zotero_Atom::createAtomFeed($this->getFeedNamePrefix($this->objectLibraryID) . $title, $this->uri, $tags, $totalResults, $this->queryParams, $this->apiVersion, $this->permissions, $fixedValues);
        $this->end();
    }