Updated the 19 build version to 20101023
[moodle.git] / admin / lang.php
blob7eb2aa5735c1f2566fa694580c9b9ead2b1ef22c
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 $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);
35 } else {
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;
43 } else {
44 $uselocal = LANG_DEFAULT_USELOCAL;
46 } else {
47 $SESSION->langtranslateintolocal = $uselocal;
50 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id, false)) {
51 // Force using _local
52 $uselocal = 1;
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();
88 switch ($mode) {
89 case "missing":
90 // Missing array keys are not bugs here but missing strings
91 error_reporting(E_ALL ^ E_NOTICE);
92 $title = $strmissingstrings;
93 break;
94 case "compare":
95 $title = $streditstrings;
96 break;
97 case "helpfiles":
98 $title = $stredithelpdocs;
99 default:
100 $title = $strlanguage;
101 break;
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
109 $firstrow = array();
110 $secondrow = array();
111 $inactive = NULL;
112 $activated = NULL;
113 $currenttab = $mode;
114 if ($uselocal) {
115 $inactive = array('uselocal');
116 $activated = array('uselocal');
117 } else {
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&amp;currentfile=$currentfile&amp;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&amp;currentfile=$currentfile&amp;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);
138 if (!$mode) {
139 // TODO this is a very nice place to put some translation statistics
140 print_box_start();
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.':');
144 print_box_end();
145 admin_externalpage_print_footer();
146 exit;
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;
155 } else {
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
188 $m = '';
189 $o = '';
191 $m_x = false;
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'];
203 unset($string);
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;
216 if (!$m_x) {
217 $m .= '<hr />';
218 $m_x = true;
220 } else {
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();
230 unset($string);
231 ksort($enstring);
233 //$lcstring = local customizations
234 $lcstring = array();
235 if (file_exists($lcfilepath)) {
236 include($lcfilepath);
237 $localfileismissing = 0;
238 if (isset($string) && is_array($string)) {
239 $lcstring = $string;
241 unset($string);
242 ksort($lcstring);
243 } else {
244 $localfileismissing = 1;
247 // $string = translated strings distibuted either in core lang pack or in plugin space
248 $string = array();
249 if (file_exists($trfilepath)) {
250 include($trfilepath);
251 $fileismissing = 0;
252 } else {
253 $fileismissing = 1;
254 $o .= notify(get_string("filemissing", "", $trfilepath), "notifyproblem", "center", true);
257 $missingcounter = 0;
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);
281 if ($first) {
282 $m .= "<a href=\"lang.php?mode=missing#$trfilename\">$trfilename";
283 $m .= $fileismissing ? '*' : '';
284 $m .= '</a> &nbsp; ';
285 $o .= "<p><a name=\"$trfilename\"></a><b>".
286 get_string("stringsnotset","", $trfilepath)."</b></p><pre>";
287 $first = false;
288 $somethingfound = true;
290 if ($missingstring) {
291 $missingcounter++;
292 $totalcounter->missing++;
294 if ($translationsdiffer) {
295 $missingcounter++;
297 if (LANG_LINK_MISSING_STRINGS && ($missingstring || $translationsdiffer)) {
298 $missinglinkstart = "<a href=\"lang.php?mode=compare&amp;currentfile=$filename#$key\">";
299 $missinglinkend = '</a>';
300 } else {
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) {
308 $o .= '// ';
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';
316 $o .= "\n";
319 if (!$first) {
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);
327 } else {
328 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
331 if ($m <> '') {
332 print_box($m, 'filenames');
335 echo $o;
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;
345 continue;
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;
356 continue;
360 if (!empty($somethingfound)) {
361 print_continue("lang.php");
362 } else {
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);
371 } else {
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);
379 } else {
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";
388 $uselocal = 0;
389 } else {
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");
409 if (!$uselocal) {
410 notify($streditingnoncorelangfile);
411 $editable = false;
413 } else {
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;
431 } else {
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;
456 if ($uselocal) {
457 if(file_exists($trfilepath)) {
458 include($trfilepath);
459 if (isset($string)) {
460 $packstring = $string;
462 unset($string);
464 $saveinto = $locallangdir;
467 if (lang_save_file($saveinto, $currentfile, $newstrings, $uselocal, $packstring)) {
468 notify(get_string("changessaved")." ($saveinto/$currentfile)", "notifysuccess");
469 } else {
470 error("Could not save the file '$saveinto/$currentfile'!", "lang.php?mode=compare&amp;currentfile=$currentfile");
472 unset($packstring);
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&amp;currentfile=", $menufiles, "choosefile",
495 $currentfile, $strchoosefiletoedit, '', '', false, 'self', $selectionlabel);
496 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
497 print_box_end();
499 if ($currentfile <> '') {
500 error_reporting(0);
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');
505 } else {
506 print_heading($strfilecreated, '', 4, 'notifysuccess');
509 if ($currentlang == "en_utf8" && !$uselocal) {
510 $editable = false;
511 print_heading($streditennotallowed, '', 4);
512 } elseif ($f = fopen("$saveto/$currentfile","r+")) {
513 $editable = true;
514 fclose($f);
515 } else {
516 $editable = false;
517 notify(get_string("makeeditable", "", "$saveto/$currentfile"), 'notifyproblem');
520 error_reporting($CFG->debug);
522 $o = ''; // stores the HTML output to be echo-ed
524 unset($string);
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 >>";
536 unset($string);
537 ksort($enstring);
539 @include($lcfilepath);
540 $localstring = isset($string) ? $string : array();
541 unset($string);
542 ksort($localstring);
544 @include($trfilepath);
545 $string = isset($string) ? $string : array();
546 ksort($string);
548 if ($editable) {
549 $o .= "<form id=\"$currentfile\" action=\"lang.php\" method=\"post\">";
550 $o .= '<div>';
552 $o .= "<table summary=\"\" width=\"100%\" class=\"translator\">";
553 $linescounter = 0;
554 $missingcounter = 0;
555 foreach ($enstring as $key => $envalue) {
556 $linescounter++ ;
557 if (LANG_SUBMIT_REPEAT && $editable && $linescounter % LANG_SUBMIT_REPEAT_EVERY == 0) {
558 $o .= '<tr><td>&nbsp;</td><td><br />';
559 $o .= '<input type="submit" tabindex="'.$missingcounter.'" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
560 $o .= '<br />&nbsp;</td></tr>';
562 $envalue = nl2br(htmlspecialchars($envalue));
563 $envalue = preg_replace('/(\$a\-\&gt;[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) {
569 $o .= 'r0';
570 } else {
571 $o .= 'r1';
573 $o .= '">';
574 $o .= '<td dir="ltr" lang="en">';
575 $o .= '<span id="'.$key.'" class="stren">'.$envalue.'</span>';
576 $o .= '<br />'."\n";
577 $o .= '<span class="strkey">'.$key.'</span>';
578 $o .= '</td>'."\n";
580 // Missing array keys are not bugs here but missing strings
581 error_reporting(E_ALL ^ E_NOTICE);
582 if ($uselocal) {
583 $value = lang_fix_value_from_file($localstring[$key]);
584 $value2 = lang_fix_value_from_file($string[$key]);
585 if ($value == '') {
586 $value = $value2;
588 } else {
589 $value = lang_fix_value_from_file($string[$key]);
590 $value2 = lang_fix_value_from_file($localstring[$key]);
592 error_reporting($CFG->debug);
593 $missingtarget = '';
594 $missingnext = '';
595 $missingprev = '';
596 $cellcolour = '';
597 $usetabindex = false;
598 if (!$value) {
599 // the string is not present in the pack being processed
600 if (!$value2) {
601 $cellcolour = 'class="bothmissing"';
602 $usetabindex = true;
603 } else {
604 $cellcolour = 'class="mastermissing"';
605 $usetabindex = true;
607 $missingcounter++;
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>';
615 } else {
616 // the string is translated in the pack being processed
617 if ($value <> $value2 && ($value2 <> '')) {
618 $cellcolour = 'class="localdifferent"';
619 $usetabindex = true;
620 $missingcounter++;
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>';
631 if ($editable) {
632 $o .= '<td '.$cellcolour.' valign="top">';
633 if ($missingcounter > 1) {
634 $o .= $missingprev;
636 $o .= $missingtarget."\n";
637 if (isset($string[$key])) {
638 $valuelen = strlen($value);
639 } else {
640 $valuelen = strlen($envalue);
642 $cols=40;
643 if ($usetabindex) {
644 $tabindex = 'tabindex="'.$missingcounter.'"';
645 } else {
646 $tabindex = '';
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";
651 } else {
652 if ($valuelen) {
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";
657 } else {
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>';
666 } else {
667 $o .= '<td '.$cellcolour.' valign="top">'.$value.'<br />'.$value2.'</td>';
669 $o .= '</tr>'."\n";
671 if ($editable) {
672 $o .= '<tr><td>&nbsp;</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.'" />';
677 $o .= '</td></tr>';
679 $o .= '</table>';
680 if ($editable) {
681 $o .= '</div>';
682 $o .= '</form>';
685 if (LANG_DISPLAY_MISSING_LINKS) {
686 if ($missingcounter > 0) {
687 print_heading(get_string('numberofmissingstrings', 'admin', $missingcounter), '', 4);
688 if ($editable) {
689 print_heading('<a href="#missing1">'.$strgotofirst.'</a>', "", 4);
691 } else {
692 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
695 echo $o;
697 } else {
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");
722 } else {
723 error("Could not save the file '$currentfile'!", "lang.php?mode=helpfiles&amp;currentfile=$currentfile&amp;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;
737 } else {
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&amp;currentfile=", $menufiles, "choosefile",
760 $currentfile, $strchoosefiletoedit, '', '', false, 'self', $selectionlabel);
761 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
762 print_box_end();
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"));
783 $editable = false;
784 } else {
786 // webserver can create new file - we can delete it now and let
787 // it create again if its filesize() > 0
789 $editable = true;
790 unlink("$saveto/$currentfile");
792 } elseif (is_writable("$saveto/$currentfile")) {
793 $editable = true;
794 } else {
796 // file exists but it is not writeable by web server process :-(
798 $editable = false;
799 notify(get_string('makeeditable', '', "$saveto/$currentfile"));
802 // master en_utf8 in dataroot is not editable
803 if ((!$uselocal) && ($currentlang == 'en_utf8')) {
804 $editable = false;
807 echo '<div>';
809 if ($uselocal) {
810 $strsavetotitle = $strlanglocalpackage . helpbutton('langpackages', $strlanglocalpackage, 'moodle', true, false, '', true);
811 $straltdirtitle = $strlangmasterpackage . helpbutton('langpackages', $strlangmasterpackage, 'moodle', true, false, '', true);
812 } else {
813 $straltdirtitle = $strlanglocalpackage . helpbutton('langpackages', $strlanglocalpackage, 'moodle', true, false, '', true);
814 $strsavetotitle = $strlangmasterpackage . helpbutton('langpackages', $strlangmasterpackage, 'moodle', true, false, '', true);
818 if ($editable) {
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"));
829 } else {
830 echo ($filetemplate);
832 echo "</textarea>\n</div>\n";
833 echo '<div class="mdl-align"><input type="submit" value="'.get_string('savechanges').'" /></div>';
834 echo '</form>';
835 $preview_url = lang_help_preview_url($currentfile, !$uselocal);
836 if ($preview_url) {
837 link_to_popup_window($preview_url, 'popup', get_string('preview'));
839 echo '</fieldset>';
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"));
849 } else {
850 echo ($filetemplate);
852 echo "</textarea>\n</div>\n";
853 $preview_url = lang_help_preview_url($currentfile, $uselocal);
854 if ($preview_url) {
855 link_to_popup_window($preview_url, 'popup', get_string('preview'));
857 echo '</fieldset>';
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";
864 } else {
865 // core help file
866 $ensrc = "$enlangdir/$currentfile";
868 if (is_readable($ensrc)) {
869 echo '<fieldset><legend>'.$strlangmasterenglish;
870 helpbutton('langpackages', $strlangmasterenglish);
871 echo '</legend>';
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
876 if ($preview_url) {
877 link_to_popup_window($preview_url, 'popup', get_string('preview'));
879 echo '</fieldset>';
882 echo '</div>'; // translator box
883 error_reporting($CFG->debug);
886 if (false && $CFG->debugdisplay && debugging('', DEBUG_DEVELOPER) ) {
887 echo '<hr />';
888 print_heading('Debugging info');
889 echo '<pre class="notifytiny">';
890 print_r($dbg);
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 = ");
901 print_r($helpfiles);
902 echo '</pre>';
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.
918 * @uses $CFG
919 * @uses $USER
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) {
928 global $CFG, $USER;
929 if (LANG_KEEP_ORPHANS) {
930 // let us load the current content of the file
931 unset($string);
932 @include("$path/$file");
933 if (isset($string)) {
934 $orphans = $string;
935 unset($string);
936 } else {
937 $orphans = array();
940 // let us rewrite the file
941 if (!$f = @fopen("$path/$file","w")) {
942 return false;
945 fwrite($f, "<?PHP // \$Id\$ \n");
946 fwrite($f, " // $file - created with Moodle $CFG->release ($CFG->version)\n");
947 if ($local) {
948 fwrite($f, " // local modifications from $CFG->wwwroot\n");
950 fwrite($f, "\n\n");
951 ksort($strings);
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");
973 fwrite($f,"\n?>\n");
974 fclose($f);
975 return true;
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("&","&amp;",$value); // Fixes MDL-9248
995 $value = str_replace("<","&lt;",$value);
996 $value = str_replace(">","&gt;",$value);
997 $value = str_replace('"',"&quot;",$value);
998 return $value;
1002 * Fix value of the translated string before it is saved into the file
1004 * @uses $CFG
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='') {
1009 global $CFG;
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
1025 return $value;
1029 * Try and create a new language directory.
1031 * Uses PHP>=5.0 syntax of mkdir and tries to create directories recursively.
1033 * @uses $CFG
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) {
1038 global $CFG;
1039 umask(0000);
1040 if (! file_exists($dir)) {
1041 if (! @mkdir($dir, $CFG->directorypermissions, true)) { // recursive=true; PHP>=5.0 needed
1042 return false;
1044 //@chmod($dir, $CFG->directorypermissions); // Just in case mkdir didn't do it
1046 return $dir;
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 &#46, 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 &amp; 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 ;)
1091 return "";
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);
1099 } else {
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))) {
1113 return '';
1116 //calculate start position
1117 if ($start >= 0) {
1118 $real_start = $chars[$start][1];
1119 } else {
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);
1133 } else {
1134 //return $length characters
1135 return substr($str, $real_start, $chars[max($start,0)+$length][1] - $real_start);
1137 } else {
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() {
1156 global $CFG;
1157 $files = array();
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,
1166 'location' => '',
1167 'plugin' => '',
1168 'prefix' => '',
1173 return $files;
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() {
1191 global $CFG;
1192 $files = array();
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,
1214 return $files;
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) {
1229 $found = false;
1230 foreach ($stringfiles as $path=>$stringfile) {
1231 if ($stringfile['filename'] == $currentfile) {
1232 $found = true;
1233 $ret = $stringfile;
1234 $ret['fullpath'] = $path;
1235 break;
1238 if ($found) {
1239 return $ret;
1240 } else {
1241 return null;
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() {
1258 global $CFG;
1259 $files = array();
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,
1268 'location' => '',
1269 'plugin' => '',
1270 'prefix' => '',
1275 return $files;
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() {
1293 global $CFG;
1294 $files = array();
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,
1316 return $files;
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)).'&amp;file='.end($currentpathexp);
1331 } else {
1332 $url = '/help.php?module=moodle&amp;file='.$currentfile;
1334 if ($skiplocal) {
1335 $url .= '&amp;skiplocal=1';
1337 if ($forcelang) {
1338 $url .= '&amp;forcelang='.$forcelang;
1340 return $url;
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) {
1353 global $CFG, $USER;
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'));
1360 return true;
1363 error_reporting(0);
1364 if (!$f = fopen("$helproot/$file","w")) {
1365 error_reporting($CFG->debug);
1366 return false;
1368 error_reporting($CFG->debug);
1370 fwrite($f, stripslashes($content));
1371 fclose($f);
1373 // Remove file if its empty
1374 if (filesize("$helproot/$file") == 0) {
1375 unlink("$helproot/$file");
1378 return true;