aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/front/controller/visitor/TagCloudController.php
blob: 46d62779dc1a5eed946e9b7e3e3c504fa0a0bec9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<?php

declare(strict_types=1);

namespace Shaarli\Front\Controller\Visitor;

use Slim\Http\Request;
use Slim\Http\Response;

/**
 * Class TagCloud
 *
 * Slim controller used to render the tag cloud and tag list pages.
 */
class TagCloudController extends ShaarliVisitorController
{
    protected const TYPE_CLOUD = 'cloud';
    protected const TYPE_LIST = 'list';

    /**
     * Display the tag cloud 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
     */
    public function cloud(Request $request, Response $response): Response
    {
        return $this->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<string, int> $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;
    }
}