3 declare(strict_types
=1);
5 namespace Shaarli\Front\Controller\Visitor
;
8 use Slim\Http\Response
;
13 * Slim controller used to render the tag cloud and tag list pages.
15 class TagCloudController
extends ShaarliVisitorController
17 protected const TYPE_CLOUD
= 'cloud';
18 protected const TYPE_LIST
= 'list';
21 * Display the tag cloud through the template engine.
22 * This controller a few filters:
23 * - Visibility stored in the session for logged in users
24 * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark
26 public function cloud(Request
$request, Response
$response): Response
28 return $this->processRequest(static::TYPE_CLOUD
, $request, $response);
32 * Display the tag list through the template engine.
33 * This controller a few filters:
34 * - Visibility stored in the session for logged in users
35 * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark
36 * - `sort` query parameters:
37 * + `usage` (default): most used tags first
38 * + `alpha`: alphabetical order
40 public function list(Request
$request, Response
$response): Response
42 return $this->processRequest(static::TYPE_LIST
, $request, $response);
46 * Process the request for both tag cloud and tag list endpoints.
48 protected function processRequest(string $type, Request
$request, Response
$response): Response
50 $tagsSeparator = $this->container
->conf
->get('general.tags_separator', ' ');
51 if ($this->container
->loginManager
->isLoggedIn() === true) {
52 $visibility = $this->container
->sessionManager
->getSessionParameter('visibility');
55 $sort = $request->getQueryParam('sort');
56 $searchTags = $request->getQueryParam('searchtags');
57 $filteringTags = $searchTags !== null ? explode($tagsSeparator, $searchTags) : [];
59 $tags = $this->container
->bookmarkService
->bookmarksCountPerTag($filteringTags, $visibility ?? null);
61 if (static::TYPE_CLOUD
=== $type || 'alpha' === $sort) {
62 // TODO: the sorting should be handled by bookmarkService instead of the controller
63 alphabetical_sort($tags, false, true);
66 if (static::TYPE_CLOUD
=== $type) {
67 $tags = $this->formatTagsForCloud($tags);
71 foreach ($tags as $tag => $value) {
72 $tagsUrl[escape($tag)] = urlencode((string) $tag);
75 $searchTags = tags_array2str($filteringTags, $tagsSeparator);
76 $searchTags = !empty($searchTags) ? trim($searchTags, $tagsSeparator) . $tagsSeparator : '';
77 $searchTagsUrl = urlencode($searchTags);
79 'search_tags' => escape($searchTags),
80 'search_tags_url' => $searchTagsUrl,
81 'tags' => escape($tags),
82 'tags_url' => $tagsUrl,
84 $this->executePageHooks('render_tag' . $type, $data, 'tag.' . $type);
85 $this->assignAllView($data);
87 $searchTags = !empty($searchTags) ? trim(str_replace($tagsSeparator, ' ', $searchTags)) . ' - ' : '';
90 $searchTags . t('Tag ' . $type) . ' - ' . $this->container
->conf
->get('general.title', 'Shaarli')
93 return $response->write($this->render('tag.' . $type));
97 * Format the tags array for the tag cloud template.
99 * @param array<string, int> $tags List of tags as key with count as value
101 * @return mixed[] List of tags as key, with count and expected font size in a subarray
103 protected function formatTagsForCloud(array $tags): array
105 // We sort tags alphabetically, then choose a font size according to count.
106 // First, find max value.
107 $maxCount = count($tags) > 0 ? max($tags) : 0;
108 $logMaxCount = $maxCount > 1 ? log($maxCount, 30) : 1;
110 foreach ($tags as $key => $value) {
111 // Tag font size scaling:
112 // default 15 and 30 logarithm bases affect scaling,
113 // 2.2 and 0.8 are arbitrary font sizes in em.
114 $size = log($value, 15) / $logMaxCount * 2.2 +
0.8;
117 'size' => number_format($size, 2, '.', ''),