*/ 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; } }