diff options
Diffstat (limited to 'application/front/controller/visitor/TagCloudController.php')
-rw-r--r-- | application/front/controller/visitor/TagCloudController.php | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/application/front/controller/visitor/TagCloudController.php b/application/front/controller/visitor/TagCloudController.php new file mode 100644 index 00000000..15b6d7b7 --- /dev/null +++ b/application/front/controller/visitor/TagCloudController.php | |||
@@ -0,0 +1,131 @@ | |||
1 | <?php | ||
2 | |||
3 | declare(strict_types=1); | ||
4 | |||
5 | namespace Shaarli\Front\Controller\Visitor; | ||
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 | class TagCloudController extends ShaarliVisitorController | ||
16 | { | ||
17 | protected const TYPE_CLOUD = 'cloud'; | ||
18 | protected const TYPE_LIST = 'list'; | ||
19 | |||
20 | /** | ||
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 | ||
25 | */ | ||
26 | public function cloud(Request $request, Response $response): Response | ||
27 | { | ||
28 | return $this->processRequest(static::TYPE_CLOUD, $request, $response); | ||
29 | } | ||
30 | |||
31 | /** | ||
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 | ||
39 | */ | ||
40 | public function list(Request $request, Response $response): Response | ||
41 | { | ||
42 | return $this->processRequest(static::TYPE_LIST, $request, $response); | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Process the request for both tag cloud and tag list endpoints. | ||
47 | */ | ||
48 | protected function processRequest(string $type, Request $request, Response $response): Response | ||
49 | { | ||
50 | if ($this->container->loginManager->isLoggedIn() === true) { | ||
51 | $visibility = $this->container->sessionManager->getSessionParameter('visibility'); | ||
52 | } | ||
53 | |||
54 | $sort = $request->getQueryParam('sort'); | ||
55 | $searchTags = $request->getQueryParam('searchtags'); | ||
56 | $filteringTags = $searchTags !== null ? explode(' ', $searchTags) : []; | ||
57 | |||
58 | $tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null); | ||
59 | |||
60 | if (static::TYPE_CLOUD === $type || 'alpha' === $sort) { | ||
61 | // TODO: the sorting should be handled by bookmarkService instead of the controller | ||
62 | alphabetical_sort($tags, false, true); | ||
63 | } | ||
64 | |||
65 | if (static::TYPE_CLOUD === $type) { | ||
66 | $tags = $this->formatTagsForCloud($tags); | ||
67 | } | ||
68 | |||
69 | $searchTags = implode(' ', escape($filteringTags)); | ||
70 | $data = [ | ||
71 | 'search_tags' => $searchTags, | ||
72 | 'tags' => $tags, | ||
73 | ]; | ||
74 | $data = $this->executeHooks('tag' . $type, $data); | ||
75 | foreach ($data as $key => $value) { | ||
76 | $this->assignView($key, $value); | ||
77 | } | ||
78 | |||
79 | $searchTags = !empty($searchTags) ? $searchTags .' - ' : ''; | ||
80 | $this->assignView( | ||
81 | 'pagetitle', | ||
82 | $searchTags . t('Tag '. $type) .' - '. $this->container->conf->get('general.title', 'Shaarli') | ||
83 | ); | ||
84 | |||
85 | return $response->write($this->render('tag.'. $type)); | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * Format the tags array for the tag cloud template. | ||
90 | * | ||
91 | * @param array<string, int> $tags List of tags as key with count as value | ||
92 | * | ||
93 | * @return mixed[] List of tags as key, with count and expected font size in a subarray | ||
94 | */ | ||
95 | protected function formatTagsForCloud(array $tags): array | ||
96 | { | ||
97 | // We sort tags alphabetically, then choose a font size according to count. | ||
98 | // First, find max value. | ||
99 | $maxCount = count($tags) > 0 ? max($tags) : 0; | ||
100 | $logMaxCount = $maxCount > 1 ? log($maxCount, 30) : 1; | ||
101 | $tagList = []; | ||
102 | foreach ($tags as $key => $value) { | ||
103 | // Tag font size scaling: | ||
104 | // default 15 and 30 logarithm bases affect scaling, | ||
105 | // 2.2 and 0.8 are arbitrary font sizes in em. | ||
106 | $size = log($value, 15) / $logMaxCount * 2.2 + 0.8; | ||
107 | $tagList[$key] = [ | ||
108 | 'count' => $value, | ||
109 | 'size' => number_format($size, 2, '.', ''), | ||
110 | ]; | ||
111 | } | ||
112 | |||
113 | return $tagList; | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * @param mixed[] $data Template data | ||
118 | * | ||
119 | * @return mixed[] Template data after active plugins hook execution. | ||
120 | */ | ||
121 | protected function executeHooks(string $template, array $data): array | ||
122 | { | ||
123 | $this->container->pluginManager->executeHooks( | ||
124 | 'render_'. $template, | ||
125 | $data, | ||
126 | ['loggedin' => $this->container->loginManager->isLoggedIn()] | ||
127 | ); | ||
128 | |||
129 | return $data; | ||
130 | } | ||
131 | } | ||