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