MDL-22015 AMOS - making room for new string manager
[moodle.git] / admin / lang.php
1 <?php
2     /**
3     * Display the admin/language menu and process strings translation.
4     *
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);
37     }
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;
45         }
46     } else {
47         $SESSION->langtranslateintolocal = $uselocal;
48     }
50     if (!has_capability('moodle/site:langeditmaster', $context, $USER->id, false)) {
51         // Force using _local
52         $uselocal = 1;
53     }
55     if (!has_capability('moodle/site:langeditmaster', $context, $USER->id, false) && (!$uselocal)) {
56         print_error('cannoteditmasterlang', 'error');
57     }
59     if ((!has_capability('moodle/site:langeditlocal', $context, $USER->id, false)) && ($uselocal)) {
60         print_error('cannotcustomizelocallang', 'error');
61     }
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;
102     }
104     echo $OUTPUT->header();
106     // Prepare and render menu tabs
107     $firstrow = array();
108     $secondrow = array();
109     $inactive = NULL;
110     $activated = NULL;
111     $currenttab = $mode;
112     if ($uselocal) {
113         $inactive = array('uselocal');
114         $activated = array('uselocal');
115     } else {
116         $inactive = array('usemaster');
117         $activated = array('usemaster');
118     }
119     if (has_capability('moodle/site:langeditlocal', $context, $USER->id, false)) {
120         $firstrow[] = new tabobject('uselocal',
121             "$CFG->wwwroot/$CFG->admin/lang.php?mode=$mode&amp;currentfile=$currentfile&amp;uselocal=1",
122             $strlocalstringcustomization );
123     }
124     if (has_capability('moodle/site:langeditmaster', $context, $USER->id, false)) {
125         $firstrow[] = new tabobject('usemaster',
126             "$CFG->wwwroot/$CFG->admin/lang.php?mode=$mode&amp;currentfile=$currentfile&amp;uselocal=0",
127             $strlangpackmaintaining );
128     }
129     $secondrow[] = new tabobject('missing', "$CFG->wwwroot/$CFG->admin/lang.php?mode=missing", $strmissingstrings );
130     $secondrow[] = new tabobject('compare', "$CFG->wwwroot/$CFG->admin/lang.php?mode=compare", $streditstrings );
131     $secondrow[] = new tabobject('helpfiles', "$CFG->wwwroot/$CFG->admin/lang.php?mode=helpfiles", $stredithelpdocs );
132     $tabs = array($firstrow, $secondrow);
133     print_tabs($tabs, $currenttab, $inactive, $activated);
136     if (!$mode) {
137         // TODO this is a very nice place to put some translation statistics
138         echo $OUTPUT->box_start();
139         $currlang = current_language();
140         $langs = get_list_of_languages(false, true);
141         $select = new single_select(new moodle_url('/admin/lang.php'), 'lang', $langs, $currlang, null);
142         $select->label = $strcurrentlanguage.':';
143         $select->formid = 'chooselang';
144         echo $OUTPUT->render($select);
145         echo $OUTPUT->box_end();
146         echo $OUTPUT->footer();
147         exit;
148     }
150     // Get a list of all the root files in the English directory
152     $langbase = $CFG->dataroot . '/lang';
153     $enlangdir = "$CFG->dirroot/lang/en_utf8";
154     if ($currentlang == 'en_utf8') {
155         $langdir = $enlangdir;
156     } else {
157         $langdir = "$langbase/$currentlang";
158     }
159     $locallangdir = "$langbase/{$currentlang}_local";
160     $saveto = $uselocal ? $locallangdir : $langdir;
162     if (($mode == 'missing') || ($mode == 'compare')) {
163         // get the list of all English stringfiles
164         $stringfiles = lang_standard_locations();
165         if (LANG_SEARCH_EXTRA) {
166             $stringfiles += lang_extra_locations();
167         }
168         if (count($stringfiles) == 0) {
169             print_error('cannotfindlang', 'error', '', 'English');
170         }
171     } elseif ($mode == 'helpfiles') {
172         $helpfiles = lang_help_standard_locations();
173         if (LANG_SEARCH_EXTRA) {
174             $helpfiles += lang_help_extra_locations();
175         }
176         if (count($helpfiles) == 0) {
177             print_error("cannotfindhelp", 'error', '', 'English');
178         }
179     }
183     if ($mode == 'missing') {
184         if (!file_exists($langdir)) {
185             print_error('invalidlangpack');
186         }
188         // Following variables store the HTML output to be echo-ed
189         $m = '';
190         $o = '';
192         $m_x = false;
194         // Total number of strings and missing strings
195         $totalcounter->strings = 0;
196         $totalcounter->missing = 0;
198         // For each file, check that a counterpart exists, then check all the strings
199         foreach ($stringfiles as $stringfile) {
200             $location = $stringfile['location'];
201             $plugin = $stringfile['plugin'];
202             $prefix = $stringfile['prefix'];
203             $filename = $stringfile['filename'];
204             unset($string);
206             // Get some information about file locations:
207             //  $enfilepath = the path to the English file distributed either in the core space or in plugin space
208             //  $trfilepath = the path to the translated file distributed either in the lang pack or in plugin space
209             //  $lcfilepath = the path to the _local customization
210             //  $trfilename = the filename of the translated version of the file (including prefix for non-core files)
211             if ($location || $plugin) {
212                 // non-core file in an extra location
213                 $enfilepath = "$CFG->dirroot/$location/$plugin/lang/en_utf8/$filename";
214                 $trfilepath = "$CFG->dirroot/$location/$plugin/lang/$currentlang/$filename";
215                 $lcfilepath = "$locallangdir/$filename";
216                 $trfilename = $filename;
217                 if (!$m_x) {
218                     $m .= '<hr />';
219                     $m_x = true;
220                 }
221             } else {
222                 // core file in standard location
223                 $enfilepath = "$CFG->dirroot/lang/en_utf8/$filename";
224                 $trfilepath = "$langdir/$filename";
225                 $lcfilepath = "$locallangdir/$filename";
226                 $trfilename = $filename;
227             }
228             // $enstring = English strings distributed either in the core space or in plugin space
229             include($enfilepath);
230             $enstring = isset($string) ? $string : array();
231             unset($string);
232             ksort($enstring);
234             //$lcstring = local customizations
235             $lcstring = array();
236             if (file_exists($lcfilepath)) {
237                 include($lcfilepath);
238                 $localfileismissing = 0;
239                 if (isset($string) && is_array($string)) {
240                     $lcstring = $string;
241                 }
242                 unset($string);
243                 ksort($lcstring);
244             } else {
245                 $localfileismissing = 1;
246             }
248             // $string = translated strings distibuted either in core lang pack or in plugin space
249             $string = array();
250             if (file_exists($trfilepath)) {
251                 include($trfilepath);
252                 $fileismissing = 0;
253             } else {
254                 $fileismissing = 1;
255                 $o .= $OUTPUT->notification(get_string("filemissing", "", $trfilepath), "notifyproblem");
256             }
258             $missingcounter = 0;
260             $first = true; // first missing string found in the file
261             // For all English strings in the current file check distributed translations and _local customizations
262             foreach ($enstring as $key => $value) {
263                 $totalcounter->strings++;
264                 $missingstring = false;
265                 $missinglocalstring = false;
266                 $translationsdiffer = false;
267                 if (empty($string[$key]) and $string[$key] != "0") { // MDL-4735
268                     // string is missing in distributed pack
269                     $missingstring = true;
270                 }
271                 if (empty($lcstring[$key]) and $lcstring[$key] != "0") { // MDL-4735
272                     // string is missing in _local customization
273                     $missinglocalstring = true;
274                 }
275                 if (!$missingstring && !$missinglocalstring && ($lcstring[$key] != $string[$key])) {
276                     $translationsdiffer = true;
277                 }
278                 if ($missingstring || $translationsdiffer) {
279                     $value = htmlspecialchars($value);
280                     $value = str_replace("$"."a", "\\$"."a", $value);
281                     $value = str_replace("%%","%",$value);
282                     if ($first) {
283                         $m .= "<a href=\"lang.php?mode=missing#$trfilename\">$trfilename";
284                         $m .= $fileismissing ? '*' : '';
285                         $m .= '</a> &nbsp; ';
286                         $o .= "<p><a name=\"$trfilename\"></a><b>".
287                             get_string("stringsnotset","", $trfilepath)."</b></p><pre>";
288                         $first = false;
289                         $somethingfound = true;
290                     }
291                     if ($missingstring) {
292                         $missingcounter++;
293                         $totalcounter->missing++;
294                     }
295                     if ($translationsdiffer) {
296                         $missingcounter++;
297                     }
298                     if (LANG_LINK_MISSING_STRINGS && ($missingstring || $translationsdiffer)) {
299                         $missinglinkstart = "<a href=\"lang.php?mode=compare&amp;currentfile=$filename#$key\">";
300                         $missinglinkend = '</a>';
301                     } else {
302                         $missinglinkstart = '';
303                         $missinglinkend = '';
304                     }
305                     if (strlen($value) > LANG_MISSING_TEXT_MAX_LEN) {
306                         $value = lang_xhtml_save_substr($value, 0, LANG_MISSING_TEXT_MAX_LEN) . ' ...'; // MDL-8852
307                     }
308                     if ($translationsdiffer) {
309                         $o .= '// ';
310                     }
311                     $o .= "$"."string['".$missinglinkstart.$key.$missinglinkend."'] = \"$value\";";
312                     if ($translationsdiffer) {
313                         $o .= '    // differs from the translation in _local';
314                     } elseif (!$missinglocalstring) {
315                         $o .= '    // translated only in _local';
316                     }
317                     $o .= "\n";
318                 }
319             }
320             if (!$first) {
321                 $o .= '</pre><hr />';
322             }
323         }
325         if ($totalcounter->missing > 0) {
326             $totalcounter->missingpercent = sprintf('%02.1f', ($totalcounter->missing / $totalcounter->strings * 100));
327             echo $OUTPUT->heading(get_string('numberofstrings', 'admin', $totalcounter), 4);
328         } else {
329             echo $OUTPUT->heading($strnomissingstrings, 4, 'notifysuccess');
330         }
332         if ($m <> '') {
333             echo $OUTPUT->box($m, 'filenames');
334         }
336         echo $o;
338         if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/help", "CVS")) {
339             print_error("cannotfindhelp", 'error', '', 'English');
340         }
342         foreach ($files as $filekey => $file) {    // check all the help files.
343             if (!file_exists("$langdir/help/$file")) {
344                 echo $OUTPUT->notification(get_string("filemissing", "", "$langdir/help/$file"), 'notifyproblem');
345                 $somethingfound = true;
346                 continue;
347             }
348         }
350         if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/docs", "CVS")) {
351             print_error("cannotfinddoc", 'error', '', 'English');
352         }
353         foreach ($files as $filekey => $file) {    // check all the docs files.
354             if (!file_exists("$langdir/docs/$file")) {
355                 echo $OUTPUT->notification(get_string("filemissing", "", "$langdir/docs/$file"), 'notifyproblem');
356                 $somethingfound = true;
357                 continue;
358             }
359         }
361         if (!empty($somethingfound)) {
362             echo $OUTPUT->continue_button("lang.php");
363         } else {
364             notice(get_string("languagegood"), "lang.php" );
365         }
367     } else if ($mode == 'compare') {
369         if (!file_exists($langbase) ){
370             if (!lang_make_directory($langbase) ){
371                 print_error('cannotcreatelangbase', 'error');
372             } else {
373                 echo '<div class="notifysuccess">Created directory '.
374                                                      $langbase .'</div>'."<br />\n";
375             }
376         }
377         if (!$uselocal && !file_exists($langdir)) {
378             if (!lang_make_directory($langdir)) {
379                 print_error('cannotcreatelangdir', 'error');
380             } else {
381                 echo '<div class="notifysuccess">Created directory '.
382                                                      $langdir .'</div>'."<br />\n";
383             }
384         }
385         if ($uselocal && !file_exists($locallangdir)) {
386             if (!lang_make_directory($locallangdir)) {
387                 echo '<div class="notifyproblem">ERROR: Could not create directory '.
388                                      $locallangdir .'</div>'."<br />\n";
389                 $uselocal = 0;
390             } else {
391                 echo '<div class="notifysuccess">Created directory '.
392                                                      $locallangdir .'</div>'."<br />\n";
393             }
394         }
396         if ($currentfile <> '') {
397             if (!$fileinfo = lang_get_file_info($currentfile, $stringfiles)) {
398                 print_error('cannotfindinfo', 'error', '', $currentfile);
399             }
400             // check the filename is set up correctly, prevents bugs similar to MDL-10920
401             $location = $fileinfo['location'];
402             $plugin = $fileinfo['plugin'];
403             $prefix = $fileinfo['prefix'];
404             $filename = $fileinfo['filename'];
405             if ($location || $plugin) {
406                 // file in an extra location
407                 if ($currentfile != "{$prefix}{$plugin}.php") {
408                     print_error('filemismatch', 'error', '', (object)array('current'=>$currectfile, 'file'=>$prefix.$plugin.'.php'));
409                 }
410                 if (!$uselocal) {
411                     echo $OUTPUT->notification($streditingnoncorelangfile);
412                     $editable = false;
413                 }
414             } else {
415                 // file in standard location
416                 if ($currentfile != $filename) {
417                     print_error('filemismatch', 'error', '', (object)array('current'=>$currectfile, 'file'=>$filename.'.php'));
418                 }
419             }
421             // Get some information about file locations:
422             //  $enfilepath = the path to the English file distributed either in the core space or in plugin space
423             //  $trfilepath = the path to the translated file distributed either in the lang pack or in plugin space
424             //  $lcfilepath = the path to the _local customization
425             //  $trfilename = the filename of the translated version of the file (including prefix for non-core files)
426             if ($location || $plugin) {
427                 // non-core file in an extra location
428                 $enfilepath = "$CFG->dirroot/$location/$plugin/lang/en_utf8/$filename";
429                 $trfilepath = "$CFG->dirroot/$location/$plugin/lang/$currentlang/$filename";
430                 $lcfilepath = "$locallangdir/$filename";
431                 $trfilename = $filename;
432             } else {
433                 // core file in standard location
434                 $enfilepath = "$CFG->dirroot/lang/en_utf8/$filename";
435                 $trfilepath = "$langdir/$filename";
436                 $lcfilepath = "$locallangdir/$filename";
437                 $trfilename = $filename;
438             }
439         }
441         if (isset($_POST['currentfile'])){   // Save a file
442             if (!confirm_sesskey()) {
443                 print_error('confirmsesskeybad', 'error');
444             }
446             $newstrings = array();
448             foreach ($_POST as $postkey => $postval) {
449                 $stringkey = lang_file_string_key($postkey);
450                 $newstrings[$stringkey] = $postval;
451             }
453             unset($newstrings['currentfile']);
455             $packstring = array();
456             $saveinto = $langdir;
457             if ($uselocal) {
458                 if(file_exists($trfilepath)) {
459                     include($trfilepath);
460                     if (isset($string)) {
461                         $packstring = $string;
462                     }
463                     unset($string);
464                 }
465                 $saveinto = $locallangdir;
466             }
468             if (lang_save_file($saveinto, $currentfile, $newstrings, $uselocal, $packstring)) {
469                 echo $OUTPUT->notification(get_string("changessaved")." ($saveinto/$currentfile)", "notifysuccess");
470             } else {
471                 print_error('cannotsavefile', 'error', 'lang.php?mode=compare&amp;currentfile=$currentfile', $saveinto.'/'.$currentfile);
472             }
473             unset($packstring);
474         }
476         echo $OUTPUT->box_start('generalbox editstrings');
477         $menufiles = array();
478         $menufiles_coregrp = 1;
479         foreach ($stringfiles as $stringfile) {
480             $item_key = $stringfile['filename'];
481             $item_label = $stringfile['filename'];
482             if ($stringfile['location'] != '' && $stringfile['plugin'] != '') {
483                 $item_label .= ' ('.$stringfile['location'].'/'.$stringfile['plugin'].')';
484                 if ($menufiles_coregrp == 1) {
485                     $menufiles['extra'] = '------------';
486                     $menufiles_coregrp = 0;
487                 }
488             }
489             $menufiles[$item_key] = $item_label;
490         }
491         $selectionlabel = '<code class="path">';
492         //$selectionlabel .= $strfilestoredin;
493         $selectionlabel .= $uselocal ? "{$currentlang}_local" : $currentlang;
494         $selectionlabel .= '/</code>';
495         $select = new single_select(new moodle_url('/admin/lang.php?mode=compare'), 'currentfile', $menufiles, $currentfile, array(''=>$strchoosefiletoedit));
496         $select->label = $selectionlabel;
497         $select->formid = 'choosefile';
498         echo $OUTPUT->render($select);
499         echo $OUTPUT->help_icon('langswitchstorage', $strfilestoredinhelp);
500         echo $OUTPUT->box_end();
502         if ($currentfile <> '') {
503             error_reporting(0);
504             if (!isset($editable) || $editable) {
505                 if (!file_exists("$saveto/$currentfile")) {
506                     if (!@touch("$saveto/$currentfile")) {
507                         echo $OUTPUT->heading(get_string("filemissing", "", "$saveto/$currentfile"), 4, 'error');
508                     } else {
509                         echo $OUTPUT->heading($strfilecreated, 4, 'notifysuccess');
510                     }
511                 }
512                 if ($currentlang == "en_utf8" && !$uselocal) {
513                     $editable = false;
514                     echo $OUTPUT->heading($streditennotallowed, 4);
515                 } elseif ($f = fopen("$saveto/$currentfile","r+")) {
516                     $editable = true;
517                     fclose($f);
518                 } else {
519                     $editable = false;
520                     echo $OUTPUT->notification(get_string("makeeditable", "", "$saveto/$currentfile"), 'notifyproblem');
521                 }
522             }
523             error_reporting($CFG->debug);
525             $o = '';    // stores the HTML output to be echo-ed
527             unset($string);
528             include($enfilepath);
529             $enstring = isset($string) ? $string : array();
530             //
531             // Following strings have moved into langconfig.php, but keep the here for backward compatibility
532             //
533             if ($currentlang != 'en' and $currentfile == 'moodle.php') {
534                 $enstring['thislanguage'] = "<< TRANSLATORS: Specify the name of your language here.  If possible use Unicode Numeric Character References >>";
535                 $enstring['thischarset'] = "<< TRANSLATORS:  Charset encoding - always use utf-8 >>";
536                 $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. >>";
537                 $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             }
539             unset($string);
540             ksort($enstring);
542             @include($lcfilepath);
543             $localstring = isset($string) ? $string : array();
544             unset($string);
545             ksort($localstring);
547             @include($trfilepath);
548             $string = isset($string) ? $string : array();
549             ksort($string);
551             if ($editable) {
552                 $o .= "<form id=\"$currentfile\" action=\"lang.php\" method=\"post\">";
553                 $o .= '<div>';
554             }
555             $o .= "<table summary=\"\" width=\"100%\" class=\"translator\">";
556             $linescounter = 0;
557             $missingcounter = 0;
558             foreach ($enstring as $key => $envalue) {
559                 $linescounter++ ;
560                 if (LANG_SUBMIT_REPEAT &&  $editable && $linescounter % LANG_SUBMIT_REPEAT_EVERY == 0) {
561                     $o .= '<tr><td>&nbsp;</td><td><br />';
562                     $o .= '<input type="submit" tabindex="'.$missingcounter.'" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
563                     $o .= '<br />&nbsp;</td></tr>';
564                 }
565                 $envalue = nl2br(htmlspecialchars($envalue));
566                 $envalue = preg_replace('/(\$a\-\&gt;[a-zA-Z0-9]*|\$a)/', '<b>$0</b>', $envalue);  // Make variables bold.
567                 $envalue = str_replace("%%","%",$envalue);
568                 $envalue = str_replace("\\","",$envalue);              // Delete all slashes
570                 $o .= "\n\n".'<tr class="';
571                 if ($linescounter % 2 == 0) {
572                     $o .= 'r0';
573                 } else {
574                     $o .= 'r1';
575                 }
576                 $o .= '">';
577                 $o .= '<td dir="ltr" lang="en">';
578                 $o .= '<span id="'.$key.'" class="stren">'.$envalue.'</span>';
579                 $o .= '<br />'."\n";
580                 $o .= '<span class="strkey">'.$key.'</span>';
581                 $o .= '</td>'."\n";
583                 // Missing array keys are not bugs here but missing strings
584                 error_reporting(E_ALL ^ E_NOTICE);
585                 if ($uselocal) {
586                     $value = lang_fix_value_from_file($localstring[$key]);
587                     $value2 = lang_fix_value_from_file($string[$key]);
588                     if ($value == '') {
589                         $value = $value2;
590                     }
591                 } else {
592                     $value = lang_fix_value_from_file($string[$key]);
593                     $value2 = lang_fix_value_from_file($localstring[$key]);
594                 }
595                 error_reporting($CFG->debug);
596                 $missingtarget = '';
597                 $missingnext = '';
598                 $missingprev = '';
599                 $cellcolour = '';
600                 $usetabindex = false;
601                 if (!$value) {
602                     // the string is not present in the pack being processed
603                     if (!$value2) {
604                         $cellcolour = 'class="bothmissing"';
605                         $usetabindex = true;
606                     } else {
607                         $cellcolour = 'class="mastermissing"';
608                         $usetabindex = true;
609                     }
610                     $missingcounter++;
611                     if (LANG_DISPLAY_MISSING_LINKS) {
612                         $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
613                         $missingnext = '<a href="#missing'.($missingcounter+1).'">'.
614                         '<img src="' . $OUTPUT->pix_url('t/down') . '" class="iconsmall" alt="'.$strnext.'" /></a>';
615                         $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
616                         '<img src="' . $OUTPUT->pix_url('t/up') . '" class="iconsmall" alt="'.$strprev.'" /></a>';
617                     }
618                 } else {
619                     // the string is translated in the pack being processed
620                     if ($value <> $value2 && ($value2 <> '')) {
621                         $cellcolour = 'class="localdifferent"';
622                         $usetabindex = true;
623                         $missingcounter++;
624                         if (LANG_DISPLAY_MISSING_LINKS) {
625                             $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
626                             $missingnext = '<a href="#missing'.($missingcounter+1).'">'.
627                             '<img src="' . $OUTPUT->pix_url('t/down') . '" class="iconsmall" alt="'.$strnext.'" /></a>';
628                             $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
629                             '<img src="' . $OUTPUT->pix_url('t/up') . '" class="iconsmall" alt="'.$strprev.'" /></a>';
630                         }
631                     }
632                 }
634                 if ($editable) {
635                     $o .= '<td '.$cellcolour.' valign="top">';
636                     if ($missingcounter > 1) {
637                         $o .= $missingprev;
638                     }
639                     $o .= $missingtarget."\n";
640                     if (isset($string[$key])) {
641                         $valuelen = strlen($value);
642                     } else {
643                         $valuelen = strlen($envalue);
644                     }
645                     $cols=40;
646                     if ($usetabindex) {
647                         $tabindex = 'tabindex="'.$missingcounter.'"';
648                     } else {
649                         $tabindex = '';
650                     }
651                     if (strstr($value, "\r") or strstr($value, "\n") or $valuelen > $cols) {
652                         $rows = ceil($valuelen / $cols);
653                         $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="'.$rows.'" '.$tabindex.'>'.$value.'</textarea>'."\n";
654                     } else {
655                         if ($valuelen) {
656                             $cols = $valuelen + 5;
657                         }
658                         if (LANG_ALWAYS_TEXTAREA) {
659                             $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="1" '.$tabindex.'>'.$value.'</textarea>'."\n";
660                         } else {
661                             $o .= '<input type="text" name="stringXXX'.lang_form_string_key($key).'" value="'.$value.'" size="'.$cols.'" '.$tabindex.' />';
662                         }
663                     }
664                     if ($value2 <> '' && $value <> $value2) {
665                         $o .= '<br /><span style="font-size:small">'.$value2.'</span>';
666                     }
667                     $o .= $missingnext . '</td>';
669                 } else {
670                     $o .= '<td '.$cellcolour.' valign="top">'.$value.'<br />'.$value2.'</td>';
671                 }
672                 $o .= '</tr>'."\n";
673             }
674             if ($editable) {
675                 $o .= '<tr><td>&nbsp;</td><td><br />';
676                 $o .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
677                 $o .= '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
678                 $o .= '<input type="hidden" name="mode" value="compare" />';
679                 $o .= '<input type="submit" name="update" tabindex="'.$missingcounter.'" value="'.get_string('savechanges').': '.$currentfile.'" />';
680                 $o .= '</td></tr>';
681             }
682             $o .= '</table>';
683             if ($editable) {
684                 $o .= '</div>';
685                 $o .= '</form>';
686             }
688             if (LANG_DISPLAY_MISSING_LINKS) {
689                 if ($missingcounter > 0) {
690                     echo $OUTPUT->heading(get_string('numberofmissingstrings', 'admin', $missingcounter), 4);
691                     if ($editable) {
692                         echo $OUTPUT->heading('<a href="#missing1">'.$strgotofirst.'</a>', 4);
693                     }
694                 } else {
695                     echo $OUTPUT->heading($strnomissingstrings, 4, 'notifysuccess');
696                 }
697             }
698             echo $o;
700         } else {
701             // no $currentfile specified
702             // no useful information to display - maybe some help? instructions?
703         }
705     } elseif ($mode == 'helpfiles') {
707         $saveto = $saveto.'/help';                      // the edited content will be saved to
708         $enlangdir = $enlangdir.'/help';                // English master help files localtion
709         $langdir = $langdir.'/help';                    // current language master help files location
710         $locallangdir = $locallangdir.'/help';          // local modifications of help files location
711         $altdir = $uselocal ? $langdir : $locallangdir; // alternative to $saveto
713         $fileeditorrows = 10;           // number of textareas' rows
714         $fileeditorcols = 100;          // dtto cols
715         $filemissingmark = ' (***)';    // mark to add to non-existing or zero-length files
716         $fileoldmark = ' (old?)';       // mark to add to filenames in selection form if the English version is newer
717         $filetemplate = '';             // template for new files, e.g. CVS identification
719         if (isset($_POST['currentfile'])) {  // Save a file
720             if (!confirm_sesskey()) {
721                 print_error('confirmsesskeybad', 'error');
722             }
723             if (lang_help_save_file($saveto, $currentfile, $_POST['filedata'])) {
724                 echo $OUTPUT->notification(get_string("changessaved")." ($saveto/$currentfile)", "notifysuccess");
725             } else {
726                 print_error('cannotsavefile', 'error', 'lang.php?mode=compare&amp;currentfile=$currentfile', array($saveinto, $currentfile));
727             }
728         }
730         echo $OUTPUT->box_start('generalbox editstrings');
731         $menufiles = array();
732         $menufiles_coregrp = 1;
733         $origlocation = ''; // the location of the currentfile's English source will be stored here
734         $origplugin = '';   // dtto plugin
735         foreach ($helpfiles as $helppath => $helpfile) {
736             $item_key = $helpfile['filename'];
737             $item_label = $helpfile['filename'];
738             if ((!file_exists($saveto.'/'.$helpfile['filename'])) || (filesize($saveto.'/'.$helpfile['filename']) == 0)) {
739                 $item_label .= $filemissingmark;
740             } else {
741                 if (filemtime($saveto.'/'.$helpfile['filename']) < filemtime($helppath)) {
742                     $item_label .= $fileoldmark;
743                 }
744                 if ($helpfile['location'] != '' && $helpfile['plugin'] != '') {
745                     $item_label .= ' ('.$helpfile['location'].'/'.$helpfile['plugin'].')';
746                     if ($menufiles_coregrp == 1) {
747                         $menufiles['extra'] = '------------';
748                         $menufiles_coregrp = 0;
749                     }
750                 }
751             }
752             $menufiles[$item_key] = $item_label;
753             if ($currentfile == $helpfile['filename']) {
754                 $origlocation = $helpfile['location'];
755                 $origplugin = $helpfile['plugin'];
756             }
757         }
758         $selectionlabel = '<code class="path">';
759         //$selectionlabel .= $strfilestoredin;
760         $selectionlabel .= $uselocal ? "{$currentlang}_local" : $currentlang;
761         $selectionlabel .= '/help/</code>';
762         $select = new single_select(new moodle_url('/admin/lang.php?mode=helpfiles'), 'currentfile', $menufiles, $currentfile, array(''=>$strchoosefiletoedit));
763         $select->label = $selectionlabel;
764         $select->formid = 'choosefile';
765         echo $OUTPUT->render($select);
766         echo $OUTPUT->help_icon('langswitchstorage', $strfilestoredinhelp);
767         echo $OUTPUT->box_end();
769         if (!empty($currentfile)) {
771             if (!file_exists("$saveto/$currentfile")) {
772                 $dbg .= "File does not exist: $saveto/$currentfile\n";
773                 //check if directory exist
774                 if (!file_exists(dirname("$saveto/$currentfile"))) {
775                      if(!lang_make_directory(dirname("$saveto/$currentfile"))) {
776                          echo ('Cannot create directory: '.dirname("$saveto/$currentfile"));
777                      }
778                 }
779                 //
780                 // file doesn't exist - let's check webserver's permission to create it
781                 //
782                 if (!@touch("$saveto/$currentfile")) {
783                     //
784                     // webserver is unable to create new file
785                     //
786                     echo $OUTPUT->notification(get_string('filemissing', '', "$saveto/$currentfile" ));
787                     echo $OUTPUT->notification(get_string('makeeditable', '', "$saveto/$currentfile"));
788                     $editable = false;
789                 } else {
790                     //
791                     // webserver can create new file - we can delete it now and let
792                     // it create again if its filesize() > 0
793                     //
794                     $editable = true;
795                     unlink("$saveto/$currentfile");
796                 }
797             } elseif (is_writable("$saveto/$currentfile")) {
798                 $editable = true;
799             } else {
800                 //
801                 // file exists but it is not writeable by web server process :-(
802                 //
803                 $editable = false;
804                 echo $OUTPUT->notification(get_string('makeeditable', '', "$saveto/$currentfile"));
805             }
807             // master en_utf8 in dataroot is not editable
808             if ((!$uselocal) && ($currentlang == 'en_utf8')) {
809                 $editable = false;
810             }
812             echo '<div>';
814             if ($uselocal) {
815                 $strsavetotitle = $strlanglocalpackage . $OUTPUT->help_icon('langpackages', $strlanglocalpackage);
816                 $straltdirtitle = $strlangmasterpackage . $OUTPUT->help_icon('langpackages', $strlangmasterpackage);
817             } else {
818                 $straltdirtitle = $strlanglocalpackage . $OUTPUT->help_icon('langpackages', $strlanglocalpackage);
819                 $strsavetotitle = $strlangmasterpackage . $OUTPUT->help_icon('langpackages', $strlangmasterpackage);
821             }
823             if ($editable) {
824                 // generate an editor for the current help file in $saveto
825                 echo '<fieldset><legend>'.$strsavetotitle.'</legend>';
826                 echo "<form id=\"helpfileeditor\" action=\"lang.php\" method=\"post\">";
827                 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
828                 echo '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
829                 echo '<input type="hidden" name="mode" value="helpfiles" />';
830                 echo "<div class='mdl-align'>\n";
831                 echo "<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"filedata\">";
832                 if (file_exists("$saveto/$currentfile")) {
833                     echo htmlspecialchars(file_get_contents("$saveto/$currentfile"));
834                 } else {
835                     echo ($filetemplate);
836                 }
837                 echo "</textarea>\n</div>\n";
838                 echo '<div class="mdl-align"><input type="submit" value="'.get_string('savechanges').'" /></div>';
839                 echo '</form>';
840                 $preview_url = lang_help_preview_url($currentfile, !$uselocal);
841                 if ($preview_url) {
842                     echo $OUTPUT->action_link($preview_url, get_string('preview'), new popup_action('click', $preview_url));
843                 }
844                 echo '</fieldset>';
845             }
847             if (is_readable("$altdir/$currentfile")) {
848                 // show the content of the same help file in alternative location
849                 echo '<fieldset><legend>'.$straltdirtitle.'</legend>';
850                 echo "<div class='mdl-align'>\n";
851                 echo "<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"\">";
852                 if (file_exists("$altdir/$currentfile")) {
853                     echo htmlspecialchars(file_get_contents("$altdir/$currentfile"));
854                 } else {
855                     echo ($filetemplate);
856                 }
857                 echo "</textarea>\n</div>\n";
858                 $preview_url = lang_help_preview_url($currentfile, $uselocal);
859                 if ($preview_url) {
860                     echo $OUTPUT->action_link($preview_url, get_string('preview'), new popup_action('click', $preview_url));
861                 }
862                 echo '</fieldset>';
863             }
865             // show the content of the original English file either in core space or plugin space
866             if ($origlocation != '' && $origplugin != '') {
867                 // non-core help file
868                 $ensrc = "$CFG->dirroot/$origlocation/$origplugin/lang/en_utf8/help/$currentfile";
869             } else {
870                 // core help file
871                 $ensrc = "$enlangdir/$currentfile";
872             }
873             if (is_readable($ensrc)) {
874                 echo '<fieldset><legend>'.$strlangmasterenglish;
875                 echo $OUTPUT->help_icon('langpackages', $strlangmasterenglish);
876                 echo '</legend>';
877                 echo "<div class='mdl-align'>\n<textarea rows=\"$fileeditorrows\" cols=\"$fileeditorcols\" name=\"\">";
878                 echo htmlspecialchars(file_get_contents($ensrc));
879                 echo "</textarea>\n</div>\n";
880                 $preview_url = lang_help_preview_url($currentfile, true, 'en_utf8');   // do not display en_utf8_local
881                 if ($preview_url) {
882                     echo $OUTPUT->action_link($preview_url, get_string('preview'), new popup_action('click', $preview_url));
883                 }
884                 echo '</fieldset>';
885             }
887             echo '</div>'; // translator box
888             error_reporting($CFG->debug);
889         }
891         if (false && $CFG->debugdisplay && debugging('', DEBUG_DEVELOPER) ) {
892             echo '<hr />';
893             echo $OUTPUT->heading('Debugging info');
894             echo '<pre class="notifytiny">';
895             print_r($dbg);
896             print_r("\n\$currentfile = $currentfile");
897             print_r("\n\$enlangdir = $enlangdir");
898             print_r("\n\$langdir = $langdir");
899             print_r("\n\$locallangdir = $locallangdir");
900             print_r("\n\$saveto = $saveto");
901             print_r("\n\$altdir = $altdir");
902             print_r("\n\$origlocation = $origlocation");
903             print_r("\n\$origplugin = $origplugin");
904             print_r("\n\$ensrc = $ensrc");
905             print_r("\n\$helpfiles = ");
906             print_r($helpfiles);
907             echo '</pre>';
908         }
910     } // fi $mode == 'helpfiles'
913     echo $OUTPUT->footer();
915 //////////////////////////////////////////////////////////////////////
917 /**
918  * Save language translation file.
919  *
920  * Thanks to Petri Asikainen for the original version of code
921  * used to save language files.
922  *
923  * @uses $CFG
924  * @uses $USER
925  * @param string $path Full pathname to the directory to use
926  * @param string $file File to overwrite
927  * @param array $strings Array of strings to write
928  * @param bool $local Should *_local version be saved?
929  * @param array $packstrings Array of default langpack strings (needed if $local)
930  * @return bool Created successfully?
931  */
932 function lang_save_file($path, $file, $strings, $local, $packstrings) {
933     global $CFG, $USER;
934     if (LANG_KEEP_ORPHANS) {
935         // let us load the current content of the file
936         unset($string);
937         @include("$path/$file");
938         if (isset($string)) {
939             $orphans = $string;
940             unset($string);
941         } else {
942             $orphans = array();
943         }
944     }
945     // let us rewrite the file
946     if (!$f = @fopen("$path/$file","w")) {
947         return false;
948     }
950     fwrite($f, "<?PHP // \$Id\$ \n");
951     fwrite($f, "      // $file - created with Moodle $CFG->release ($CFG->version)\n");
952     if ($local) {
953         fwrite($f, "      // local modifications from $CFG->wwwroot\n");
954     }
955     fwrite($f, "\n\n");
956     ksort($strings);
957     foreach ($strings as $key => $value) {
958         @list($id, $stringname) = explode('XXX',$key);
959         $value = lang_fix_value_before_save($value);
960         if ($id == "string" and $value != ""){
961             if ((!$local) || (!isset($packstrings[$stringname])) || (lang_fix_value_from_file($packstrings[$stringname]) <> lang_fix_value_from_file($value))) {
962                 // Either we are saving the master language pack
963                 // or the string is not saved in packstring - fixes PHP notices about missing key
964                 // or we are saving local language pack and the strings differ.
965                 fwrite($f,"\$string['$stringname'] = '$value';\n");
966             }
967             if (LANG_KEEP_ORPHANS && isset($orphans[$stringname])) {
968                 unset($orphans[$stringname]);
969             }
970         }
971     }
972     if (LANG_KEEP_ORPHANS) {
973         // let us add orphaned strings, i.e. already translated strings without the English referential source
974         foreach ($orphans as $key => $value) {
975             fwrite($f,"\$string['$key'] = '".lang_fix_value_before_save($value)."'; // ORPHANED\n");
976         }
977     }
978     fwrite($f,"\n?>\n");
979     fclose($f);
980     return true;
983 /**
984  * Fix value of the translated string after it is load from the file.
985  *
986  * These modifications are typically necessary to work with the same string coming from two sources.
987  * We need to compare the content of these sources and we want to have e.g. "This string\r\n"
988  * to be the same as " This string\n".
989  *
990  * @param string $value Original string from the file
991  * @return string Fixed value
992  */
993 function lang_fix_value_from_file($value='') {
994     $value = str_replace("\r","",$value);              // Bad character caused by Windows
995     $value = preg_replace("/\n{3,}/", "\n\n", $value); // Collapse runs of blank lines
996     $value = trim($value);                             // Delete leading/trailing white space
997     $value = str_replace("\\","",$value);              // Delete all slashes
998     $value = str_replace("%%","%",$value);
999     $value = str_replace("&","&amp;",$value);          // Fixes MDL-9248
1000     $value = str_replace("<","&lt;",$value);
1001     $value = str_replace(">","&gt;",$value);
1002     $value = str_replace('"',"&quot;",$value);
1003     return $value;
1006 /**
1007  * Fix value of the translated string before it is saved into the file
1008  *
1009  * @uses $CFG
1010  * @param string $value Raw string to be saved into the lang pack
1011  * @return string Fixed value
1012  */
1013 function lang_fix_value_before_save($value='') {
1014     global $CFG;
1015     if ($CFG->lang != "zh_hk" and $CFG->lang != "zh_tw") {  // Some MB languages include backslash bytes
1016         $value = str_replace("\\","",$value);           // Delete all slashes
1017     }
1018     if (ini_get_bool('magic_quotes_sybase')) {          // Unescape escaped sybase quotes
1019         $value = str_replace("''", "'", $value);
1020     }
1021     // escape all embedded variables
1022     $value = str_replace('$', '\$', $value);            // Add slashes for $
1023     // unescape placeholders: only $a and $a->something are allowed. All other $variables are left escaped
1024     $value = preg_replace('/\\\\\$a($|[^_a-zA-Z0-9\-]|\->[a-zA-Z0-9_]+)/', '$a\\1', $value);
1025     $value = str_replace("'", "\\'", $value);           // Add slashes for '
1026     $value = str_replace('"', "\\\"", $value);          // Add slashes for "
1027     $value = str_replace("%","%%",$value);              // Escape % characters
1028     $value = str_replace("\r", "",$value);              // Remove linefeed characters
1029     $value = trim($value);                              // Delete leading/trailing white space
1030     return $value;
1033 /**
1034  * Try and create a new language directory.
1035  *
1036  * Uses PHP>=5.0 syntax of mkdir and tries to create directories recursively.
1037  *
1038  * @uses $CFG
1039  * @param string $directory full path to the directory under $langbase
1040  * @return string|false Returns full path to directory if successful, false if not
1041  */
1042 function lang_make_directory($dir, $shownotices=true) {
1043     global $CFG;
1044     umask(0000);
1045     if (! file_exists($dir)) {
1046         if (! @mkdir($dir, $CFG->directorypermissions, true)) { // recursive=true; PHP>=5.0 needed
1047             return false;
1048         }
1049         //@chmod($dir, $CFG->directorypermissions);  // Just in case mkdir didn't do it
1050     }
1051     return $dir;
1054 /**
1055  * Return the string key name for use in HTML form.
1056  *
1057  * Required because '.' in form input names get replaced by '_' by PHP.
1058  *
1059  * @param string $keyfromfile The key name containing '.'
1060  * @return string The key name without '.'
1061  */
1062 function lang_form_string_key($keyfromfile) {
1063     return str_replace('.', '##46#', $keyfromfile);  /// Derived from &#46, the ascii value for a period.
1066 /**
1067  * Return the string key name for use in file.
1068  *
1069  * Required because '.' in form input names get replaced by '_' by PHP.
1070  *
1071  * @param string $keyfromfile The key name without '.'
1072  * @return string The key name containing '.'
1073  */
1074 function lang_file_string_key($keyfromform) {
1075     return str_replace('##46#', '.', $keyfromform);
1078 /**
1079  * Return the substring of the string and take care of XHTML compliance.
1080  *
1081  * There was a problem with pure substr() which could possibly produce XHTML parsing error:
1082  *  substr('Marks &amp; Spencer', 0, 9) -> 'Marks &am' ... is not XHTML compliance
1083  * This function takes care of these cases. Fixes MDL-8852.
1084  *
1085  * Thanks to kovacsendre, the author of the function at http://php.net/substr
1086  *
1087  * @param string $str The original string
1088  * @param int $start Start position in the $value string
1089  * @param int $length Optional length of the returned substring
1090  * @return string The substring as returned by substr() with XHTML compliance
1091  * @todo Seems the function does not work with negative $start together with $length being set
1092  */
1093 function lang_xhtml_save_substr($str, $start, $length = NULL) {
1094     if ($length === 0) {
1095         //stop wasting our time ;)
1096         return "";
1097     }
1099     //check if we can simply use the built-in functions
1100     if (strpos($str, '&') === false) {
1101         // No entities. Use built-in functions
1102         if ($length === NULL) {
1103             return substr($str, $start);
1104         } else {
1105             return substr($str, $start, $length);
1106         }
1107     }
1109     // create our array of characters and html entities
1110     $chars = preg_split('/(&[^;\s]+;)|/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
1111     $html_length = count($chars);
1113     // check if we can predict the return value and save some processing time, i.e.:
1114     // input string was empty OR
1115     // $start is longer than the input string OR
1116     // all characters would be omitted
1117     if (($html_length === 0) or ($start >= $html_length) or (isset($length) and ($length <= -$html_length))) {
1118         return '';
1119     }
1121     //calculate start position
1122     if ($start >= 0) {
1123         $real_start = $chars[$start][1];
1124     } else {
1125         //start'th character from the end of string
1126         $start = max($start,-$html_length);
1127         $real_start = $chars[$html_length+$start][1];
1128     }
1130     if (!isset($length)) {
1131         // no $length argument passed, return all remaining characters
1132         return substr($str, $real_start);
1133     } elseif ($length > 0) {
1134         // copy $length chars
1135         if ($start+$length >= $html_length) {
1136             // return all remaining characters
1137             return substr($str, $real_start);
1138         } else {
1139             //return $length characters
1140             return substr($str, $real_start, $chars[max($start,0)+$length][1] - $real_start);
1141         }
1142     } else {
1143         //negative $length. Omit $length characters from end
1144         return substr($str, $real_start, $chars[$html_length+$length][1] - $real_start);
1145     }
1148 /**
1149 * Finds all English string files in the standard lang/en_utf8 location.
1151 * Core lang files should always be stored here and not in the module space (MDL-10920).
1152 * The English version of the file may be found in
1153 *  $CFG->dirroot/lang/en_utf8/filename
1154 * The localised version of the found file should be saved into
1155 *  $CFG->dataroot/lang/currentlang[_local]/filename
1156 * where "filename" is returned as a part of the file record.
1158 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
1159 */
1160 function lang_standard_locations() {
1161     global $CFG;
1162     $files = array();
1163     // Standard location of master English string files.
1164     $places = array($CFG->dirroot.'/lang/en_utf8');
1165         foreach ($places as $place) {
1166             foreach (get_directory_list($place, '', false) as $file) {
1167                 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
1168                     $fullpath = $place.'/'.$file;
1169                     $files[$fullpath] = array(
1170                         'filename' => $file,
1171                         'location' => '',
1172                         'plugin' => '',
1173                         'prefix' => '',
1174                     );
1175                 }
1176             }
1177         }
1178     return $files;
1181 /**
1182 * Finds all English string files in non-standard location.
1184 * Searches for lang/en_utf8/*.php in various types of plugins (blocks, database presets, question types,
1185 * 3rd party modules etc.) and returns an array of found files details.
1187 * The English version of the file may be found in
1188 *  $CFG->dirroot/location/plugin/lang/en_utf8/filename
1189 * The localised version of the found file should be saved into
1190 *  $CFG->dataroot/lang/currentlang[_local]/prefix_plugin.php
1191 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
1193 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
1194 */
1195 function lang_extra_locations() {
1196     global $CFG;
1197     $files = array();
1198     $places = get_string_manager()->get_registered_plugin_types();
1199     foreach ($places as $prefix => $directories) {
1200         foreach ($directories as $directory) {
1201             foreach (get_list_of_plugins($directory) as $plugin) {
1202                 $enlangdirlocation = $CFG->dirroot.'/'.$directory.'/'.$plugin.'/lang/en_utf8';
1203                 foreach (get_directory_list($enlangdirlocation, '', false) as $file) {
1204                     if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
1205                         $fullpath = $enlangdirlocation.'/'.$file;
1206                         $files[$fullpath] = array(
1207                             'filename' => $file,
1208                             'location' => $directory,
1209                             'plugin' => $plugin,
1210                             'prefix' => $prefix,
1211                         );
1212                     }
1213                 }
1214             }
1215         }
1216     }
1217     return $files;
1220 /**
1221  * Lookup for a stringfile details.
1222  *
1223  * English files can be stored in several places (core space or module/plugin space). Their translations
1224  * go into the one directory - the current language pack. Therefore, the name of the stringfile may be
1225  * considered as a key of the list of all stringfiles.
1226  *
1227  * @param string $currentfile the filename
1228  * @param array $stringfiles the array of file info returned by {@link lang_extra_locations()}
1229  * @return array Array of a file information (filename, location, plugin, prefix) or null.
1230  */
1231 function lang_get_file_info($currentfile, $stringfiles) {
1232     $found = false;
1233     foreach ($stringfiles as $path=>$stringfile) {
1234         if ($stringfile['filename'] == $currentfile) {
1235             $found = true;
1236             $ret = $stringfile;
1237             $ret['fullpath'] = $path;
1238             break;
1239         }
1240     }
1241     if ($found) {
1242         return $ret;
1243     } else {
1244         return null;
1245     }
1248 /**
1249  * Returns all English help files in the standard lang/en_utf8/help location.
1250  *
1251  * Core help files should always be stored here and not in the module space (MDL-10920).
1252  * The English version of the file may be found in
1253  *  $CFG->dirroot/lang/en_utf8/help/filename
1254  * The localised version of the found file should be saved into
1255  *  $CFG->dataroot/lang/currentlang[_local]/help/filename
1256  * where "filename" is returned as a part of the file record.
1257  *
1258  * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
1259  */
1260 function lang_help_standard_locations() {
1261     global $CFG;
1262     $files = array();
1263     // Standard location of master English help files.
1264     $places = array($CFG->dirroot.'/lang/en_utf8/help');
1265         foreach ($places as $place) {
1266             foreach (get_directory_list($place, 'CVS') as $file) {
1267                 if ((substr($file, -5) == '.html') || (substr($file, -4) == '.txt' )) {
1268                     $fullpath = $place.'/'.$file;
1269                     $files[$fullpath] = array(
1270                         'filename' => $file,
1271                         'location' => '',
1272                         'plugin' => '',
1273                         'prefix' => '',
1274                     );
1275                 }
1276             }
1277         }
1278     return $files;
1281 /**
1282  * Returns all English help files in non-standard location.
1283  *
1284  * Searches for lang/en_utf8/help/* files in various types of plugins (blocks, database presets, question types,
1285  * 3rd party modules etc.) and returns an array of found files details.
1286  *
1287  * The English version of the file may be found in
1288  *  $CFG->dirroot/location/plugin/lang/en_utf8/help/filename
1289  * The localised version of the found file should be saved into
1290  *  $CFG->dataroot/lang/currentlang[_local]/help/prefix_plugin/filename (XXX is "prefix" here right?)
1291  * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
1292  *
1293  * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
1294  */
1295 function lang_help_extra_locations() {
1296     global $CFG;
1297     $files = array();
1298     $places = get_string_manager()->get_registered_plugin_types();
1299     foreach ($places as $prefix => $directories) {
1300         foreach ($directories as $directory) {
1301             foreach (get_list_of_plugins($directory) as $plugin) {
1302                 $enlangdirlocation = $CFG->dirroot.'/'.$directory.'/'.$plugin.'/lang/en_utf8/help';
1303                 foreach (get_directory_list($enlangdirlocation, 'CVS') as $file) {
1304                     if ((substr($file, -5) == '.html') || (substr($file, -4) == '.txt' )) {
1305                         $fullpath = $enlangdirlocation.'/'.$file;
1306                         $files[$fullpath] = array(
1307                             'filename' => $file,
1308                             'location' => $directory,
1309                             'plugin' => $plugin,
1310                             'prefix' => $prefix,
1311                         );
1312                     }
1313                 }
1314             }
1315         }
1316     }
1317     return $files;
1320 /**
1321  * Return a preview URL for help file, if available.
1322  *
1323  * @param string $currentfile The relative path to the help file, e.g. "assignment/types.html" - MDL-12291
1324  * @param bool $skiplocal Force displaying the helpfile from a master lang pack
1325  * @param string $forcelang Force language of the help, e.g. "en_utf8"
1326  * @return string $url
1327  */
1328 function lang_help_preview_url($currentfile, $skiplocal=false, $forcelang = '') {
1329     $currentpathexp = explode('/', $currentfile);
1330     if (count($currentpathexp) > 1) {
1331         $url = '/help.php?module='.implode('/',array_slice($currentpathexp,0,count($currentpathexp)-1)).'&amp;file='.end($currentpathexp);
1332     } else {
1333         $url = '/help.php?module=moodle&amp;file='.$currentfile;
1334     }
1335     if ($skiplocal) {
1336         $url .= '&amp;skiplocal=1';
1337     }
1338     if ($forcelang) {
1339         $url .= '&amp;forcelang='.$forcelang;
1340     }
1341     return $url;
1345 /**
1346  * Saves (overwrites) translated help file.
1347  *
1348  * @param string $helproot The path to the "help" folder
1349  * @param string $file The relative path to the html help file
1350  * @param string $content HTML data to be saved
1351  * @return bool False if save failed, true otherwise
1352  */
1353 function lang_help_save_file($helproot, $file, $content) {
1354     global $CFG, $USER, $OUTPUT;
1356     $content = str_replace("\r", "",$content);              // Remove linefeed characters
1357     $content = preg_replace("/\n{3,}/", "\n\n", $content);  // Collapse runs of blank lines
1358     $content = trim($content);                              // Delete leading/trailing whitespace
1359     if (is_readable("$helproot/$file") && filesize("$helproot/$file") > 0 && $content == '') {
1360         echo $OUTPUT->notification(get_string('langrmyourself', 'admin'));
1361         return true;
1362     }
1364     error_reporting(0);
1365     if (!$f = fopen("$helproot/$file","w")) {
1366         error_reporting($CFG->debug);
1367         return false;
1368     }
1369     error_reporting($CFG->debug);
1371     fwrite($f, $content);
1372     fclose($f);
1374     // Remove file if its empty
1375     if (filesize("$helproot/$file") == 0) {
1376         unlink("$helproot/$file");
1377     }
1379     return true;