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