xref: /plugin/dev/LangProcessor.php (revision 5586e97bc4b65dc3b74336eb94fcf840074db1c4)
1<?php
2
3namespace dokuwiki\plugin\dev;
4
5use splitbrain\phpcli\CLI;
6
7class LangProcessor
8{
9
10    /** @var CLI */
11    protected $logger;
12
13    /** @var array The language keys used in the code */
14    protected $found;
15
16    public function __construct(CLI $logger)
17    {
18        $this->logger = $logger;
19        $this->found = $this->findLanguageKeysInCode();
20    }
21
22    /**
23     * Remove the obsolete strings from the given lang.php
24     *
25     * @param string $file
26     * @return void
27     */
28    public function processLangFile($file)
29    {
30        $lang = [];
31        include $file;
32
33        $drop = array_diff_key($lang, $this->found);
34        if (isset($found['js']) && isset($lang['js'])) {
35            $drop['js'] = array_diff_key($lang['js'], $found['js']);
36            if (!count($drop['js'])) unset($drop['js']);
37        }
38
39        foreach ($drop as $key => $value) {
40            if (is_array($value)) {
41                foreach ($value as $subkey => $subvalue) {
42                    $this->removeLangKey($file, $subkey, $key);
43                }
44            } else {
45                $this->removeLangKey($file, $key);
46            }
47        }
48    }
49
50    /**
51     * Remove the given key from the given language file
52     *
53     * @param string $file
54     * @param string $key
55     * @param string $sub
56     * @return void
57     */
58    protected function removeLangKey($file, $key, $sub = '')
59    {
60        $q = '[\'"]';
61
62        if ($sub) {
63            $re = '/\$lang\[' . $q . $sub . $q . '\]\[' . $q . $key . $q . '\]/';
64        } else {
65            $re = '/\$lang\[' . $q . $key . $q . '\]/';
66        }
67
68        if (io_deleteFromFile($file, $re, true)) {
69            $this->logger->success('{key} removed from {file}', [
70                'key' => $sub ? "$sub.$key" : $key,
71                'file' => $file,
72            ]);
73        }
74    }
75
76    /**
77     * Find used language keys in the actual code
78     */
79    public function findLanguageKeysInCode()
80    {
81        // get all non-hidden php and js files
82        $ite = new \RecursiveIteratorIterator(
83            new \RecursiveCallbackFilterIterator(
84                new \RecursiveDirectoryIterator('.', \RecursiveDirectoryIterator::SKIP_DOTS),
85                function ($file) {
86                    /** @var \SplFileInfo $file */
87                    if ($file->isFile() && $file->getExtension() != 'php' && $file->getExtension() != 'js') return false;
88                    return $file->getFilename()[0] !== '.';
89                }
90            )
91        );
92
93        $found = [];
94        foreach ($ite as $file) {
95            /** @var \SplFileInfo $file */
96            $path = str_replace('\\', '/', $file->getPathname());
97            if (substr($path, 0, 7) == './lang/') continue; // skip language files
98            if (substr($path, 0, 9) == './vendor/') continue; // skip vendor files
99
100            if ($file->getExtension() == 'php') {
101                $found = array_merge($found, $this->phpExtract($path));
102            } elseif ($file->getExtension() == 'js') {
103                if (!isset($found['js'])) $found['js'] = [];
104                $found['js'] = array_merge($found['js'], $this->jsExtract($path));
105            }
106        }
107
108        return $found;
109    }
110
111    /**
112     * Extract language keys from given javascript file
113     *
114     * @param string $file
115     * @return array (key => file:line)
116     */
117    public function jsExtract($file)
118    {
119        $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet
120        $any = '[^\[\]\.\'"]+'; // stuff we don't care for
121        $close = '[\]\'"]*'; // closes brackets
122
123        $dotvalue = '\.(\w+)';
124        $bracketvalue = '\[[\'"](.*?)[\'"]\]';
125
126        // https://regex101.com/r/uTjHwc/1
127        $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/';
128        // echo "\n\n$regex\n\n";
129
130        return $this->extract($file, $regex);
131    }
132
133    /**
134     * Extract language keys from given php file
135     *
136     * @param string $file
137     * @return array (key => file:line)
138     */
139    public function phpExtract($file)
140    {
141        $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/';
142        return $this->extract($file, $regex);
143    }
144
145    /**
146     * Use the given regex to extract language keys from the given file
147     *
148     * @param string $file
149     * @param string $regex
150     * @return array
151     */
152    private function extract($file, $regex)
153    {
154        $found = [];
155        $lines = file($file);
156        foreach ($lines as $lno => $line) {
157            if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) {
158                continue;
159            }
160            foreach ($matches as $match) {
161                $key = $match[2] ?? $match[1];
162                $key = trim($key, '\'"');
163                if (!isset($found[$key])) {
164                    $found[$key] = $file . ':' . ($lno + 1);
165                }
166
167            }
168        }
169
170        return $found;
171    }
172}
173