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?
10 $dbg = ''; // debug output;
12 require_once('../config.php');
13 require_once($CFG->libdir
.'/adminlib.php');
15 admin_externalpage_setup('langedit');
17 $context = get_context_instance(CONTEXT_SYSTEM
);
19 define('LANG_SUBMIT_REPEAT', 1); // repeat displaying submit button?
20 define('LANG_SUBMIT_REPEAT_EVERY', 20); // if so, after how many lines?
21 define('LANG_DISPLAY_MISSING_LINKS', 1); // display "go to first/next missing string" links?
22 define('LANG_DEFAULT_FILE', ''); // default file to translate. Empty allowed
23 define('LANG_DEFAULT_HELPFILE', ''); // default helpfile to translate. Empty allowed
24 define('LANG_LINK_MISSING_STRINGS', 1); // create links from "missing" page to "compare" page?
25 define('LANG_DEFAULT_USELOCAL', 0); // should *_utf8_local be used by default?
26 define('LANG_MISSING_TEXT_MAX_LEN', 60); // maximum length of the missing text to display
27 define('LANG_KEEP_ORPHANS', 1); // keep orphaned strings (i.e. strings w/o English reference)
28 define('LANG_SEARCH_EXTRA', 1); // search lang files in extra locations
29 define('LANG_ALWAYS_TEXTAREA', 1); // always use <textarea> even for short strings MDL-15738
31 $mode = optional_param('mode', '', PARAM_ALPHA
);
32 if ($mode == 'helpfiles') {
33 // use different PARAM_ options according to mode
34 $currentfile = optional_param('currentfile', LANG_DEFAULT_HELPFILE
, PARAM_PATH
);
36 $currentfile = optional_param('currentfile', LANG_DEFAULT_FILE
, PARAM_FILE
);
38 $uselocal = optional_param('uselocal', -1, PARAM_INT
);
40 if ($uselocal == -1) {
41 if (isset($SESSION->langtranslateintolocal
)) {
42 $uselocal = $SESSION->langtranslateintolocal
;
44 $uselocal = LANG_DEFAULT_USELOCAL
;
47 $SESSION->langtranslateintolocal
= $uselocal;
50 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id
, false)) {
55 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id
, false) && (!$uselocal)) {
56 print_error('cannoteditmasterlang');
59 if ((!has_capability('moodle/site:langeditlocal', $context, $USER->id
, false)) && ($uselocal)) {
60 print_error('cannotcustomizelocallang');
63 $strlanguage = get_string("language");
64 $strcurrentlanguage = get_string("currentlanguage");
65 $strmissingstrings = get_string("missingstrings");
66 $streditstrings = get_string("editstrings", 'admin');
67 $stredithelpdocs = get_string("edithelpdocs", 'admin');
68 $strthislanguage = get_string("thislanguage");
69 $strgotofirst = get_string('gotofirst','admin');
70 $strfilestoredin = get_string('filestoredin', 'admin');
71 $strfilestoredinhelp = get_string('filestoredinhelp', 'admin');
72 $strswitchlang = get_string('switchlang', 'admin');
73 $strchoosefiletoedit = get_string('choosefiletoedit', 'admin');
74 $streditennotallowed = get_string('langnoeditenglish', 'admin');
75 $strfilecreated = get_string('filecreated', 'admin');
76 $strprev = get_string('previous');
77 $strnext = get_string('next');
78 $strlocalstringcustomization = get_string('localstringcustomization', 'admin');
79 $strlangpackmaintaining = get_string('langpackmaintaining', 'admin');
80 $strnomissingstrings = get_string('nomissingstrings', 'admin');
81 $streditingnoncorelangfile = get_string('editingnoncorelangfile', 'admin');
82 $strlanglocalpackage = get_string('langlocalpackage', 'admin');
83 $strlangmasterpackage = get_string('langmasterpackage', 'admin');
84 $strlangmasterenglish = get_string('langmasterenglish', 'admin');
86 $currentlang = current_language();
90 // Missing array keys are not bugs here but missing strings
91 error_reporting(E_ALL ^ E_NOTICE
);
92 $title = $strmissingstrings;
95 $title = $streditstrings;
98 $title = $stredithelpdocs;
100 $title = $strlanguage;
103 $navlinks[] = array('name' => $strlanguage, 'link' => "$CFG->wwwroot/$CFG->admin/lang.php", 'type' => 'misc');
104 $navigation = build_navigation($navlinks);
106 admin_externalpage_print_header();
108 // Prepare and render menu tabs
110 $secondrow = array();
115 $inactive = array('uselocal');
116 $activated = array('uselocal');
118 $inactive = array('usemaster');
119 $activated = array('usemaster');
121 if (has_capability('moodle/site:langeditlocal', $context, $USER->id
, false)) {
122 $firstrow[] = new tabobject('uselocal',
123 "$CFG->wwwroot/$CFG->admin/lang.php?mode=$mode&currentfile=$currentfile&uselocal=1",
124 $strlocalstringcustomization );
126 if (has_capability('moodle/site:langeditmaster', $context, $USER->id
, false)) {
127 $firstrow[] = new tabobject('usemaster',
128 "$CFG->wwwroot/$CFG->admin/lang.php?mode=$mode&currentfile=$currentfile&uselocal=0",
129 $strlangpackmaintaining );
131 $secondrow[] = new tabobject('missing', "$CFG->wwwroot/$CFG->admin/lang.php?mode=missing", $strmissingstrings );
132 $secondrow[] = new tabobject('compare', "$CFG->wwwroot/$CFG->admin/lang.php?mode=compare", $streditstrings );
133 $secondrow[] = new tabobject('helpfiles', "$CFG->wwwroot/$CFG->admin/lang.php?mode=helpfiles", $stredithelpdocs );
134 $tabs = array($firstrow, $secondrow);
135 print_tabs($tabs, $currenttab, $inactive, $activated);
139 // TODO this is a very nice place to put some translation statistics
141 $currlang = current_language();
142 $langs = get_list_of_languages(false, true);
143 popup_form ("$CFG->wwwroot/$CFG->admin/lang.php?lang=", $langs, "chooselang", $currlang, "", "", "", false, 'self', $strcurrentlanguage.':');
145 admin_externalpage_print_footer();
149 // Get a list of all the root files in the English directory
151 $langbase = $CFG->dataroot
. '/lang';
152 $enlangdir = "$CFG->dirroot/lang/en_utf8";
153 if ($currentlang == 'en_utf8') {
154 $langdir = $enlangdir;
156 $langdir = "$langbase/$currentlang";
158 $locallangdir = "$langbase/{$currentlang}_local";
159 $saveto = $uselocal ?
$locallangdir : $langdir;
161 if (($mode == 'missing') ||
($mode == 'compare')) {
162 // get the list of all English stringfiles
163 $stringfiles = lang_standard_locations();
164 if (LANG_SEARCH_EXTRA
) {
165 $stringfiles +
= lang_extra_locations();
167 if (count($stringfiles) == 0) {
168 error("Could not find English language pack!");
170 } elseif ($mode == 'helpfiles') {
171 $helpfiles = lang_help_standard_locations();
172 if (LANG_SEARCH_EXTRA
) {
173 $helpfiles +
= lang_help_extra_locations();
175 if (count($helpfiles) == 0) {
176 error("Could not find help files in the English language pack!");
182 if ($mode == 'missing') {
183 if (!file_exists($langdir)) {
184 error ('to edit this language pack, you need to put it in '.$CFG->dataroot
.'/lang');
187 // Following variables store the HTML output to be echo-ed
193 // Total number of strings and missing strings
194 $totalcounter->strings
= 0;
195 $totalcounter->missing
= 0;
197 // For each file, check that a counterpart exists, then check all the strings
198 foreach ($stringfiles as $stringfile) {
199 $location = $stringfile['location'];
200 $plugin = $stringfile['plugin'];
201 $prefix = $stringfile['prefix'];
202 $filename = $stringfile['filename'];
205 // Get some information about file locations:
206 // $enfilepath = the path to the English file distributed either in the core space or in plugin space
207 // $trfilepath = the path to the translated file distributed either in the lang pack or in plugin space
208 // $lcfilepath = the path to the _local customization
209 // $trfilename = the filename of the translated version of the file (including prefix for non-core files)
210 if ($location ||
$plugin) {
211 // non-core file in an extra location
212 $enfilepath = "$CFG->dirroot/$location/$plugin/lang/en_utf8/$filename";
213 $trfilepath = "$CFG->dirroot/$location/$plugin/lang/$currentlang/$filename";
214 $lcfilepath = "$locallangdir/$filename";
215 $trfilename = $filename;
221 // core file in standard location
222 $enfilepath = "$CFG->dirroot/lang/en_utf8/$filename";
223 $trfilepath = "$langdir/$filename";
224 $lcfilepath = "$locallangdir/$filename";
225 $trfilename = $filename;
227 // $enstring = English strings distributed either in the core space or in plugin space
228 include($enfilepath);
229 $enstring = isset($string) ?
$string : array();
233 //$lcstring = local customizations
235 if (file_exists($lcfilepath)) {
236 include($lcfilepath);
237 $localfileismissing = 0;
238 if (isset($string) && is_array($string)) {
244 $localfileismissing = 1;
247 // $string = translated strings distibuted either in core lang pack or in plugin space
249 if (file_exists($trfilepath)) {
250 include($trfilepath);
254 $o .= notify(get_string("filemissing", "", $trfilepath), "notifyproblem", "center", true);
259 $first = true; // first missing string found in the file
260 // For all English strings in the current file check distributed translations and _local customizations
261 foreach ($enstring as $key => $value) {
262 $totalcounter->strings++
;
263 $missingstring = false;
264 $missinglocalstring = false;
265 $translationsdiffer = false;
266 if (empty($string[$key]) and $string[$key] != "0") { // MDL-4735
267 // string is missing in distributed pack
268 $missingstring = true;
270 if (empty($lcstring[$key]) and $lcstring[$key] != "0") { // MDL-4735
271 // string is missing in _local customization
272 $missinglocalstring = true;
274 if (!$missingstring && !$missinglocalstring && ($lcstring[$key] != $string[$key])) {
275 $translationsdiffer = true;
277 if ($missingstring ||
$translationsdiffer) {
278 $value = htmlspecialchars($value);
279 $value = str_replace("$"."a", "\\$"."a", $value);
280 $value = str_replace("%%","%",$value);
282 $m .= "<a href=\"lang.php?mode=missing#$trfilename\">$trfilename";
283 $m .= $fileismissing ?
'*' : '';
284 $m .= '</a> ';
285 $o .= "<p><a name=\"$trfilename\"></a><b>".
286 get_string("stringsnotset","", $trfilepath)."</b></p><pre>";
288 $somethingfound = true;
290 if ($missingstring) {
292 $totalcounter->missing++
;
294 if ($translationsdiffer) {
297 if (LANG_LINK_MISSING_STRINGS
&& ($missingstring ||
$translationsdiffer)) {
298 $missinglinkstart = "<a href=\"lang.php?mode=compare&currentfile=$filename#$key\">";
299 $missinglinkend = '</a>';
301 $missinglinkstart = '';
302 $missinglinkend = '';
304 if (strlen($value) > LANG_MISSING_TEXT_MAX_LEN
) {
305 $value = lang_xhtml_save_substr($value, 0, LANG_MISSING_TEXT_MAX_LEN
) . ' ...'; // MDL-8852
307 if ($translationsdiffer) {
310 $o .= "$"."string['".$missinglinkstart.$key.$missinglinkend."'] = \"$value\";";
311 if ($translationsdiffer) {
312 $o .= ' // differs from the translation in _local';
313 } elseif (!$missinglocalstring) {
314 $o .= ' // translated only in _local';
320 $o .= '</pre><hr />';
324 if ($totalcounter->missing
> 0) {
325 $totalcounter->missingpercent
= sprintf('%02.1f', ($totalcounter->missing
/ $totalcounter->strings
* 100));
326 print_heading(get_string('numberofstrings', 'admin', $totalcounter), '', 4);
328 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
332 print_box($m, 'filenames');
337 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/help", "CVS")) {
338 error("Could not find English language help files!");
341 foreach ($files as $filekey => $file) { // check all the help files.
342 if (!file_exists("$langdir/help/$file")) {
343 notify(get_string("filemissing", "", "$langdir/help/$file"), 'notifyproblem');
344 $somethingfound = true;
349 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/docs", "CVS")) {
350 error("Could not find English language docs files!");
352 foreach ($files as $filekey => $file) { // check all the docs files.
353 if (!file_exists("$langdir/docs/$file")) {
354 notify(get_string("filemissing", "", "$langdir/docs/$file"), 'notifyproblem');
355 $somethingfound = true;
360 if (!empty($somethingfound)) {
361 print_continue("lang.php");
363 notice(get_string("languagegood"), "lang.php" );
366 } else if ($mode == 'compare') {
368 if (!file_exists($langbase) ){
369 if (!lang_make_directory($langbase) ){
370 error('ERROR: Could not create base lang directory ' . $langbase);
372 echo '<div class="notifysuccess">Created directory '.
373 $langbase .'</div>'."<br />\n";
376 if (!$uselocal && !file_exists($langdir)) {
377 if (!lang_make_directory($langdir)) {
378 error('ERROR: Could not create directory '.$langdir);
380 echo '<div class="notifysuccess">Created directory '.
381 $langdir .'</div>'."<br />\n";
384 if ($uselocal && !file_exists($locallangdir)) {
385 if (!lang_make_directory($locallangdir)) {
386 echo '<div class="notifyproblem">ERROR: Could not create directory '.
387 $locallangdir .'</div>'."<br />\n";
390 echo '<div class="notifysuccess">Created directory '.
391 $locallangdir .'</div>'."<br />\n";
395 if ($currentfile <> '') {
396 if (!$fileinfo = lang_get_file_info($currentfile, $stringfiles)) {
397 error('Unable to find info for: '.$currentfile);
399 // check the filename is set up correctly, prevents bugs similar to MDL-10920
400 $location = $fileinfo['location'];
401 $plugin = $fileinfo['plugin'];
402 $prefix = $fileinfo['prefix'];
403 $filename = $fileinfo['filename'];
404 if ($location ||
$plugin) {
405 // file in an extra location
406 if ($currentfile != "{$prefix}{$plugin}.php") {
407 error("Non-core filename mismatch. The file $currentfile should be {$prefix}{$plugin}.php");
410 notify($streditingnoncorelangfile);
414 // file in standard location
415 if ($currentfile != $filename) {
416 error("Core filename mismatch. The file $currentfile should be $filename");
420 // Get some information about file locations:
421 // $enfilepath = the path to the English file distributed either in the core space or in plugin space
422 // $trfilepath = the path to the translated file distributed either in the lang pack or in plugin space
423 // $lcfilepath = the path to the _local customization
424 // $trfilename = the filename of the translated version of the file (including prefix for non-core files)
425 if ($location ||
$plugin) {
426 // non-core file in an extra location
427 $enfilepath = "$CFG->dirroot/$location/$plugin/lang/en_utf8/$filename";
428 $trfilepath = "$CFG->dirroot/$location/$plugin/lang/$currentlang/$filename";
429 $lcfilepath = "$locallangdir/$filename";
430 $trfilename = $filename;
432 // core file in standard location
433 $enfilepath = "$CFG->dirroot/lang/en_utf8/$filename";
434 $trfilepath = "$langdir/$filename";
435 $lcfilepath = "$locallangdir/$filename";
436 $trfilename = $filename;
440 if (isset($_POST['currentfile'])){ // Save a file
441 if (!confirm_sesskey()) {
442 print_error('confirmsesskeybad', 'error');
445 $newstrings = array();
447 foreach ($_POST as $postkey => $postval) {
448 $stringkey = lang_file_string_key($postkey);
449 $newstrings[$stringkey] = $postval;
452 unset($newstrings['currentfile']);
454 $packstring = array();
455 $saveinto = $langdir;
457 if(file_exists($trfilepath)) {
458 include($trfilepath);
459 if (isset($string)) {
460 $packstring = $string;
464 $saveinto = $locallangdir;
467 if (lang_save_file($saveinto, $currentfile, $newstrings, $uselocal, $packstring)) {
468 notify(get_string("changessaved")." ($saveinto/$currentfile)", "notifysuccess");
470 error("Could not save the file '$saveinto/$currentfile'!", "lang.php?mode=compare&currentfile=$currentfile");
475 print_box_start('generalbox editstrings');
476 $menufiles = array();
477 $menufiles_coregrp = 1;
478 foreach ($stringfiles as $stringfile) {
479 $item_key = $stringfile['filename'];
480 $item_label = $stringfile['filename'];
481 if ($stringfile['location'] != '' && $stringfile['plugin'] != '') {
482 $item_label .= ' ('.$stringfile['location'].'/'.$stringfile['plugin'].')';
483 if ($menufiles_coregrp == 1) {
484 $menufiles['extra'] = '------------';
485 $menufiles_coregrp = 0;
488 $menufiles[$item_key] = $item_label;
490 $selectionlabel = '<code class="path">';
491 //$selectionlabel .= $strfilestoredin;
492 $selectionlabel .= $uselocal ?
"{$currentlang}_local" : $currentlang;
493 $selectionlabel .= '/</code>';
494 popup_form("$CFG->wwwroot/$CFG->admin/lang.php?mode=compare&currentfile=", $menufiles, "choosefile",
495 $currentfile, $strchoosefiletoedit, '', '', false, 'self', $selectionlabel);
496 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
499 if ($currentfile <> '') {
501 if (!isset($editable) ||
$editable) {
502 if (!file_exists("$saveto/$currentfile")) {
503 if (!@touch
("$saveto/$currentfile")) {
504 print_heading(get_string("filemissing", "", "$saveto/$currentfile"), '', 4, 'error');
506 print_heading($strfilecreated, '', 4, 'notifysuccess');
509 if ($currentlang == "en_utf8" && !$uselocal) {
511 print_heading($streditennotallowed, '', 4);
512 } elseif ($f = fopen("$saveto/$currentfile","r+")) {
517 notify(get_string("makeeditable", "", "$saveto/$currentfile"), 'notifyproblem');
520 error_reporting($CFG->debug
);
522 $o = ''; // stores the HTML output to be echo-ed
525 include($enfilepath);
526 $enstring = isset($string) ?
$string : array();
528 // Following strings have moved into langconfig.php, but keep the here for backward compatibility
530 if ($currentlang != 'en' and $currentfile == 'moodle.php') {
531 $enstring['thislanguage'] = "<< TRANSLATORS: Specify the name of your language here. If possible use Unicode Numeric Character References >>";
532 $enstring['thischarset'] = "<< TRANSLATORS: Charset encoding - always use utf-8 >>";
533 $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. >>";
534 $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 >>";
539 @include
($lcfilepath);
540 $localstring = isset($string) ?
$string : array();
544 @include
($trfilepath);
545 $string = isset($string) ?
$string : array();
549 $o .= "<form id=\"$currentfile\" action=\"lang.php\" method=\"post\">";
552 $o .= "<table summary=\"\" width=\"100%\" class=\"translator\">";
555 foreach ($enstring as $key => $envalue) {
557 if (LANG_SUBMIT_REPEAT
&& $editable && $linescounter % LANG_SUBMIT_REPEAT_EVERY
== 0) {
558 $o .= '<tr><td> </td><td><br />';
559 $o .= '<input type="submit" tabindex="'.$missingcounter.'" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
560 $o .= '<br /> </td></tr>';
562 $envalue = nl2br(htmlspecialchars($envalue));
563 $envalue = preg_replace('/(\$a\-\>[a-zA-Z0-9]*|\$a)/', '<b>$0</b>', $envalue); // Make variables bold.
564 $envalue = str_replace("%%","%",$envalue);
565 $envalue = str_replace("\\","",$envalue); // Delete all slashes
567 $o .= "\n\n".'<tr class="';
568 if ($linescounter %
2 == 0) {
574 $o .= '<td dir="ltr" lang="en">';
575 $o .= '<span id="'.$key.'" class="stren">'.$envalue.'</span>';
577 $o .= '<span class="strkey">'.$key.'</span>';
580 // Missing array keys are not bugs here but missing strings
581 error_reporting(E_ALL ^ E_NOTICE
);
583 $value = lang_fix_value_from_file($localstring[$key]);
584 $value2 = lang_fix_value_from_file($string[$key]);
589 $value = lang_fix_value_from_file($string[$key]);
590 $value2 = lang_fix_value_from_file($localstring[$key]);
592 error_reporting($CFG->debug
);
597 $usetabindex = false;
599 // the string is not present in the pack being processed
601 $cellcolour = 'class="bothmissing"';
604 $cellcolour = 'class="mastermissing"';
608 if (LANG_DISPLAY_MISSING_LINKS
) {
609 $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
610 $missingnext = '<a href="#missing'.($missingcounter+
1).'">'.
611 '<img src="' . $CFG->pixpath
. '/t/down.gif" class="iconsmall" alt="'.$strnext.'" /></a>';
612 $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
613 '<img src="' . $CFG->pixpath
. '/t/up.gif" class="iconsmall" alt="'.$strprev.'" /></a>';
616 // the string is translated in the pack being processed
617 if ($value <> $value2 && ($value2 <> '')) {
618 $cellcolour = 'class="localdifferent"';
621 if (LANG_DISPLAY_MISSING_LINKS
) {
622 $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
623 $missingnext = '<a href="#missing'.($missingcounter+
1).'">'.
624 '<img src="' . $CFG->pixpath
. '/t/down.gif" class="iconsmall" alt="'.$strnext.'" /></a>';
625 $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
626 '<img src="' . $CFG->pixpath
. '/t/up.gif" class="iconsmall" alt="'.$strprev.'" /></a>';
632 $o .= '<td '.$cellcolour.' valign="top">';
633 if ($missingcounter > 1) {
636 $o .= $missingtarget."\n";
637 if (isset($string[$key])) {
638 $valuelen = strlen($value);
640 $valuelen = strlen($envalue);
644 $tabindex = 'tabindex="'.$missingcounter.'"';
648 if (strstr($value, "\r") or strstr($value, "\n") or $valuelen > $cols) {
649 $rows = ceil($valuelen / $cols);
650 $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="'.$rows.'" '.$tabindex.'>'.$value.'</textarea>'."\n";
653 $cols = $valuelen +
5;
655 if (LANG_ALWAYS_TEXTAREA
) {
656 $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="1" '.$tabindex.'>'.$value.'</textarea>'."\n";
658 $o .= '<input type="text" name="stringXXX'.lang_form_string_key($key).'" value="'.$value.'" size="'.$cols.'" '.$tabindex.' />';
661 if ($value2 <> '' && $value <> $value2) {
662 $o .= '<br /><span style="font-size:small">'.$value2.'</span>';
664 $o .= $missingnext . '</td>';
667 $o .= '<td '.$cellcolour.' valign="top">'.$value.'<br />'.$value2.'</td>';
672 $o .= '<tr><td> </td><td><br />';
673 $o .= '<input type="hidden" name="sesskey" value="'.$USER->sesskey
.'" />';
674 $o .= '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
675 $o .= '<input type="hidden" name="mode" value="compare" />';
676 $o .= '<input type="submit" name="update" tabindex="'.$missingcounter.'" value="'.get_string('savechanges').': '.$currentfile.'" />';
685 if (LANG_DISPLAY_MISSING_LINKS
) {
686 if ($missingcounter > 0) {
687 print_heading(get_string('numberofmissingstrings', 'admin', $missingcounter), '', 4);
689 print_heading('<a href="#missing1">'.$strgotofirst.'</a>', "", 4);
692 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
698 // no $currentfile specified
699 // no useful information to display - maybe some help? instructions?
702 } elseif ($mode == 'helpfiles') {
704 $saveto = $saveto.'/help'; // the edited content will be saved to
705 $enlangdir = $enlangdir.'/help'; // English master help files localtion
706 $langdir = $langdir.'/help'; // current language master help files location
707 $locallangdir = $locallangdir.'/help'; // local modifications of help files location
708 $altdir = $uselocal ?
$langdir : $locallangdir; // alternative to $saveto
710 $fileeditorrows = 10; // number of textareas' rows
711 $fileeditorcols = 100; // dtto cols
712 $filemissingmark = ' (***)'; // mark to add to non-existing or zero-length files
713 $fileoldmark = ' (old?)'; // mark to add to filenames in selection form if the English version is newer
714 $filetemplate = ''; // template for new files, e.g. CVS identification
716 if (isset($_POST['currentfile'])) { // Save a file
717 if (!confirm_sesskey()) {
718 print_error('confirmsesskeybad', 'error');
720 if (lang_help_save_file($saveto, $currentfile, $_POST['filedata'])) {
721 notify(get_string("changessaved")." ($saveto/$currentfile)", "notifysuccess");
723 error("Could not save the file '$currentfile'!", "lang.php?mode=helpfiles&currentfile=$currentfile&sesskey=$USER->sesskey");
727 print_box_start('generalbox editstrings');
728 $menufiles = array();
729 $menufiles_coregrp = 1;
730 $origlocation = ''; // the location of the currentfile's English source will be stored here
731 $origplugin = ''; // dtto plugin
732 foreach ($helpfiles as $helppath => $helpfile) {
733 $item_key = $helpfile['filename'];
734 $item_label = $helpfile['filename'];
735 if ((!file_exists($saveto.'/'.$helpfile['filename'])) ||
(filesize($saveto.'/'.$helpfile['filename']) == 0)) {
736 $item_label .= $filemissingmark;
738 if (filemtime($saveto.'/'.$helpfile['filename']) < filemtime($helppath)) {
739 $item_label .= $fileoldmark;
741 if ($helpfile['location'] != '' && $helpfile['plugin'] != '') {
742 $item_label .= ' ('.$helpfile['location'].'/'.$helpfile['plugin'].')';
743 if ($menufiles_coregrp == 1) {
744 $menufiles['extra'] = '------------';
745 $menufiles_coregrp = 0;
749 $menufiles[$item_key] = $item_label;
750 if ($currentfile == $helpfile['filename']) {
751 $origlocation = $helpfile['location'];
752 $origplugin = $helpfile['plugin'];
755 $selectionlabel = '<code class="path">';
756 //$selectionlabel .= $strfilestoredin;
757 $selectionlabel .= $uselocal ?
"{$currentlang}_local" : $currentlang;
758 $selectionlabel .= '/help/</code>';
759 popup_form("$CFG->wwwroot/$CFG->admin/lang.php?mode=helpfiles&currentfile=", $menufiles, "choosefile",
760 $currentfile, $strchoosefiletoedit, '', '', false, 'self', $selectionlabel);
761 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
764 if (!empty($currentfile)) {
766 if (!file_exists("$saveto/$currentfile")) {
767 $dbg .= "File does not exist: $saveto/$currentfile\n";
768 //check if directory exist
769 if (!file_exists(dirname("$saveto/$currentfile"))) {
770 if(!lang_make_directory(dirname("$saveto/$currentfile"))) {
771 echo ('Cannot create directory: '.dirname("$saveto/$currentfile"));
775 // file doesn't exist - let's check webserver's permission to create it
777 if (!@touch
("$saveto/$currentfile")) {
779 // webserver is unable to create new file
781 notify(get_string('filemissing', '', "$saveto/$currentfile" ));
782 notify(get_string('makeeditable', '', "$saveto/$currentfile"));
786 // webserver can create new file - we can delete it now and let
787 // it create again if its filesize() > 0
790 unlink("$saveto/$currentfile");
792 } elseif (is_writable("$saveto/$currentfile")) {
796 // file exists but it is not writeable by web server process :-(
799 notify(get_string('makeeditable', '', "$saveto/$currentfile"));
802 // master en_utf8 in dataroot is not editable
803 if ((!$uselocal) && ($currentlang == 'en_utf8')) {
810 $strsavetotitle = $strlanglocalpackage . helpbutton('langpackages', $strlanglocalpackage, 'moodle', true, false, '', true);
811 $straltdirtitle = $strlangmasterpackage . helpbutton('langpackages', $strlangmasterpackage, 'moodle', true, false, '', true);
813 $straltdirtitle = $strlanglocalpackage . helpbutton('langpackages', $strlanglocalpackage, 'moodle', true, false, '', true);
814 $strsavetotitle = $strlangmasterpackage . helpbutton('langpackages', $strlangmasterpackage, 'moodle', true, false, '', true);
819 // generate an editor for the current help file in $saveto
820 echo '<fieldset><legend>'.$strsavetotitle.'</legend>';
821 echo "<form id=\"helpfileeditor\" action=\"lang.php\" method=\"post\">";
822 echo '<input type="hidden" name="sesskey" value="'.$USER->sesskey
.'" />';
823 echo '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
824 echo '<input type="hidden" name="mode" value="helpfiles" />';
825 echo "<div class='mdl-align'>\n";
826 echo "<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"filedata\">";
827 if (file_exists("$saveto/$currentfile")) {
828 echo htmlspecialchars(file_get_contents("$saveto/$currentfile"));
830 echo ($filetemplate);
832 echo "</textarea>\n</div>\n";
833 echo '<div class="mdl-align"><input type="submit" value="'.get_string('savechanges').'" /></div>';
835 $preview_url = lang_help_preview_url($currentfile, !$uselocal);
837 link_to_popup_window($preview_url, 'popup', get_string('preview'));
842 if (is_readable("$altdir/$currentfile")) {
843 // show the content of the same help file in alternative location
844 echo '<fieldset><legend>'.$straltdirtitle.'</legend>';
845 echo "<div class='mdl-align'>\n";
846 echo "<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"\">";
847 if (file_exists("$altdir/$currentfile")) {
848 echo htmlspecialchars(file_get_contents("$altdir/$currentfile"));
850 echo ($filetemplate);
852 echo "</textarea>\n</div>\n";
853 $preview_url = lang_help_preview_url($currentfile, $uselocal);
855 link_to_popup_window($preview_url, 'popup', get_string('preview'));
860 // show the content of the original English file either in core space or plugin space
861 if ($origlocation != '' && $origplugin != '') {
862 // non-core help file
863 $ensrc = "$CFG->dirroot/$origlocation/$origplugin/lang/en_utf8/help/$currentfile";
866 $ensrc = "$enlangdir/$currentfile";
868 if (is_readable($ensrc)) {
869 echo '<fieldset><legend>'.$strlangmasterenglish;
870 helpbutton('langpackages', $strlangmasterenglish);
872 echo "<div class='mdl-align'>\n<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"\">";
873 echo htmlspecialchars(file_get_contents($ensrc));
874 echo "</textarea>\n</div>\n";
875 $preview_url = lang_help_preview_url($currentfile, true, 'en_utf8'); // do not display en_utf8_local
877 link_to_popup_window($preview_url, 'popup', get_string('preview'));
882 echo '</div>'; // translator box
883 error_reporting($CFG->debug
);
886 if (false && $CFG->debugdisplay
&& debugging('', DEBUG_DEVELOPER
) ) {
888 print_heading('Debugging info');
889 echo '<pre class="notifytiny">';
891 print_r("\n\$currentfile = $currentfile");
892 print_r("\n\$enlangdir = $enlangdir");
893 print_r("\n\$langdir = $langdir");
894 print_r("\n\$locallangdir = $locallangdir");
895 print_r("\n\$saveto = $saveto");
896 print_r("\n\$altdir = $altdir");
897 print_r("\n\$origlocation = $origlocation");
898 print_r("\n\$origplugin = $origplugin");
899 print_r("\n\$ensrc = $ensrc");
900 print_r("\n\$helpfiles = ");
905 } // fi $mode == 'helpfiles'
908 admin_externalpage_print_footer();
910 //////////////////////////////////////////////////////////////////////
913 * Save language translation file.
915 * Thanks to Petri Asikainen for the original version of code
916 * used to save language files.
920 * @param string $path Full pathname to the directory to use
921 * @param string $file File to overwrite
922 * @param array $strings Array of strings to write
923 * @param bool $local Should *_local version be saved?
924 * @param array $packstrings Array of default langpack strings (needed if $local)
925 * @return bool Created successfully?
927 function lang_save_file($path, $file, $strings, $local, $packstrings) {
929 if (LANG_KEEP_ORPHANS
) {
930 // let us load the current content of the file
932 @include
("$path/$file");
933 if (isset($string)) {
940 // let us rewrite the file
941 if (!$f = @fopen
("$path/$file","w")) {
945 fwrite($f, "<?PHP // \$Id\$ \n");
946 fwrite($f, " // $file - created with Moodle $CFG->release ($CFG->version)\n");
948 fwrite($f, " // local modifications from $CFG->wwwroot\n");
952 foreach ($strings as $key => $value) {
953 @list
($id, $stringname) = explode('XXX',$key);
954 $value = lang_fix_value_before_save($value);
955 if ($id == "string" and $value != ""){
956 if ((!$local) ||
(!isset($packstrings[$stringname])) ||
(lang_fix_value_from_file($packstrings[$stringname]) <> lang_fix_value_from_file($value))) {
957 // Either we are saving the master language pack
958 // or the string is not saved in packstring - fixes PHP notices about missing key
959 // or we are saving local language pack and the strings differ.
960 fwrite($f,"\$string['$stringname'] = '$value';\n");
962 if (LANG_KEEP_ORPHANS
&& isset($orphans[$stringname])) {
963 unset($orphans[$stringname]);
967 if (LANG_KEEP_ORPHANS
) {
968 // let us add orphaned strings, i.e. already translated strings without the English referential source
969 foreach ($orphans as $key => $value) {
970 fwrite($f,"\$string['$key'] = '".lang_fix_value_before_save($value)."'; // ORPHANED\n");
979 * Fix value of the translated string after it is load from the file.
981 * These modifications are typically necessary to work with the same string coming from two sources.
982 * We need to compare the content of these sources and we want to have e.g. "This string\r\n"
983 * to be the same as " This string\n".
985 * @param string $value Original string from the file
986 * @return string Fixed value
988 function lang_fix_value_from_file($value='') {
989 $value = str_replace("\r","",$value); // Bad character caused by Windows
990 $value = preg_replace("/\n{3,}/", "\n\n", $value); // Collapse runs of blank lines
991 $value = trim($value); // Delete leading/trailing white space
992 $value = str_replace("\\","",$value); // Delete all slashes
993 $value = str_replace("%%","%",$value);
994 $value = str_replace("&","&",$value); // Fixes MDL-9248
995 $value = str_replace("<","<",$value);
996 $value = str_replace(">",">",$value);
997 $value = str_replace('"',""",$value);
1002 * Fix value of the translated string before it is saved into the file
1005 * @param string $value Raw string to be saved into the lang pack
1006 * @return string Fixed value
1008 function lang_fix_value_before_save($value='') {
1010 if ($CFG->lang
!= "zh_hk" and $CFG->lang
!= "zh_tw") { // Some MB languages include backslash bytes
1011 $value = str_replace("\\","",$value); // Delete all slashes
1013 if (ini_get_bool('magic_quotes_sybase')) { // Unescape escaped sybase quotes
1014 $value = str_replace("''", "'", $value);
1016 // escape all embedded variables
1017 $value = str_replace('$', '\$', $value); // Add slashes for $
1018 // unescape placeholders: only $a and $a->something are allowed. All other $variables are left escaped
1019 $value = preg_replace('/\\\\\$a($|[^_a-zA-Z0-9\-]|\->[a-zA-Z0-9_]+)/', '$a\\1', $value);
1020 $value = str_replace("'", "\\'", $value); // Add slashes for '
1021 $value = str_replace('"', "\\\"", $value); // Add slashes for "
1022 $value = str_replace("%","%%",$value); // Escape % characters
1023 $value = str_replace("\r", "",$value); // Remove linefeed characters
1024 $value = trim($value); // Delete leading/trailing white space
1029 * Try and create a new language directory.
1031 * Uses PHP>=5.0 syntax of mkdir and tries to create directories recursively.
1034 * @param string $directory full path to the directory under $langbase
1035 * @return string|false Returns full path to directory if successful, false if not
1037 function lang_make_directory($dir, $shownotices=true) {
1040 if (! file_exists($dir)) {
1041 if (! @mkdir
($dir, $CFG->directorypermissions
, true)) { // recursive=true; PHP>=5.0 needed
1044 //@chmod($dir, $CFG->directorypermissions); // Just in case mkdir didn't do it
1050 * Return the string key name for use in HTML form.
1052 * Required because '.' in form input names get replaced by '_' by PHP.
1054 * @param string $keyfromfile The key name containing '.'
1055 * @return string The key name without '.'
1057 function lang_form_string_key($keyfromfile) {
1058 return str_replace('.', '##46#', $keyfromfile); /// Derived from ., the ascii value for a period.
1062 * Return the string key name for use in file.
1064 * Required because '.' in form input names get replaced by '_' by PHP.
1066 * @param string $keyfromfile The key name without '.'
1067 * @return string The key name containing '.'
1069 function lang_file_string_key($keyfromform) {
1070 return str_replace('##46#', '.', $keyfromform);
1074 * Return the substring of the string and take care of XHTML compliance.
1076 * There was a problem with pure substr() which could possibly produce XHTML parsing error:
1077 * substr('Marks & Spencer', 0, 9) -> 'Marks &am' ... is not XHTML compliance
1078 * This function takes care of these cases. Fixes MDL-8852.
1080 * Thanks to kovacsendre, the author of the function at http://php.net/substr
1082 * @param string $str The original string
1083 * @param int $start Start position in the $value string
1084 * @param int $length Optional length of the returned substring
1085 * @return string The substring as returned by substr() with XHTML compliance
1086 * @todo Seems the function does not work with negative $start together with $length being set
1088 function lang_xhtml_save_substr($str, $start, $length = NULL) {
1089 if ($length === 0) {
1090 //stop wasting our time ;)
1094 //check if we can simply use the built-in functions
1095 if (strpos($str, '&') === false) {
1096 // No entities. Use built-in functions
1097 if ($length === NULL) {
1098 return substr($str, $start);
1100 return substr($str, $start, $length);
1104 // create our array of characters and html entities
1105 $chars = preg_split('/(&[^;\s]+;)|/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE
);
1106 $html_length = count($chars);
1108 // check if we can predict the return value and save some processing time, i.e.:
1109 // input string was empty OR
1110 // $start is longer than the input string OR
1111 // all characters would be omitted
1112 if (($html_length === 0) or ($start >= $html_length) or (isset($length) and ($length <= -$html_length))) {
1116 //calculate start position
1118 $real_start = $chars[$start][1];
1120 //start'th character from the end of string
1121 $start = max($start,-$html_length);
1122 $real_start = $chars[$html_length+
$start][1];
1125 if (!isset($length)) {
1126 // no $length argument passed, return all remaining characters
1127 return substr($str, $real_start);
1128 } elseif ($length > 0) {
1129 // copy $length chars
1130 if ($start+
$length >= $html_length) {
1131 // return all remaining characters
1132 return substr($str, $real_start);
1134 //return $length characters
1135 return substr($str, $real_start, $chars[max($start,0)+
$length][1] - $real_start);
1138 //negative $length. Omit $length characters from end
1139 return substr($str, $real_start, $chars[$html_length+
$length][1] - $real_start);
1144 * Finds all English string files in the standard lang/en_utf8 location.
1146 * Core lang files should always be stored here and not in the module space (MDL-10920).
1147 * The English version of the file may be found in
1148 * $CFG->dirroot/lang/en_utf8/filename
1149 * The localised version of the found file should be saved into
1150 * $CFG->dataroot/lang/currentlang[_local]/filename
1151 * where "filename" is returned as a part of the file record.
1153 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
1155 function lang_standard_locations() {
1158 // Standard location of master English string files.
1159 $places = array($CFG->dirroot
.'/lang/en_utf8');
1160 foreach ($places as $place) {
1161 foreach (get_directory_list($place, '', false) as $file) {
1162 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
1163 $fullpath = $place.'/'.$file;
1164 $files[$fullpath] = array(
1165 'filename' => $file,
1177 * Finds all English string files in non-standard location.
1179 * Searches for lang/en_utf8/*.php in various types of plugins (blocks, database presets, question types,
1180 * 3rd party modules etc.) and returns an array of found files details.
1182 * The English version of the file may be found in
1183 * $CFG->dirroot/location/plugin/lang/en_utf8/filename
1184 * The localised version of the found file should be saved into
1185 * $CFG->dataroot/lang/currentlang[_local]/prefix_plugin.php
1186 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
1188 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
1190 function lang_extra_locations() {
1193 $places = places_to_search_for_lang_strings();
1194 foreach ($places as $prefix => $directories) {
1195 if ($prefix != '__exceptions') {
1196 foreach ($directories as $directory) {
1197 foreach (get_list_of_plugins($directory) as $plugin) {
1198 $enlangdirlocation = $CFG->dirroot
.'/'.$directory.'/'.$plugin.'/lang/en_utf8';
1199 foreach (get_directory_list($enlangdirlocation, '', false) as $file) {
1200 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
1201 $fullpath = $enlangdirlocation.'/'.$file;
1202 $files[$fullpath] = array(
1203 'filename' => $file,
1204 'location' => $directory,
1205 'plugin' => $plugin,
1206 'prefix' => $prefix,
1218 * Lookup for a stringfile details.
1220 * English files can be stored in several places (core space or module/plugin space). Their translations
1221 * go into the one directory - the current language pack. Therefore, the name of the stringfile may be
1222 * considered as a key of the list of all stringfiles.
1224 * @param string $currentfile the filename
1225 * @param array $stringfiles the array of file info returned by {@link lang_extra_locations()}
1226 * @return array Array of a file information (filename, location, plugin, prefix) or null.
1228 function lang_get_file_info($currentfile, $stringfiles) {
1230 foreach ($stringfiles as $path=>$stringfile) {
1231 if ($stringfile['filename'] == $currentfile) {
1234 $ret['fullpath'] = $path;
1246 * Returns all English help files in the standard lang/en_utf8/help location.
1248 * Core help files should always be stored here and not in the module space (MDL-10920).
1249 * The English version of the file may be found in
1250 * $CFG->dirroot/lang/en_utf8/help/filename
1251 * The localised version of the found file should be saved into
1252 * $CFG->dataroot/lang/currentlang[_local]/help/filename
1253 * where "filename" is returned as a part of the file record.
1255 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
1257 function lang_help_standard_locations() {
1260 // Standard location of master English help files.
1261 $places = array($CFG->dirroot
.'/lang/en_utf8/help');
1262 foreach ($places as $place) {
1263 foreach (get_directory_list($place, 'CVS') as $file) {
1264 if ((substr($file, -5) == '.html') ||
(substr($file, -4) == '.txt' )) {
1265 $fullpath = $place.'/'.$file;
1266 $files[$fullpath] = array(
1267 'filename' => $file,
1279 * Returns all English help files in non-standard location.
1281 * Searches for lang/en_utf8/help/* files in various types of plugins (blocks, database presets, question types,
1282 * 3rd party modules etc.) and returns an array of found files details.
1284 * The English version of the file may be found in
1285 * $CFG->dirroot/location/plugin/lang/en_utf8/help/filename
1286 * The localised version of the found file should be saved into
1287 * $CFG->dataroot/lang/currentlang[_local]/help/prefix_plugin/filename (XXX is "prefix" here right?)
1288 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
1290 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
1292 function lang_help_extra_locations() {
1295 $places = places_to_search_for_lang_strings();
1296 foreach ($places as $prefix => $directories) {
1297 if ($prefix != '__exceptions') {
1298 foreach ($directories as $directory) {
1299 foreach (get_list_of_plugins($directory) as $plugin) {
1300 $enlangdirlocation = $CFG->dirroot
.'/'.$directory.'/'.$plugin.'/lang/en_utf8/help';
1301 foreach (get_directory_list($enlangdirlocation, 'CVS') as $file) {
1302 if ((substr($file, -5) == '.html') ||
(substr($file, -4) == '.txt' )) {
1303 $fullpath = $enlangdirlocation.'/'.$file;
1304 $files[$fullpath] = array(
1305 'filename' => $file,
1306 'location' => $directory,
1307 'plugin' => $plugin,
1308 'prefix' => $prefix,
1320 * Return a preview URL for help file, if available.
1322 * @param string $currentfile The relative path to the help file, e.g. "assignment/types.html" - MDL-12291
1323 * @param bool $skiplocal Force displaying the helpfile from a master lang pack
1324 * @param string $forcelang Force language of the help, e.g. "en_utf8"
1325 * @return string $url
1327 function lang_help_preview_url($currentfile, $skiplocal=false, $forcelang = '') {
1328 $currentpathexp = explode('/', $currentfile);
1329 if (count($currentpathexp) > 1) {
1330 $url = '/help.php?module='.implode('/',array_slice($currentpathexp,0,count($currentpathexp)-1)).'&file='.end($currentpathexp);
1332 $url = '/help.php?module=moodle&file='.$currentfile;
1335 $url .= '&skiplocal=1';
1338 $url .= '&forcelang='.$forcelang;
1345 * Saves (overwrites) translated help file.
1347 * @param string $helproot The path to the "help" folder
1348 * @param string $file The relative path to the html help file
1349 * @param string $content HTML data to be saved
1350 * @return bool False if save failed, true otherwise
1352 function lang_help_save_file($helproot, $file, $content) {
1355 $content = str_replace("\r", "",$content); // Remove linefeed characters
1356 $content = preg_replace("/\n{3,}/", "\n\n", $content); // Collapse runs of blank lines
1357 $content = trim($content); // Delete leading/trailing whitespace
1358 if (is_readable("$helproot/$file") && filesize("$helproot/$file") > 0 && $content == '') {
1359 notify(get_string('langrmyourself', 'admin'));
1364 if (!$f = fopen("$helproot/$file","w")) {
1365 error_reporting($CFG->debug
);
1368 error_reporting($CFG->debug
);
1370 fwrite($f, stripslashes($content));
1373 // Remove file if its empty
1374 if (filesize("$helproot/$file") == 0) {
1375 unlink("$helproot/$file");