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