80f6dcf73a
If cache-categories was stored with a type other than mixed or array, for example set by hand with occ, the typed read throws. Treat that as a missing cache so it is refreshed instead of breaking the menu and the settings page.
204 lines
6.3 KiB
PHP
204 lines
6.3 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\Exceptions\AppConfigTypeConflictException;
|
|
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 {
|
|
try {
|
|
$stored = $this->appConfig->getValueArray(Application::APP_ID, self::CACHE_KEY, [], lazy: true);
|
|
} catch (AppConfigTypeConflictException) {
|
|
// Stored with another type, e.g. set by hand; refreshed below
|
|
$stored = [];
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|