xref: /plugin/dev/LangProcessor.php (revision d6c76996d9f85b2829ea1dd61ab590b6485ea830)
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 */
145b2e8f12SAndreas Gohr    protected $codeKeys;
155b2e8f12SAndreas Gohr
165b2e8f12SAndreas Gohr    /** @var array The language keys matching the configuration settings */
175b2e8f12SAndreas Gohr    protected $settingKeys;
185586e97bSAndreas Gohr
195586e97bSAndreas Gohr    public function __construct(CLI $logger)
205586e97bSAndreas Gohr    {
215586e97bSAndreas Gohr        $this->logger = $logger;
225b2e8f12SAndreas Gohr        $this->codeKeys = $this->findLanguageKeysInCode();
235b2e8f12SAndreas 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
375b2e8f12SAndreas 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    /**
555b2e8f12SAndreas Gohr     * Remove obsolete string from the given settings.php
565b2e8f12SAndreas Gohr     *
575b2e8f12SAndreas Gohr     * @param string $file
585b2e8f12SAndreas Gohr     * @return void
595b2e8f12SAndreas Gohr     */
605b2e8f12SAndreas Gohr    public function processSettingsFile($file)
615b2e8f12SAndreas Gohr    {
625b2e8f12SAndreas Gohr        $lang = [];
635b2e8f12SAndreas Gohr        include $file;
645b2e8f12SAndreas Gohr
655b2e8f12SAndreas Gohr        $drop = array_diff_key($lang, $this->settingKeys);
665b2e8f12SAndreas Gohr        foreach ($drop as $key => $value) {
675b2e8f12SAndreas Gohr            $this->removeLangKey($file, $key);
685b2e8f12SAndreas Gohr        }
695b2e8f12SAndreas Gohr    }
705b2e8f12SAndreas Gohr
715b2e8f12SAndreas 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    /**
985b2e8f12SAndreas Gohr     * @return array
995b2e8f12SAndreas Gohr     */
1005b2e8f12SAndreas Gohr    public function findLanguageKeysInSettings()
1015b2e8f12SAndreas Gohr    {
1025b2e8f12SAndreas Gohr        if (file_exists('./conf/metadata.php')) {
1035b2e8f12SAndreas Gohr            return $this->metaExtract('./conf/metadata.php');
1045b2e8f12SAndreas Gohr        }
1055b2e8f12SAndreas Gohr        return [];
1065b2e8f12SAndreas Gohr    }
1075b2e8f12SAndreas Gohr
1085b2e8f12SAndreas Gohr    /**
1095586e97bSAndreas Gohr     * Find used language keys in the actual code
1105b2e8f12SAndreas 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
141*d6c76996SAndreas Gohr        // admin menu entry
142*d6c76996SAndreas Gohr        if (is_dir('admin')) {
143*d6c76996SAndreas Gohr            $found['menu'] = 'admin/';
144*d6c76996SAndreas Gohr        }
145*d6c76996SAndreas Gohr        if (file_exists('admin.php')) {
146*d6c76996SAndreas Gohr            $found['menu'] = 'admin.php';
147*d6c76996SAndreas Gohr        }
148*d6c76996SAndreas Gohr
1495586e97bSAndreas Gohr        return $found;
1505586e97bSAndreas Gohr    }
1515586e97bSAndreas Gohr
152*d6c76996SAndreas Gohr    /**
153*d6c76996SAndreas Gohr     * Extract language keys from given settings file
154*d6c76996SAndreas Gohr     *
155*d6c76996SAndreas Gohr     * @param string $file
156*d6c76996SAndreas Gohr     * @return array
157*d6c76996SAndreas Gohr     */
1585b2e8f12SAndreas Gohr    public function metaExtract($file)
1595b2e8f12SAndreas Gohr    {
1605b2e8f12SAndreas Gohr        $meta = [];
1615b2e8f12SAndreas Gohr        include $file;
1625b2e8f12SAndreas Gohr
1635b2e8f12SAndreas Gohr        $found = [];
1645b2e8f12SAndreas Gohr        foreach ($meta as $key => $info) {
1655b2e8f12SAndreas Gohr            $found[$key] = $file;
1665b2e8f12SAndreas Gohr
1675b2e8f12SAndreas Gohr            if (isset($info['_choices'])) {
1685b2e8f12SAndreas Gohr                foreach ($info['_choices'] as $choice) {
1695b2e8f12SAndreas Gohr                    $found[$key . '_o_' . $choice] = $file;
1705b2e8f12SAndreas Gohr                }
1715b2e8f12SAndreas Gohr            }
1725b2e8f12SAndreas Gohr        }
1735b2e8f12SAndreas Gohr
1745b2e8f12SAndreas Gohr        return $found;
1755b2e8f12SAndreas Gohr    }
1765b2e8f12SAndreas Gohr
1775586e97bSAndreas Gohr    /**
1785586e97bSAndreas Gohr     * Extract language keys from given javascript file
1795586e97bSAndreas Gohr     *
1805586e97bSAndreas Gohr     * @param string $file
1815586e97bSAndreas Gohr     * @return array (key => file:line)
1825586e97bSAndreas Gohr     */
1835586e97bSAndreas Gohr    public function jsExtract($file)
1845586e97bSAndreas Gohr    {
1855586e97bSAndreas Gohr        $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet
1865586e97bSAndreas Gohr        $any = '[^\[\]\.\'"]+'; // stuff we don't care for
1875586e97bSAndreas Gohr        $close = '[\]\'"]*'; // closes brackets
1885586e97bSAndreas Gohr
1895586e97bSAndreas Gohr        $dotvalue = '\.(\w+)';
1905586e97bSAndreas Gohr        $bracketvalue = '\[[\'"](.*?)[\'"]\]';
1915586e97bSAndreas Gohr
1925586e97bSAndreas Gohr        // https://regex101.com/r/uTjHwc/1
1935586e97bSAndreas Gohr        $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/';
1945586e97bSAndreas Gohr        // echo "\n\n$regex\n\n";
1955586e97bSAndreas Gohr
1965586e97bSAndreas Gohr        return $this->extract($file, $regex);
1975586e97bSAndreas Gohr    }
1985586e97bSAndreas Gohr
1995586e97bSAndreas Gohr    /**
2005586e97bSAndreas Gohr     * Extract language keys from given php file
2015586e97bSAndreas Gohr     *
2025586e97bSAndreas Gohr     * @param string $file
2035586e97bSAndreas Gohr     * @return array (key => file:line)
2045586e97bSAndreas Gohr     */
2055586e97bSAndreas Gohr    public function phpExtract($file)
2065586e97bSAndreas Gohr    {
2075586e97bSAndreas Gohr        $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/';
2085586e97bSAndreas Gohr        return $this->extract($file, $regex);
2095586e97bSAndreas Gohr    }
2105586e97bSAndreas Gohr
2115586e97bSAndreas Gohr    /**
2125586e97bSAndreas Gohr     * Use the given regex to extract language keys from the given file
2135586e97bSAndreas Gohr     *
2145586e97bSAndreas Gohr     * @param string $file
2155586e97bSAndreas Gohr     * @param string $regex
2165586e97bSAndreas Gohr     * @return array
2175586e97bSAndreas Gohr     */
2185586e97bSAndreas Gohr    private function extract($file, $regex)
2195586e97bSAndreas Gohr    {
2205586e97bSAndreas Gohr        $found = [];
2215586e97bSAndreas Gohr        $lines = file($file);
2225586e97bSAndreas Gohr        foreach ($lines as $lno => $line) {
2235586e97bSAndreas Gohr            if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) {
2245586e97bSAndreas Gohr                continue;
2255586e97bSAndreas Gohr            }
2265586e97bSAndreas Gohr            foreach ($matches as $match) {
2275586e97bSAndreas Gohr                $key = $match[2] ?? $match[1];
2285586e97bSAndreas Gohr                $key = trim($key, '\'"');
2295586e97bSAndreas Gohr                if (!isset($found[$key])) {
2305586e97bSAndreas Gohr                    $found[$key] = $file . ':' . ($lno + 1);
2315586e97bSAndreas Gohr                }
2325586e97bSAndreas Gohr
2335586e97bSAndreas Gohr            }
2345586e97bSAndreas Gohr        }
2355586e97bSAndreas Gohr
2365586e97bSAndreas Gohr        return $found;
2375586e97bSAndreas Gohr    }
2385586e97bSAndreas Gohr}
239