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 /** @var CLI */ 105586e97bSAndreas Gohr protected $logger; 115586e97bSAndreas Gohr 125586e97bSAndreas Gohr /** @var array The language keys used in the code */ 135b2e8f12SAndreas Gohr protected $codeKeys; 145b2e8f12SAndreas Gohr 155b2e8f12SAndreas Gohr /** @var array The language keys matching the configuration settings */ 165b2e8f12SAndreas Gohr protected $settingKeys; 175586e97bSAndreas Gohr 185586e97bSAndreas Gohr public function __construct(CLI $logger) 195586e97bSAndreas Gohr { 205586e97bSAndreas Gohr $this->logger = $logger; 215b2e8f12SAndreas Gohr $this->codeKeys = $this->findLanguageKeysInCode(); 225b2e8f12SAndreas Gohr $this->settingKeys = $this->findLanguageKeysInSettings(); 235586e97bSAndreas Gohr } 245586e97bSAndreas Gohr 255586e97bSAndreas Gohr /** 265586e97bSAndreas Gohr * Remove the obsolete strings from the given lang.php 275586e97bSAndreas Gohr * 285586e97bSAndreas Gohr * @param string $file 295586e97bSAndreas Gohr * @return void 305586e97bSAndreas Gohr */ 315586e97bSAndreas Gohr public function processLangFile($file) 325586e97bSAndreas Gohr { 335586e97bSAndreas Gohr $lang = []; 345586e97bSAndreas Gohr include $file; 355586e97bSAndreas Gohr 365b2e8f12SAndreas Gohr $drop = array_diff_key($lang, $this->codeKeys); 375586e97bSAndreas Gohr if (isset($found['js']) && isset($lang['js'])) { 385586e97bSAndreas Gohr $drop['js'] = array_diff_key($lang['js'], $found['js']); 39*fe060d0dSAndreas Gohr if ($drop['js'] === []) unset($drop['js']); 405586e97bSAndreas Gohr } 415586e97bSAndreas Gohr 425586e97bSAndreas Gohr foreach ($drop as $key => $value) { 435586e97bSAndreas Gohr if (is_array($value)) { 44*fe060d0dSAndreas Gohr foreach (array_keys($value) as $subkey) { 455586e97bSAndreas Gohr $this->removeLangKey($file, $subkey, $key); 465586e97bSAndreas Gohr } 475586e97bSAndreas Gohr } else { 485586e97bSAndreas Gohr $this->removeLangKey($file, $key); 495586e97bSAndreas Gohr } 505586e97bSAndreas Gohr } 515586e97bSAndreas Gohr } 525586e97bSAndreas Gohr 535586e97bSAndreas Gohr /** 545b2e8f12SAndreas Gohr * Remove obsolete string from the given settings.php 555b2e8f12SAndreas Gohr * 565b2e8f12SAndreas Gohr * @param string $file 575b2e8f12SAndreas Gohr * @return void 585b2e8f12SAndreas Gohr */ 595b2e8f12SAndreas Gohr public function processSettingsFile($file) 605b2e8f12SAndreas Gohr { 615b2e8f12SAndreas Gohr $lang = []; 625b2e8f12SAndreas Gohr include $file; 635b2e8f12SAndreas Gohr 645b2e8f12SAndreas Gohr $drop = array_diff_key($lang, $this->settingKeys); 65*fe060d0dSAndreas Gohr foreach (array_keys($drop) as $key) { 665b2e8f12SAndreas Gohr $this->removeLangKey($file, $key); 675b2e8f12SAndreas Gohr } 685b2e8f12SAndreas Gohr } 695b2e8f12SAndreas Gohr 705b2e8f12SAndreas Gohr /** 715586e97bSAndreas Gohr * Remove the given key from the given language file 725586e97bSAndreas Gohr * 735586e97bSAndreas Gohr * @param string $file 745586e97bSAndreas Gohr * @param string $key 755586e97bSAndreas Gohr * @param string $sub 765586e97bSAndreas Gohr * @return void 775586e97bSAndreas Gohr */ 785586e97bSAndreas Gohr protected function removeLangKey($file, $key, $sub = '') 795586e97bSAndreas Gohr { 805586e97bSAndreas Gohr $q = '[\'"]'; 815586e97bSAndreas Gohr 825586e97bSAndreas Gohr if ($sub) { 835586e97bSAndreas Gohr $re = '/\$lang\[' . $q . $sub . $q . '\]\[' . $q . $key . $q . '\]/'; 845586e97bSAndreas Gohr } else { 855586e97bSAndreas Gohr $re = '/\$lang\[' . $q . $key . $q . '\]/'; 865586e97bSAndreas Gohr } 875586e97bSAndreas Gohr 885586e97bSAndreas Gohr if (io_deleteFromFile($file, $re, true)) { 895586e97bSAndreas Gohr $this->logger->success('{key} removed from {file}', [ 905586e97bSAndreas Gohr 'key' => $sub ? "$sub.$key" : $key, 915586e97bSAndreas Gohr 'file' => $file, 925586e97bSAndreas Gohr ]); 935586e97bSAndreas Gohr } 945586e97bSAndreas Gohr } 955586e97bSAndreas Gohr 965586e97bSAndreas Gohr /** 975b2e8f12SAndreas Gohr * @return array 985b2e8f12SAndreas Gohr */ 995b2e8f12SAndreas Gohr public function findLanguageKeysInSettings() 1005b2e8f12SAndreas Gohr { 1015b2e8f12SAndreas Gohr if (file_exists('./conf/metadata.php')) { 1025b2e8f12SAndreas Gohr return $this->metaExtract('./conf/metadata.php'); 1035b2e8f12SAndreas Gohr } 1045b2e8f12SAndreas Gohr return []; 1055b2e8f12SAndreas Gohr } 1065b2e8f12SAndreas Gohr 1075b2e8f12SAndreas Gohr /** 1085586e97bSAndreas Gohr * Find used language keys in the actual code 1095b2e8f12SAndreas Gohr * @return array 1105586e97bSAndreas Gohr */ 1115586e97bSAndreas Gohr public function findLanguageKeysInCode() 1125586e97bSAndreas Gohr { 1135586e97bSAndreas Gohr // get all non-hidden php and js files 1145586e97bSAndreas Gohr $ite = new \RecursiveIteratorIterator( 1155586e97bSAndreas Gohr new \RecursiveCallbackFilterIterator( 1165586e97bSAndreas Gohr new \RecursiveDirectoryIterator('.', \RecursiveDirectoryIterator::SKIP_DOTS), 1175586e97bSAndreas Gohr function ($file) { 1185586e97bSAndreas Gohr /** @var \SplFileInfo $file */ 119*fe060d0dSAndreas Gohr if ($file->isFile() && $file->getExtension() != 'php' && $file->getExtension() != 'js') { 120*fe060d0dSAndreas Gohr return false; 121*fe060d0dSAndreas Gohr } 1225586e97bSAndreas Gohr return $file->getFilename()[0] !== '.'; 1235586e97bSAndreas Gohr } 1245586e97bSAndreas Gohr ) 1255586e97bSAndreas Gohr ); 1265586e97bSAndreas Gohr 1275586e97bSAndreas Gohr $found = []; 1285586e97bSAndreas Gohr foreach ($ite as $file) { 1295586e97bSAndreas Gohr /** @var \SplFileInfo $file */ 1305586e97bSAndreas Gohr $path = str_replace('\\', '/', $file->getPathname()); 131*fe060d0dSAndreas Gohr if (str_starts_with($path, './lang/')) continue; // skip language files 132*fe060d0dSAndreas Gohr if (str_starts_with($path, './vendor/')) continue; // skip vendor files 1335586e97bSAndreas Gohr 1345586e97bSAndreas Gohr if ($file->getExtension() == 'php') { 1355586e97bSAndreas Gohr $found = array_merge($found, $this->phpExtract($path)); 1365586e97bSAndreas Gohr } elseif ($file->getExtension() == 'js') { 1375586e97bSAndreas Gohr if (!isset($found['js'])) $found['js'] = []; 1385586e97bSAndreas Gohr $found['js'] = array_merge($found['js'], $this->jsExtract($path)); 1395586e97bSAndreas Gohr } 1405586e97bSAndreas Gohr } 1415586e97bSAndreas Gohr 142d6c76996SAndreas Gohr // admin menu entry 143d6c76996SAndreas Gohr if (is_dir('admin')) { 144d6c76996SAndreas Gohr $found['menu'] = 'admin/'; 145d6c76996SAndreas Gohr } 146d6c76996SAndreas Gohr if (file_exists('admin.php')) { 147d6c76996SAndreas Gohr $found['menu'] = 'admin.php'; 148d6c76996SAndreas Gohr } 149d6c76996SAndreas Gohr 1505586e97bSAndreas Gohr return $found; 1515586e97bSAndreas Gohr } 1525586e97bSAndreas Gohr 153d6c76996SAndreas Gohr /** 154d6c76996SAndreas Gohr * Extract language keys from given settings file 155d6c76996SAndreas Gohr * 156d6c76996SAndreas Gohr * @param string $file 157d6c76996SAndreas Gohr * @return array 158d6c76996SAndreas Gohr */ 1595b2e8f12SAndreas Gohr public function metaExtract($file) 1605b2e8f12SAndreas Gohr { 1615b2e8f12SAndreas Gohr $meta = []; 1625b2e8f12SAndreas Gohr include $file; 1635b2e8f12SAndreas Gohr 1645b2e8f12SAndreas Gohr $found = []; 1655b2e8f12SAndreas Gohr foreach ($meta as $key => $info) { 1665b2e8f12SAndreas Gohr $found[$key] = $file; 1675b2e8f12SAndreas Gohr 1685b2e8f12SAndreas Gohr if (isset($info['_choices'])) { 1695b2e8f12SAndreas Gohr foreach ($info['_choices'] as $choice) { 1705b2e8f12SAndreas Gohr $found[$key . '_o_' . $choice] = $file; 1715b2e8f12SAndreas Gohr } 1725b2e8f12SAndreas Gohr } 1735b2e8f12SAndreas Gohr } 1745b2e8f12SAndreas Gohr 1755b2e8f12SAndreas Gohr return $found; 1765b2e8f12SAndreas Gohr } 1775b2e8f12SAndreas Gohr 1785586e97bSAndreas Gohr /** 1795586e97bSAndreas Gohr * Extract language keys from given javascript file 1805586e97bSAndreas Gohr * 1815586e97bSAndreas Gohr * @param string $file 1825586e97bSAndreas Gohr * @return array (key => file:line) 1835586e97bSAndreas Gohr */ 1845586e97bSAndreas Gohr public function jsExtract($file) 1855586e97bSAndreas Gohr { 1865586e97bSAndreas Gohr $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet 1875586e97bSAndreas Gohr $any = '[^\[\]\.\'"]+'; // stuff we don't care for 1885586e97bSAndreas Gohr $close = '[\]\'"]*'; // closes brackets 1895586e97bSAndreas Gohr 1905586e97bSAndreas Gohr $dotvalue = '\.(\w+)'; 1915586e97bSAndreas Gohr $bracketvalue = '\[[\'"](.*?)[\'"]\]'; 1925586e97bSAndreas Gohr 1935586e97bSAndreas Gohr // https://regex101.com/r/uTjHwc/1 1945586e97bSAndreas Gohr $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/'; 1955586e97bSAndreas Gohr // echo "\n\n$regex\n\n"; 1965586e97bSAndreas Gohr 1975586e97bSAndreas Gohr return $this->extract($file, $regex); 1985586e97bSAndreas Gohr } 1995586e97bSAndreas Gohr 2005586e97bSAndreas Gohr /** 2015586e97bSAndreas Gohr * Extract language keys from given php file 2025586e97bSAndreas Gohr * 2035586e97bSAndreas Gohr * @param string $file 2045586e97bSAndreas Gohr * @return array (key => file:line) 2055586e97bSAndreas Gohr */ 2065586e97bSAndreas Gohr public function phpExtract($file) 2075586e97bSAndreas Gohr { 2085586e97bSAndreas Gohr $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/'; 2095586e97bSAndreas Gohr return $this->extract($file, $regex); 2105586e97bSAndreas Gohr } 2115586e97bSAndreas Gohr 2125586e97bSAndreas Gohr /** 2135586e97bSAndreas Gohr * Use the given regex to extract language keys from the given file 2145586e97bSAndreas Gohr * 2155586e97bSAndreas Gohr * @param string $file 2165586e97bSAndreas Gohr * @param string $regex 2175586e97bSAndreas Gohr * @return array 2185586e97bSAndreas Gohr */ 2195586e97bSAndreas Gohr private function extract($file, $regex) 2205586e97bSAndreas Gohr { 2215586e97bSAndreas Gohr $found = []; 2225586e97bSAndreas Gohr $lines = file($file); 2235586e97bSAndreas Gohr foreach ($lines as $lno => $line) { 2245586e97bSAndreas Gohr if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) { 2255586e97bSAndreas Gohr continue; 2265586e97bSAndreas Gohr } 2275586e97bSAndreas Gohr foreach ($matches as $match) { 2285586e97bSAndreas Gohr $key = $match[2] ?? $match[1]; 2295586e97bSAndreas Gohr $key = trim($key, '\'"'); 2305586e97bSAndreas Gohr if (!isset($found[$key])) { 2315586e97bSAndreas Gohr $found[$key] = $file . ':' . ($lno + 1); 2325586e97bSAndreas Gohr } 2335586e97bSAndreas Gohr } 2345586e97bSAndreas Gohr } 2355586e97bSAndreas Gohr 2365586e97bSAndreas Gohr return $found; 2375586e97bSAndreas Gohr } 2385586e97bSAndreas Gohr} 239