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 */ 145b2e8f12SAndreas Gohr protected $codeKeys; 155b2e8f12SAndreas Gohr 165b2e8f12SAndreas Gohr /** @var array The language keys matching the configuration settings */ 175b2e8f12SAndreas Gohr protected $settingKeys; 185586e97bSAndreas Gohr 195586e97bSAndreas Gohr public function __construct(CLI $logger) 205586e97bSAndreas Gohr { 215586e97bSAndreas Gohr $this->logger = $logger; 225b2e8f12SAndreas Gohr $this->codeKeys = $this->findLanguageKeysInCode(); 235b2e8f12SAndreas 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 375b2e8f12SAndreas 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 /** 555b2e8f12SAndreas Gohr * Remove obsolete string from the given settings.php 565b2e8f12SAndreas Gohr * 575b2e8f12SAndreas Gohr * @param string $file 585b2e8f12SAndreas Gohr * @return void 595b2e8f12SAndreas Gohr */ 605b2e8f12SAndreas Gohr public function processSettingsFile($file) 615b2e8f12SAndreas Gohr { 625b2e8f12SAndreas Gohr $lang = []; 635b2e8f12SAndreas Gohr include $file; 645b2e8f12SAndreas Gohr 655b2e8f12SAndreas Gohr $drop = array_diff_key($lang, $this->settingKeys); 665b2e8f12SAndreas Gohr foreach ($drop as $key => $value) { 675b2e8f12SAndreas Gohr $this->removeLangKey($file, $key); 685b2e8f12SAndreas Gohr } 695b2e8f12SAndreas Gohr } 705b2e8f12SAndreas Gohr 715b2e8f12SAndreas 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 /** 985b2e8f12SAndreas Gohr * @return array 995b2e8f12SAndreas Gohr */ 1005b2e8f12SAndreas Gohr public function findLanguageKeysInSettings() 1015b2e8f12SAndreas Gohr { 1025b2e8f12SAndreas Gohr if (file_exists('./conf/metadata.php')) { 1035b2e8f12SAndreas Gohr return $this->metaExtract('./conf/metadata.php'); 1045b2e8f12SAndreas Gohr } 1055b2e8f12SAndreas Gohr return []; 1065b2e8f12SAndreas Gohr } 1075b2e8f12SAndreas Gohr 1085b2e8f12SAndreas Gohr /** 1095586e97bSAndreas Gohr * Find used language keys in the actual code 1105b2e8f12SAndreas 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 141*d6c76996SAndreas Gohr // admin menu entry 142*d6c76996SAndreas Gohr if (is_dir('admin')) { 143*d6c76996SAndreas Gohr $found['menu'] = 'admin/'; 144*d6c76996SAndreas Gohr } 145*d6c76996SAndreas Gohr if (file_exists('admin.php')) { 146*d6c76996SAndreas Gohr $found['menu'] = 'admin.php'; 147*d6c76996SAndreas Gohr } 148*d6c76996SAndreas Gohr 1495586e97bSAndreas Gohr return $found; 1505586e97bSAndreas Gohr } 1515586e97bSAndreas Gohr 152*d6c76996SAndreas Gohr /** 153*d6c76996SAndreas Gohr * Extract language keys from given settings file 154*d6c76996SAndreas Gohr * 155*d6c76996SAndreas Gohr * @param string $file 156*d6c76996SAndreas Gohr * @return array 157*d6c76996SAndreas Gohr */ 1585b2e8f12SAndreas Gohr public function metaExtract($file) 1595b2e8f12SAndreas Gohr { 1605b2e8f12SAndreas Gohr $meta = []; 1615b2e8f12SAndreas Gohr include $file; 1625b2e8f12SAndreas Gohr 1635b2e8f12SAndreas Gohr $found = []; 1645b2e8f12SAndreas Gohr foreach ($meta as $key => $info) { 1655b2e8f12SAndreas Gohr $found[$key] = $file; 1665b2e8f12SAndreas Gohr 1675b2e8f12SAndreas Gohr if (isset($info['_choices'])) { 1685b2e8f12SAndreas Gohr foreach ($info['_choices'] as $choice) { 1695b2e8f12SAndreas Gohr $found[$key . '_o_' . $choice] = $file; 1705b2e8f12SAndreas Gohr } 1715b2e8f12SAndreas Gohr } 1725b2e8f12SAndreas Gohr } 1735b2e8f12SAndreas Gohr 1745b2e8f12SAndreas Gohr return $found; 1755b2e8f12SAndreas Gohr } 1765b2e8f12SAndreas Gohr 1775586e97bSAndreas Gohr /** 1785586e97bSAndreas Gohr * Extract language keys from given javascript file 1795586e97bSAndreas Gohr * 1805586e97bSAndreas Gohr * @param string $file 1815586e97bSAndreas Gohr * @return array (key => file:line) 1825586e97bSAndreas Gohr */ 1835586e97bSAndreas Gohr public function jsExtract($file) 1845586e97bSAndreas Gohr { 1855586e97bSAndreas Gohr $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet 1865586e97bSAndreas Gohr $any = '[^\[\]\.\'"]+'; // stuff we don't care for 1875586e97bSAndreas Gohr $close = '[\]\'"]*'; // closes brackets 1885586e97bSAndreas Gohr 1895586e97bSAndreas Gohr $dotvalue = '\.(\w+)'; 1905586e97bSAndreas Gohr $bracketvalue = '\[[\'"](.*?)[\'"]\]'; 1915586e97bSAndreas Gohr 1925586e97bSAndreas Gohr // https://regex101.com/r/uTjHwc/1 1935586e97bSAndreas Gohr $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/'; 1945586e97bSAndreas Gohr // echo "\n\n$regex\n\n"; 1955586e97bSAndreas Gohr 1965586e97bSAndreas Gohr return $this->extract($file, $regex); 1975586e97bSAndreas Gohr } 1985586e97bSAndreas Gohr 1995586e97bSAndreas Gohr /** 2005586e97bSAndreas Gohr * Extract language keys from given php file 2015586e97bSAndreas Gohr * 2025586e97bSAndreas Gohr * @param string $file 2035586e97bSAndreas Gohr * @return array (key => file:line) 2045586e97bSAndreas Gohr */ 2055586e97bSAndreas Gohr public function phpExtract($file) 2065586e97bSAndreas Gohr { 2075586e97bSAndreas Gohr $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/'; 2085586e97bSAndreas Gohr return $this->extract($file, $regex); 2095586e97bSAndreas Gohr } 2105586e97bSAndreas Gohr 2115586e97bSAndreas Gohr /** 2125586e97bSAndreas Gohr * Use the given regex to extract language keys from the given file 2135586e97bSAndreas Gohr * 2145586e97bSAndreas Gohr * @param string $file 2155586e97bSAndreas Gohr * @param string $regex 2165586e97bSAndreas Gohr * @return array 2175586e97bSAndreas Gohr */ 2185586e97bSAndreas Gohr private function extract($file, $regex) 2195586e97bSAndreas Gohr { 2205586e97bSAndreas Gohr $found = []; 2215586e97bSAndreas Gohr $lines = file($file); 2225586e97bSAndreas Gohr foreach ($lines as $lno => $line) { 2235586e97bSAndreas Gohr if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) { 2245586e97bSAndreas Gohr continue; 2255586e97bSAndreas Gohr } 2265586e97bSAndreas Gohr foreach ($matches as $match) { 2275586e97bSAndreas Gohr $key = $match[2] ?? $match[1]; 2285586e97bSAndreas Gohr $key = trim($key, '\'"'); 2295586e97bSAndreas Gohr if (!isset($found[$key])) { 2305586e97bSAndreas Gohr $found[$key] = $file . ':' . ($lno + 1); 2315586e97bSAndreas Gohr } 2325586e97bSAndreas Gohr 2335586e97bSAndreas Gohr } 2345586e97bSAndreas Gohr } 2355586e97bSAndreas Gohr 2365586e97bSAndreas Gohr return $found; 2375586e97bSAndreas Gohr } 2385586e97bSAndreas Gohr} 239