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
, SITEID
);
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
30 $mode = optional_param('mode', '', PARAM_ALPHA
);
31 if ($mode == 'helpfiles') {
32 // use different PARAM_ options according to mode
33 $currentfile = optional_param('currentfile', LANG_DEFAULT_HELPFILE
, PARAM_PATH
);
35 $currentfile = optional_param('currentfile', LANG_DEFAULT_FILE
, PARAM_FILE
);
37 $uselocal = optional_param('uselocal', -1, PARAM_INT
);
39 if ($uselocal == -1) {
40 if (isset($SESSION->langtranslateintolocal
)) {
41 $uselocal = $SESSION->langtranslateintolocal
;
43 $uselocal = LANG_DEFAULT_USELOCAL
;
46 $SESSION->langtranslateintolocal
= $uselocal;
49 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id
, false)) {
54 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id
, false) && (!$uselocal)) {
55 print_error('cannoteditmasterlang');
58 if ((!has_capability('moodle/site:langeditlocal', $context, $USER->id
, false)) && ($uselocal)) {
59 print_error('cannotcustomizelocallang');
62 $strlanguage = get_string("language");
63 $strcurrentlanguage = get_string("currentlanguage");
64 $strmissingstrings = get_string("missingstrings");
65 $streditstrings = get_string("editstrings", 'admin');
66 $stredithelpdocs = get_string("edithelpdocs", 'admin');
67 $strthislanguage = get_string("thislanguage");
68 $strgotofirst = get_string('gotofirst','admin');
69 $strfilestoredin = get_string('filestoredin', 'admin');
70 $strfilestoredinhelp = get_string('filestoredinhelp', 'admin');
71 $strswitchlang = get_string('switchlang', 'admin');
72 $strchoosefiletoedit = get_string('choosefiletoedit', 'admin');
73 $streditennotallowed = get_string('langnoeditenglish', 'admin');
74 $strfilecreated = get_string('filecreated', 'admin');
75 $strprev = get_string('previous');
76 $strnext = get_string('next');
77 $strlocalstringcustomization = get_string('localstringcustomization', 'admin');
78 $strlangpackmaintaining = get_string('langpackmaintaining', 'admin');
79 $strnomissingstrings = get_string('nomissingstrings', 'admin');
80 $streditingnoncorelangfile = get_string('editingnoncorelangfile', 'admin');
81 $strlanglocalpackage = get_string('langlocalpackage', 'admin');
82 $strlangmasterpackage = get_string('langmasterpackage', 'admin');
83 $strlangmasterenglish = get_string('langmasterenglish', 'admin');
85 $currentlang = current_language();
89 // Missing array keys are not bugs here but missing strings
90 error_reporting(E_ALL ^ E_NOTICE
);
91 $title = $strmissingstrings;
94 $title = $streditstrings;
97 $title = $stredithelpdocs;
99 $title = $strlanguage;
102 $navlinks[] = array('name' => $strlanguage, 'link' => "$CFG->wwwroot/$CFG->admin/lang.php", 'type' => 'misc');
103 $navigation = build_navigation($navlinks);
105 admin_externalpage_print_header();
107 // Prepare and render menu tabs
109 $secondrow = array();
114 $inactive = array('uselocal');
115 $activated = array('uselocal');
117 $inactive = array('usemaster');
118 $activated = array('usemaster');
120 if (has_capability('moodle/site:langeditlocal', $context, $USER->id
, false)) {
121 $firstrow[] = new tabobject('uselocal',
122 "$CFG->wwwroot/$CFG->admin/lang.php?mode=$mode&currentfile=$currentfile&uselocal=1",
123 $strlocalstringcustomization );
125 if (has_capability('moodle/site:langeditmaster', $context, $USER->id
, false)) {
126 $firstrow[] = new tabobject('usemaster',
127 "$CFG->wwwroot/$CFG->admin/lang.php?mode=$mode&currentfile=$currentfile&uselocal=0",
128 $strlangpackmaintaining );
130 $secondrow[] = new tabobject('missing', "$CFG->wwwroot/$CFG->admin/lang.php?mode=missing", $strmissingstrings );
131 $secondrow[] = new tabobject('compare', "$CFG->wwwroot/$CFG->admin/lang.php?mode=compare", $streditstrings );
132 $secondrow[] = new tabobject('helpfiles', "$CFG->wwwroot/$CFG->admin/lang.php?mode=helpfiles", $stredithelpdocs );
133 $tabs = array($firstrow, $secondrow);
134 print_tabs($tabs, $currenttab, $inactive, $activated);
138 // TODO this is a very nice place to put some translation statistics
140 $currlang = current_language();
141 $langs = get_list_of_languages(false, true);
142 popup_form ("$CFG->wwwroot/$CFG->admin/lang.php?lang=", $langs, "chooselang", $currlang, "", "", "", false, 'self', $strcurrentlanguage.':');
144 admin_externalpage_print_footer();
148 // Get a list of all the root files in the English directory
150 $langbase = $CFG->dataroot
. '/lang';
151 $enlangdir = "$CFG->dirroot/lang/en_utf8";
152 if ($currentlang == 'en_utf8') {
153 $langdir = $enlangdir;
155 $langdir = "$langbase/$currentlang";
157 $locallangdir = "$langbase/{$currentlang}_local";
158 $saveto = $uselocal ?
$locallangdir : $langdir;
160 if (($mode == 'missing') ||
($mode == 'compare')) {
161 // get the list of all English stringfiles
162 $stringfiles = lang_standard_locations();
163 if (LANG_SEARCH_EXTRA
) {
164 $stringfiles +
= lang_extra_locations();
166 if (count($stringfiles) == 0) {
167 error("Could not find English language pack!");
169 } elseif ($mode == 'helpfiles') {
170 $helpfiles = lang_help_standard_locations();
171 if (LANG_SEARCH_EXTRA
) {
172 $helpfiles +
= lang_help_extra_locations();
174 if (count($helpfiles) == 0) {
175 error("Could not find help files in the English language pack!");
181 if ($mode == 'missing') {
182 if (!file_exists($langdir)) {
183 error ('to edit this language pack, you need to put it in '.$CFG->dataroot
.'/lang');
186 // Following variables store the HTML output to be echo-ed
192 // Total number of strings and missing strings
193 $totalcounter->strings
= 0;
194 $totalcounter->missing
= 0;
196 // For each file, check that a counterpart exists, then check all the strings
197 foreach ($stringfiles as $stringfile) {
198 $location = $stringfile['location'];
199 $plugin = $stringfile['plugin'];
200 $prefix = $stringfile['prefix'];
201 $filename = $stringfile['filename'];
204 // Get some information about file locations:
205 // $enfilepath = the path to the English file distributed either in the core space or in plugin space
206 // $trfilepath = the path to the translated file distributed either in the lang pack or in plugin space
207 // $lcfilepath = the path to the _local customization
208 // $trfilename = the filename of the translated version of the file (including prefix for non-core files)
209 if ($location ||
$plugin) {
210 // non-core file in an extra location
211 $enfilepath = "$CFG->dirroot/$location/$plugin/lang/en_utf8/$filename";
212 $trfilepath = "$CFG->dirroot/$location/$plugin/lang/$currentlang/$filename";
213 $lcfilepath = "$locallangdir/$filename";
214 $trfilename = $filename;
220 // core file in standard location
221 $enfilepath = "$CFG->dirroot/lang/en_utf8/$filename";
222 $trfilepath = "$langdir/$filename";
223 $lcfilepath = "$locallangdir/$filename";
224 $trfilename = $filename;
226 // $enstring = English strings distributed either in the core space or in plugin space
227 include($enfilepath);
228 $enstring = isset($string) ?
$string : array();
232 //$lcstring = local customizations
234 if (file_exists($lcfilepath)) {
235 include($lcfilepath);
236 $localfileismissing = 0;
237 if (isset($string) && is_array($string)) {
243 $localfileismissing = 1;
246 // $string = translated strings distibuted either in core lang pack or in plugin space
248 if (file_exists($trfilepath)) {
249 include($trfilepath);
253 $o .= notify(get_string("filemissing", "", $trfilepath), "notifyproblem", "center", true);
258 $first = true; // first missing string found in the file
259 // For all English strings in the current file check distributed translations and _local customizations
260 foreach ($enstring as $key => $value) {
261 $totalcounter->strings++
;
262 $missingstring = false;
263 $missinglocalstring = false;
264 $translationsdiffer = false;
265 if (empty($string[$key]) and $string[$key] != "0") { // MDL-4735
266 // string is missing in distributed pack
267 $missingstring = true;
269 if (empty($lcstring[$key]) and $lcstring[$key] != "0") { // MDL-4735
270 // string is missing in _local customization
271 $missinglocalstring = true;
273 if (!$missingstring && !$missinglocalstring && ($lcstring[$key] != $string[$key])) {
274 $translationsdiffer = true;
276 if ($missingstring ||
$translationsdiffer) {
277 $value = htmlspecialchars($value);
278 $value = str_replace("$"."a", "\\$"."a", $value);
279 $value = str_replace("%%","%",$value);
281 $m .= "<a href=\"lang.php?mode=missing#$trfilename\">$trfilename";
282 $m .= $fileismissing ?
'*' : '';
283 $m .= '</a> ';
284 $o .= "<p><a name=\"$trfilename\"></a><b>".
285 get_string("stringsnotset","", $trfilepath)."</b></p><pre>";
287 $somethingfound = true;
289 if ($missingstring) {
291 $totalcounter->missing++
;
293 if ($translationsdiffer) {
296 if (LANG_LINK_MISSING_STRINGS
&& ($missingstring ||
$translationsdiffer)) {
297 $missinglinkstart = "<a href=\"lang.php?mode=compare&currentfile=$filename#missing$missingcounter\">";
298 $missinglinkend = '</a>';
300 $missinglinkstart = '';
301 $missinglinkend = '';
303 if (strlen($value) > LANG_MISSING_TEXT_MAX_LEN
) {
304 $value = lang_xhtml_save_substr($value, 0, LANG_MISSING_TEXT_MAX_LEN
) . ' ...'; // MDL-8852
306 if ($translationsdiffer) {
309 $o .= "$"."string['".$missinglinkstart.$key.$missinglinkend."'] = \"$value\";";
310 if ($translationsdiffer) {
311 $o .= ' // differs from the translation in _local';
312 } elseif (!$missinglocalstring) {
313 $o .= ' // translated only in _local';
319 $o .= '</pre><hr />';
323 if ($totalcounter->missing
> 0) {
324 $totalcounter->missingpercent
= sprintf('%02.1f', ($totalcounter->missing
/ $totalcounter->strings
* 100));
325 print_heading(get_string('numberofstrings', 'admin', $totalcounter), '', 4);
327 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
331 print_box($m, 'filenames');
336 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/help", "CVS")) {
337 error("Could not find English language help files!");
340 foreach ($files as $filekey => $file) { // check all the help files.
341 if (!file_exists("$langdir/help/$file")) {
342 notify(get_string("filemissing", "", "$langdir/help/$file"), 'notifyproblem');
343 $somethingfound = true;
348 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/docs", "CVS")) {
349 error("Could not find English language docs files!");
351 foreach ($files as $filekey => $file) { // check all the docs files.
352 if (!file_exists("$langdir/docs/$file")) {
353 notify(get_string("filemissing", "", "$langdir/docs/$file"), 'notifyproblem');
354 $somethingfound = true;
359 if (!empty($somethingfound)) {
360 print_continue("lang.php");
362 notice(get_string("languagegood"), "lang.php" );
365 } else if ($mode == 'compare') {
367 if (!file_exists($langbase) ){
368 if (!lang_make_directory($langbase) ){
369 error('ERROR: Could not create base lang directory ' . $langbase);
371 echo '<div class="notifysuccess">Created directory '.
372 $langbase .'</div>'."<br />\n";
375 if (!$uselocal && !file_exists($langdir)) {
376 if (!lang_make_directory($langdir)) {
377 error('ERROR: Could not create directory '.$langdir);
379 echo '<div class="notifysuccess">Created directory '.
380 $langdir .'</div>'."<br />\n";
383 if ($uselocal && !file_exists($locallangdir)) {
384 if (!lang_make_directory($locallangdir)) {
385 echo '<div class="notifyproblem">ERROR: Could not create directory '.
386 $locallangdir .'</div>'."<br />\n";
389 echo '<div class="notifysuccess">Created directory '.
390 $locallangdir .'</div>'."<br />\n";
394 if ($currentfile <> '') {
395 if (!$fileinfo = lang_get_file_info($currentfile, $stringfiles)) {
396 error('Unable to find info for: '.$currentfile);
398 // check the filename is set up correctly, prevents bugs similar to MDL-10920
399 $location = $fileinfo['location'];
400 $plugin = $fileinfo['plugin'];
401 $prefix = $fileinfo['prefix'];
402 $filename = $fileinfo['filename'];
403 if ($location ||
$plugin) {
404 // file in an extra location
405 if ($currentfile != "{$prefix}{$plugin}.php") {
406 error("Non-core filename mismatch. The file $currentfile should be {$prefix}{$plugin}.php");
409 notify($streditingnoncorelangfile);
413 // file in standard location
414 if ($currentfile != $filename) {
415 error("Core filename mismatch. The file $currentfile should be $filename");
419 // Get some information about file locations:
420 // $enfilepath = the path to the English file distributed either in the core space or in plugin space
421 // $trfilepath = the path to the translated file distributed either in the lang pack or in plugin space
422 // $lcfilepath = the path to the _local customization
423 // $trfilename = the filename of the translated version of the file (including prefix for non-core files)
424 if ($location ||
$plugin) {
425 // non-core file in an extra location
426 $enfilepath = "$CFG->dirroot/$location/$plugin/lang/en_utf8/$filename";
427 $trfilepath = "$CFG->dirroot/$location/$plugin/lang/$currentlang/$filename";
428 $lcfilepath = "$locallangdir/$filename";
429 $trfilename = $filename;
431 // core file in standard location
432 $enfilepath = "$CFG->dirroot/lang/en_utf8/$filename";
433 $trfilepath = "$langdir/$filename";
434 $lcfilepath = "$locallangdir/$filename";
435 $trfilename = $filename;
439 if (isset($_POST['currentfile'])){ // Save a file
440 if (!confirm_sesskey()) {
441 print_error('confirmsesskeybad', 'error');
444 $newstrings = array();
446 foreach ($_POST as $postkey => $postval) {
447 $stringkey = lang_file_string_key($postkey);
448 $newstrings[$stringkey] = $postval;
451 unset($newstrings['currentfile']);
453 $packstring = array();
454 $saveinto = $langdir;
456 if(file_exists($trfilepath)) {
457 include($trfilepath);
458 if (isset($string)) {
459 $packstring = $string;
463 $saveinto = $locallangdir;
466 if (lang_save_file($saveinto, $currentfile, $newstrings, $uselocal, $packstring)) {
467 notify(get_string("changessaved")." ($saveinto/$currentfile)", "notifysuccess");
469 error("Could not save the file '$saveinto/$currentfile'!", "lang.php?mode=compare&currentfile=$currentfile");
474 print_box_start('generalbox editstrings');
475 $menufiles = array();
476 $menufiles_coregrp = 1;
477 foreach ($stringfiles as $stringfile) {
478 $item_key = $stringfile['filename'];
479 $item_label = $stringfile['filename'];
480 if ($stringfile['location'] != '' && $stringfile['plugin'] != '') {
481 $item_label .= ' ('.$stringfile['location'].'/'.$stringfile['plugin'].')';
482 if ($menufiles_coregrp == 1) {
483 $menufiles['extra'] = '------------';
484 $menufiles_coregrp = 0;
487 $menufiles[$item_key] = $item_label;
489 $selectionlabel = '<code class="path">';
490 //$selectionlabel .= $strfilestoredin;
491 $selectionlabel .= $uselocal ?
"{$currentlang}_local" : $currentlang;
492 $selectionlabel .= '/</code>';
493 popup_form("$CFG->wwwroot/$CFG->admin/lang.php?mode=compare&currentfile=", $menufiles, "choosefile",
494 $currentfile, $strchoosefiletoedit, '', '', false, 'self', $selectionlabel);
495 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
498 if ($currentfile <> '') {
500 if (!isset($editable) ||
$editable) {
501 if (!file_exists("$saveto/$currentfile")) {
502 if (!@touch
("$saveto/$currentfile")) {
503 print_heading(get_string("filemissing", "", "$saveto/$currentfile"), '', 4, 'error');
505 print_heading($strfilecreated, '', 4, 'notifysuccess');
508 if ($currentlang == "en_utf8" && !$uselocal) {
510 print_heading($streditennotallowed, '', 4);
511 } elseif ($f = fopen("$saveto/$currentfile","r+")) {
516 notify(get_string("makeeditable", "", "$saveto/$currentfile"), 'notifyproblem');
519 error_reporting($CFG->debug
);
521 $o = ''; // stores the HTML output to be echo-ed
524 include($enfilepath);
525 $enstring = isset($string) ?
$string : array();
527 // Following strings have moved into langconfig.php, but keep the here for backward compatibility
529 if ($currentlang != 'en' and $currentfile == 'moodle.php') {
530 $enstring['thislanguage'] = "<< TRANSLATORS: Specify the name of your language here. If possible use Unicode Numeric Character References >>";
531 $enstring['thischarset'] = "<< TRANSLATORS: Charset encoding - always use utf-8 >>";
532 $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. >>";
533 $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 >>";
538 @include
($lcfilepath);
539 $localstring = isset($string) ?
$string : array();
543 @include
($trfilepath);
544 $string = isset($string) ?
$string : array();
548 $o .= "<form id=\"$currentfile\" action=\"lang.php\" method=\"post\">";
551 $o .= "<table summary=\"\" width=\"100%\" class=\"translator\">";
554 foreach ($enstring as $key => $envalue) {
556 if (LANG_SUBMIT_REPEAT
&& $editable && $linescounter % LANG_SUBMIT_REPEAT_EVERY
== 0) {
557 $o .= '<tr><td> </td><td><br />';
558 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
559 $o .= '<br /> </td></tr>';
561 $envalue = nl2br(htmlspecialchars($envalue));
562 $envalue = preg_replace('/(\$a\-\>[a-zA-Z0-9]*|\$a)/', '<b>$0</b>', $envalue); // Make variables bold.
563 $envalue = str_replace("%%","%",$envalue);
564 $envalue = str_replace("\\","",$envalue); // Delete all slashes
566 $o .= "\n\n".'<tr class="';
567 if ($linescounter %
2 == 0) {
573 $o .= '<td dir="ltr" lang="en">';
574 $o .= '<span class="stren">'.$envalue.'</span>';
576 $o .= '<span class="strkey">'.$key.'</span>';
579 // Missing array keys are not bugs here but missing strings
580 error_reporting(E_ALL ^ E_NOTICE
);
582 $value = lang_fix_value_from_file($localstring[$key]);
583 $value2 = lang_fix_value_from_file($string[$key]);
588 $value = lang_fix_value_from_file($string[$key]);
589 $value2 = lang_fix_value_from_file($localstring[$key]);
591 error_reporting($CFG->debug
);
596 $usetabindex = false;
598 // the string is not present in the pack being processed
600 $cellcolour = 'class="bothmissing"';
603 $cellcolour = 'class="mastermissing"';
607 if (LANG_DISPLAY_MISSING_LINKS
) {
608 $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
609 $missingnext = '<a href="#missing'.($missingcounter+
1).'">'.
610 '<img src="' . $CFG->pixpath
. '/t/down.gif" class="iconsmall" alt="'.$strnext.'" /></a>';
611 $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
612 '<img src="' . $CFG->pixpath
. '/t/up.gif" class="iconsmall" alt="'.$strprev.'" /></a>';
615 // the string is translated in the pack being processed
616 if ($value <> $value2 && ($uselocal ||
$value2 <> '')) {
617 $cellcolour = 'class="localdifferent"';
620 if (LANG_DISPLAY_MISSING_LINKS
) {
621 $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
622 $missingnext = '<a href="#missing'.($missingcounter+
1).'">'.
623 '<img src="' . $CFG->pixpath
. '/t/down.gif" class="iconsmall" alt="'.$strnext.'" /></a>';
624 $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
625 '<img src="' . $CFG->pixpath
. '/t/up.gif" class="iconsmall" alt="'.$strprev.'" /></a>';
631 $o .= '<td '.$cellcolour.' valign="top">';
632 if ($missingcounter > 1) {
635 $o .= $missingtarget."\n";
636 if (isset($string[$key])) {
637 $valuelen = strlen($value);
639 $valuelen = strlen($envalue);
643 $tabindex = 'tabindex="'.$missingcounter.'"';
647 if (strstr($value, "\r") or strstr($value, "\n") or $valuelen > $cols) {
648 $rows = ceil($valuelen / $cols);
649 $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="'.$rows.'" '.$tabindex.'>'.$value.'</textarea>'."\n";
652 $cols = $valuelen +
5;
654 $o .= '<input type="text" name="stringXXX'.lang_form_string_key($key).'" value="'.$value.'" size="'.$cols.'" '.$tabindex.' />';
656 if ($value2 <> '' && $value <> $value2) {
657 $o .= '<br /><span style="font-size:small">'.$value2.'</span>';
659 $o .= $missingnext . '</td>';
662 $o .= '<td '.$cellcolour.' valign="top">'.$value.'<br />'.$value2.'</td>';
667 $o .= '<tr><td> </td><td><br />';
668 $o .= '<input type="hidden" name="sesskey" value="'.$USER->sesskey
.'" />';
669 $o .= '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
670 $o .= '<input type="hidden" name="mode" value="compare" />';
671 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
680 if (LANG_DISPLAY_MISSING_LINKS
) {
681 if ($missingcounter > 0) {
682 print_heading(get_string('numberofmissingstrings', 'admin', $missingcounter), '', 4);
684 print_heading('<a href="#missing1">'.$strgotofirst.'</a>', "", 4);
687 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
693 // no $currentfile specified
694 // no useful information to display - maybe some help? instructions?
697 } elseif ($mode == 'helpfiles') {
699 $saveto = $saveto.'/help'; // the edited content will be saved to
700 $enlangdir = $enlangdir.'/help'; // English master help files localtion
701 $langdir = $langdir.'/help'; // current language master help files location
702 $locallangdir = $locallangdir.'/help'; // local modifications of help files location
703 $altdir = $uselocal ?
$langdir : $locallangdir; // alternative to $saveto
705 $fileeditorrows = 10; // number of textareas' rows
706 $fileeditorcols = 100; // dtto cols
707 $filemissingmark = ' (***)'; // mark to add to non-existing or zero-length files
708 $fileoldmark = ' (old?)'; // mark to add to filenames in selection form if the English version is newer
709 $filetemplate = ''; // template for new files, e.g. CVS identification
711 if (isset($_POST['currentfile'])) { // Save a file
712 if (!confirm_sesskey()) {
713 print_error('confirmsesskeybad', 'error');
715 if (lang_help_save_file($saveto, $currentfile, $_POST['filedata'])) {
716 notify(get_string("changessaved")." ($saveto/$currentfile)", "notifysuccess");
718 error("Could not save the file '$currentfile'!", "lang.php?mode=helpfiles&currentfile=$currentfile&sesskey=$USER->sesskey");
722 print_box_start('generalbox editstrings');
723 $menufiles = array();
724 $menufiles_coregrp = 1;
725 $origlocation = ''; // the location of the currentfile's English source will be stored here
726 $origplugin = ''; // dtto plugin
727 foreach ($helpfiles as $helppath => $helpfile) {
728 $item_key = $helpfile['filename'];
729 $item_label = $helpfile['filename'];
730 if ((!file_exists($saveto.'/'.$helpfile['filename'])) ||
(filesize($saveto.'/'.$helpfile['filename']) == 0)) {
731 $item_label .= $filemissingmark;
733 if (filemtime($saveto.'/'.$helpfile['filename']) < filemtime($helppath)) {
734 $item_label .= $fileoldmark;
736 if ($helpfile['location'] != '' && $helpfile['plugin'] != '') {
737 $item_label .= ' ('.$helpfile['location'].'/'.$helpfile['plugin'].')';
738 if ($menufiles_coregrp == 1) {
739 $menufiles['extra'] = '------------';
740 $menufiles_coregrp = 0;
744 $menufiles[$item_key] = $item_label;
745 if ($currentfile == $helpfile['filename']) {
746 $origlocation = $helpfile['location'];
747 $origplugin = $helpfile['plugin'];
750 $selectionlabel = '<code class="path">';
751 //$selectionlabel .= $strfilestoredin;
752 $selectionlabel .= $uselocal ?
"{$currentlang}_local" : $currentlang;
753 $selectionlabel .= '/help/</code>';
754 popup_form("$CFG->wwwroot/$CFG->admin/lang.php?mode=helpfiles&currentfile=", $menufiles, "choosefile",
755 $currentfile, $strchoosefiletoedit, '', '', false, 'self', $selectionlabel);
756 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
759 if (!empty($currentfile)) {
761 if (!file_exists("$saveto/$currentfile")) {
762 $dbg .= "File does not exist: $saveto/$currentfile\n";
763 //check if directory exist
764 if (!file_exists(dirname("$saveto/$currentfile"))) {
765 if(!lang_make_directory(dirname("$saveto/$currentfile"))) {
766 echo ('Cannot create directory: '.dirname("$saveto/$currentfile"));
770 // file doesn't exist - let's check webserver's permission to create it
772 if (!@touch
("$saveto/$currentfile")) {
774 // webserver is unable to create new file
776 notify(get_string('filemissing', '', "$saveto/$currentfile" ));
777 notify(get_string('makeeditable', '', "$saveto/$currentfile"));
781 // webserver can create new file - we can delete it now and let
782 // it create again if its filesize() > 0
785 unlink("$saveto/$currentfile");
787 } elseif (is_writable("$saveto/$currentfile")) {
791 // file exists but it is not writeable by web server process :-(
794 notify(get_string('makeeditable', '', "$saveto/$currentfile"));
797 // master en_utf8 in dataroot is not editable
798 if ((!$uselocal) && ($currentlang == 'en_utf8')) {
805 $strsavetotitle = $strlanglocalpackage . helpbutton('langpackages', $strlanglocalpackage, 'moodle', true, false, '', true);
806 $straltdirtitle = $strlangmasterpackage . helpbutton('langpackages', $strlangmasterpackage, 'moodle', true, false, '', true);
808 $straltdirtitle = $strlanglocalpackage . helpbutton('langpackages', $strlanglocalpackage, 'moodle', true, false, '', true);
809 $strsavetotitle = $strlangmasterpackage . helpbutton('langpackages', $strlangmasterpackage, 'moodle', true, false, '', true);
814 // generate an editor for the current help file in $saveto
815 echo '<fieldset><legend>'.$strsavetotitle.'</legend>';
816 echo "<form id=\"helpfileeditor\" action=\"lang.php\" method=\"post\">";
817 echo '<input type="hidden" name="sesskey" value="'.$USER->sesskey
.'" />';
818 echo '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
819 echo '<input type="hidden" name="mode" value="helpfiles" />';
820 echo "<div align=\"center\">\n";
821 echo "<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"filedata\">";
822 if (file_exists("$saveto/$currentfile")) {
823 echo htmlspecialchars(file_get_contents("$saveto/$currentfile"));
825 echo ($filetemplate);
827 echo "</textarea>\n</div>\n";
828 echo '<div align="center"><input type="submit" value="'.get_string('savechanges').'" /></div>';
830 $preview_url = lang_help_preview_url($currentfile, !$uselocal);
832 link_to_popup_window($preview_url, 'popup', get_string('preview'));
837 if (is_readable("$altdir/$currentfile")) {
838 // show the content of the same help file in alternative location
839 echo '<fieldset><legend>'.$straltdirtitle.'</legend>';
840 echo "<div align=\"center\">\n";
841 echo "<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"\">";
842 if (file_exists("$altdir/$currentfile")) {
843 echo htmlspecialchars(file_get_contents("$altdir/$currentfile"));
845 echo ($filetemplate);
847 echo "</textarea>\n</div>\n";
848 $preview_url = lang_help_preview_url($currentfile, $uselocal);
850 link_to_popup_window($preview_url, 'popup', get_string('preview'));
855 // show the content of the original English file either in core space or plugin space
856 if ($origlocation != '' && $origplugin != '') {
857 // non-core help file
858 $ensrc = "$CFG->dirroot/$origlocation/$origplugin/lang/en_utf8/help/$currentfile";
861 $ensrc = "$enlangdir/$currentfile";
863 if (is_readable($ensrc)) {
864 echo '<fieldset><legend>'.$strlangmasterenglish;
865 helpbutton('langpackages', $strlangmasterenglish);
867 echo "<div align=\"center\">\n<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"\">";
868 echo htmlspecialchars(file_get_contents($ensrc));
869 echo "</textarea>\n</div>\n";
870 $preview_url = lang_help_preview_url($currentfile, true, 'en_utf8'); // do not display en_utf8_local
872 link_to_popup_window($preview_url, 'popup', get_string('preview'));
877 echo '</div>'; // translator box
878 error_reporting($CFG->debug
);
881 if (false && $CFG->debugdisplay
&& debugging('', DEBUG_DEVELOPER
) ) {
883 print_heading('Debugging info');
884 echo '<pre class="notifytiny">';
886 print_r("\n\$currentfile = $currentfile");
887 print_r("\n\$enlangdir = $enlangdir");
888 print_r("\n\$langdir = $langdir");
889 print_r("\n\$locallangdir = $locallangdir");
890 print_r("\n\$saveto = $saveto");
891 print_r("\n\$altdir = $altdir");
892 print_r("\n\$origlocation = $origlocation");
893 print_r("\n\$origplugin = $origplugin");
894 print_r("\n\$ensrc = $ensrc");
895 print_r("\n\$helpfiles = ");
900 } // fi $mode == 'helpfiles'
903 admin_externalpage_print_footer();
905 //////////////////////////////////////////////////////////////////////
908 * Save language translation file.
910 * Thanks to Petri Asikainen for the original version of code
911 * used to save language files.
915 * @param string $path Full pathname to the directory to use
916 * @param string $file File to overwrite
917 * @param array $strings Array of strings to write
918 * @param bool $local Should *_local version be saved?
919 * @param array $packstrings Array of default langpack strings (needed if $local)
920 * @return bool Created successfully?
922 function lang_save_file($path, $file, $strings, $local, $packstrings) {
924 if (LANG_KEEP_ORPHANS
) {
925 // let us load the current content of the file
927 @include
("$path/$file");
928 if (isset($string)) {
935 // let us rewrite the file
936 if (!$f = @fopen
("$path/$file","w")) {
940 fwrite($f, "<?PHP // \$Id\$ \n");
941 fwrite($f, " // $file - created with Moodle $CFG->release ($CFG->version)\n");
943 fwrite($f, " // local modifications from $CFG->wwwroot\n");
947 foreach ($strings as $key => $value) {
948 @list
($id, $stringname) = explode('XXX',$key);
949 $value = lang_fix_value_before_save($value);
950 if ($id == "string" and $value != ""){
951 if ((!$local) ||
(!isset($packstrings[$stringname])) ||
(lang_fix_value_from_file($packstrings[$stringname]) <> lang_fix_value_from_file($value))) {
952 // Either we are saving the master language pack
953 // or the string is not saved in packstring - fixes PHP notices about missing key
954 // or we are saving local language pack and the strings differ.
955 fwrite($f,"\$string['$stringname'] = '$value';\n");
957 if (LANG_KEEP_ORPHANS
&& isset($orphans[$stringname])) {
958 unset($orphans[$stringname]);
962 if (LANG_KEEP_ORPHANS
) {
963 // let us add orphaned strings, i.e. already translated strings without the English referential source
964 foreach ($orphans as $key => $value) {
965 fwrite($f,"\$string['$key'] = '".lang_fix_value_before_save($value)."'; // ORPHANED\n");
974 * Fix value of the translated string after it is load from the file.
976 * These modifications are typically necessary to work with the same string coming from two sources.
977 * We need to compare the content of these sources and we want to have e.g. "This string\r\n"
978 * to be the same as " This string\n".
980 * @param string $value Original string from the file
981 * @return string Fixed value
983 function lang_fix_value_from_file($value='') {
984 $value = str_replace("\r","",$value); // Bad character caused by Windows
985 $value = preg_replace("/\n{3,}/", "\n\n", $value); // Collapse runs of blank lines
986 $value = trim($value); // Delete leading/trailing white space
987 $value = str_replace("\\","",$value); // Delete all slashes
988 $value = str_replace("%%","%",$value);
989 $value = str_replace("&","&",$value); // Fixes MDL-9248
990 $value = str_replace("<","<",$value);
991 $value = str_replace(">",">",$value);
992 $value = str_replace('"',""",$value);
997 * Fix value of the translated string before it is saved into the file
1000 * @param string $value Raw string to be saved into the lang pack
1001 * @return string Fixed value
1003 function lang_fix_value_before_save($value='') {
1005 if ($CFG->lang
!= "zh_hk" and $CFG->lang
!= "zh_tw") { // Some MB languages include backslash bytes
1006 $value = str_replace("\\","",$value); // Delete all slashes
1008 if (ini_get_bool('magic_quotes_sybase')) { // Unescape escaped sybase quotes
1009 $value = str_replace("''", "'", $value);
1011 $value = str_replace("'", "\\'", $value); // Add slashes for '
1012 $value = str_replace('"', "\\\"", $value); // Add slashes for "
1013 $value = str_replace("%","%%",$value); // Escape % characters
1014 $value = str_replace("\r", "",$value); // Remove linefeed characters
1015 $value = trim($value); // Delete leading/trailing white space
1020 * Try and create a new language directory.
1022 * Uses PHP>=5.0 syntax of mkdir and tries to create directories recursively.
1025 * @param string $directory full path to the directory under $langbase
1026 * @return string|false Returns full path to directory if successful, false if not
1028 function lang_make_directory($dir, $shownotices=true) {
1031 if (! file_exists($dir)) {
1032 if (! @mkdir
($dir, $CFG->directorypermissions
, true)) { // recursive=true; PHP>=5.0 needed
1035 //@chmod($dir, $CFG->directorypermissions); // Just in case mkdir didn't do it
1041 * Return the string key name for use in HTML form.
1043 * Required because '.' in form input names get replaced by '_' by PHP.
1045 * @param string $keyfromfile The key name containing '.'
1046 * @return string The key name without '.'
1048 function lang_form_string_key($keyfromfile) {
1049 return str_replace('.', '##46#', $keyfromfile); /// Derived from ., the ascii value for a period.
1053 * Return the string key name for use in file.
1055 * Required because '.' in form input names get replaced by '_' by PHP.
1057 * @param string $keyfromfile The key name without '.'
1058 * @return string The key name containing '.'
1060 function lang_file_string_key($keyfromform) {
1061 return str_replace('##46#', '.', $keyfromform);
1065 * Return the substring of the string and take care of XHTML compliance.
1067 * There was a problem with pure substr() which could possibly produce XHTML parsing error:
1068 * substr('Marks & Spencer', 0, 9) -> 'Marks &am' ... is not XHTML compliance
1069 * This function takes care of these cases. Fixes MDL-8852.
1071 * Thanks to kovacsendre, the author of the function at http://php.net/substr
1073 * @param string $str The original string
1074 * @param int $start Start position in the $value string
1075 * @param int $length Optional length of the returned substring
1076 * @return string The substring as returned by substr() with XHTML compliance
1077 * @todo Seems the function does not work with negative $start together with $length being set
1079 function lang_xhtml_save_substr($str, $start, $length = NULL) {
1080 if ($length === 0) {
1081 //stop wasting our time ;)
1085 //check if we can simply use the built-in functions
1086 if (strpos($str, '&') === false) {
1087 // No entities. Use built-in functions
1088 if ($length === NULL) {
1089 return substr($str, $start);
1091 return substr($str, $start, $length);
1095 // create our array of characters and html entities
1096 $chars = preg_split('/(&[^;\s]+;)|/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE
);
1097 $html_length = count($chars);
1099 // check if we can predict the return value and save some processing time, i.e.:
1100 // input string was empty OR
1101 // $start is longer than the input string OR
1102 // all characters would be omitted
1103 if (($html_length === 0) or ($start >= $html_length) or (isset($length) and ($length <= -$html_length))) {
1107 //calculate start position
1109 $real_start = $chars[$start][1];
1111 //start'th character from the end of string
1112 $start = max($start,-$html_length);
1113 $real_start = $chars[$html_length+
$start][1];
1116 if (!isset($length)) {
1117 // no $length argument passed, return all remaining characters
1118 return substr($str, $real_start);
1119 } elseif ($length > 0) {
1120 // copy $length chars
1121 if ($start+
$length >= $html_length) {
1122 // return all remaining characters
1123 return substr($str, $real_start);
1125 //return $length characters
1126 return substr($str, $real_start, $chars[max($start,0)+
$length][1] - $real_start);
1129 //negative $length. Omit $length characters from end
1130 return substr($str, $real_start, $chars[$html_length+
$length][1] - $real_start);
1135 * Finds all English string files in the standard lang/en_utf8 location.
1137 * Core lang files should always be stored here and not in the module space (MDL-10920).
1138 * The English version of the file may be found in
1139 * $CFG->dirroot/lang/en_utf8/filename
1140 * The localised version of the found file should be saved into
1141 * $CFG->dataroot/lang/currentlang[_local]/filename
1142 * where "filename" is returned as a part of the file record.
1144 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
1146 function lang_standard_locations() {
1149 // Standard location of master English string files.
1150 $places = array($CFG->dirroot
.'/lang/en_utf8');
1151 foreach ($places as $place) {
1152 foreach (get_directory_list($place, '', false) as $file) {
1153 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
1154 $fullpath = $place.'/'.$file;
1155 $files[$fullpath] = array(
1156 'filename' => $file,
1168 * Finds all English string files in non-standard location.
1170 * Searches for lang/en_utf8/*.php in various types of plugins (blocks, database presets, question types,
1171 * 3rd party modules etc.) and returns an array of found files details.
1173 * The English version of the file may be found in
1174 * $CFG->dirroot/location/plugin/lang/en_utf8/filename
1175 * The localised version of the found file should be saved into
1176 * $CFG->dataroot/lang/currentlang[_local]/prefix_plugin.php
1177 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
1179 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
1181 function lang_extra_locations() {
1184 $places = places_to_search_for_lang_strings();
1185 foreach ($places as $prefix => $directories) {
1186 if ($prefix != '__exceptions') {
1187 foreach ($directories as $directory) {
1188 foreach (get_list_of_plugins($directory) as $plugin) {
1189 $enlangdirlocation = $CFG->dirroot
.'/'.$directory.'/'.$plugin.'/lang/en_utf8';
1190 foreach (get_directory_list($enlangdirlocation, '', false) as $file) {
1191 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
1192 $fullpath = $enlangdirlocation.'/'.$file;
1193 $files[$fullpath] = array(
1194 'filename' => $file,
1195 'location' => $directory,
1196 'plugin' => $plugin,
1197 'prefix' => $prefix,
1209 * Lookup for a stringfile details.
1211 * English files can be stored in several places (core space or module/plugin space). Their translations
1212 * go into the one directory - the current language pack. Therefore, the name of the stringfile may be
1213 * considered as a key of the list of all stringfiles.
1215 * @param string $currentfile the filename
1216 * @param array $stringfiles the array of file info returned by {@link lang_extra_locations()}
1217 * @return array Array of a file information (filename, location, plugin, prefix) or null.
1219 function lang_get_file_info($currentfile, $stringfiles) {
1221 foreach ($stringfiles as $path=>$stringfile) {
1222 if ($stringfile['filename'] == $currentfile) {
1225 $ret['fullpath'] = $path;
1237 * Returns all English help files in the standard lang/en_utf8/help location.
1239 * Core help files should always be stored here and not in the module space (MDL-10920).
1240 * The English version of the file may be found in
1241 * $CFG->dirroot/lang/en_utf8/help/filename
1242 * The localised version of the found file should be saved into
1243 * $CFG->dataroot/lang/currentlang[_local]/help/filename
1244 * where "filename" is returned as a part of the file record.
1246 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
1248 function lang_help_standard_locations() {
1251 // Standard location of master English help files.
1252 $places = array($CFG->dirroot
.'/lang/en_utf8/help');
1253 foreach ($places as $place) {
1254 foreach (get_directory_list($place, 'CVS') as $file) {
1255 if ((substr($file, -5) == '.html') ||
(substr($file, -4) == '.txt' )) {
1256 $fullpath = $place.'/'.$file;
1257 $files[$fullpath] = array(
1258 'filename' => $file,
1270 * Returns all English help files in non-standard location.
1272 * Searches for lang/en_utf8/help/* files in various types of plugins (blocks, database presets, question types,
1273 * 3rd party modules etc.) and returns an array of found files details.
1275 * The English version of the file may be found in
1276 * $CFG->dirroot/location/plugin/lang/en_utf8/help/filename
1277 * The localised version of the found file should be saved into
1278 * $CFG->dataroot/lang/currentlang[_local]/help/prefix_plugin/filename (XXX is "prefix" here right?)
1279 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
1281 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
1283 function lang_help_extra_locations() {
1286 $places = places_to_search_for_lang_strings();
1287 foreach ($places as $prefix => $directories) {
1288 if ($prefix != '__exceptions') {
1289 foreach ($directories as $directory) {
1290 foreach (get_list_of_plugins($directory) as $plugin) {
1291 $enlangdirlocation = $CFG->dirroot
.'/'.$directory.'/'.$plugin.'/lang/en_utf8/help';
1292 foreach (get_directory_list($enlangdirlocation, 'CVS') as $file) {
1293 if ((substr($file, -5) == '.html') ||
(substr($file, -4) == '.txt' )) {
1294 $fullpath = $enlangdirlocation.'/'.$file;
1295 $files[$fullpath] = array(
1296 'filename' => $file,
1297 'location' => $directory,
1298 'plugin' => $plugin,
1299 'prefix' => $prefix,
1311 * Return a preview URL for help file, if available.
1313 * @param string $currentfile The relative path to the help file, e.g. "assignment/types.html" - MDL-12291
1314 * @param bool $skiplocal Force displaying the helpfile from a master lang pack
1315 * @param string $forcelang Force language of the help, e.g. "en_utf8"
1316 * @return string $url
1318 function lang_help_preview_url($currentfile, $skiplocal=false, $forcelang = '') {
1319 $currentpathexp = explode('/', $currentfile);
1320 if (count($currentpathexp) > 1) {
1321 $url = '/help.php?module='.implode('/',array_slice($currentpathexp,0,count($currentpathexp)-1)).'&file='.end($currentpathexp);
1323 $url = '/help.php?module=moodle&file='.$currentfile;
1326 $url .= '&skiplocal=1';
1329 $url .= '&forcelang='.$forcelang;
1336 * Saves (overwrites) translated help file.
1338 * @param string $helproot The path to the "help" folder
1339 * @param string $file The relative path to the html help file
1340 * @param string $content HTML data to be saved
1341 * @return bool False if save failed, true otherwise
1343 function lang_help_save_file($helproot, $file, $content) {
1346 $content = str_replace("\r", "",$content); // Remove linefeed characters
1347 $content = preg_replace("/\n{3,}/", "\n\n", $content); // Collapse runs of blank lines
1348 $content = trim($content); // Delete leading/trailing whitespace
1349 if (is_readable("$helproot/$file") && filesize("$helproot/$file") > 0 && $content == '') {
1350 notify(get_string('langrmyourself', 'admin'));
1355 if (!$f = fopen("$helproot/$file","w")) {
1356 error_reporting($CFG->debug
);
1359 error_reporting($CFG->debug
);
1361 fwrite($f, stripslashes($content));
1364 // Remove file if its empty
1365 if (filesize("$helproot/$file") == 0) {
1366 unlink("$helproot/$file");