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 */ 14*5b2e8f12SAndreas Gohr protected $codeKeys; 15*5b2e8f12SAndreas Gohr 16*5b2e8f12SAndreas Gohr /** @var array The language keys matching the configuration settings */ 17*5b2e8f12SAndreas Gohr protected $settingKeys; 185586e97bSAndreas Gohr 195586e97bSAndreas Gohr public function __construct(CLI $logger) 205586e97bSAndreas Gohr { 215586e97bSAndreas Gohr $this->logger = $logger; 22*5b2e8f12SAndreas Gohr $this->codeKeys = $this->findLanguageKeysInCode(); 23*5b2e8f12SAndreas 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 37*5b2e8f12SAndreas 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 /** 55*5b2e8f12SAndreas Gohr * Remove obsolete string from the given settings.php 56*5b2e8f12SAndreas Gohr * 57*5b2e8f12SAndreas Gohr * @param string $file 58*5b2e8f12SAndreas Gohr * @return void 59*5b2e8f12SAndreas Gohr */ 60*5b2e8f12SAndreas Gohr public function processSettingsFile($file) 61*5b2e8f12SAndreas Gohr { 62*5b2e8f12SAndreas Gohr $lang = []; 63*5b2e8f12SAndreas Gohr include $file; 64*5b2e8f12SAndreas Gohr 65*5b2e8f12SAndreas Gohr $drop = array_diff_key($lang, $this->settingKeys); 66*5b2e8f12SAndreas Gohr foreach ($drop as $key => $value) { 67*5b2e8f12SAndreas Gohr $this->removeLangKey($file, $key); 68*5b2e8f12SAndreas Gohr } 69*5b2e8f12SAndreas Gohr } 70*5b2e8f12SAndreas Gohr 71*5b2e8f12SAndreas 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 /** 98*5b2e8f12SAndreas Gohr * @return array 99*5b2e8f12SAndreas Gohr */ 100*5b2e8f12SAndreas Gohr public function findLanguageKeysInSettings() 101*5b2e8f12SAndreas Gohr { 102*5b2e8f12SAndreas Gohr if (file_exists('./conf/metadata.php')) { 103*5b2e8f12SAndreas Gohr return $this->metaExtract('./conf/metadata.php'); 104*5b2e8f12SAndreas Gohr } 105*5b2e8f12SAndreas Gohr return []; 106*5b2e8f12SAndreas Gohr } 107*5b2e8f12SAndreas Gohr 108*5b2e8f12SAndreas Gohr /** 1095586e97bSAndreas Gohr * Find used language keys in the actual code 110*5b2e8f12SAndreas 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 1415586e97bSAndreas Gohr return $found; 1425586e97bSAndreas Gohr } 1435586e97bSAndreas Gohr 144*5b2e8f12SAndreas Gohr public function metaExtract($file) 145*5b2e8f12SAndreas Gohr { 146*5b2e8f12SAndreas Gohr $meta = []; 147*5b2e8f12SAndreas Gohr include $file; 148*5b2e8f12SAndreas Gohr 149*5b2e8f12SAndreas Gohr $found = []; 150*5b2e8f12SAndreas Gohr foreach ($meta as $key => $info) { 151*5b2e8f12SAndreas Gohr $found[$key] = $file; 152*5b2e8f12SAndreas Gohr 153*5b2e8f12SAndreas Gohr if (isset($info['_choices'])) { 154*5b2e8f12SAndreas Gohr foreach ($info['_choices'] as $choice) { 155*5b2e8f12SAndreas Gohr $found[$key . '_o_' . $choice] = $file; 156*5b2e8f12SAndreas Gohr } 157*5b2e8f12SAndreas Gohr } 158*5b2e8f12SAndreas Gohr } 159*5b2e8f12SAndreas Gohr 160*5b2e8f12SAndreas Gohr return $found; 161*5b2e8f12SAndreas Gohr } 162*5b2e8f12SAndreas Gohr 1635586e97bSAndreas Gohr /** 1645586e97bSAndreas Gohr * Extract language keys from given javascript file 1655586e97bSAndreas Gohr * 1665586e97bSAndreas Gohr * @param string $file 1675586e97bSAndreas Gohr * @return array (key => file:line) 1685586e97bSAndreas Gohr */ 1695586e97bSAndreas Gohr public function jsExtract($file) 1705586e97bSAndreas Gohr { 1715586e97bSAndreas Gohr $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet 1725586e97bSAndreas Gohr $any = '[^\[\]\.\'"]+'; // stuff we don't care for 1735586e97bSAndreas Gohr $close = '[\]\'"]*'; // closes brackets 1745586e97bSAndreas Gohr 1755586e97bSAndreas Gohr $dotvalue = '\.(\w+)'; 1765586e97bSAndreas Gohr $bracketvalue = '\[[\'"](.*?)[\'"]\]'; 1775586e97bSAndreas Gohr 1785586e97bSAndreas Gohr // https://regex101.com/r/uTjHwc/1 1795586e97bSAndreas Gohr $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/'; 1805586e97bSAndreas Gohr // echo "\n\n$regex\n\n"; 1815586e97bSAndreas Gohr 1825586e97bSAndreas Gohr return $this->extract($file, $regex); 1835586e97bSAndreas Gohr } 1845586e97bSAndreas Gohr 1855586e97bSAndreas Gohr /** 1865586e97bSAndreas Gohr * Extract language keys from given php file 1875586e97bSAndreas Gohr * 1885586e97bSAndreas Gohr * @param string $file 1895586e97bSAndreas Gohr * @return array (key => file:line) 1905586e97bSAndreas Gohr */ 1915586e97bSAndreas Gohr public function phpExtract($file) 1925586e97bSAndreas Gohr { 1935586e97bSAndreas Gohr $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/'; 1945586e97bSAndreas Gohr return $this->extract($file, $regex); 1955586e97bSAndreas Gohr } 1965586e97bSAndreas Gohr 1975586e97bSAndreas Gohr /** 1985586e97bSAndreas Gohr * Use the given regex to extract language keys from the given file 1995586e97bSAndreas Gohr * 2005586e97bSAndreas Gohr * @param string $file 2015586e97bSAndreas Gohr * @param string $regex 2025586e97bSAndreas Gohr * @return array 2035586e97bSAndreas Gohr */ 2045586e97bSAndreas Gohr private function extract($file, $regex) 2055586e97bSAndreas Gohr { 2065586e97bSAndreas Gohr $found = []; 2075586e97bSAndreas Gohr $lines = file($file); 2085586e97bSAndreas Gohr foreach ($lines as $lno => $line) { 2095586e97bSAndreas Gohr if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) { 2105586e97bSAndreas Gohr continue; 2115586e97bSAndreas Gohr } 2125586e97bSAndreas Gohr foreach ($matches as $match) { 2135586e97bSAndreas Gohr $key = $match[2] ?? $match[1]; 2145586e97bSAndreas Gohr $key = trim($key, '\'"'); 2155586e97bSAndreas Gohr if (!isset($found[$key])) { 2165586e97bSAndreas Gohr $found[$key] = $file . ':' . ($lno + 1); 2175586e97bSAndreas Gohr } 2185586e97bSAndreas Gohr 2195586e97bSAndreas Gohr } 2205586e97bSAndreas Gohr } 2215586e97bSAndreas Gohr 2225586e97bSAndreas Gohr return $found; 2235586e97bSAndreas Gohr } 2245586e97bSAndreas Gohr} 225