73dea84d89
A cache belongs in Nextcloud's memory cache rather than in the app config. Use the distributed cache (e.g. Redis) with a one-week TTL when one is available; Nextcloud falls back to the local cache (APCu) when only that is configured. Without any memory cache the lazy app config value stays as the fallback. A leftover app config value is removed when the categories are stored in the memory cache, and saving the settings clears both.
198 lines
6.1 KiB
PHP
198 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace OCA\SideMenu\Service;
|
|
|
|
use OC\App\AppStore\Fetcher\CategoryFetcher;
|
|
use OCA\SideMenu\AppInfo\Application;
|
|
use OCP\AppFramework\Utility\ITimeFactory;
|
|
use OCP\IAppConfig;
|
|
use OCP\ICache;
|
|
use OCP\ICacheFactory;
|
|
use OCP\IConfig;
|
|
use OCP\IUserSession;
|
|
use OCP\L10N\IFactory;
|
|
|
|
/**
|
|
* class CategoryRepository.
|
|
*
|
|
* @author Simon Vieille <simon@deblan.fr>
|
|
*/
|
|
class CategoryRepository
|
|
{
|
|
public const CACHE_KEY = 'cache-categories';
|
|
|
|
/**
|
|
* The app store categories rarely change, refresh them once a week.
|
|
*/
|
|
protected const CACHE_TTL = 604800;
|
|
|
|
public function __construct(
|
|
protected CategoryFetcher $categoryFetcher,
|
|
protected ConfigProxy $config,
|
|
protected IConfig $iConfig,
|
|
protected IFactory $l10nFactory,
|
|
protected IUserSession $userSession,
|
|
protected IAppConfig $appConfig,
|
|
protected ITimeFactory $timeFactory,
|
|
protected LangRepository $langRepository,
|
|
protected ICacheFactory $cacheFactory,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Retrieves categories.
|
|
*/
|
|
public function getOrderedCategories(): array
|
|
{
|
|
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
|
|
$type = $this->config->getAppValue('categories-order-type', 'default');
|
|
$order = $this->config->getAppValueArray('categories-order', '[]');
|
|
$categoriesLabels = $this->getCategories();
|
|
$customCategories = $this->config->getAppValueArray('categories-custom', '[]');
|
|
|
|
foreach ($categoriesLabels as $k => $category) {
|
|
$categoriesLabels[$category['id']] = $category['translations'][$currentLanguage]['name'] ??
|
|
$category['translations']['en']['name'];
|
|
|
|
unset($categoriesLabels[$k]);
|
|
}
|
|
|
|
$categoriesLabels['external_links'] = $this->l10nFactory->get('external')->t('External sites');
|
|
$categoriesLabels['other'] = '';
|
|
|
|
$user = $this->userSession->getUser();
|
|
|
|
if ($user) {
|
|
$lang = $this->iConfig->getUserValue($user->getUid(), 'core', 'lang');
|
|
} else {
|
|
$lang = 'en';
|
|
}
|
|
|
|
foreach ($customCategories as $category) {
|
|
$categoriesLabels[$category['id']] = $category[$lang] ?? $category['en'];
|
|
}
|
|
|
|
asort($categoriesLabels);
|
|
|
|
if ('custom' === $type) {
|
|
$ordered = [];
|
|
|
|
foreach ($order as $id) {
|
|
if (isset($categoriesLabels[$id])) {
|
|
$ordered[$id] = $categoriesLabels[$id];
|
|
}
|
|
}
|
|
|
|
foreach ($categoriesLabels as $id => $value) {
|
|
if (!isset($ordered[$id])) {
|
|
$ordered[$id] = $value;
|
|
}
|
|
}
|
|
|
|
$categoriesLabels = $ordered;
|
|
}
|
|
|
|
return $categoriesLabels;
|
|
}
|
|
|
|
public function clearCache(): void
|
|
{
|
|
$this->getDistributedCache()?->remove(self::CACHE_KEY);
|
|
$this->appConfig->deleteKey(Application::APP_ID, self::CACHE_KEY);
|
|
}
|
|
|
|
/**
|
|
* The languages to keep in the cache: English as fallback, the
|
|
* default language and the languages of the users, as two-letter
|
|
* codes like getOrderedCategories() looks them up. A user who picks
|
|
* a new language sees English names until the cache is refreshed,
|
|
* which saving the settings does right away.
|
|
*/
|
|
protected function getCachedLangs(): array
|
|
{
|
|
$langs = $this->langRepository->getUsedLangs();
|
|
$langs[] = (string) $this->iConfig->getSystemValue('default_language', 'en');
|
|
$langs[] = (string) $this->iConfig->getSystemValue('force_language', 'en');
|
|
|
|
return array_values(array_unique(array_filter(array_map(
|
|
static fn (string $lang) => strtolower(substr($lang, 0, 2)),
|
|
array_merge(['en'], $langs)
|
|
))));
|
|
}
|
|
|
|
/**
|
|
* Returns the app store categories with only their names. They are
|
|
* cached in the distributed cache (e.g. Redis) when one is configured,
|
|
* otherwise in a lazy app config value, so they are not loaded on
|
|
* every request with the other settings.
|
|
*/
|
|
protected function getCategories(): array
|
|
{
|
|
$cache = $this->getDistributedCache();
|
|
|
|
if ($cache !== null) {
|
|
$categories = $cache->get(self::CACHE_KEY);
|
|
|
|
if (is_array($categories)) {
|
|
return $categories;
|
|
}
|
|
} else {
|
|
$stored = $this->appConfig->getValueArray(Application::APP_ID, self::CACHE_KEY, [], lazy: true);
|
|
|
|
if (isset($stored['expires'], $stored['categories']) && $stored['expires'] > $this->timeFactory->getTime()) {
|
|
return $stored['categories'];
|
|
}
|
|
}
|
|
|
|
$categories = $this->fetchCategories();
|
|
|
|
// Don't cache an empty list, e.g. when the app store is unreachable
|
|
if (empty($categories)) {
|
|
return $categories;
|
|
}
|
|
|
|
$this->clearCache();
|
|
|
|
if ($cache !== null) {
|
|
$cache->set(self::CACHE_KEY, $categories, self::CACHE_TTL);
|
|
} else {
|
|
$this->appConfig->setValueArray(Application::APP_ID, self::CACHE_KEY, [
|
|
'expires' => $this->timeFactory->getTime() + self::CACHE_TTL,
|
|
'categories' => $categories,
|
|
], lazy: true);
|
|
}
|
|
|
|
return $categories;
|
|
}
|
|
|
|
protected function fetchCategories(): array
|
|
{
|
|
$langs = $this->getCachedLangs();
|
|
$categories = [];
|
|
|
|
foreach ($this->categoryFetcher->get() as $category) {
|
|
$names = [];
|
|
|
|
foreach ($category['translations'] ?? [] as $lang => $translation) {
|
|
if (in_array($lang, $langs, true)) {
|
|
$names[$lang] = ['name' => $translation['name'] ?? ''];
|
|
}
|
|
}
|
|
|
|
$categories[] = [
|
|
'id' => $category['id'],
|
|
'translations' => $names,
|
|
];
|
|
}
|
|
|
|
return $categories;
|
|
}
|
|
|
|
protected function getDistributedCache(): ?ICache
|
|
{
|
|
return $this->cacheFactory->isAvailable()
|
|
? $this->cacheFactory->createDistributed(Application::APP_ID)
|
|
: null;
|
|
}
|
|
}
|