xref: /plugin/dev/LangProcessor.php (revision 5b2e8f123702dd4a7bf5310061f1cb273792e789)
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 $codeKeys;
15
16    /** @var array The language keys matching the configuration settings */
17    protected $settingKeys;
18
19    public function __construct(CLI $logger)
20    {
21        $this->logger = $logger;
22        $this->codeKeys = $this->findLanguageKeysInCode();
23        $this->settingKeys = $this->findLanguageKeysInSettings();
24    }
25
26    /**
27     * Remove the obsolete strings from the given lang.php
28     *
29     * @param string $file
30     * @return void
31     */
32    public function processLangFile($file)
33    {
34        $lang = [];
35        include $file;
36
37        $drop = array_diff_key($lang, $this->codeKeys);
38        if (isset($found['js']) && isset($lang['js'])) {
39            $drop['js'] = array_diff_key($lang['js'], $found['js']);
40            if (!count($drop['js'])) unset($drop['js']);
41        }
42
43        foreach ($drop as $key => $value) {
44            if (is_array($value)) {
45                foreach ($value as $subkey => $subvalue) {
46                    $this->removeLangKey($file, $subkey, $key);
47                }
48            } else {
49                $this->removeLangKey($file, $key);
50            }
51        }
52    }
53
54    /**
55     * Remove obsolete string from the given settings.php
56     *
57     * @param string $file
58     * @return void
59     */
60    public function processSettingsFile($file)
61    {
62        $lang = [];
63        include $file;
64
65        $drop = array_diff_key($lang, $this->settingKeys);
66        foreach ($drop as $key => $value) {
67            $this->removeLangKey($file, $key);
68        }
69    }
70
71    /**
72     * Remove the given key from the given language file
73     *
74     * @param string $file
75     * @param string $key
76     * @param string $sub
77     * @return void
78     */
79    protected function removeLangKey($file, $key, $sub = '')
80    {
81        $q = '[\'"]';
82
83        if ($sub) {
84            $re = '/\$lang\[' . $q . $sub . $q . '\]\[' . $q . $key . $q . '\]/';
85        } else {
86            $re = '/\$lang\[' . $q . $key . $q . '\]/';
87        }
88
89        if (io_deleteFromFile($file, $re, true)) {
90            $this->logger->success('{key} removed from {file}', [
91                'key' => $sub ? "$sub.$key" : $key,
92                'file' => $file,
93            ]);
94        }
95    }
96
97    /**
98     * @return array
99     */
100    public function findLanguageKeysInSettings()
101    {
102        if (file_exists('./conf/metadata.php')) {
103            return $this->metaExtract('./conf/metadata.php');
104        }
105        return [];
106    }
107
108    /**
109     * Find used language keys in the actual code
110     * @return array
111     */
112    public function findLanguageKeysInCode()
113    {
114        // get all non-hidden php and js files
115        $ite = new \RecursiveIteratorIterator(
116            new \RecursiveCallbackFilterIterator(
117                new \RecursiveDirectoryIterator('.', \RecursiveDirectoryIterator::SKIP_DOTS),
118                function ($file) {
119                    /** @var \SplFileInfo $file */
120                    if ($file->isFile() && $file->getExtension() != 'php' && $file->getExtension() != 'js') return false;
121                    return $file->getFilename()[0] !== '.';
122                }
123            )
124        );
125
126        $found = [];
127        foreach ($ite as $file) {
128            /** @var \SplFileInfo $file */
129            $path = str_replace('\\', '/', $file->getPathname());
130            if (substr($path, 0, 7) == './lang/') continue; // skip language files
131            if (substr($path, 0, 9) == './vendor/') continue; // skip vendor files
132
133            if ($file->getExtension() == 'php') {
134                $found = array_merge($found, $this->phpExtract($path));
135            } elseif ($file->getExtension() == 'js') {
136                if (!isset($found['js'])) $found['js'] = [];
137                $found['js'] = array_merge($found['js'], $this->jsExtract($path));
138            }
139        }
140
141        return $found;
142    }
143
144    public function metaExtract($file)
145    {
146        $meta = [];
147        include $file;
148
149        $found = [];
150        foreach ($meta as $key => $info) {
151            $found[$key] = $file;
152
153            if (isset($info['_choices'])) {
154                foreach ($info['_choices'] as $choice) {
155                    $found[$key . '_o_' . $choice] = $file;
156                }
157            }
158        }
159
160        return $found;
161    }
162
163    /**
164     * Extract language keys from given javascript file
165     *
166     * @param string $file
167     * @return array (key => file:line)
168     */
169    public function jsExtract($file)
170    {
171        $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet
172        $any = '[^\[\]\.\'"]+'; // stuff we don't care for
173        $close = '[\]\'"]*'; // closes brackets
174
175        $dotvalue = '\.(\w+)';
176        $bracketvalue = '\[[\'"](.*?)[\'"]\]';
177
178        // https://regex101.com/r/uTjHwc/1
179        $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/';
180        // echo "\n\n$regex\n\n";
181
182        return $this->extract($file, $regex);
183    }
184
185    /**
186     * Extract language keys from given php file
187     *
188     * @param string $file
189     * @return array (key => file:line)
190     */
191    public function phpExtract($file)
192    {
193        $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/';
194        return $this->extract($file, $regex);
195    }
196
197    /**
198     * Use the given regex to extract language keys from the given file
199     *
200     * @param string $file
201     * @param string $regex
202     * @return array
203     */
204    private function extract($file, $regex)
205    {
206        $found = [];
207        $lines = file($file);
208        foreach ($lines as $lno => $line) {
209            if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) {
210                continue;
211            }
212            foreach ($matches as $match) {
213                $key = $match[2] ?? $match[1];
214                $key = trim($key, '\'"');
215                if (!isset($found[$key])) {
216                    $found[$key] = $file . ':' . ($lno + 1);
217                }
218
219            }
220        }
221
222        return $found;
223    }
224}
225