1<?php 2/** 3 * Delete unnecessary languages -> administration function 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Taggic <taggic@t-online.de> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12/** Implicit data type: 13 * 14 * ^Lang is an array that looks like the following 15 * { "core": [ $lang... ], 16 * "templates": [ $tpl_name: [ $lang... ], ... ], 17 * "plugins": [ $plugin_name: [ $lang... ], ... ] 18 * } 19 * where $lang is a DokuWiki language code 20 * $tpl_name is the template name 21 * $plugin_name is the plugin name 22 * The $lang arrays are zero-indexed 23 */ 24 25/** CSS Classes: 26 * 27 * ul.languages is an inline list of language codes 28 * if li.active is set, the text will be highlighted 29 * if li.enabled is set, the text is normal, 30 * otherwise it's red and striked-out 31 * .module is set on text that represent module names: template names, 32 * plugin names and "dokuwiki" 33 * 34 * #langshortlist is the list of language with checkboxes 35 * #langlonglist is the list of list of languages available for each module 36 * .langdelete__text is the class set on the section wrapper around all the text 37 */ 38 39/** 40 * All DokuWiki plugins to extend the admin function 41 * need to inherit from this class 42 */ 43class admin_plugin_langdelete extends DokuWiki_Admin_Plugin { 44 45 /** Fallback language */ 46 const DEFAULT_LANG = 'en'; 47 /** data stdObject assigned by ->handle() and used in ->html() */ 48 private $d; 49 50 /** return sort order for position in admin menu */ 51 function getMenuSort() { return 20; } 52 53 /** Called when dispatching the DokuWiki action; 54 * Puts the required data for ->html() in $->d */ 55 function handle() { 56 $d =& $this->d; 57 $d = new stdClass; // reset 58 59 $d->submit = isset($_REQUEST['submit']); 60 $submit =& $d->submit; 61 62 /* Check security token */ 63 if ($submit) { 64 $valid =& $d->valid; 65 $valid = True; 66 if (!checkSecurityToken()) { 67 $valid = False; 68 return; 69 } 70 } 71 72 /* Set DokuWiki language info */ 73 $d->langs = $this->list_languages(); 74 $langs =& $d->langs; 75 76 // $u_langs is in alphabetical (?) order because directory listing 77 $d->u_langs = $this->lang_unique($langs); 78 $u_langs =& $d->u_langs; 79 80 /* Grab form data */ 81 if ($submit) { 82 $d->dryrun = $_REQUEST['dryrun']; 83 $lang_str = $_REQUEST['langdelete_w']; 84 } 85 86 /* What languages do we keep ? */ 87 $lang_keep[] = self::DEFAULT_LANG; // add 'en', the fallback 88 $lang_keep[] = $conf['lang']; // add current lang 89 90 if ($submit) { 91 /* Add form data to languages to keep */ 92 if (strlen ($lang_str) > 0) { 93 $lang_keep = array_merge ($lang_keep, explode(',', $lang_str)); 94 } 95 } else { 96 // Keep every language on first run 97 $lang_keep = $u_langs; 98 } 99 100 $lang_keep = array_values(array_filter(array_unique($lang_keep))); 101 $d->lang_keep =& $lang_keep; 102 103 /* Does the language we want to keep actually exist ? */ 104 $non_langs = array_diff ($lang_keep, $u_langs); 105 if ($non_langs) { 106 $d->nolang_s = implode (",", $non_langs); 107 } 108 109 /* Prepare data for deletion */ 110 if ($submit) { 111 $d->langs_to_delete = $this->_filter_out_lang ($langs, $lang_keep); 112 } 113 114 /* What do the checkboxes say ? */ 115 if ($submit) { 116 /* Grab checkboxes */ 117 $d->shortlang = array_keys ($_REQUEST['shortlist']); 118 $shortlang =& $d->shortlang; 119 120 /* Prevent discrepancy between shortlist and text form */ 121 if (array_diff ($lang_keep, $shortlang) 122 || array_diff ($shortlang, $lang_keep)) 123 { 124 $d->discrepancy = True; 125 } 126 } else { 127 // Keep every language on first run 128 $d->shortlang = $u_langs; 129 } 130 } 131 132 /** 133 * langdelete Output function 134 * 135 * Prints a table with all found language folders. 136 * HTML and data processing are done here at the same time 137 * 138 * @author Taggic <taggic@t-online.de> 139 */ 140 function html() { 141 global $conf; // access DW configuration array 142 $d =& $this->d; // from ->handle() 143 144 // In case we want to fetch the files from gh 145 #$version = getVersionData(); 146 147 // langdelete__intro 148 echo $this->locale_xhtml('intro'); 149 150 // input anchor 151 echo '<a name="langdelete_inputbox"></a>'.NL; 152 echo $this->locale_xhtml('guide'); 153 // input form 154 $this->_html_form($d); 155 156 157 $langs = $this->list_languages(); 158 $u_langs = $this->lang_unique($langs); 159 160 161 /* Switch on form submission state */ 162 if (!$d->submit) { 163 /* Show available languages */ 164 echo '<section class="langdelete__text">'; 165 echo $this->getLang('available_langs'); 166 $this->print_shortlist ($d); 167 $this->html_print_langs($d->langs); 168 echo '</section>'; 169 170 } else { 171 /* Process form */ 172 173 /* Check token */ 174 if (!$d->valid) { 175 echo "<p>Invalid security token</p>"; 176 return; 177 } 178 179 if ($d->discrepancy) { 180 msg($this->getLang('discrepancy_warn'), 2); 181 } 182 if ($d->nolang_s) { 183 msg($this->getLang('nolang') . $d->nolang_s , 2); 184 } 185 186 echo '<h2>'.$this->getLang('h2_output').'</h2>'.NL; 187 188 if ($d->dryrun) { 189 /* Display what will be deleted */ 190 msg($this->getLang('langdelete_willmsg'), 2); 191 echo '<section class="langdelete__text">'; 192 echo $this->getLang('available_langs'); 193 $this->print_shortlist ($d); 194 $this->html_print_langs($d->langs, $d->lang_keep); 195 echo '</section>'; 196 197 msg($this->getLang('langdelete_attention'), 2); 198 echo '<a href="#langdelete_inputbox">'.$this->getLang('backto_inputbox').'</a>'.NL; 199 200 } else { 201 /* Delete and report what was deleted */ 202 msg($this->getLang('langdelete_delmsg'), 0); 203 204 echo '<section class="langdelete__text">'; 205 $this->html_print_langs($d->langs_to_delete); 206 echo '</section>'; 207 208 echo '<pre>'; 209 $this->remove_langs($d->langs_to_delete); 210 echo '</pre>'; 211 } 212 } 213 } 214 215 /** 216 * Display the form with input control to let the user specify, 217 * which languages to be kept beside en 218 * 219 * @author Taggic <taggic@t-online.de> 220 */ 221 private function _html_form (&$d) { 222 global $ID, $conf; 223 224 echo '<form id="langdelete__form" action="'.wl($ID).'" method="post">'; 225 echo '<input type="hidden" name="do" value="admin" />'.NL; 226 echo '<input type="hidden" name="page" value="'.$this->getPluginName().'" />'.NL; 227 formSecurityToken(); 228 229 echo '<fieldset class="langdelete__fieldset"><legend>'.$this->getLang('i_legend').'</legend>'.NL; 230 231 echo '<label class="formTitle">'.$this->getLang('i_using').':</label>'; 232 echo '<div class="box">'.$conf['lang'].'</div>'.NL; 233 234 echo '<label class="formTitle" for="langdelete_w">'.$this->getLang('i_shouldkeep').':</label>'; 235 echo '<input type="text" name="langdelete_w" class="edit" value="'.hsc(implode(',', $d->lang_keep)).'" />'.NL; 236 237 echo '<label class="formTitle" for="option">'.$this->getLang('i_runoption').':</label>'; 238 echo '<div class="box">'.NL; 239 echo '<input type="checkbox" name="dryrun" checked="checked" /> '; 240 echo '<label for="dryrun">'.$this->getLang('i_dryrun').'</label>'.NL; 241 echo '</div>'.NL; 242 243 echo '<button name="submit">'.$this->getLang('btn_start').'</button>'.NL; 244 245 echo '</fieldset>'.NL; 246 echo '</form>'.NL; 247 } 248 249 /** Print the language shortlist and cross-out those not in $keep */ 250 function print_shortlist (&$d) { 251 $shortlang =& $d->shortlang; 252 253 echo '<ul id="langshortlist" class="languages">'; 254 255 # As the disabled input won't POST 256 echo '<input type="hidden" name="shortlist['.self::DEFAULT_LANG.']"' 257 .' form="langdelete__form" />'; 258 259 foreach ($d->u_langs as $l) { 260 $checked = in_array($l, $shortlang) || $l == self::DEFAULT_LANG; 261 262 echo '<li'.($checked ? ' class="enabled"' : '').'>'; 263 264 echo '<input type="checkbox" id="shortlang-'.$l.'"' 265 .' name="shortlist['.$l.']"' 266 .' form="langdelete__form"' 267 .($checked ? ' checked' : '') 268 .($l == self::DEFAULT_LANG ? ' disabled' : '') 269 .' />'; 270 271 echo '<label for="shortlang-'.$l.'">'; 272 if ($checked) { 273 echo $l; 274 } else { 275 echo '<del>'.$l.'</del>'; 276 } 277 echo '</label>'; 278 279 echo '</li>'; 280 } 281 echo '</ul>'; 282 } 283 284 285 /** Display the languages in $langs for each module as a HTML list; 286 * Cross-out those not in $keep 287 * 288 * Signature: ^Lang, Array => () */ 289 private function html_print_langs ($langs, $keep = null) { 290 /* Print language list, $langs being an array; 291 * Cross out those not in $keep */ 292 $print_lang_li = function ($langs) use ($keep) { 293 echo '<ul class="languages">'; 294 foreach ($langs as $val) { 295 // If $keep is null, we keep everything 296 $enabled = is_null($keep) || in_array ($val, $keep); 297 298 echo '<li val="'.$val.'"' 299 .($enabled ? ' class="enabled"' : '') 300 .'>'; 301 if ($enabled) { 302 echo $val; 303 } else { 304 echo '<del>'.$val.'</del>'; 305 } 306 echo '</li>'; 307 } 308 echo '</ul>'; 309 }; 310 311 312 echo '<ul id="langlonglist">'; 313 314 // Core 315 echo '<li><span class="module">'.$this->getLang('dokuwiki_core').'</span>'; 316 $print_lang_li ($langs['core']); 317 echo '</li>'; 318 319 // Templates 320 echo '<li>'.$this->getLang('templates'); 321 echo '<ul>'; 322 foreach ($langs['templates'] as $name => $l) { 323 echo '<li><span class="module">'.$name.':</span>'; 324 $print_lang_li ($l); 325 echo '</li>'; 326 } 327 echo '</ul>'; 328 echo '</li>'; 329 330 // Plugins 331 echo '<li>'.$this->getLang('plugins'); 332 echo '<ul>'; 333 foreach ($langs['plugins'] as $name => $l) { 334 echo '<li><span class="module">'.$name.':</span>'; 335 $print_lang_li ($l); 336 echo '</li>'; 337 } 338 echo '</ul>'; 339 echo '</li>'; 340 341 echo '</ul>'; 342 } 343 344 /** Returns the available languages for each module 345 * (core, template or plugin) 346 * 347 * Signature: () => ^Lang 348 */ 349 private function list_languages () { 350 // See https://www.dokuwiki.org/devel:localization 351 352 /* Returns the subfolders of $dir as an array */ 353 $dir_subfolders = function ($dir) { 354 $sub = scandir($dir); 355 $sub = array_filter ($sub, function ($e) use ($dir) { 356 return is_dir ("$dir/$e") 357 && !in_array ($e, array('.', '..')); 358 } ); 359 return $sub; 360 }; 361 362 /* Return an array of template names */ 363 $list_templates = function () use ($dir_subfolders) { 364 return $dir_subfolders (DOKU_INC."lib/tpl"); 365 }; 366 367 /* Return an array of languages available for the module 368 * (core, template or plugin) given its $root directory */ 369 $list_langs = function ($root) use ($dir_subfolders) { 370 $dir = "$root/lang"; 371 if (!is_dir ($dir)) return; 372 373 return $dir_subfolders ($dir); 374 }; 375 376 /* Get templates and plugins names */ 377 global $plugin_controller; 378 $plugins = $plugin_controller->getList(); 379 $templates = $list_templates(); 380 381 return array( 382 "core" => $list_langs (DOKU_INC."inc"), 383 "templates" => array_combine ($templates, 384 array_map ($list_langs, 385 array_prefix ($templates, DOKU_INC."lib/tpl/"))), 386 "plugins" => array_combine ($plugins, 387 array_map ($list_langs, 388 array_prefix ($plugins, DOKU_PLUGIN))) 389 ); 390 } 391 392 /** Remove $lang_keep from the module languages $e 393 * 394 * Signature: ^Lang, Array => ^Lang */ 395 private function _filter_out_lang ($e, $lang_keep) { 396 // Recursive function with cases being an array of arrays, or an array 397 if (count ($e) > 0 && is_array (array_values($e)[0])) { 398 foreach ($e as $k => $elt) { 399 $out[$k] = $this->_filter_out_lang ($elt, $lang_keep); 400 } 401 return $out; 402 403 } else { 404 return array_filter ($e, function ($v) use ($lang_keep) { 405 return !in_array ($v, $lang_keep); 406 }); 407 } 408 } 409 410 /** Return an array of the languages in $l 411 * 412 * Signature: ^Lang => Array */ 413 private function lang_unique ($l) { 414 foreach ($l['core'] as $lang) { 415 $count[$lang]++; 416 } 417 foreach ($l['templates'] as $tpl => $arr) { 418 foreach ($arr as $lang) { 419 $count[$lang]++; 420 } 421 } 422 foreach ($l['plugins'] as $plug => $arr) { 423 foreach ($arr as $lang) { 424 $count[$lang]++; 425 } 426 } 427 428 return array_keys ($count); 429 } 430 431 /** Delete the languages from the modules as specified by $langs 432 * 433 * Signature: ^Lang => () */ 434 private function remove_langs($langs) { 435 foreach ($langs['core'] as $l) { 436 $this->rrm(DOKU_INC."inc/lang/$l"); 437 } 438 439 foreach ($langs['templates'] as $tpl => $arr) { 440 foreach ($arr as $l) { 441 $this->rrm(DOKU_INC."lib/tpl/$tpl/lang/$l"); 442 } 443 } 444 445 foreach ($langs['plugins'] as $plug => $arr) { 446 foreach ($arr as $l) { 447 $this->rrm(DOKU_INC."lib/plugins/$plug/lang/$l"); 448 } 449 } 450 } 451 452 /** Recursive file removal of $path with reporting */ 453 private function rrm ($path) { 454 if (is_dir ($path)) { 455 $objects = scandir ($path); 456 foreach ($objects as $object) { 457 if (!in_array ($object, array('.', '..'))) { 458 $this->rrm("$path/$object"); 459 } 460 } 461 $sucess = @rmdir ($path); 462 if (!$sucess) { echo "Failed to delete $path/\n"; } 463 else echo "Delete $path\n"; 464 } else { 465 $sucess = @unlink ($path); 466 if (!$sucess) { echo "Failed to delete $path\n"; } 467 else echo "Delete $path\n"; 468 } 469 } 470} 471 472/** Returns an array with each element of $arr prefixed with $prefix */ 473function array_prefix ($arr, $prefix) { 474 return array_map ( 475 function ($p) use ($prefix) { return $prefix.$p; }, 476 $arr); 477} 478