xref: /plugin/dev/LangProcessor.php (revision fe060d0d4c4c42403a614910f1d8cdcd8d360b96)
15586e97bSAndreas Gohr<?php
25586e97bSAndreas Gohr
35586e97bSAndreas Gohrnamespace dokuwiki\plugin\dev;
45586e97bSAndreas Gohr
55586e97bSAndreas Gohruse splitbrain\phpcli\CLI;
65586e97bSAndreas Gohr
75586e97bSAndreas Gohrclass LangProcessor
85586e97bSAndreas Gohr{
95586e97bSAndreas Gohr    /** @var CLI */
105586e97bSAndreas Gohr    protected $logger;
115586e97bSAndreas Gohr
125586e97bSAndreas Gohr    /** @var array The language keys used in the code */
135b2e8f12SAndreas Gohr    protected $codeKeys;
145b2e8f12SAndreas Gohr
155b2e8f12SAndreas Gohr    /** @var array The language keys matching the configuration settings */
165b2e8f12SAndreas Gohr    protected $settingKeys;
175586e97bSAndreas Gohr
185586e97bSAndreas Gohr    public function __construct(CLI $logger)
195586e97bSAndreas Gohr    {
205586e97bSAndreas Gohr        $this->logger = $logger;
215b2e8f12SAndreas Gohr        $this->codeKeys = $this->findLanguageKeysInCode();
225b2e8f12SAndreas Gohr        $this->settingKeys = $this->findLanguageKeysInSettings();
235586e97bSAndreas Gohr    }
245586e97bSAndreas Gohr
255586e97bSAndreas Gohr    /**
265586e97bSAndreas Gohr     * Remove the obsolete strings from the given lang.php
275586e97bSAndreas Gohr     *
285586e97bSAndreas Gohr     * @param string $file
295586e97bSAndreas Gohr     * @return void
305586e97bSAndreas Gohr     */
315586e97bSAndreas Gohr    public function processLangFile($file)
325586e97bSAndreas Gohr    {
335586e97bSAndreas Gohr        $lang = [];
345586e97bSAndreas Gohr        include $file;
355586e97bSAndreas Gohr
365b2e8f12SAndreas Gohr        $drop = array_diff_key($lang, $this->codeKeys);
375586e97bSAndreas Gohr        if (isset($found['js']) && isset($lang['js'])) {
385586e97bSAndreas Gohr            $drop['js'] = array_diff_key($lang['js'], $found['js']);
39*fe060d0dSAndreas Gohr            if ($drop['js'] === []) unset($drop['js']);
405586e97bSAndreas Gohr        }
415586e97bSAndreas Gohr
425586e97bSAndreas Gohr        foreach ($drop as $key => $value) {
435586e97bSAndreas Gohr            if (is_array($value)) {
44*fe060d0dSAndreas Gohr                foreach (array_keys($value) as $subkey) {
455586e97bSAndreas Gohr                    $this->removeLangKey($file, $subkey, $key);
465586e97bSAndreas Gohr                }
475586e97bSAndreas Gohr            } else {
485586e97bSAndreas Gohr                $this->removeLangKey($file, $key);
495586e97bSAndreas Gohr            }
505586e97bSAndreas Gohr        }
515586e97bSAndreas Gohr    }
525586e97bSAndreas Gohr
535586e97bSAndreas Gohr    /**
545b2e8f12SAndreas Gohr     * Remove obsolete string from the given settings.php
555b2e8f12SAndreas Gohr     *
565b2e8f12SAndreas Gohr     * @param string $file
575b2e8f12SAndreas Gohr     * @return void
585b2e8f12SAndreas Gohr     */
595b2e8f12SAndreas Gohr    public function processSettingsFile($file)
605b2e8f12SAndreas Gohr    {
615b2e8f12SAndreas Gohr        $lang = [];
625b2e8f12SAndreas Gohr        include $file;
635b2e8f12SAndreas Gohr
645b2e8f12SAndreas Gohr        $drop = array_diff_key($lang, $this->settingKeys);
65*fe060d0dSAndreas Gohr        foreach (array_keys($drop) as $key) {
665b2e8f12SAndreas Gohr            $this->removeLangKey($file, $key);
675b2e8f12SAndreas Gohr        }
685b2e8f12SAndreas Gohr    }
695b2e8f12SAndreas Gohr
705b2e8f12SAndreas Gohr    /**
715586e97bSAndreas Gohr     * Remove the given key from the given language file
725586e97bSAndreas Gohr     *
735586e97bSAndreas Gohr     * @param string $file
745586e97bSAndreas Gohr     * @param string $key
755586e97bSAndreas Gohr     * @param string $sub
765586e97bSAndreas Gohr     * @return void
775586e97bSAndreas Gohr     */
785586e97bSAndreas Gohr    protected function removeLangKey($file, $key, $sub = '')
795586e97bSAndreas Gohr    {
805586e97bSAndreas Gohr        $q = '[\'"]';
815586e97bSAndreas Gohr
825586e97bSAndreas Gohr        if ($sub) {
835586e97bSAndreas Gohr            $re = '/\$lang\[' . $q . $sub . $q . '\]\[' . $q . $key . $q . '\]/';
845586e97bSAndreas Gohr        } else {
855586e97bSAndreas Gohr            $re = '/\$lang\[' . $q . $key . $q . '\]/';
865586e97bSAndreas Gohr        }
875586e97bSAndreas Gohr
885586e97bSAndreas Gohr        if (io_deleteFromFile($file, $re, true)) {
895586e97bSAndreas Gohr            $this->logger->success('{key} removed from {file}', [
905586e97bSAndreas Gohr                'key' => $sub ? "$sub.$key" : $key,
915586e97bSAndreas Gohr                'file' => $file,
925586e97bSAndreas Gohr            ]);
935586e97bSAndreas Gohr        }
945586e97bSAndreas Gohr    }
955586e97bSAndreas Gohr
965586e97bSAndreas Gohr    /**
975b2e8f12SAndreas Gohr     * @return array
985b2e8f12SAndreas Gohr     */
995b2e8f12SAndreas Gohr    public function findLanguageKeysInSettings()
1005b2e8f12SAndreas Gohr    {
1015b2e8f12SAndreas Gohr        if (file_exists('./conf/metadata.php')) {
1025b2e8f12SAndreas Gohr            return $this->metaExtract('./conf/metadata.php');
1035b2e8f12SAndreas Gohr        }
1045b2e8f12SAndreas Gohr        return [];
1055b2e8f12SAndreas Gohr    }
1065b2e8f12SAndreas Gohr
1075b2e8f12SAndreas Gohr    /**
1085586e97bSAndreas Gohr     * Find used language keys in the actual code
1095b2e8f12SAndreas Gohr     * @return array
1105586e97bSAndreas Gohr     */
1115586e97bSAndreas Gohr    public function findLanguageKeysInCode()
1125586e97bSAndreas Gohr    {
1135586e97bSAndreas Gohr        // get all non-hidden php and js files
1145586e97bSAndreas Gohr        $ite = new \RecursiveIteratorIterator(
1155586e97bSAndreas Gohr            new \RecursiveCallbackFilterIterator(
1165586e97bSAndreas Gohr                new \RecursiveDirectoryIterator('.', \RecursiveDirectoryIterator::SKIP_DOTS),
1175586e97bSAndreas Gohr                function ($file) {
1185586e97bSAndreas Gohr                    /** @var \SplFileInfo $file */
119*fe060d0dSAndreas Gohr                    if ($file->isFile() && $file->getExtension() != 'php' && $file->getExtension() != 'js') {
120*fe060d0dSAndreas Gohr                        return false;
121*fe060d0dSAndreas Gohr                    }
1225586e97bSAndreas Gohr                    return $file->getFilename()[0] !== '.';
1235586e97bSAndreas Gohr                }
1245586e97bSAndreas Gohr            )
1255586e97bSAndreas Gohr        );
1265586e97bSAndreas Gohr
1275586e97bSAndreas Gohr        $found = [];
1285586e97bSAndreas Gohr        foreach ($ite as $file) {
1295586e97bSAndreas Gohr            /** @var \SplFileInfo $file */
1305586e97bSAndreas Gohr            $path = str_replace('\\', '/', $file->getPathname());
131*fe060d0dSAndreas Gohr            if (str_starts_with($path, './lang/')) continue; // skip language files
132*fe060d0dSAndreas Gohr            if (str_starts_with($path, './vendor/')) continue; // skip vendor files
1335586e97bSAndreas Gohr
1345586e97bSAndreas Gohr            if ($file->getExtension() == 'php') {
1355586e97bSAndreas Gohr                $found = array_merge($found, $this->phpExtract($path));
1365586e97bSAndreas Gohr            } elseif ($file->getExtension() == 'js') {
1375586e97bSAndreas Gohr                if (!isset($found['js'])) $found['js'] = [];
1385586e97bSAndreas Gohr                $found['js'] = array_merge($found['js'], $this->jsExtract($path));
1395586e97bSAndreas Gohr            }
1405586e97bSAndreas Gohr        }
1415586e97bSAndreas Gohr
142d6c76996SAndreas Gohr        // admin menu entry
143d6c76996SAndreas Gohr        if (is_dir('admin')) {
144d6c76996SAndreas Gohr            $found['menu'] = 'admin/';
145d6c76996SAndreas Gohr        }
146d6c76996SAndreas Gohr        if (file_exists('admin.php')) {
147d6c76996SAndreas Gohr            $found['menu'] = 'admin.php';
148d6c76996SAndreas Gohr        }
149d6c76996SAndreas Gohr
1505586e97bSAndreas Gohr        return $found;
1515586e97bSAndreas Gohr    }
1525586e97bSAndreas Gohr
153d6c76996SAndreas Gohr    /**
154d6c76996SAndreas Gohr     * Extract language keys from given settings file
155d6c76996SAndreas Gohr     *
156d6c76996SAndreas Gohr     * @param string $file
157d6c76996SAndreas Gohr     * @return array
158d6c76996SAndreas Gohr     */
1595b2e8f12SAndreas Gohr    public function metaExtract($file)
1605b2e8f12SAndreas Gohr    {
1615b2e8f12SAndreas Gohr        $meta = [];
1625b2e8f12SAndreas Gohr        include $file;
1635b2e8f12SAndreas Gohr
1645b2e8f12SAndreas Gohr        $found = [];
1655b2e8f12SAndreas Gohr        foreach ($meta as $key => $info) {
1665b2e8f12SAndreas Gohr            $found[$key] = $file;
1675b2e8f12SAndreas Gohr
1685b2e8f12SAndreas Gohr            if (isset($info['_choices'])) {
1695b2e8f12SAndreas Gohr                foreach ($info['_choices'] as $choice) {
1705b2e8f12SAndreas Gohr                    $found[$key . '_o_' . $choice] = $file;
1715b2e8f12SAndreas Gohr                }
1725b2e8f12SAndreas Gohr            }
1735b2e8f12SAndreas Gohr        }
1745b2e8f12SAndreas Gohr
1755b2e8f12SAndreas Gohr        return $found;
1765b2e8f12SAndreas Gohr    }
1775b2e8f12SAndreas Gohr
1785586e97bSAndreas Gohr    /**
1795586e97bSAndreas Gohr     * Extract language keys from given javascript file
1805586e97bSAndreas Gohr     *
1815586e97bSAndreas Gohr     * @param string $file
1825586e97bSAndreas Gohr     * @return array (key => file:line)
1835586e97bSAndreas Gohr     */
1845586e97bSAndreas Gohr    public function jsExtract($file)
1855586e97bSAndreas Gohr    {
1865586e97bSAndreas Gohr        $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet
1875586e97bSAndreas Gohr        $any = '[^\[\]\.\'"]+'; // stuff we don't care for
1885586e97bSAndreas Gohr        $close = '[\]\'"]*'; // closes brackets
1895586e97bSAndreas Gohr
1905586e97bSAndreas Gohr        $dotvalue = '\.(\w+)';
1915586e97bSAndreas Gohr        $bracketvalue = '\[[\'"](.*?)[\'"]\]';
1925586e97bSAndreas Gohr
1935586e97bSAndreas Gohr        // https://regex101.com/r/uTjHwc/1
1945586e97bSAndreas Gohr        $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/';
1955586e97bSAndreas Gohr        // echo "\n\n$regex\n\n";
1965586e97bSAndreas Gohr
1975586e97bSAndreas Gohr        return $this->extract($file, $regex);
1985586e97bSAndreas Gohr    }
1995586e97bSAndreas Gohr
2005586e97bSAndreas Gohr    /**
2015586e97bSAndreas Gohr     * Extract language keys from given php file
2025586e97bSAndreas Gohr     *
2035586e97bSAndreas Gohr     * @param string $file
2045586e97bSAndreas Gohr     * @return array (key => file:line)
2055586e97bSAndreas Gohr     */
2065586e97bSAndreas Gohr    public function phpExtract($file)
2075586e97bSAndreas Gohr    {
2085586e97bSAndreas Gohr        $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/';
2095586e97bSAndreas Gohr        return $this->extract($file, $regex);
2105586e97bSAndreas Gohr    }
2115586e97bSAndreas Gohr
2125586e97bSAndreas Gohr    /**
2135586e97bSAndreas Gohr     * Use the given regex to extract language keys from the given file
2145586e97bSAndreas Gohr     *
2155586e97bSAndreas Gohr     * @param string $file
2165586e97bSAndreas Gohr     * @param string $regex
2175586e97bSAndreas Gohr     * @return array
2185586e97bSAndreas Gohr     */
2195586e97bSAndreas Gohr    private function extract($file, $regex)
2205586e97bSAndreas Gohr    {
2215586e97bSAndreas Gohr        $found = [];
2225586e97bSAndreas Gohr        $lines = file($file);
2235586e97bSAndreas Gohr        foreach ($lines as $lno => $line) {
2245586e97bSAndreas Gohr            if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) {
2255586e97bSAndreas Gohr                continue;
2265586e97bSAndreas Gohr            }
2275586e97bSAndreas Gohr            foreach ($matches as $match) {
2285586e97bSAndreas Gohr                $key = $match[2] ?? $match[1];
2295586e97bSAndreas Gohr                $key = trim($key, '\'"');
2305586e97bSAndreas Gohr                if (!isset($found[$key])) {
2315586e97bSAndreas Gohr                    $found[$key] = $file . ':' . ($lno + 1);
2325586e97bSAndreas Gohr                }
2335586e97bSAndreas Gohr            }
2345586e97bSAndreas Gohr        }
2355586e97bSAndreas Gohr
2365586e97bSAndreas Gohr        return $found;
2375586e97bSAndreas Gohr    }
2385586e97bSAndreas Gohr}
239