xref: /plugin/dev/LangProcessor.php (revision 5b2e8f123702dd4a7bf5310061f1cb273792e789)
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
105586e97bSAndreas Gohr    /** @var CLI */
115586e97bSAndreas Gohr    protected $logger;
125586e97bSAndreas Gohr
135586e97bSAndreas Gohr    /** @var array The language keys used in the code */
14*5b2e8f12SAndreas Gohr    protected $codeKeys;
15*5b2e8f12SAndreas Gohr
16*5b2e8f12SAndreas Gohr    /** @var array The language keys matching the configuration settings */
17*5b2e8f12SAndreas Gohr    protected $settingKeys;
185586e97bSAndreas Gohr
195586e97bSAndreas Gohr    public function __construct(CLI $logger)
205586e97bSAndreas Gohr    {
215586e97bSAndreas Gohr        $this->logger = $logger;
22*5b2e8f12SAndreas Gohr        $this->codeKeys = $this->findLanguageKeysInCode();
23*5b2e8f12SAndreas Gohr        $this->settingKeys = $this->findLanguageKeysInSettings();
245586e97bSAndreas Gohr    }
255586e97bSAndreas Gohr
265586e97bSAndreas Gohr    /**
275586e97bSAndreas Gohr     * Remove the obsolete strings from the given lang.php
285586e97bSAndreas Gohr     *
295586e97bSAndreas Gohr     * @param string $file
305586e97bSAndreas Gohr     * @return void
315586e97bSAndreas Gohr     */
325586e97bSAndreas Gohr    public function processLangFile($file)
335586e97bSAndreas Gohr    {
345586e97bSAndreas Gohr        $lang = [];
355586e97bSAndreas Gohr        include $file;
365586e97bSAndreas Gohr
37*5b2e8f12SAndreas Gohr        $drop = array_diff_key($lang, $this->codeKeys);
385586e97bSAndreas Gohr        if (isset($found['js']) && isset($lang['js'])) {
395586e97bSAndreas Gohr            $drop['js'] = array_diff_key($lang['js'], $found['js']);
405586e97bSAndreas Gohr            if (!count($drop['js'])) unset($drop['js']);
415586e97bSAndreas Gohr        }
425586e97bSAndreas Gohr
435586e97bSAndreas Gohr        foreach ($drop as $key => $value) {
445586e97bSAndreas Gohr            if (is_array($value)) {
455586e97bSAndreas Gohr                foreach ($value as $subkey => $subvalue) {
465586e97bSAndreas Gohr                    $this->removeLangKey($file, $subkey, $key);
475586e97bSAndreas Gohr                }
485586e97bSAndreas Gohr            } else {
495586e97bSAndreas Gohr                $this->removeLangKey($file, $key);
505586e97bSAndreas Gohr            }
515586e97bSAndreas Gohr        }
525586e97bSAndreas Gohr    }
535586e97bSAndreas Gohr
545586e97bSAndreas Gohr    /**
55*5b2e8f12SAndreas Gohr     * Remove obsolete string from the given settings.php
56*5b2e8f12SAndreas Gohr     *
57*5b2e8f12SAndreas Gohr     * @param string $file
58*5b2e8f12SAndreas Gohr     * @return void
59*5b2e8f12SAndreas Gohr     */
60*5b2e8f12SAndreas Gohr    public function processSettingsFile($file)
61*5b2e8f12SAndreas Gohr    {
62*5b2e8f12SAndreas Gohr        $lang = [];
63*5b2e8f12SAndreas Gohr        include $file;
64*5b2e8f12SAndreas Gohr
65*5b2e8f12SAndreas Gohr        $drop = array_diff_key($lang, $this->settingKeys);
66*5b2e8f12SAndreas Gohr        foreach ($drop as $key => $value) {
67*5b2e8f12SAndreas Gohr            $this->removeLangKey($file, $key);
68*5b2e8f12SAndreas Gohr        }
69*5b2e8f12SAndreas Gohr    }
70*5b2e8f12SAndreas Gohr
71*5b2e8f12SAndreas Gohr    /**
725586e97bSAndreas Gohr     * Remove the given key from the given language file
735586e97bSAndreas Gohr     *
745586e97bSAndreas Gohr     * @param string $file
755586e97bSAndreas Gohr     * @param string $key
765586e97bSAndreas Gohr     * @param string $sub
775586e97bSAndreas Gohr     * @return void
785586e97bSAndreas Gohr     */
795586e97bSAndreas Gohr    protected function removeLangKey($file, $key, $sub = '')
805586e97bSAndreas Gohr    {
815586e97bSAndreas Gohr        $q = '[\'"]';
825586e97bSAndreas Gohr
835586e97bSAndreas Gohr        if ($sub) {
845586e97bSAndreas Gohr            $re = '/\$lang\[' . $q . $sub . $q . '\]\[' . $q . $key . $q . '\]/';
855586e97bSAndreas Gohr        } else {
865586e97bSAndreas Gohr            $re = '/\$lang\[' . $q . $key . $q . '\]/';
875586e97bSAndreas Gohr        }
885586e97bSAndreas Gohr
895586e97bSAndreas Gohr        if (io_deleteFromFile($file, $re, true)) {
905586e97bSAndreas Gohr            $this->logger->success('{key} removed from {file}', [
915586e97bSAndreas Gohr                'key' => $sub ? "$sub.$key" : $key,
925586e97bSAndreas Gohr                'file' => $file,
935586e97bSAndreas Gohr            ]);
945586e97bSAndreas Gohr        }
955586e97bSAndreas Gohr    }
965586e97bSAndreas Gohr
975586e97bSAndreas Gohr    /**
98*5b2e8f12SAndreas Gohr     * @return array
99*5b2e8f12SAndreas Gohr     */
100*5b2e8f12SAndreas Gohr    public function findLanguageKeysInSettings()
101*5b2e8f12SAndreas Gohr    {
102*5b2e8f12SAndreas Gohr        if (file_exists('./conf/metadata.php')) {
103*5b2e8f12SAndreas Gohr            return $this->metaExtract('./conf/metadata.php');
104*5b2e8f12SAndreas Gohr        }
105*5b2e8f12SAndreas Gohr        return [];
106*5b2e8f12SAndreas Gohr    }
107*5b2e8f12SAndreas Gohr
108*5b2e8f12SAndreas Gohr    /**
1095586e97bSAndreas Gohr     * Find used language keys in the actual code
110*5b2e8f12SAndreas Gohr     * @return array
1115586e97bSAndreas Gohr     */
1125586e97bSAndreas Gohr    public function findLanguageKeysInCode()
1135586e97bSAndreas Gohr    {
1145586e97bSAndreas Gohr        // get all non-hidden php and js files
1155586e97bSAndreas Gohr        $ite = new \RecursiveIteratorIterator(
1165586e97bSAndreas Gohr            new \RecursiveCallbackFilterIterator(
1175586e97bSAndreas Gohr                new \RecursiveDirectoryIterator('.', \RecursiveDirectoryIterator::SKIP_DOTS),
1185586e97bSAndreas Gohr                function ($file) {
1195586e97bSAndreas Gohr                    /** @var \SplFileInfo $file */
1205586e97bSAndreas Gohr                    if ($file->isFile() && $file->getExtension() != 'php' && $file->getExtension() != 'js') return false;
1215586e97bSAndreas Gohr                    return $file->getFilename()[0] !== '.';
1225586e97bSAndreas Gohr                }
1235586e97bSAndreas Gohr            )
1245586e97bSAndreas Gohr        );
1255586e97bSAndreas Gohr
1265586e97bSAndreas Gohr        $found = [];
1275586e97bSAndreas Gohr        foreach ($ite as $file) {
1285586e97bSAndreas Gohr            /** @var \SplFileInfo $file */
1295586e97bSAndreas Gohr            $path = str_replace('\\', '/', $file->getPathname());
1305586e97bSAndreas Gohr            if (substr($path, 0, 7) == './lang/') continue; // skip language files
1315586e97bSAndreas Gohr            if (substr($path, 0, 9) == './vendor/') continue; // skip vendor files
1325586e97bSAndreas Gohr
1335586e97bSAndreas Gohr            if ($file->getExtension() == 'php') {
1345586e97bSAndreas Gohr                $found = array_merge($found, $this->phpExtract($path));
1355586e97bSAndreas Gohr            } elseif ($file->getExtension() == 'js') {
1365586e97bSAndreas Gohr                if (!isset($found['js'])) $found['js'] = [];
1375586e97bSAndreas Gohr                $found['js'] = array_merge($found['js'], $this->jsExtract($path));
1385586e97bSAndreas Gohr            }
1395586e97bSAndreas Gohr        }
1405586e97bSAndreas Gohr
1415586e97bSAndreas Gohr        return $found;
1425586e97bSAndreas Gohr    }
1435586e97bSAndreas Gohr
144*5b2e8f12SAndreas Gohr    public function metaExtract($file)
145*5b2e8f12SAndreas Gohr    {
146*5b2e8f12SAndreas Gohr        $meta = [];
147*5b2e8f12SAndreas Gohr        include $file;
148*5b2e8f12SAndreas Gohr
149*5b2e8f12SAndreas Gohr        $found = [];
150*5b2e8f12SAndreas Gohr        foreach ($meta as $key => $info) {
151*5b2e8f12SAndreas Gohr            $found[$key] = $file;
152*5b2e8f12SAndreas Gohr
153*5b2e8f12SAndreas Gohr            if (isset($info['_choices'])) {
154*5b2e8f12SAndreas Gohr                foreach ($info['_choices'] as $choice) {
155*5b2e8f12SAndreas Gohr                    $found[$key . '_o_' . $choice] = $file;
156*5b2e8f12SAndreas Gohr                }
157*5b2e8f12SAndreas Gohr            }
158*5b2e8f12SAndreas Gohr        }
159*5b2e8f12SAndreas Gohr
160*5b2e8f12SAndreas Gohr        return $found;
161*5b2e8f12SAndreas Gohr    }
162*5b2e8f12SAndreas Gohr
1635586e97bSAndreas Gohr    /**
1645586e97bSAndreas Gohr     * Extract language keys from given javascript file
1655586e97bSAndreas Gohr     *
1665586e97bSAndreas Gohr     * @param string $file
1675586e97bSAndreas Gohr     * @return array (key => file:line)
1685586e97bSAndreas Gohr     */
1695586e97bSAndreas Gohr    public function jsExtract($file)
1705586e97bSAndreas Gohr    {
1715586e97bSAndreas Gohr        $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet
1725586e97bSAndreas Gohr        $any = '[^\[\]\.\'"]+'; // stuff we don't care for
1735586e97bSAndreas Gohr        $close = '[\]\'"]*'; // closes brackets
1745586e97bSAndreas Gohr
1755586e97bSAndreas Gohr        $dotvalue = '\.(\w+)';
1765586e97bSAndreas Gohr        $bracketvalue = '\[[\'"](.*?)[\'"]\]';
1775586e97bSAndreas Gohr
1785586e97bSAndreas Gohr        // https://regex101.com/r/uTjHwc/1
1795586e97bSAndreas Gohr        $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/';
1805586e97bSAndreas Gohr        // echo "\n\n$regex\n\n";
1815586e97bSAndreas Gohr
1825586e97bSAndreas Gohr        return $this->extract($file, $regex);
1835586e97bSAndreas Gohr    }
1845586e97bSAndreas Gohr
1855586e97bSAndreas Gohr    /**
1865586e97bSAndreas Gohr     * Extract language keys from given php file
1875586e97bSAndreas Gohr     *
1885586e97bSAndreas Gohr     * @param string $file
1895586e97bSAndreas Gohr     * @return array (key => file:line)
1905586e97bSAndreas Gohr     */
1915586e97bSAndreas Gohr    public function phpExtract($file)
1925586e97bSAndreas Gohr    {
1935586e97bSAndreas Gohr        $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/';
1945586e97bSAndreas Gohr        return $this->extract($file, $regex);
1955586e97bSAndreas Gohr    }
1965586e97bSAndreas Gohr
1975586e97bSAndreas Gohr    /**
1985586e97bSAndreas Gohr     * Use the given regex to extract language keys from the given file
1995586e97bSAndreas Gohr     *
2005586e97bSAndreas Gohr     * @param string $file
2015586e97bSAndreas Gohr     * @param string $regex
2025586e97bSAndreas Gohr     * @return array
2035586e97bSAndreas Gohr     */
2045586e97bSAndreas Gohr    private function extract($file, $regex)
2055586e97bSAndreas Gohr    {
2065586e97bSAndreas Gohr        $found = [];
2075586e97bSAndreas Gohr        $lines = file($file);
2085586e97bSAndreas Gohr        foreach ($lines as $lno => $line) {
2095586e97bSAndreas Gohr            if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) {
2105586e97bSAndreas Gohr                continue;
2115586e97bSAndreas Gohr            }
2125586e97bSAndreas Gohr            foreach ($matches as $match) {
2135586e97bSAndreas Gohr                $key = $match[2] ?? $match[1];
2145586e97bSAndreas Gohr                $key = trim($key, '\'"');
2155586e97bSAndreas Gohr                if (!isset($found[$key])) {
2165586e97bSAndreas Gohr                    $found[$key] = $file . ':' . ($lno + 1);
2175586e97bSAndreas Gohr                }
2185586e97bSAndreas Gohr
2195586e97bSAndreas Gohr            }
2205586e97bSAndreas Gohr        }
2215586e97bSAndreas Gohr
2225586e97bSAndreas Gohr        return $found;
2235586e97bSAndreas Gohr    }
2245586e97bSAndreas Gohr}
225