processRequest(static::TYPE_CLOUD, $request, $response); } /** * Display the tag list through the template engine. * This controller a few filters: * - Visibility stored in the session for logged in users * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark * - `sort` query parameters: * + `usage` (default): most used tags first * + `alpha`: alphabetical order */ public function list(Request $request, Response $response): Response { return $this->processRequest(static::TYPE_LIST, $request, $response); } /** * Process the request for both tag cloud and tag list endpoints. */ protected function processRequest(string $type, Request $request, Response $response): Response { $tagsSeparator = $this->container->conf->get('general.tags_separator', ' '); if ($this->container->loginManager->isLoggedIn() === true) { $visibility = $this->container->sessionManager->getSessionParameter('visibility'); } $sort = $request->getQueryParam('sort'); $searchTags = $request->getQueryParam('searchtags'); $filteringTags = $searchTags !== null ? explode($tagsSeparator, $searchTags) : []; $tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null); if (static::TYPE_CLOUD === $type || 'alpha' === $sort) { // TODO: the sorting should be handled by bookmarkService instead of the controller alphabetical_sort($tags, false, true); } if (static::TYPE_CLOUD === $type) { $tags = $this->formatTagsForCloud($tags); } $tagsUrl = []; foreach ($tags as $tag => $value) { $tagsUrl[escape($tag)] = urlencode((string) $tag); } $searchTags = tags_array2str($filteringTags, $tagsSeparator); $searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : ''; $searchTagsUrl = urlencode($searchTags); $data = [ 'search_tags' => escape($searchTags), 'search_tags_url' => $searchTagsUrl, 'tags' => escape($tags), 'tags_url' => $tagsUrl, ]; $this->executePageHooks('render_tag' . $type, $data, 'tag.' . $type); $this->assignAllView($data); $searchTags = !empty($searchTags) ? trim(str_replace($tagsSeparator, ' ', $searchTags)) . ' - ' : ''; $this->assignView( 'pagetitle', $searchTags . t('Tag ' . $type) . ' - ' . $this->container->conf->get('general.title', 'Shaarli') ); return $response->write($this->render('tag.' . $type)); } /** * Format the tags array for the tag cloud template. * * @param array $tags List of tags as key with count as value * * @return mixed[] List of tags as key, with count and expected font size in a subarray */ protected function formatTagsForCloud(array $tags): array { // We sort tags alphabetically, then choose a font size according to count. // First, find max value. $maxCount = count($tags) > 0 ? max($tags) : 0; $logMaxCount = $maxCount > 1 ? log($maxCount, 30) : 1; $tagList = []; foreach ($tags as $key => $value) { // Tag font size scaling: // default 15 and 30 logarithm bases affect scaling, // 2.2 and 0.8 are arbitrary font sizes in em. $size = log($value, 15) / $logMaxCount * 2.2 + 0.8; $tagList[$key] = [ 'count' => $value, 'size' => number_format($size, 2, '.', ''), ]; } return $tagList; } }