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 $codeKeys; 15 16 /** @var array The language keys matching the configuration settings */ 17 protected $settingKeys; 18 19 public function __construct(CLI $logger) 20 { 21 $this->logger = $logger; 22 $this->codeKeys = $this->findLanguageKeysInCode(); 23 $this->settingKeys = $this->findLanguageKeysInSettings(); 24 } 25 26 /** 27 * Remove the obsolete strings from the given lang.php 28 * 29 * @param string $file 30 * @return void 31 */ 32 public function processLangFile($file) 33 { 34 $lang = []; 35 include $file; 36 37 $drop = array_diff_key($lang, $this->codeKeys); 38 if (isset($found['js']) && isset($lang['js'])) { 39 $drop['js'] = array_diff_key($lang['js'], $found['js']); 40 if (!count($drop['js'])) unset($drop['js']); 41 } 42 43 foreach ($drop as $key => $value) { 44 if (is_array($value)) { 45 foreach ($value as $subkey => $subvalue) { 46 $this->removeLangKey($file, $subkey, $key); 47 } 48 } else { 49 $this->removeLangKey($file, $key); 50 } 51 } 52 } 53 54 /** 55 * Remove obsolete string from the given settings.php 56 * 57 * @param string $file 58 * @return void 59 */ 60 public function processSettingsFile($file) 61 { 62 $lang = []; 63 include $file; 64 65 $drop = array_diff_key($lang, $this->settingKeys); 66 foreach ($drop as $key => $value) { 67 $this->removeLangKey($file, $key); 68 } 69 } 70 71 /** 72 * Remove the given key from the given language file 73 * 74 * @param string $file 75 * @param string $key 76 * @param string $sub 77 * @return void 78 */ 79 protected function removeLangKey($file, $key, $sub = '') 80 { 81 $q = '[\'"]'; 82 83 if ($sub) { 84 $re = '/\$lang\[' . $q . $sub . $q . '\]\[' . $q . $key . $q . '\]/'; 85 } else { 86 $re = '/\$lang\[' . $q . $key . $q . '\]/'; 87 } 88 89 if (io_deleteFromFile($file, $re, true)) { 90 $this->logger->success('{key} removed from {file}', [ 91 'key' => $sub ? "$sub.$key" : $key, 92 'file' => $file, 93 ]); 94 } 95 } 96 97 /** 98 * @return array 99 */ 100 public function findLanguageKeysInSettings() 101 { 102 if (file_exists('./conf/metadata.php')) { 103 return $this->metaExtract('./conf/metadata.php'); 104 } 105 return []; 106 } 107 108 /** 109 * Find used language keys in the actual code 110 * @return array 111 */ 112 public function findLanguageKeysInCode() 113 { 114 // get all non-hidden php and js files 115 $ite = new \RecursiveIteratorIterator( 116 new \RecursiveCallbackFilterIterator( 117 new \RecursiveDirectoryIterator('.', \RecursiveDirectoryIterator::SKIP_DOTS), 118 function ($file) { 119 /** @var \SplFileInfo $file */ 120 if ($file->isFile() && $file->getExtension() != 'php' && $file->getExtension() != 'js') return false; 121 return $file->getFilename()[0] !== '.'; 122 } 123 ) 124 ); 125 126 $found = []; 127 foreach ($ite as $file) { 128 /** @var \SplFileInfo $file */ 129 $path = str_replace('\\', '/', $file->getPathname()); 130 if (substr($path, 0, 7) == './lang/') continue; // skip language files 131 if (substr($path, 0, 9) == './vendor/') continue; // skip vendor files 132 133 if ($file->getExtension() == 'php') { 134 $found = array_merge($found, $this->phpExtract($path)); 135 } elseif ($file->getExtension() == 'js') { 136 if (!isset($found['js'])) $found['js'] = []; 137 $found['js'] = array_merge($found['js'], $this->jsExtract($path)); 138 } 139 } 140 141 return $found; 142 } 143 144 public function metaExtract($file) 145 { 146 $meta = []; 147 include $file; 148 149 $found = []; 150 foreach ($meta as $key => $info) { 151 $found[$key] = $file; 152 153 if (isset($info['_choices'])) { 154 foreach ($info['_choices'] as $choice) { 155 $found[$key . '_o_' . $choice] = $file; 156 } 157 } 158 } 159 160 return $found; 161 } 162 163 /** 164 * Extract language keys from given javascript file 165 * 166 * @param string $file 167 * @return array (key => file:line) 168 */ 169 public function jsExtract($file) 170 { 171 $sep = '[\[\]\.\'"]+'; // closes and opens brackets and dots - we don't care yet 172 $any = '[^\[\]\.\'"]+'; // stuff we don't care for 173 $close = '[\]\'"]*'; // closes brackets 174 175 $dotvalue = '\.(\w+)'; 176 $bracketvalue = '\[[\'"](.*?)[\'"]\]'; 177 178 // https://regex101.com/r/uTjHwc/1 179 $regex = '/\bLANG' . $sep . 'plugins' . $sep . $any . $close . '(?:' . $dotvalue . '|' . $bracketvalue . ')/'; 180 // echo "\n\n$regex\n\n"; 181 182 return $this->extract($file, $regex); 183 } 184 185 /** 186 * Extract language keys from given php file 187 * 188 * @param string $file 189 * @return array (key => file:line) 190 */ 191 public function phpExtract($file) 192 { 193 $regex = '/(?:tpl_getLang|->getLang) ?\((.*?)\)/'; 194 return $this->extract($file, $regex); 195 } 196 197 /** 198 * Use the given regex to extract language keys from the given file 199 * 200 * @param string $file 201 * @param string $regex 202 * @return array 203 */ 204 private function extract($file, $regex) 205 { 206 $found = []; 207 $lines = file($file); 208 foreach ($lines as $lno => $line) { 209 if (!preg_match_all($regex, $line, $matches, PREG_SET_ORDER)) { 210 continue; 211 } 212 foreach ($matches as $match) { 213 $key = $match[2] ?? $match[1]; 214 $key = trim($key, '\'"'); 215 if (!isset($found[$key])) { 216 $found[$key] = $file . ':' . ($lno + 1); 217 } 218 219 } 220 } 221 222 return $found; 223 } 224} 225