2020-01-26 14:35:25 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-05-22 13:20:31 +02:00
|
|
|
namespace Shaarli\Front\Controller\Visitor;
|
2020-01-26 14:35:25 +01:00
|
|
|
|
|
|
|
use Slim\Http\Request;
|
|
|
|
use Slim\Http\Response;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class TagCloud
|
|
|
|
*
|
2020-05-16 14:56:22 +02:00
|
|
|
* Slim controller used to render the tag cloud and tag list pages.
|
2020-01-26 14:35:25 +01:00
|
|
|
*/
|
2020-05-22 13:20:31 +02:00
|
|
|
class TagCloudController extends ShaarliVisitorController
|
2020-01-26 14:35:25 +01:00
|
|
|
{
|
2020-05-16 14:56:22 +02:00
|
|
|
protected const TYPE_CLOUD = 'cloud';
|
|
|
|
protected const TYPE_LIST = 'list';
|
|
|
|
|
2020-05-16 13:33:39 +02:00
|
|
|
/**
|
|
|
|
* 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
|
2020-05-16 14:56:22 +02:00
|
|
|
{
|
|
|
|
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
|
2020-01-26 14:35:25 +01:00
|
|
|
{
|
|
|
|
if ($this->container->loginManager->isLoggedIn() === true) {
|
|
|
|
$visibility = $this->container->sessionManager->getSessionParameter('visibility');
|
|
|
|
}
|
|
|
|
|
2020-05-16 14:56:22 +02:00
|
|
|
$sort = $request->getQueryParam('sort');
|
2020-01-26 14:35:25 +01:00
|
|
|
$searchTags = $request->getQueryParam('searchtags');
|
|
|
|
$filteringTags = $searchTags !== null ? explode(' ', $searchTags) : [];
|
|
|
|
|
|
|
|
$tags = $this->container->bookmarkService->bookmarksCountPerTag($filteringTags, $visibility ?? null);
|
|
|
|
|
2020-05-16 14:56:22 +02:00
|
|
|
if (static::TYPE_CLOUD === $type || 'alpha' === $sort) {
|
|
|
|
// TODO: the sorting should be handled by bookmarkService instead of the controller
|
|
|
|
alphabetical_sort($tags, false, true);
|
|
|
|
}
|
2020-01-26 14:35:25 +01:00
|
|
|
|
2020-05-16 14:56:22 +02:00
|
|
|
if (static::TYPE_CLOUD === $type) {
|
|
|
|
$tags = $this->formatTagsForCloud($tags);
|
|
|
|
}
|
2020-01-26 14:35:25 +01:00
|
|
|
|
|
|
|
$searchTags = implode(' ', escape($filteringTags));
|
|
|
|
$data = [
|
|
|
|
'search_tags' => $searchTags,
|
2020-05-16 14:56:22 +02:00
|
|
|
'tags' => $tags,
|
2020-01-26 14:35:25 +01:00
|
|
|
];
|
2020-05-16 14:56:22 +02:00
|
|
|
$data = $this->executeHooks('tag' . $type, $data);
|
2020-01-26 14:35:25 +01:00
|
|
|
foreach ($data as $key => $value) {
|
|
|
|
$this->assignView($key, $value);
|
|
|
|
}
|
|
|
|
|
|
|
|
$searchTags = !empty($searchTags) ? $searchTags .' - ' : '';
|
|
|
|
$this->assignView(
|
|
|
|
'pagetitle',
|
2020-05-16 14:56:22 +02:00
|
|
|
$searchTags . t('Tag '. $type) .' - '. $this->container->conf->get('general.title', 'Shaarli')
|
2020-01-26 14:35:25 +01:00
|
|
|
);
|
|
|
|
|
2020-05-16 14:56:22 +02:00
|
|
|
return $response->write($this->render('tag.'. $type));
|
2020-01-26 14:35:25 +01:00
|
|
|
}
|
|
|
|
|
2020-05-16 14:56:22 +02:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2020-05-16 13:33:39 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-01-26 14:35:25 +01:00
|
|
|
/**
|
|
|
|
* @param mixed[] $data Template data
|
|
|
|
*
|
2020-05-16 14:56:22 +02:00
|
|
|
* @return mixed[] Template data after active plugins hook execution.
|
2020-01-26 14:35:25 +01:00
|
|
|
*/
|
2020-05-16 14:56:22 +02:00
|
|
|
protected function executeHooks(string $template, array $data): array
|
2020-01-26 14:35:25 +01:00
|
|
|
{
|
|
|
|
$this->container->pluginManager->executeHooks(
|
2020-05-16 14:56:22 +02:00
|
|
|
'render_'. $template,
|
2020-01-26 14:35:25 +01:00
|
|
|
$data,
|
|
|
|
['loggedin' => $this->container->loginManager->isLoggedIn()]
|
|
|
|
);
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
}
|