adding current groupid to grade_export class - soon to be used in plugins
[moodle-pu.git] / admin / lang.php
blob0df3b502adadcfea63d35a390a196fa8ece291b6
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 if ($uselocal) {
426 include($trfilepath);
427 if (isset($string)) {
428 $packstring = $string;
429 } else {
430 $packstring = array();
432 unset($string);
433 $saveinto = $locallangdir;
434 } else {
435 $packstring = array();
436 $saveinto = $langdir;
439 if (lang_save_file($saveinto, $currentfile, $newstrings, $uselocal, $packstring)) {
440 notify(get_string("changessaved")." ($saveinto/$currentfile)", "green");
441 } else {
442 error("Could not save the file '$saveinto/$currentfile'!", "lang.php?mode=compare&amp;currentfile=$currentfile");
444 unset($packstring);
447 print_box_start('generalbox editstrings');
448 $menufiles = array();
449 $menufiles_coregrp = 1;
450 foreach ($stringfiles as $stringfile) {
451 $item_key = $stringfile['filename'];
452 $item_label = $stringfile['filename'];
453 if ($stringfile['location'] != '' && $stringfile['plugin'] != '') {
454 $item_label .= ' ('.$stringfile['location'].'/'.$stringfile['plugin'].')';
455 if ($menufiles_coregrp == 1) {
456 $menufiles['extra'] = '------------';
457 $menufiles_coregrp = 0;
460 $menufiles[$item_key] = $item_label;
462 popup_form("$CFG->wwwroot/$CFG->admin/lang.php?mode=compare&amp;currentfile=", $menufiles, "choosefile",
463 $currentfile, $strchoosefiletoedit);
465 echo '<div class="filestorageinfobox">';
466 echo $strfilestoredin;
467 echo '<code class="path">';
468 echo $uselocal ? "{$currentlang}_local" : $currentlang;
469 echo '</code>';
470 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
471 echo '</div>';
472 print_box_end();
474 if ($currentfile <> '') {
475 $saveto = $uselocal ? $locallangdir : $langdir;
476 error_reporting(0);
477 if (!isset($editable) || $editable) {
478 if (!file_exists("$saveto/$currentfile")) {
479 if (!@touch("$saveto/$currentfile")) {
480 print_heading(get_string("filemissing", "", "$saveto/$currentfile"), '', 4, 'error');
481 } else {
482 print_heading($strfilecreated, '', 4, 'notifysuccess');
485 if ($currentlang == "en_utf8" && !$uselocal) {
486 $editable = false;
487 print_heading($streditennotallowed, '', 4);
488 } elseif ($f = fopen("$saveto/$currentfile","r+")) {
489 $editable = true;
490 fclose($f);
491 } else {
492 $editable = false;
493 notify(get_string("makeeditable", "", "$saveto/$currentfile"), 'notifyproblem');
496 error_reporting($CFG->debug);
498 $o = ''; // stores the HTML output to be echo-ed
500 unset($string);
501 include($enfilepath);
502 $enstring = $string;
504 // TODO/FIXME: IMHO following should not be here as the strings have moved into langconfig.php -- mudrd8mz
506 if ($currentlang != 'en' and $currentfile == 'moodle.php') {
507 $enstring['thislanguage'] = "<< TRANSLATORS: Specify the name of your language here. If possible use Unicode Numeric Character References >>";
508 $enstring['thischarset'] = "<< TRANSLATORS: Charset encoding - always use utf-8 >>";
509 $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. >>";
510 $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 >>";
512 unset($string);
513 ksort($enstring);
515 @include($lcfilepath);
516 $localstring = isset($string) ? $string : array();
517 unset($string);
518 ksort($localstring);
520 @include($trfilepath);
521 $string = isset($string) ? $string : array();
522 ksort($string);
524 if ($editable) {
525 $o .= "<form id=\"$currentfile\" action=\"lang.php\" method=\"post\">";
526 $o .= '<div>';
528 $o .= "<table summary=\"\" width=\"100%\" class=\"translator\">";
529 $linescounter = 0;
530 $missingcounter = 0;
531 foreach ($enstring as $key => $envalue) {
532 $linescounter++ ;
533 if (LANG_SUBMIT_REPEAT && $editable && $linescounter % LANG_SUBMIT_REPEAT_EVERY == 0) {
534 $o .= '<tr><td>&nbsp;</td><td><br />';
535 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
536 $o .= '<br />&nbsp;</td></tr>';
538 $envalue = nl2br(htmlspecialchars($envalue));
539 $envalue = preg_replace('/(\$a\-\&gt;[a-zA-Z0-9]*|\$a)/', '<b>$0</b>', $envalue); // Make variables bold.
540 $envalue = str_replace("%%","%",$envalue);
541 $envalue = str_replace("\\","",$envalue); // Delete all slashes
543 $o .= "\n\n".'<tr class="';
544 if ($linescounter % 2 == 0) {
545 $o .= 'r0';
546 } else {
547 $o .= 'r1';
549 $o .= '">';
550 $o .= '<td dir="ltr" lang="en">';
551 $o .= '<span class="stren">'.$envalue.'</span>';
552 $o .= '<br />'."\n";
553 $o .= '<span class="strkey">'.$key.'</span>';
554 $o .= '</td>'."\n";
556 // Missing array keys are not bugs here but missing strings
557 error_reporting(E_ALL ^ E_NOTICE);
558 if ($uselocal) {
559 $value = lang_fix_value_from_file($localstring[$key]);
560 $value2 = lang_fix_value_from_file($string[$key]);
561 if ($value == '') {
562 $value = $value2;
564 } else {
565 $value = lang_fix_value_from_file($string[$key]);
566 $value2 = lang_fix_value_from_file($localstring[$key]);
568 error_reporting($CFG->debug);
570 // Color highlighting:
571 // red #ef6868 - translation missing in both system and local pack
572 // yellow #feff7f - translation missing in system pack but is translated in local
573 // green #AAFFAA - translation present in both system and local but is different
574 if (!$value) {
575 if (!$value2) {
576 $cellcolour = 'class="bothmissing"';
577 } else {
578 $cellcolour = 'class="mastermissing"';
580 $missingcounter++;
581 if (LANG_DISPLAY_MISSING_LINKS) {
582 $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
583 $missingnext = '<a href="#missing'.($missingcounter+1).'">'.
584 '<img src="' . $CFG->pixpath . '/t/down.gif" class="iconsmall" alt="'.$strnext.'" /></a>';
585 $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
586 '<img src="' . $CFG->pixpath . '/t/up.gif" class="iconsmall" alt="'.$strprev.'" /></a>';
587 } else {
588 $missingtarget = '';
589 $missingnext = '';
590 $missingprev = '';
592 } else {
593 if ($value <> $value2 && $value2 <> '') {
594 $cellcolour = 'class="localdifferent"';
595 } else {
596 $cellcolour = '';
598 $missingtarget = '';
599 $missingnext = '';
600 $missingprev = '';
603 if ($editable) {
604 $o .= '<td '.$cellcolour.' valign="top">';
605 if ($missingcounter > 1) {
606 $o .= $missingprev;
608 $o .= $missingtarget."\n";
609 if (isset($string[$key])) {
610 $valuelen = strlen($value);
611 } else {
612 $valuelen = strlen($envalue);
614 $cols=40;
615 if (strstr($value, "\r") or strstr($value, "\n") or $valuelen > $cols) {
616 $rows = ceil($valuelen / $cols);
617 $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="'.$rows.'">'.$value.'</textarea>'."\n";
618 } else {
619 if ($valuelen) {
620 $cols = $valuelen + 5;
622 $o .= '<input type="text" name="stringXXX'.lang_form_string_key($key).'" value="'.$value.'" size="'.$cols.'" />';
624 if ($value2 <> '' && $value <> $value2) {
625 $o .= '<br /><span style="font-size:small">'.$value2.'</span>';
627 $o .= $missingnext . '</td>';
629 } else {
630 $o .= '<td '.$cellcolour.' valign="top">'.$value.'<br />'.$value2.'</td>';
632 $o .= '</tr>'."\n";
634 if ($editable) {
635 $o .= '<tr><td>&nbsp;</td><td><br />';
636 $o .= '<input type="hidden" name="sesskey" value="'.$USER->sesskey.'" />';
637 $o .= '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
638 $o .= '<input type="hidden" name="mode" value="compare" />';
639 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
640 $o .= '</td></tr>';
642 $o .= '</table>';
643 if ($editable) {
644 $o .= '</div>';
645 $o .= '</form>';
648 if (LANG_DISPLAY_MISSING_LINKS) {
649 if ($missingcounter > 0) {
650 print_heading(get_string('numberofmissingstrings', 'admin', $missingcounter), '', 4);
651 if ($editable) {
652 print_heading('<a href="#missing1">'.$strgotofirst.'</a>', "", 4);
654 } else {
655 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
658 echo $o;
660 } else {
661 // no $currentfile specified
662 // no useful information to display - maybe some help? instructions?
666 admin_externalpage_print_footer();
668 //////////////////////////////////////////////////////////////////////
671 * Save language translation file.
673 * Thanks to Petri Asikainen for the original version of code
674 * used to save language files.
676 * @uses $CFG
677 * @uses $USER
678 * @param string $path Full pathname to the directory to use
679 * @param string $file File to overwrite
680 * @param array $strings Array of strings to write
681 * @param bool $local Should *_local version be saved?
682 * @param array $packstrings Array of default langpack strings (needed if $local)
683 * @return bool Created successfully?
685 function lang_save_file($path, $file, $strings, $local, $packstrings) {
686 global $CFG, $USER;
687 if (LANG_KEEP_ORPHANS) {
688 // let us load the current content of the file
689 unset($string);
690 @include("$path/$file");
691 if (isset($string)) {
692 $orphans = $string;
693 unset($string);
694 } else {
695 $orphans = array();
698 // let us rewrite the file
699 if (!$f = @fopen("$path/$file","w")) {
700 return false;
703 fwrite($f, "<?PHP // \$Id\$ \n");
704 fwrite($f, " // $file - created with Moodle $CFG->release ($CFG->version)\n");
705 if ($local) {
706 fwrite($f, " // local modifications from $CFG->wwwroot\n");
708 fwrite($f, "\n\n");
709 ksort($strings);
710 foreach ($strings as $key => $value) {
711 @list($id, $stringname) = explode('XXX',$key);
712 $value = lang_fix_value_before_save($value);
713 if ($id == "string" and $value != ""){
714 if ((!$local) || (!isset($packstrings[$stringname])) || (lang_fix_value_from_file($packstrings[$stringname]) <> lang_fix_value_from_file($value))) {
715 // Either we are saving the master language pack
716 // or the string is not saved in packstring - fixes PHP notices about missing key
717 // or we are saving local language pack and the strings differ.
718 fwrite($f,"\$string['$stringname'] = '$value';\n");
720 if (LANG_KEEP_ORPHANS && isset($orphans[$stringname])) {
721 unset($orphans[$stringname]);
725 if (LANG_KEEP_ORPHANS) {
726 // let us add orphaned strings, i.e. already translated strings without the English referential source
727 foreach ($orphans as $key => $value) {
728 fwrite($f,"\$string['$key'] = '".lang_fix_value_before_save($value)."'; // ORPHANED\n");
731 fwrite($f,"\n?>\n");
732 fclose($f);
733 return true;
737 * Fix value of the translated string after it is load from the file.
739 * These modifications are typically necessary to work with the same string coming from two sources.
740 * We need to compare the content of these sources and we want to have e.g. "This string\r\n"
741 * to be the same as " This string\n".
743 * @param string $value Original string from the file
744 * @return string Fixed value
746 function lang_fix_value_from_file($value='') {
747 $value = str_replace("\r","",$value); // Bad character caused by Windows
748 $value = preg_replace("/\n{3,}/", "\n\n", $value); // Collapse runs of blank lines
749 $value = trim($value); // Delete leading/trailing white space
750 $value = str_replace("\\","",$value); // Delete all slashes
751 $value = str_replace("%%","%",$value);
752 $value = str_replace("&","&amp;",$value); // Fixes MDL-9248
753 $value = str_replace("<","&lt;",$value);
754 $value = str_replace(">","&gt;",$value);
755 $value = str_replace('"',"&quot;",$value);
756 return $value;
760 * Fix value of the translated string before it is saved into the file
762 * @uses $CFG
763 * @param string $value Raw string to be saved into the lang pack
764 * @return string Fixed value
766 function lang_fix_value_before_save($value='') {
767 global $CFG;
768 if ($CFG->lang != "zh_hk" and $CFG->lang != "zh_tw") { // Some MB languages include backslash bytes
769 $value = str_replace("\\","",$value); // Delete all slashes
771 if (ini_get_bool('magic_quotes_sybase')) { // Unescape escaped sybase quotes
772 $value = str_replace("''", "'", $value);
774 $value = str_replace("'", "\\'", $value); // Add slashes for '
775 $value = str_replace('"', "\\\"", $value); // Add slashes for "
776 $value = str_replace("%","%%",$value); // Escape % characters
777 $value = str_replace("\r", "",$value); // Remove linefeed characters
778 $value = trim($value); // Delete leading/trailing white space
779 return $value;
783 * Try and create a new language directory.
785 * @uses $CFG
786 * @param string $directory full path to the directory under $langbase
787 * @return string|false Returns full path to directory if successful, false if not
789 function lang_make_directory($dir, $shownotices=true) {
790 global $CFG;
791 umask(0000);
792 if (! file_exists($dir)) {
793 if (! @mkdir($dir, $CFG->directorypermissions)) {
794 return false;
796 //@chmod($dir, $CFG->directorypermissions); // Just in case mkdir didn't do it
798 return $dir;
802 * Return the string key name for use in HTML form.
804 * Required because '.' in form input names get replaced by '_' by PHP.
806 * @param string $keyfromfile The key name containing '.'
807 * @return string The key name without '.'
809 function lang_form_string_key($keyfromfile) {
810 return str_replace('.', '##46#', $keyfromfile); /// Derived from &#46, the ascii value for a period.
814 * Return the string key name for use in file.
816 * Required because '.' in form input names get replaced by '_' by PHP.
818 * @param string $keyfromfile The key name without '.'
819 * @return string The key name containing '.'
821 function lang_file_string_key($keyfromform) {
822 return str_replace('##46#', '.', $keyfromform);
826 * Return the substring of the string and take care of XHTML compliance.
828 * There was a problem with pure substr() which could possibly produce XHTML parsing error:
829 * substr('Marks &amp; Spencer', 0, 9) -> 'Marks &am' ... is not XHTML compliance
830 * This function takes care of these cases. Fixes MDL-8852.
832 * Thanks to kovacsendre, the author of the function at http://php.net/substr
834 * @param string $str The original string
835 * @param int $start Start position in the $value string
836 * @param int $length Optional length of the returned substring
837 * @return string The substring as returned by substr() with XHTML compliance
838 * @todo Seems the function does not work with negative $start together with $length being set
840 function lang_xhtml_save_substr($str, $start, $length = NULL) {
841 if ($length === 0) {
842 //stop wasting our time ;)
843 return "";
846 //check if we can simply use the built-in functions
847 if (strpos($str, '&') === false) {
848 // No entities. Use built-in functions
849 if ($length === NULL) {
850 return substr($str, $start);
851 } else {
852 return substr($str, $start, $length);
856 // create our array of characters and html entities
857 $chars = preg_split('/(&[^;\s]+;)|/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
858 $html_length = count($chars);
860 // check if we can predict the return value and save some processing time, i.e.:
861 // input string was empty OR
862 // $start is longer than the input string OR
863 // all characters would be omitted
864 if (($html_length === 0) or ($start >= $html_length) or (isset($length) and ($length <= -$html_length))) {
865 return '';
868 //calculate start position
869 if ($start >= 0) {
870 $real_start = $chars[$start][1];
871 } else {
872 //start'th character from the end of string
873 $start = max($start,-$html_length);
874 $real_start = $chars[$html_length+$start][1];
877 if (!isset($length)) {
878 // no $length argument passed, return all remaining characters
879 return substr($str, $real_start);
880 } elseif ($length > 0) {
881 // copy $length chars
882 if ($start+$length >= $html_length) {
883 // return all remaining characters
884 return substr($str, $real_start);
885 } else {
886 //return $length characters
887 return substr($str, $real_start, $chars[max($start,0)+$length][1] - $real_start);
889 } else {
890 //negative $length. Omit $length characters from end
891 return substr($str, $real_start, $chars[$html_length+$length][1] - $real_start);
896 * Finds all English string files in the standard lang/en_utf8 location.
898 * Core lang files should always be stored here and not in the module space (MDL-10920).
899 * The English version of the file may be found in
900 * $CFG->dirroot/lang/en_utf8/filename
901 * The localised version of the found file should be saved into
902 * $CFG->dataroot/lang/currentlang[_local]/filename
903 * where "filename" is returned as a part of the file record.
905 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
907 function lang_standard_locations() {
908 global $CFG;
909 $files = array();
910 // Standard location of master English string files.
911 $places = array($CFG->dirroot.'/lang/en_utf8');
912 foreach ($places as $place) {
913 foreach (get_directory_list($place, '', false) as $file) {
914 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
915 $fullpath = $place.'/'.$file;
916 $files[$fullpath] = array(
917 'filename' => $file,
918 'location' => '',
919 'plugin' => '',
920 'prefix' => '',
925 return $files;
929 * Finds all English string files in non-standard location.
931 * Searches for lang/en_utf8/*.php in various types of plugins (blocks, database presets, question types,
932 * 3rd party modules etc.) and returns an array of found files details.
934 * The English version of the file may be found in
935 * $CFG->dirroot/location/plugin/lang/en_utf8/filename
936 * The localised version of the found file should be saved into
937 * $CFG->dataroot/lang/currentlang[_local]/prefix_plugin.php
938 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
940 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
942 function lang_extra_locations() {
943 global $CFG;
944 $files = array();
945 $places = places_to_search_for_lang_strings();
946 foreach ($places as $prefix => $directories) {
947 if ($prefix != '__exceptions') {
948 foreach ($directories as $directory) {
949 foreach (get_list_of_plugins($directory) as $plugin) {
950 $enlangdirlocation = $CFG->dirroot.'/'.$directory.'/'.$plugin.'/lang/en_utf8';
951 foreach (get_directory_list($enlangdirlocation, '', false) as $file) {
952 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
953 $fullpath = $enlangdirlocation.'/'.$file;
954 $files[$fullpath] = array(
955 'filename' => $file,
956 'location' => $directory,
957 'plugin' => $plugin,
958 'prefix' => $prefix,
966 return $files;
970 * Lookup for a stringfile details.
972 * English files can be stored in several places (core space or module/plugin space). Their translations
973 * go into the one directory - the current language pack. Therefore, the name of the stringfile may be
974 * considered as a key of the list of all stringfiles.
976 * @param string $currentfile the filename
977 * @param array $stringfiles the array of file info returned by {@link lang_extra_locations()}
978 * @return array Array of a file information (filename, location, plugin, prefix) or null.
980 function lang_get_file_info($currentfile, $stringfiles) {
981 $found = false;
982 foreach ($stringfiles as $path=>$stringfile) {
983 if ($stringfile['filename'] == $currentfile) {
984 $found = true;
985 $ret = $stringfile;
986 $ret['fullpath'] = $path;
987 break;
990 if ($found) {
991 return $ret;
992 } else {
993 return null;