]>
Commit | Line | Data |
---|---|---|
1 | <?php | |
2 | ||
3 | declare(strict_types=1); | |
4 | ||
5 | namespace Shaarli\Front\Controller; | |
6 | ||
7 | use Slim\Http\Request; | |
8 | use Slim\Http\Response; | |
9 | ||
10 | /** | |
11 | * Class TagCloud | |
12 | * | |
13 | * Slim controller used to render the tag cloud and tag list pages. | |
14 | * | |
15 | * @package Front\Controller | |
16 | */ | |
17 | class TagCloudController extends ShaarliController | |
18 | { | |
19 | protected const TYPE_CLOUD = 'cloud'; | |
20 | protected const TYPE_LIST = 'list'; | |
21 | ||
22 | /** | |
23 | * Display the tag cloud through the template engine. | |
24 | * This controller a few filters: | |
25 | * - Visibility stored in the session for logged in users | |
26 | * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark | |
27 | */ | |
28 | public function cloud(Request $request, Response $response): Response | |
29 | { | |
30 | return $this->processRequest(static::TYPE_CLOUD, $request, $response); | |
31 | } | |
32 | ||
33 | /** | |
34 | * Display the tag list through the template engine. | |
35 | * This controller a few filters: | |
36 | * - Visibility stored in the session for logged in users | |
37 | * - `searchtags` query parameter: will return tags associated with filter in at least one bookmark | |
38 | * - `sort` query parameters: | |
39 | * + `usage` (default): most used tags first | |
40 | * + `alpha`: alphabetical order | |
41 | */ | |
42 | public function list(Request $request, Response $response): Response | |
43 | { | |
44 | return $this->processRequest(static::TYPE_LIST, $request, $response); | |
45 | } | |
46 | ||
47 | /** | |
48 | * Process the request for both tag cloud and tag list endpoints. | |
49 | */ | |
50 | protected function processRequest(string $type, Request $request, Response $response): Response | |
51 | { | |
52 | if ($this->container->loginManager->isLoggedIn() === true) { | |
53 | $visibility = $this->container->sessionManager->getSessionParameter('visibility'); | |
54 | } | |
55 | ||
56 | $sort = $request->getQueryParam('sort'); | |
57 | $searchTags = $request->getQueryParam('searchtags'); | |
58 | $filteringTags = $searchTags !== null ? explode(' ', $searchTags) : []; | |
59 | ||
60 | $tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null); | |
61 | ||
62 | if (static::TYPE_CLOUD === $type || 'alpha' === $sort) { | |
63 | // TODO: the sorting should be handled by bookmarkService instead of the controller | |
64 | alphabetical_sort($tags, false, true); | |
65 | } | |
66 | ||
67 | if (static::TYPE_CLOUD === $type) { | |
68 | $tags = $this->formatTagsForCloud($tags); | |
69 | } | |
70 | ||
71 | $searchTags = implode(' ', escape($filteringTags)); | |
72 | $data = [ | |
73 | 'search_tags' => $searchTags, | |
74 | 'tags' => $tags, | |
75 | ]; | |
76 | $data = $this->executeHooks('tag' . $type, $data); | |
77 | foreach ($data as $key => $value) { | |
78 | $this->assignView($key, $value); | |
79 | } | |
80 | ||
81 | $searchTags = !empty($searchTags) ? $searchTags .' - ' : ''; | |
82 | $this->assignView( | |
83 | 'pagetitle', | |
84 | $searchTags . t('Tag '. $type) .' - '. $this->container->conf->get('general.title', 'Shaarli') | |
85 | ); | |
86 | ||
87 | return $response->write($this->render('tag.'. $type)); | |
88 | } | |
89 | ||
90 | /** | |
91 | * Format the tags array for the tag cloud template. | |
92 | * | |
93 | * @param array<string, int> $tags List of tags as key with count as value | |
94 | * | |
95 | * @return mixed[] List of tags as key, with count and expected font size in a subarray | |
96 | */ | |
97 | protected function formatTagsForCloud(array $tags): array | |
98 | { | |
99 | // We sort tags alphabetically, then choose a font size according to count. | |
100 | // First, find max value. | |
101 | $maxCount = count($tags) > 0 ? max($tags) : 0; | |
102 | $logMaxCount = $maxCount > 1 ? log($maxCount, 30) : 1; | |
103 | $tagList = []; | |
104 | foreach ($tags as $key => $value) { | |
105 | // Tag font size scaling: | |
106 | // default 15 and 30 logarithm bases affect scaling, | |
107 | // 2.2 and 0.8 are arbitrary font sizes in em. | |
108 | $size = log($value, 15) / $logMaxCount * 2.2 + 0.8; | |
109 | $tagList[$key] = [ | |
110 | 'count' => $value, | |
111 | 'size' => number_format($size, 2, '.', ''), | |
112 | ]; | |
113 | } | |
114 | ||
115 | return $tagList; | |
116 | } | |
117 | ||
118 | /** | |
119 | * @param mixed[] $data Template data | |
120 | * | |
121 | * @return mixed[] Template data after active plugins hook execution. | |
122 | */ | |
123 | protected function executeHooks(string $template, array $data): array | |
124 | { | |
125 | $this->container->pluginManager->executeHooks( | |
126 | 'render_'. $template, | |
127 | $data, | |
128 | ['loggedin' => $this->container->loginManager->isLoggedIn()] | |
129 | ); | |
130 | ||
131 | return $data; | |
132 | } | |
133 | } |