Files
intotheeast-com-content/plugins/api/classes/Api/Services/DisabledPluginLangIndex.php
T

131 lines
3.9 KiB
PHP

<?php
declare(strict_types=1);
namespace Grav\Plugin\Api\Services;
use Grav\Common\Grav;
use Grav\Common\Utils;
use Grav\Common\Yaml;
/**
* Index of translation keys contributed exclusively by disabled plugins, keyed
* by language code.
*
* Grav core's `Languages::flattenByLang()` reads every plugin's lang yaml
* regardless of whether the plugin is enabled — fine for legacy admin, broken
* for admin2 where a disabled plugin (most notably admin classic, mid-migration
* on Grav 2 sites) would otherwise leak its strings into both the dictionary
* served to the SPA and the server-side blueprint label resolver.
*
* This service walks `user/plugins/<name>/languages/<lang>.yaml` and
* `user/plugins/<name>/languages.yaml` (single-file multi-lang format), buckets
* keys by enabled-vs-disabled provenance, and returns the keys present only in
* the disabled bucket. Keys also contributed by an enabled plugin are kept —
* the enabled plugin owns them, even if a disabled plugin happens to ship the
* same key.
*
* The result is cached per-language for the request lifecycle since the
* underlying YAML files don't change mid-request.
*/
final class DisabledPluginLangIndex
{
/** @var array<string, array<int, string>> */
private array $cache = [];
public function __construct(private readonly Grav $grav)
{
}
/**
* @return array<int, string> flat translation keys (e.g. `PLUGIN_ADMIN.ADD_FOLDER`)
*/
public function disabledOnlyKeys(string $lang): array
{
if (isset($this->cache[$lang])) {
return $this->cache[$lang];
}
$plugins = $this->grav['plugins'];
$config = $this->grav['config'];
$locator = $this->grav['locator'];
$enabled = [];
$disabled = [];
foreach ($plugins as $plugin) {
$name = $plugin->name;
$resolved = $locator->findResource("plugin://{$name}");
if (!$resolved || !is_dir($resolved)) {
continue;
}
$keys = $this->collectPluginLangKeys($resolved, $lang);
if (empty($keys)) {
continue;
}
$isEnabled = (bool) $config->get("plugins.{$name}.enabled", false);
foreach ($keys as $k) {
if ($isEnabled) {
$enabled[$k] = true;
} else {
$disabled[$k] = true;
}
}
}
$result = array_keys(array_diff_key($disabled, $enabled));
$this->cache[$lang] = $result;
return $result;
}
/**
* True if `$key` is contributed only by disabled plugins for `$lang`.
*/
public function isDisabledOnly(string $key, string $lang): bool
{
return in_array($key, $this->disabledOnlyKeys($lang), true);
}
/**
* @return array<int, string>
*/
private function collectPluginLangKeys(string $pluginDir, string $lang): array
{
$keys = [];
$perLangFile = "{$pluginDir}/languages/{$lang}.yaml";
if (is_file($perLangFile)) {
$data = $this->safeParseYaml($perLangFile);
if (is_array($data)) {
foreach (array_keys(Utils::arrayFlattenDotNotation($data)) as $k) {
$keys[$k] = true;
}
}
}
$singleFile = "{$pluginDir}/languages.yaml";
if (is_file($singleFile)) {
$data = $this->safeParseYaml($singleFile);
$langData = is_array($data) ? ($data[$lang] ?? null) : null;
if (is_array($langData)) {
foreach (array_keys(Utils::arrayFlattenDotNotation($langData)) as $k) {
$keys[$k] = true;
}
}
}
return array_keys($keys);
}
private function safeParseYaml(string $file): mixed
{
try {
return Yaml::parse(file_get_contents($file));
} catch (\Throwable) {
return null;
}
}
}