MDL-11515:
[moodle-linuxchix.git] / admin / lang.php
blob2c5de4db98c122aad8e17e3d5c5579f2c3558757
1 <?PHP // $Id$
2 /**
3 * Display the admin/language menu and process strings translation.
5 * @param string $mode the mode of the script: null, "compare", "missing"
6 * @param string $currentfile the filename of the English file to edit (if mode==compare)
7 * @param bool $uselocal save translations into *_local pack?
8 */
10 require_once('../config.php');
11 require_once($CFG->libdir.'/adminlib.php');
13 admin_externalpage_setup('langedit');
15 $context = get_context_instance(CONTEXT_SYSTEM, SITEID);
17 define('LANG_SUBMIT_REPEAT', 1); // repeat displaying submit button?
18 define('LANG_SUBMIT_REPEAT_EVERY', 20); // if so, after how many lines?
19 define('LANG_DISPLAY_MISSING_LINKS', 1); // display "go to first/next missing string" links?
20 define('LANG_DEFAULT_FILE', ''); // default file to translate. Empty allowed
21 define('LANG_LINK_MISSING_STRINGS', 1); // create links from "missing" page to "compare" page?
22 define('LANG_DEFAULT_USELOCAL', 0); // should *_utf8_local be used by default?
23 define('LANG_MISSING_TEXT_MAX_LEN', 60); // maximum length of the missing text to display
24 define('LANG_KEEP_ORPHANS', 1); // keep orphaned strings (i.e. strings w/o English reference)
25 define('LANG_SEARCH_EXTRA', 1); // search lang files in extra locations
27 $mode = optional_param('mode', '', PARAM_ALPHA);
28 $currentfile = optional_param('currentfile', LANG_DEFAULT_FILE, PARAM_FILE);
29 $uselocal = optional_param('uselocal', -1, PARAM_INT);
31 if ($uselocal == -1) {
32 if (isset($SESSION->langtranslateintolocal)) {
33 $uselocal = $SESSION->langtranslateintolocal;
34 } else {
35 $uselocal = LANG_DEFAULT_USELOCAL;
37 } else {
38 $SESSION->langtranslateintolocal = $uselocal;
41 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id, false)) {
42 // Force using _local
43 $uselocal = 1;
46 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id, false) && (!$uselocal)) {
47 print_error('cannoteditmasterlang');
50 if ((!has_capability('moodle/site:langeditlocal', $context, $USER->id, false)) && ($uselocal)) {
51 print_error('cannotcustomizelocallang');
54 $strlanguage = get_string("language");
55 $strcurrentlanguage = get_string("currentlanguage");
56 $strmissingstrings = get_string("missingstrings");
57 $streditstrings = get_string("editstrings", 'admin');
58 $stredithelpdocs = get_string("edithelpdocs", 'admin');
59 $strthislanguage = get_string("thislanguage");
60 $strgotofirst = get_string('gotofirst','admin');
61 $strfilestoredin = get_string('filestoredin', 'admin');
62 $strfilestoredinhelp = get_string('filestoredinhelp', 'admin');
63 $strswitchlang = get_string('switchlang', 'admin');
64 $strchoosefiletoedit = get_string('choosefiletoedit', 'admin');
65 $streditennotallowed = get_string('langnoeditenglish', 'admin');
66 $strfilecreated = get_string('filecreated', 'admin');
67 $strprev = get_string('previous');
68 $strnext = get_string('next');
69 $strlocalstringcustomization = get_string('localstringcustomization', 'admin');
70 $strlangpackmaintaining = get_string('langpackmaintaining', 'admin');
71 $strnomissingstrings = get_string('nomissingstrings', 'admin');
72 $streditingnoncorelangfile = get_string('editingnoncorelangfile', 'admin');
74 $currentlang = current_language();
76 switch ($mode) {
77 case "missing":
78 // Missing array keys are not bugs here but missing strings
79 error_reporting(E_ALL ^ E_NOTICE);
80 $title = $strmissingstrings;
81 break;
82 case "compare":
83 $title = $streditstrings;
84 break;
85 default:
86 $title = $strlanguage;
87 break;
89 $navlinks[] = array('name' => $strlanguage, 'link' => "$CFG->wwwroot/admin/lang.php", 'type' => 'misc');
90 $navigation = build_navigation($navlinks);
92 admin_externalpage_print_header();
94 // Prepare and render menu tabs
95 $firstrow = array();
96 $secondrow = array();
97 $inactive = NULL;
98 $activated = NULL;
99 $currenttab = $mode;
100 if ($uselocal) {
101 $inactive = array('uselocal');
102 $activated = array('uselocal');
103 } else {
104 $inactive = array('usemaster');
105 $activated = array('usemaster');
107 if (has_capability('moodle/site:langeditlocal', $context, $USER->id, false)) {
108 $firstrow[] = new tabobject('uselocal',
109 $CFG->wwwroot."/admin/lang.php?mode=$mode&amp;currentfile=$currentfile&amp;uselocal=1",
110 $strlocalstringcustomization );
112 if (has_capability('moodle/site:langeditmaster', $context, $USER->id, false)) {
113 $firstrow[] = new tabobject('usemaster',
114 $CFG->wwwroot."/admin/lang.php?mode=$mode&amp;currentfile=$currentfile&amp;uselocal=0",
115 $strlangpackmaintaining );
117 $secondrow[] = new tabobject('missing', $CFG->wwwroot.'/admin/lang.php?mode=missing', $strmissingstrings );
118 $secondrow[] = new tabobject('compare', $CFG->wwwroot.'/admin/lang.php?mode=compare', $streditstrings );
119 // TODO
120 // langdoc.php functionality is planned to be merged into lang.php
121 $secondrow[] = new tabobject('langdoc', $CFG->wwwroot.'/admin/langdoc.php', $stredithelpdocs );
122 $tabs = array($firstrow, $secondrow);
123 print_tabs($tabs, $currenttab, $inactive, $activated);
126 if (!$mode) {
127 print_box_start();
128 $currlang = current_language();
129 $langs = get_list_of_languages(false, true);
130 popup_form ("$CFG->wwwroot/$CFG->admin/lang.php?lang=", $langs, "chooselang", $currlang, "", "", "", false, 'self', $strcurrentlanguage.':');
131 print_box_end();
132 admin_externalpage_print_footer();
133 exit;
136 // Get a list of all the root files in the English directory
138 $langbase = $CFG->dataroot . '/lang';
139 $enlangdir = "$CFG->dirroot/lang/en_utf8";
140 if ($currentlang == 'en_utf8') {
141 $langdir = $enlangdir;
142 } else {
143 $langdir = "$langbase/$currentlang";
145 $locallangdir = "$langbase/{$currentlang}_local";
147 // get the list of all English stringfiles
148 $stringfiles = lang_standard_locations();
149 if (LANG_SEARCH_EXTRA) {
150 $stringfiles += lang_extra_locations();
152 if (count($stringfiles) == 0) {
153 error("Could not find English language pack!");
156 if ($mode == "missing") {
157 if (!file_exists($langdir)) {
158 error ('to edit this language pack, you need to put it in '.$CFG->dataroot.'/lang');
161 // Following variables store the HTML output to be echo-ed
162 $m = '';
163 $o = '';
165 $m_x = false;
167 // Total number of strings and missing strings
168 $totalcounter->strings = 0;
169 $totalcounter->missing = 0;
171 // For each file, check that a counterpart exists, then check all the strings
172 foreach ($stringfiles as $stringfile) {
173 $location = $stringfile['location'];
174 $plugin = $stringfile['plugin'];
175 $prefix = $stringfile['prefix'];
176 $filename = $stringfile['filename'];
177 unset($string);
179 // Get some information about file locations:
180 // $enfilepath = the path to the English file distributed either in the core space or in plugin space
181 // $trfilepath = the path to the translated file distributed either in the lang pack or in plugin space
182 // $lcfilepath = the path to the _local customization
183 // $trfilename = the filename of the translated version of the file (including prefix for non-core files)
184 if ($location || $plugin) {
185 // non-core file in an extra location
186 $enfilepath = "$CFG->dirroot/$location/$plugin/lang/en_utf8/$filename";
187 $trfilepath = "$CFG->dirroot/$location/$plugin/lang/$currentlang/$filename";
188 $lcfilepath = "$locallangdir/$filename";
189 $trfilename = $filename;
190 if (!$m_x) {
191 $m .= '<hr />';
192 $m_x = true;
194 } else {
195 // core file in standard location
196 $enfilepath = "$CFG->dirroot/lang/en_utf8/$filename";
197 $trfilepath = "$langdir/$filename";
198 $lcfilepath = "$locallangdir/$filename";
199 $trfilename = $filename;
201 // $enstring = English strings distributed either in the core space or in plugin space
202 include($enfilepath);
203 $enstring = $string;
204 unset($string);
205 ksort($enstring);
207 //$lcstring = local customizations
208 $lcstring = array();
209 if (file_exists($lcfilepath)) {
210 include($lcfilepath);
211 $localfileismissing = 0;
212 if (is_array($string)) {
213 $lcstring = $string;
215 unset($string);
216 ksort($lcstring);
217 } else {
218 $localfileismissing = 1;
221 // $string = translated strings distibuted either in core lang pack or in plugin space
222 $string = array();
223 if (file_exists($trfilepath)) {
224 include($trfilepath);
225 $fileismissing = 0;
226 } else {
227 $fileismissing = 1;
228 $o .= notify(get_string("filemissing", "", $trfilepath), "notifyproblem", "center", true);
231 $missingcounter = 0;
233 $first = true; // first missing string found in the file
234 // For all English strings in the current file check distributed translations and _local customizations
235 foreach ($enstring as $key => $value) {
236 $totalcounter->strings++;
237 $missingstring = false;
238 $missinglocalstring = false;
239 $translationsdiffer = false;
240 if (empty($string[$key]) and $string[$key] != "0") { // MDL-4735
241 // string is missing in distributed pack
242 $missingstring = true;
244 if (empty($lcstring[$key]) and $lcstring[$key] != "0") { // MDL-4735
245 // string is missing in _local customization
246 $missinglocalstring = true;
248 if (!$missingstring && !$missinglocalstring && ($lcstring[$key] != $string[$key])) {
249 $translationsdiffer = true;
251 if ($missingstring || $translationsdiffer) {
252 $value = htmlspecialchars($value);
253 $value = str_replace("$"."a", "\\$"."a", $value);
254 $value = str_replace("%%","%",$value);
255 if ($first) {
256 $m .= "<a href=\"lang.php?mode=missing#$trfilename\">$trfilename";
257 $m .= $fileismissing ? '*' : '';
258 $m .= '</a> &nbsp; ';
259 $o .= "<p><a name=\"$trfilename\"></a><b>".
260 get_string("stringsnotset","", $trfilepath)."</b></p><pre>";
261 $first = false;
262 $somethingfound = true;
264 if ($missingstring) {
265 $missingcounter++;
266 $totalcounter->missing++;
268 if (LANG_LINK_MISSING_STRINGS && $missingstring) {
269 $missinglinkstart = "<a href=\"lang.php?mode=compare&amp;currentfile=$filename#missing$missingcounter\">";
270 $missinglinkend = '</a>';
271 } else {
272 $missinglinkstart = '';
273 $missinglinkend = '';
275 if (strlen($value) > LANG_MISSING_TEXT_MAX_LEN) {
276 $value = lang_xhtml_save_substr($value, 0, LANG_MISSING_TEXT_MAX_LEN) . ' ...'; // MDL-8852
278 if ($translationsdiffer) {
279 $o .= '// ';
281 $o .= "$"."string['".$missinglinkstart.$key.$missinglinkend."'] = \"$value\";";
282 if ($translationsdiffer) {
283 $o .= ' // differs from the translation in _local';
284 } elseif (!$missinglocalstring) {
285 $o .= ' // translated only in _local';
287 $o .= "\n";
290 if (!$first) {
291 $o .= '</pre><hr />';
295 if ($totalcounter->missing > 0) {
296 $totalcounter->missingpercent = sprintf('%02.1f', ($totalcounter->missing / $totalcounter->strings * 100));
297 print_heading(get_string('numberofstrings', 'admin', $totalcounter), '', 4);
298 } else {
299 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
302 if ($m <> '') {
303 print_box($m, 'filenames');
306 echo $o;
308 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/help", "CVS")) {
309 error("Could not find English language help files!");
312 foreach ($files as $filekey => $file) { // check all the help files.
313 if (!file_exists("$langdir/help/$file")) {
314 notify(get_string("filemissing", "", "$langdir/help/$file"), 'notifyproblem');
315 $somethingfound = true;
316 continue;
320 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/docs", "CVS")) {
321 error("Could not find English language docs files!");
323 foreach ($files as $filekey => $file) { // check all the docs files.
324 if (!file_exists("$langdir/docs/$file")) {
325 notify(get_string("filemissing", "", "$langdir/docs/$file"), 'notifyproblem');
326 $somethingfound = true;
327 continue;
331 if (!empty($somethingfound)) {
332 print_continue("lang.php");
333 } else {
334 notice(get_string("languagegood"), "lang.php" );
337 } else if ($mode == "compare") {
339 if (!file_exists($langbase) ){
340 if (!lang_make_directory($langbase) ){
341 error('ERROR: Could not create base lang directory ' . $langbase);
342 } else {
343 echo '<div class="notifysuccess">Created directory '.
344 $langbase .'</div>'."<br />\n";
347 if (!$uselocal && !file_exists($langdir)) {
348 if (!lang_make_directory($langdir)) {
349 error('ERROR: Could not create directory '.$langdir);
350 } else {
351 echo '<div class="notifysuccess">Created directory '.
352 $langdir .'</div>'."<br />\n";
355 if ($uselocal && !file_exists($locallangdir)) {
356 if (!lang_make_directory($locallangdir)) {
357 echo '<div class="notifyproblem">ERROR: Could not create directory '.
358 $locallangdir .'</div>'."<br />\n";
359 $uselocal = 0;
360 } else {
361 echo '<div class="notifysuccess">Created directory '.
362 $locallangdir .'</div>'."<br />\n";
366 if ($currentfile <> '') {
367 if (!$fileinfo = lang_get_file_info($currentfile, $stringfiles)) {
368 error('Unable to find info for: '.$currentfile);
370 // check the filename is set up correctly, prevents bugs similar to MDL-10920
371 $location = $fileinfo['location'];
372 $plugin = $fileinfo['plugin'];
373 $prefix = $fileinfo['prefix'];
374 $filename = $fileinfo['filename'];
375 if ($location || $plugin) {
376 // file in an extra location
377 if ($currentfile != "{$prefix}{$plugin}.php") {
378 error("Non-core filename mismatch. The file $currentfile should be {$prefix}{$plugin}.php");
380 if (!$uselocal) {
381 notify($streditingnoncorelangfile);
382 $editable = false;
384 } else {
385 // file in standard location
386 if ($currentfile != $filename) {
387 error("Core filename mismatch. The file $currentfile should be $filename");
391 // Get some information about file locations:
392 // $enfilepath = the path to the English file distributed either in the core space or in plugin space
393 // $trfilepath = the path to the translated file distributed either in the lang pack or in plugin space
394 // $lcfilepath = the path to the _local customization
395 // $trfilename = the filename of the translated version of the file (including prefix for non-core files)
396 if ($location || $plugin) {
397 // non-core file in an extra location
398 $enfilepath = "$CFG->dirroot/$location/$plugin/lang/en_utf8/$filename";
399 $trfilepath = "$CFG->dirroot/$location/$plugin/lang/$currentlang/$filename";
400 $lcfilepath = "$locallangdir/$filename";
401 $trfilename = $filename;
402 } else {
403 // core file in standard location
404 $enfilepath = "$CFG->dirroot/lang/en_utf8/$filename";
405 $trfilepath = "$langdir/$filename";
406 $lcfilepath = "$locallangdir/$filename";
407 $trfilename = $filename;
411 if (isset($_POST['currentfile'])){ // Save a file
412 if (!confirm_sesskey()) {
413 error(get_string('confirmsesskeybad', 'error'));
416 $newstrings = array();
418 foreach ($_POST as $postkey => $postval) {
419 $stringkey = lang_file_string_key($postkey);
420 $newstrings[$stringkey] = $postval;
423 unset($newstrings['currentfile']);
425 $packstring = array();
426 $saveinto = $langdir;
427 if ($uselocal) {
428 if(file_exists($trfilepath)) {
429 include($trfilepath);
430 if (isset($string)) {
431 $packstring = $string;
433 unset($string);
435 $saveinto = $locallangdir;
438 if (lang_save_file($saveinto, $currentfile, $newstrings, $uselocal, $packstring)) {
439 notify(get_string("changessaved")." ($saveinto/$currentfile)", "green");
440 } else {
441 error("Could not save the file '$saveinto/$currentfile'!", "lang.php?mode=compare&amp;currentfile=$currentfile");
443 unset($packstring);
446 print_box_start('generalbox editstrings');
447 $menufiles = array();
448 $menufiles_coregrp = 1;
449 foreach ($stringfiles as $stringfile) {
450 $item_key = $stringfile['filename'];
451 $item_label = $stringfile['filename'];
452 if ($stringfile['location'] != '' && $stringfile['plugin'] != '') {
453 $item_label .= ' ('.$stringfile['location'].'/'.$stringfile['plugin'].')';
454 if ($menufiles_coregrp == 1) {
455 $menufiles['extra'] = '------------';
456 $menufiles_coregrp = 0;
459 $menufiles[$item_key] = $item_label;
461 popup_form("$CFG->wwwroot/$CFG->admin/lang.php?mode=compare&amp;currentfile=", $menufiles, "choosefile",
462 $currentfile, $strchoosefiletoedit);
464 echo '<div class="filestorageinfobox">';
465 echo $strfilestoredin;
466 echo '<code class="path">';
467 echo $uselocal ? "{$currentlang}_local" : $currentlang;
468 echo '</code>';
469 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
470 echo '</div>';
471 print_box_end();
473 if ($currentfile <> '') {
474 $saveto = $uselocal ? $locallangdir : $langdir;
475 error_reporting(0);
476 if (!isset($editable) || $editable) {
477 if (!file_exists("$saveto/$currentfile")) {
478 if (!@touch("$saveto/$currentfile")) {
479 print_heading(get_string("filemissing", "", "$saveto/$currentfile"), '', 4, 'error');
480 } else {
481 print_heading($strfilecreated, '', 4, 'notifysuccess');
484 if ($currentlang == "en_utf8" && !$uselocal) {
485 $editable = false;
486 print_heading($streditennotallowed, '', 4);
487 } elseif ($f = fopen("$saveto/$currentfile","r+")) {
488 $editable = true;
489 fclose($f);
490 } else {
491 $editable = false;
492 notify(get_string("makeeditable", "", "$saveto/$currentfile"), 'notifyproblem');
495 error_reporting($CFG->debug);
497 $o = ''; // stores the HTML output to be echo-ed
499 unset($string);
500 include($enfilepath);
501 $enstring = $string;
503 // TODO/FIXME: IMHO following should not be here as the strings have moved into langconfig.php -- mudrd8mz
505 if ($currentlang != 'en' and $currentfile == 'moodle.php') {
506 $enstring['thislanguage'] = "<< TRANSLATORS: Specify the name of your language here. If possible use Unicode Numeric Character References >>";
507 $enstring['thischarset'] = "<< TRANSLATORS: Charset encoding - always use utf-8 >>";
508 $enstring['thisdirection'] = "<< TRANSLATORS: This string specifies the direction of your text, either left-to-right or right-to-left. Insert either 'ltr' or 'rtl' here. >>";
509 $enstring['parentlanguage'] = "<< TRANSLATORS: If your language has a Parent Language that Moodle should use when strings are missing from your language pack, then specify the code for it here. If you leave this blank then English will be used. Example: nl >>";
511 unset($string);
512 ksort($enstring);
514 @include($lcfilepath);
515 $localstring = isset($string) ? $string : array();
516 unset($string);
517 ksort($localstring);
519 @include($trfilepath);
520 $string = isset($string) ? $string : array();
521 ksort($string);
523 if ($editable) {
524 $o .= "<form id=\"$currentfile\" action=\"lang.php\" method=\"post\">";
525 $o .= '<div>';
527 $o .= "<table summary=\"\" width=\"100%\" class=\"translator\">";
528 $linescounter = 0;
529 $missingcounter = 0;
530 foreach ($enstring as $key => $envalue) {
531 $linescounter++ ;
532 if (LANG_SUBMIT_REPEAT && $editable && $linescounter % LANG_SUBMIT_REPEAT_EVERY == 0) {
533 $o .= '<tr><td>&nbsp;</td><td><br />';
534 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
535 $o .= '<br />&nbsp;</td></tr>';
537 $envalue = nl2br(htmlspecialchars($envalue));
538 $envalue = preg_replace('/(\$a\-\&gt;[a-zA-Z0-9]*|\$a)/', '<b>$0</b>', $envalue); // Make variables bold.
539 $envalue = str_replace("%%","%",$envalue);
540 $envalue = str_replace("\\","",$envalue); // Delete all slashes
542 $o .= "\n\n".'<tr class="';
543 if ($linescounter % 2 == 0) {
544 $o .= 'r0';
545 } else {
546 $o .= 'r1';
548 $o .= '">';
549 $o .= '<td dir="ltr" lang="en">';
550 $o .= '<span class="stren">'.$envalue.'</span>';
551 $o .= '<br />'."\n";
552 $o .= '<span class="strkey">'.$key.'</span>';
553 $o .= '</td>'."\n";
555 // Missing array keys are not bugs here but missing strings
556 error_reporting(E_ALL ^ E_NOTICE);
557 if ($uselocal) {
558 $value = lang_fix_value_from_file($localstring[$key]);
559 $value2 = lang_fix_value_from_file($string[$key]);
560 if ($value == '') {
561 $value = $value2;
563 } else {
564 $value = lang_fix_value_from_file($string[$key]);
565 $value2 = lang_fix_value_from_file($localstring[$key]);
567 error_reporting($CFG->debug);
569 // Color highlighting:
570 // red #ef6868 - translation missing in both system and local pack
571 // yellow #feff7f - translation missing in system pack but is translated in local
572 // green #AAFFAA - translation present in both system and local but is different
573 if (!$value) {
574 if (!$value2) {
575 $cellcolour = 'class="bothmissing"';
576 } else {
577 $cellcolour = 'class="mastermissing"';
579 $missingcounter++;
580 if (LANG_DISPLAY_MISSING_LINKS) {
581 $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
582 $missingnext = '<a href="#missing'.($missingcounter+1).'">'.
583 '<img src="' . $CFG->pixpath . '/t/down.gif" class="iconsmall" alt="'.$strnext.'" /></a>';
584 $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
585 '<img src="' . $CFG->pixpath . '/t/up.gif" class="iconsmall" alt="'.$strprev.'" /></a>';
586 } else {
587 $missingtarget = '';
588 $missingnext = '';
589 $missingprev = '';
591 } else {
592 if ($value <> $value2 && $value2 <> '') {
593 $cellcolour = 'class="localdifferent"';
594 } else {
595 $cellcolour = '';
597 $missingtarget = '';
598 $missingnext = '';
599 $missingprev = '';
602 if ($editable) {
603 $o .= '<td '.$cellcolour.' valign="top">';
604 if ($missingcounter > 1) {
605 $o .= $missingprev;
607 $o .= $missingtarget."\n";
608 if (isset($string[$key])) {
609 $valuelen = strlen($value);
610 } else {
611 $valuelen = strlen($envalue);
613 $cols=40;
614 if (strstr($value, "\r") or strstr($value, "\n") or $valuelen > $cols) {
615 $rows = ceil($valuelen / $cols);
616 $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="'.$rows.'">'.$value.'</textarea>'."\n";
617 } else {
618 if ($valuelen) {
619 $cols = $valuelen + 5;
621 $o .= '<input type="text" name="stringXXX'.lang_form_string_key($key).'" value="'.$value.'" size="'.$cols.'" />';
623 if ($value2 <> '' && $value <> $value2) {
624 $o .= '<br /><span style="font-size:small">'.$value2.'</span>';
626 $o .= $missingnext . '</td>';
628 } else {
629 $o .= '<td '.$cellcolour.' valign="top">'.$value.'<br />'.$value2.'</td>';
631 $o .= '</tr>'."\n";
633 if ($editable) {
634 $o .= '<tr><td>&nbsp;</td><td><br />';
635 $o .= '<input type="hidden" name="sesskey" value="'.$USER->sesskey.'" />';
636 $o .= '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
637 $o .= '<input type="hidden" name="mode" value="compare" />';
638 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
639 $o .= '</td></tr>';
641 $o .= '</table>';
642 if ($editable) {
643 $o .= '</div>';
644 $o .= '</form>';
647 if (LANG_DISPLAY_MISSING_LINKS) {
648 if ($missingcounter > 0) {
649 print_heading(get_string('numberofmissingstrings', 'admin', $missingcounter), '', 4);
650 if ($editable) {
651 print_heading('<a href="#missing1">'.$strgotofirst.'</a>', "", 4);
653 } else {
654 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
657 echo $o;
659 } else {
660 // no $currentfile specified
661 // no useful information to display - maybe some help? instructions?
665 admin_externalpage_print_footer();
667 //////////////////////////////////////////////////////////////////////
670 * Save language translation file.
672 * Thanks to Petri Asikainen for the original version of code
673 * used to save language files.
675 * @uses $CFG
676 * @uses $USER
677 * @param string $path Full pathname to the directory to use
678 * @param string $file File to overwrite
679 * @param array $strings Array of strings to write
680 * @param bool $local Should *_local version be saved?
681 * @param array $packstrings Array of default langpack strings (needed if $local)
682 * @return bool Created successfully?
684 function lang_save_file($path, $file, $strings, $local, $packstrings) {
685 global $CFG, $USER;
686 if (LANG_KEEP_ORPHANS) {
687 // let us load the current content of the file
688 unset($string);
689 @include("$path/$file");
690 if (isset($string)) {
691 $orphans = $string;
692 unset($string);
693 } else {
694 $orphans = array();
697 // let us rewrite the file
698 if (!$f = @fopen("$path/$file","w")) {
699 return false;
702 fwrite($f, "<?PHP // \$Id\$ \n");
703 fwrite($f, " // $file - created with Moodle $CFG->release ($CFG->version)\n");
704 if ($local) {
705 fwrite($f, " // local modifications from $CFG->wwwroot\n");
707 fwrite($f, "\n\n");
708 ksort($strings);
709 foreach ($strings as $key => $value) {
710 @list($id, $stringname) = explode('XXX',$key);
711 $value = lang_fix_value_before_save($value);
712 if ($id == "string" and $value != ""){
713 if ((!$local) || (!isset($packstrings[$stringname])) || (lang_fix_value_from_file($packstrings[$stringname]) <> lang_fix_value_from_file($value))) {
714 // Either we are saving the master language pack
715 // or the string is not saved in packstring - fixes PHP notices about missing key
716 // or we are saving local language pack and the strings differ.
717 fwrite($f,"\$string['$stringname'] = '$value';\n");
719 if (LANG_KEEP_ORPHANS && isset($orphans[$stringname])) {
720 unset($orphans[$stringname]);
724 if (LANG_KEEP_ORPHANS) {
725 // let us add orphaned strings, i.e. already translated strings without the English referential source
726 foreach ($orphans as $key => $value) {
727 fwrite($f,"\$string['$key'] = '".lang_fix_value_before_save($value)."'; // ORPHANED\n");
730 fwrite($f,"\n?>\n");
731 fclose($f);
732 return true;
736 * Fix value of the translated string after it is load from the file.
738 * These modifications are typically necessary to work with the same string coming from two sources.
739 * We need to compare the content of these sources and we want to have e.g. "This string\r\n"
740 * to be the same as " This string\n".
742 * @param string $value Original string from the file
743 * @return string Fixed value
745 function lang_fix_value_from_file($value='') {
746 $value = str_replace("\r","",$value); // Bad character caused by Windows
747 $value = preg_replace("/\n{3,}/", "\n\n", $value); // Collapse runs of blank lines
748 $value = trim($value); // Delete leading/trailing white space
749 $value = str_replace("\\","",$value); // Delete all slashes
750 $value = str_replace("%%","%",$value);
751 $value = str_replace("&","&amp;",$value); // Fixes MDL-9248
752 $value = str_replace("<","&lt;",$value);
753 $value = str_replace(">","&gt;",$value);
754 $value = str_replace('"',"&quot;",$value);
755 return $value;
759 * Fix value of the translated string before it is saved into the file
761 * @uses $CFG
762 * @param string $value Raw string to be saved into the lang pack
763 * @return string Fixed value
765 function lang_fix_value_before_save($value='') {
766 global $CFG;
767 if ($CFG->lang != "zh_hk" and $CFG->lang != "zh_tw") { // Some MB languages include backslash bytes
768 $value = str_replace("\\","",$value); // Delete all slashes
770 if (ini_get_bool('magic_quotes_sybase')) { // Unescape escaped sybase quotes
771 $value = str_replace("''", "'", $value);
773 $value = str_replace("'", "\\'", $value); // Add slashes for '
774 $value = str_replace('"', "\\\"", $value); // Add slashes for "
775 $value = str_replace("%","%%",$value); // Escape % characters
776 $value = str_replace("\r", "",$value); // Remove linefeed characters
777 $value = trim($value); // Delete leading/trailing white space
778 return $value;
782 * Try and create a new language directory.
784 * @uses $CFG
785 * @param string $directory full path to the directory under $langbase
786 * @return string|false Returns full path to directory if successful, false if not
788 function lang_make_directory($dir, $shownotices=true) {
789 global $CFG;
790 umask(0000);
791 if (! file_exists($dir)) {
792 if (! @mkdir($dir, $CFG->directorypermissions)) {
793 return false;
795 //@chmod($dir, $CFG->directorypermissions); // Just in case mkdir didn't do it
797 return $dir;
801 * Return the string key name for use in HTML form.
803 * Required because '.' in form input names get replaced by '_' by PHP.
805 * @param string $keyfromfile The key name containing '.'
806 * @return string The key name without '.'
808 function lang_form_string_key($keyfromfile) {
809 return str_replace('.', '##46#', $keyfromfile); /// Derived from &#46, the ascii value for a period.
813 * Return the string key name for use in file.
815 * Required because '.' in form input names get replaced by '_' by PHP.
817 * @param string $keyfromfile The key name without '.'
818 * @return string The key name containing '.'
820 function lang_file_string_key($keyfromform) {
821 return str_replace('##46#', '.', $keyfromform);
825 * Return the substring of the string and take care of XHTML compliance.
827 * There was a problem with pure substr() which could possibly produce XHTML parsing error:
828 * substr('Marks &amp; Spencer', 0, 9) -> 'Marks &am' ... is not XHTML compliance
829 * This function takes care of these cases. Fixes MDL-8852.
831 * Thanks to kovacsendre, the author of the function at http://php.net/substr
833 * @param string $str The original string
834 * @param int $start Start position in the $value string
835 * @param int $length Optional length of the returned substring
836 * @return string The substring as returned by substr() with XHTML compliance
837 * @todo Seems the function does not work with negative $start together with $length being set
839 function lang_xhtml_save_substr($str, $start, $length = NULL) {
840 if ($length === 0) {
841 //stop wasting our time ;)
842 return "";
845 //check if we can simply use the built-in functions
846 if (strpos($str, '&') === false) {
847 // No entities. Use built-in functions
848 if ($length === NULL) {
849 return substr($str, $start);
850 } else {
851 return substr($str, $start, $length);
855 // create our array of characters and html entities
856 $chars = preg_split('/(&[^;\s]+;)|/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
857 $html_length = count($chars);
859 // check if we can predict the return value and save some processing time, i.e.:
860 // input string was empty OR
861 // $start is longer than the input string OR
862 // all characters would be omitted
863 if (($html_length === 0) or ($start >= $html_length) or (isset($length) and ($length <= -$html_length))) {
864 return '';
867 //calculate start position
868 if ($start >= 0) {
869 $real_start = $chars[$start][1];
870 } else {
871 //start'th character from the end of string
872 $start = max($start,-$html_length);
873 $real_start = $chars[$html_length+$start][1];
876 if (!isset($length)) {
877 // no $length argument passed, return all remaining characters
878 return substr($str, $real_start);
879 } elseif ($length > 0) {
880 // copy $length chars
881 if ($start+$length >= $html_length) {
882 // return all remaining characters
883 return substr($str, $real_start);
884 } else {
885 //return $length characters
886 return substr($str, $real_start, $chars[max($start,0)+$length][1] - $real_start);
888 } else {
889 //negative $length. Omit $length characters from end
890 return substr($str, $real_start, $chars[$html_length+$length][1] - $real_start);
895 * Finds all English string files in the standard lang/en_utf8 location.
897 * Core lang files should always be stored here and not in the module space (MDL-10920).
898 * The English version of the file may be found in
899 * $CFG->dirroot/lang/en_utf8/filename
900 * The localised version of the found file should be saved into
901 * $CFG->dataroot/lang/currentlang[_local]/filename
902 * where "filename" is returned as a part of the file record.
904 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
906 function lang_standard_locations() {
907 global $CFG;
908 $files = array();
909 // Standard location of master English string files.
910 $places = array($CFG->dirroot.'/lang/en_utf8');
911 foreach ($places as $place) {
912 foreach (get_directory_list($place, '', false) as $file) {
913 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
914 $fullpath = $place.'/'.$file;
915 $files[$fullpath] = array(
916 'filename' => $file,
917 'location' => '',
918 'plugin' => '',
919 'prefix' => '',
924 return $files;
928 * Finds all English string files in non-standard location.
930 * Searches for lang/en_utf8/*.php in various types of plugins (blocks, database presets, question types,
931 * 3rd party modules etc.) and returns an array of found files details.
933 * The English version of the file may be found in
934 * $CFG->dirroot/location/plugin/lang/en_utf8/filename
935 * The localised version of the found file should be saved into
936 * $CFG->dataroot/lang/currentlang[_local]/prefix_plugin.php
937 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
939 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
941 function lang_extra_locations() {
942 global $CFG;
943 $files = array();
944 $places = places_to_search_for_lang_strings();
945 foreach ($places as $prefix => $directories) {
946 if ($prefix != '__exceptions') {
947 foreach ($directories as $directory) {
948 foreach (get_list_of_plugins($directory) as $plugin) {
949 $enlangdirlocation = $CFG->dirroot.'/'.$directory.'/'.$plugin.'/lang/en_utf8';
950 foreach (get_directory_list($enlangdirlocation, '', false) as $file) {
951 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
952 $fullpath = $enlangdirlocation.'/'.$file;
953 $files[$fullpath] = array(
954 'filename' => $file,
955 'location' => $directory,
956 'plugin' => $plugin,
957 'prefix' => $prefix,
965 return $files;
969 * Lookup for a stringfile details.
971 * English files can be stored in several places (core space or module/plugin space). Their translations
972 * go into the one directory - the current language pack. Therefore, the name of the stringfile may be
973 * considered as a key of the list of all stringfiles.
975 * @param string $currentfile the filename
976 * @param array $stringfiles the array of file info returned by {@link lang_extra_locations()}
977 * @return array Array of a file information (filename, location, plugin, prefix) or null.
979 function lang_get_file_info($currentfile, $stringfiles) {
980 $found = false;
981 foreach ($stringfiles as $path=>$stringfile) {
982 if ($stringfile['filename'] == $currentfile) {
983 $found = true;
984 $ret = $stringfile;
985 $ret['fullpath'] = $path;
986 break;
989 if ($found) {
990 return $ret;
991 } else {
992 return null;