MDL-10870 A few more fixes to the file.php page's navigation
[moodle-pu.git] / admin / lang.php
blob2cdb81f641e26cee1c392468292febe740aa1800
1 <?PHP // $Id$
2 /**
3 * Display the admin/language menu and process strings translation.
4 */
6 require_once('../config.php');
7 require_once($CFG->libdir.'/adminlib.php');
9 admin_externalpage_setup('langedit');
11 $context = get_context_instance(CONTEXT_SYSTEM, SITEID);
13 define('LANG_SUBMIT_REPEAT', 1); // repeat displaying submit button?
14 define('LANG_SUBMIT_REPEAT_EVERY', 20); // if so, after how many lines?
15 define('LANG_DISPLAY_MISSING_LINKS', 1); // display "go to first/next missing string" links?
16 define('LANG_DEFAULT_FILE', ''); // default file to translate. Empty allowed
17 define('LANG_LINK_MISSING_STRINGS', 1); // create links from "missing" page to "compare" page?
18 define('LANG_DEFAULT_USELOCAL', 0); // should *_utf8_local be used by default?
19 define('LANG_MISSING_TEXT_MAX_LEN', 60); // maximum length of the missing text to display
20 define('LANG_KEEP_ORPHANS', 1); // keep orphaned strings (i.e. strings w/o English reference)
22 $mode = optional_param('mode', '', PARAM_ALPHA);
23 $currentfile = optional_param('currentfile', LANG_DEFAULT_FILE, PARAM_FILE);
24 $uselocal = optional_param('uselocal', -1, PARAM_INT);
26 if ($uselocal == -1) {
27 if (isset($SESSION->langtranslateintolocal)) {
28 $uselocal = $SESSION->langtranslateintolocal;
29 } else {
30 $uselocal = LANG_DEFAULT_USELOCAL;
32 } else {
33 $SESSION->langtranslateintolocal = $uselocal;
36 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id, false)) {
37 // Force using _local
38 $uselocal = 1;
41 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id, false) && (!$uselocal)) {
42 print_error('cannoteditmasterlang');
45 if ((!has_capability('moodle/site:langeditlocal', $context, $USER->id, false)) && ($uselocal)) {
46 print_error('cannotcustomizelocallang');
49 $strlanguage = get_string("language");
50 $strcurrentlanguage = get_string("currentlanguage");
51 $strmissingstrings = get_string("missingstrings");
52 $streditstrings = get_string("editstrings", 'admin');
53 $stredithelpdocs = get_string("edithelpdocs", 'admin');
54 $strthislanguage = get_string("thislanguage");
55 $strgotofirst = get_string('gotofirst','admin');
56 $strfilestoredin = get_string('filestoredin', 'admin');
57 $strfilestoredinhelp = get_string('filestoredinhelp', 'admin');
58 $strswitchlang = get_string('switchlang', 'admin');
59 $strchoosefiletoedit = get_string('choosefiletoedit', 'admin');
60 $streditennotallowed = get_string('langnoeditenglish', 'admin');
61 $strfilecreated = get_string('filecreated', 'admin');
62 $strprev = get_string('previous');
63 $strnext = get_string('next');
64 $strlocalstringcustomization = get_string('localstringcustomization', 'admin');
65 $strlangpackmaintaining = get_string('langpackmaintaining', 'admin');
66 $strnomissingstrings = get_string('nomissingstrings', 'admin');
68 $currentlang = current_language();
70 switch ($mode) {
71 case "missing":
72 // Missing array keys are not bugs here but missing strings
73 error_reporting(E_ALL ^ E_NOTICE);
74 $title = $strmissingstrings;
75 break;
76 case "compare":
77 $title = $streditstrings;
78 break;
79 default:
80 $title = $strlanguage;
81 break;
83 $crumbs[] = array('name' => $strlanguage, 'link' => "$CFG->wwwroot/admin/lang.php");
84 $navigation = build_navigation($crumbs);
86 admin_externalpage_print_header();
88 // Prepare and render menu tabs
89 $firstrow = array();
90 $secondrow = array();
91 $inactive = NULL;
92 $activated = NULL;
93 $currenttab = $mode;
94 if ($uselocal) {
95 $inactive = array('uselocal');
96 $activated = array('uselocal');
97 } else {
98 $inactive = array('usemaster');
99 $activated = array('usemaster');
101 if (has_capability('moodle/site:langeditlocal', $context, $USER->id, false)) {
102 $firstrow[] = new tabobject('uselocal',
103 $CFG->wwwroot."/admin/lang.php?mode=$mode&amp;currentfile=$currentfile&amp;uselocal=1",
104 $strlocalstringcustomization );
106 if (has_capability('moodle/site:langeditmaster', $context, $USER->id, false)) {
107 $firstrow[] = new tabobject('usemaster',
108 $CFG->wwwroot."/admin/lang.php?mode=$mode&amp;currentfile=$currentfile&amp;uselocal=0",
109 $strlangpackmaintaining );
111 $secondrow[] = new tabobject('missing', $CFG->wwwroot.'/admin/lang.php?mode=missing', $strmissingstrings );
112 $secondrow[] = new tabobject('compare', $CFG->wwwroot.'/admin/lang.php?mode=compare', $streditstrings );
113 // TODO
114 // langdoc.php functionality is planned to be merged into lang.php
115 $secondrow[] = new tabobject('langdoc', $CFG->wwwroot.'/admin/langdoc.php', $stredithelpdocs );
116 $tabs = array($firstrow, $secondrow);
117 print_tabs($tabs, $currenttab, $inactive, $activated);
120 if (!$mode) {
121 print_box_start();
122 $currlang = current_language();
123 $langs = get_list_of_languages(false, true);
124 popup_form ("$CFG->wwwroot/$CFG->admin/lang.php?lang=", $langs, "chooselang", $currlang, "", "", "", false, 'self', $strcurrentlanguage.':');
125 print_box_end();
126 admin_externalpage_print_footer();
127 exit;
130 // Get a list of all the root files in the English directory
132 $langbase = $CFG->dataroot . '/lang';
133 $enlangdir = "$CFG->dirroot/lang/en_utf8";
134 if ($currentlang == 'en_utf8') {
135 $langdir = $enlangdir;
136 } else {
137 $langdir = "$langbase/$currentlang";
139 $locallangdir = "$langbase/{$currentlang}_local";
141 if (! $stringfiles = get_directory_list($enlangdir, "", false)) {
142 error("Could not find English language pack!");
145 foreach ($stringfiles as $key => $file) {
146 if (substr($file, -4) != ".php") { //Avoid non php files to be showed
147 unset($stringfiles[$key]);
149 if ($file == "langconfig.php") { //Avoid langconfig.php to be showed
150 unset($stringfiles[$key]);
154 if ($mode == "missing") {
155 if (!file_exists($langdir)) {
156 error ('to edit this language pack, you need to put it in '.$CFG->dataroot.'/lang');
159 // Following variables store the HTML output to be echo-ed
160 $m = '';
161 $o = '';
163 // For each file, check that a counterpart exists, then check all the strings
164 foreach ($stringfiles as $file) {
165 unset($string);
166 include("$enlangdir/$file");
167 $enstring = $string;
169 ksort($enstring);
171 unset($string);
173 if (file_exists("$langdir/$file")) {
174 include("$langdir/$file");
175 $fileismissing = 0;
176 } else {
177 $fileismissing = 1;
178 // notify(get_string("filemissing", "", "$langdir/$file"));
179 $o .= '<div class="notifyproblem">'.get_string("filemissing", "", "$langdir/$file").'</div><br />';
180 $string = array();
183 $missingcounter = 0;
185 $first = true;
186 foreach ($enstring as $key => $value) {
187 if (empty($string[$key]) and $string[$key] != "0") { //bug fix 4735 mits
188 $value = htmlspecialchars($value);
189 $value = str_replace("$"."a", "\\$"."a", $value);
190 $value = str_replace("%%","%",$value);
191 if ($first) {
192 $m .= "<a href=\"lang.php?mode=missing#$file\">$file";
193 $m .= $fileismissing ? '*' : '';
194 $m .= '</a> &nbsp; ';
195 $o .= "<p><a name=\"$file\"></a><b>".get_string("stringsnotset","","$langdir/$file")."</b></p><pre>";
196 $first = false;
197 $somethingfound = true;
199 $missingcounter++;
200 if (LANG_LINK_MISSING_STRINGS) {
201 $missinglinkstart = "<a href=\"lang.php?mode=compare&amp;currentfile=$file#missing$missingcounter\">";
202 $missinglinkend = '</a>';
203 } else {
204 $missinglinkstart = '';
205 $missinglinkend = '';
207 if (strlen($value) > LANG_MISSING_TEXT_MAX_LEN) {
208 $value = lang_xhtml_save_substr($value, 0, LANG_MISSING_TEXT_MAX_LEN) . ' ...'; // MDL-8852
210 $o .= "$"."string['".$missinglinkstart.$key.$missinglinkend."'] = \"$value\";<br />";
213 if (!$first) {
214 $o .= '</pre><hr />';
218 if ($m <> '') {
219 print_box($m, 'filenames');
221 echo $o;
223 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/help", "CVS")) {
224 error("Could not find English language help files!");
227 foreach ($files as $filekey => $file) { // check all the help files.
228 if (!file_exists("$langdir/help/$file")) {
229 echo "<p><font color=\"red\">".get_string("filemissing", "", "$langdir/help/$file")."</font></p>";
230 $somethingfound = true;
231 continue;
235 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/docs", "CVS")) {
236 error("Could not find English language docs files!");
238 foreach ($files as $filekey => $file) { // check all the docs files.
239 if (!file_exists("$langdir/docs/$file")) {
240 echo "<p><font color=\"red\">".get_string("filemissing", "", "$langdir/docs/$file")."</font></p>";
241 $somethingfound = true;
242 continue;
246 if (!empty($somethingfound)) {
247 print_continue("lang.php");
248 } else {
249 notice(get_string("languagegood"), "lang.php" );
252 } else if ($mode == "compare") {
254 if (!file_exists($langbase) ){
255 if (!lang_make_directory($langbase) ){
256 error('ERROR: Could not create base lang directory ' . $langbase);
257 } else {
258 echo '<div class="notifysuccess">Created directory '.
259 $langbase .'</div>'."<br />\n";
262 if (!$uselocal && !file_exists($langdir)) {
263 if (!lang_make_directory($langdir)) {
264 error('ERROR: Could not create directory '.$langdir);
265 } else {
266 echo '<div class="notifysuccess">Created directory '.
267 $langdir .'</div>'."<br />\n";
270 if ($uselocal && !file_exists($locallangdir)) {
271 if (!lang_make_directory($locallangdir)) {
272 echo '<div class="notifyproblem">ERROR: Could not create directory '.
273 $locallangdir .'</div>'."<br />\n";
274 $uselocal = 0;
275 } else {
276 echo '<div class="notifysuccess">Created directory '.
277 $locallangdir .'</div>'."<br />\n";
281 if (isset($_POST['currentfile'])){ // Save a file
282 if (!confirm_sesskey()) {
283 error(get_string('confirmsesskeybad', 'error'));
286 $newstrings = array();
288 foreach ($_POST as $postkey => $postval) {
289 $stringkey = lang_file_string_key($postkey);
290 $newstrings[$stringkey] = $postval;
293 unset($newstrings['currentfile']);
295 if ($uselocal) {
296 include("$langdir/$currentfile");
297 if (isset($string)) {
298 $packstring = $string;
299 } else {
300 $packstring = array();
302 unset($string);
303 $saveinto = $locallangdir;
304 } else {
305 $packstring = array();
306 $saveinto = $langdir;
309 if (lang_save_file($saveinto, $currentfile, $newstrings, $uselocal, $packstring)) {
310 notify(get_string("changessaved")." ($saveinto/$currentfile)", "green");
311 } else {
312 error("Could not save the file '$saveinto/$currentfile'!", "lang.php?mode=compare&amp;currentfile=$currentfile");
314 unset($packstring);
317 print_box_start('generalbox editstrings');
318 $menufiles = array();
319 foreach ($stringfiles as $file) {
320 $menufiles[$file] = $file;
322 popup_form("$CFG->wwwroot/$CFG->admin/lang.php?mode=compare&amp;currentfile=", $menufiles, "choosefile",
323 $currentfile, $strchoosefiletoedit);
325 echo '<div class="filestorageinfobox">';
326 echo $strfilestoredin;
327 echo '<code class="path">';
328 echo $uselocal ? "{$currentlang}_local" : $currentlang;
329 echo '</code>';
330 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
331 echo '</div>';
332 print_box_end();
334 if ($currentfile <> '') {
335 $saveto = $uselocal ? $locallangdir : $langdir;
336 error_reporting(0);
337 if (!file_exists("$saveto/$currentfile")) {
338 if (!@touch("$saveto/$currentfile")) {
339 print_heading(get_string("filemissing", "", "$saveto/$currentfile"), '', 4, 'error');
340 } else {
341 print_heading($strfilecreated, '', 4, 'notifysuccess');
344 if ($currentlang == "en_utf8" && !$uselocal) {
345 $editable = false;
346 print_heading($streditennotallowed, '', 4);
347 } elseif ($f = fopen("$saveto/$currentfile","r+")) {
348 $editable = true;
349 fclose($f);
350 } else {
351 $editable = false;
352 echo "<p><font size=\"1\">".get_string("makeeditable", "", "$saveto/$currentfile")."</font></p>";
354 error_reporting($CFG->debug);
356 $o = ''; // stores the HTML output to be echo-ed
357 unset($string);
358 include("$enlangdir/$currentfile");
359 $enstring = $string;
360 if ($currentlang != 'en' and $currentfile == 'moodle.php') {
361 $enstring['thislanguage'] = "<< TRANSLATORS: Specify the name of your language here. If possible use Unicode Numeric Character References >>";
362 $enstring['thischarset'] = "<< TRANSLATORS: Charset encoding - always use utf-8 >>";
363 $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. >>";
364 $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 >>";
366 ksort($enstring);
368 unset($string);
370 @include("$locallangdir/$currentfile");
371 $localstring = isset($string) ? $string : array();
372 unset($string);
374 @include("$langdir/$currentfile");
376 if ($editable) {
377 $o .= "<form id=\"$currentfile\" action=\"lang.php\" method=\"post\">";
378 $o .= '<div>';
380 $o .= "<table summary=\"\" width=\"100%\" class=\"translator\">";
381 $linescounter = 0;
382 $missingcounter = 0;
383 foreach ($enstring as $key => $envalue) {
384 $linescounter++ ;
385 if (LANG_SUBMIT_REPEAT && $editable && $linescounter % LANG_SUBMIT_REPEAT_EVERY == 0) {
386 $o .= '<tr><td>&nbsp;</td><td><br />';
387 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
388 $o .= '<br />&nbsp;</td></tr>';
390 $envalue = nl2br(htmlspecialchars($envalue));
391 $envalue = preg_replace('/(\$a\-\&gt;[a-zA-Z0-9]*|\$a)/', '<b>$0</b>', $envalue); // Make variables bold.
392 $envalue = str_replace("%%","%",$envalue);
393 $envalue = str_replace("\\","",$envalue); // Delete all slashes
395 $o .= "\n\n".'<tr class="';
396 if ($linescounter % 2 == 0) {
397 $o .= 'r0';
398 } else {
399 $o .= 'r1';
401 $o .= '">';
402 $o .= '<td dir="ltr" lang="en">';
403 $o .= '<span class="stren">'.$envalue.'</span>';
404 $o .= '<br />'."\n";
405 $o .= '<span class="strkey">'.$key.'</span>';
406 $o .= '</td>'."\n";
408 // Missing array keys are not bugs here but missing strings
409 error_reporting(E_ALL ^ E_NOTICE);
410 if ($uselocal) {
411 $value = lang_fix_value_from_file($localstring[$key]);
412 $value2 = lang_fix_value_from_file($string[$key]);
413 if ($value == '') {
414 $value = $value2;
416 } else {
417 $value = lang_fix_value_from_file($string[$key]);
418 $value2 = lang_fix_value_from_file($localstring[$key]);
420 error_reporting($CFG->debug);
422 // Color highlighting:
423 // red #ef6868 - translation missing in both system and local pack
424 // yellow #feff7f - translation missing in system pack but is translated in local
425 // green #AAFFAA - translation present in both system and local but is different
426 if (!$value) {
427 if (!$value2) {
428 $cellcolour = 'class="bothmissing"';
429 } else {
430 $cellcolour = 'class="mastermissing"';
432 $missingcounter++;
433 if (LANG_DISPLAY_MISSING_LINKS) {
434 $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
435 $missingnext = '<a href="#missing'.($missingcounter+1).'">'.
436 '<img src="' . $CFG->pixpath . '/t/down.gif" class="iconsmall" alt="'.$strnext.'" /></a>';
437 $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
438 '<img src="' . $CFG->pixpath . '/t/up.gif" class="iconsmall" alt="'.$strprev.'" /></a>';
439 } else {
440 $missingtarget = '';
441 $missingnext = '';
442 $missingprev = '';
444 } else {
445 if ($value <> $value2 && $value2 <> '') {
446 $cellcolour = 'class="localdifferent"';
447 } else {
448 $cellcolour = '';
450 $missingtarget = '';
451 $missingnext = '';
452 $missingprev = '';
455 if ($editable) {
456 $o .= '<td '.$cellcolour.' valign="top">'. $missingprev . $missingtarget."\n";
457 if (isset($string[$key])) {
458 $valuelen = strlen($value);
459 } else {
460 $valuelen = strlen($envalue);
462 $cols=40;
463 if (strstr($value, "\r") or strstr($value, "\n") or $valuelen > $cols) {
464 $rows = ceil($valuelen / $cols);
465 $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="'.$rows.'">'.$value.'</textarea>'."\n";
466 } else {
467 if ($valuelen) {
468 $cols = $valuelen + 5;
470 $o .= '<input type="text" name="stringXXX'.lang_form_string_key($key).'" value="'.$value.'" size="'.$cols.'" />';
472 if ($value2 <> '' && $value <> $value2) {
473 $o .= '<br /><span style="font-size:small">'.$value2.'</span>';
475 $o .= $missingnext . '</td>';
477 } else {
478 $o .= '<td '.$cellcolour.' valign="top">'.$value.'</td>';
480 $o .= '</tr>'."\n";
482 if ($editable) {
483 $o .= '<tr><td>&nbsp;</td><td><br />';
484 $o .= '<input type="hidden" name="sesskey" value="'.$USER->sesskey.'" />';
485 $o .= '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
486 $o .= '<input type="hidden" name="mode" value="compare" />';
487 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
488 $o .= '</td></tr>';
490 $o .= '</table>';
491 if ($editable) {
492 $o .= '</div>';
493 $o .= '</form>';
496 if (LANG_DISPLAY_MISSING_LINKS) {
497 if ($missingcounter > 0) {
498 print_heading(get_string('numberofmissingstrings', 'admin', $missingcounter), '', 4);
499 if ($editable) {
500 print_heading('<a href="#missing1">'.$strgotofirst.'</a>', "", 4);
502 } else {
503 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
506 echo $o;
508 } else {
509 // no $currentfile specified
510 // no useful information to display - maybe some help? instructions?
514 admin_externalpage_print_footer();
516 //////////////////////////////////////////////////////////////////////
519 * Save language translation file.
521 * Thanks to Petri Asikainen for the original version of code
522 * used to save language files.
524 * @uses $CFG
525 * @uses $USER
526 * @param string $path Full pathname to the directory to use
527 * @param string $file File to overwrite
528 * @param array $strings Array of strings to write
529 * @param bool $local Should *_local version be saved?
530 * @param array $packstrings Array of default langpack strings (needed if $local)
531 * @return bool Created successfully?
533 function lang_save_file($path, $file, $strings, $local, $packstrings) {
534 global $CFG, $USER;
535 if (LANG_KEEP_ORPHANS) {
536 // let us load the current content of the file
537 unset($string);
538 @include("$path/$file");
539 if (isset($string)) {
540 $orphans = $string;
541 unset($string);
542 } else {
543 $orphans = array();
546 // let us rewrite the file
547 if (!$f = @fopen("$path/$file","w")) {
548 return false;
551 fwrite($f, "<?PHP // \$Id\$ \n");
552 fwrite($f, " // $file - created with Moodle $CFG->release ($CFG->version)\n");
553 if ($local) {
554 fwrite($f, " // local modifications from $CFG->wwwroot\n");
556 fwrite($f, "\n\n");
557 ksort($strings);
558 foreach ($strings as $key => $value) {
559 @list($id, $stringname) = explode('XXX',$key);
560 $value = lang_fix_value_before_save($value);
561 if ($id == "string" and $value != ""){
562 if ((!$local) || (lang_fix_value_from_file($packstrings[$stringname]) <> lang_fix_value_from_file($value))) {
563 // Either we are saving the master language pack
564 // or we are saving local language pack and the strings differ.
565 fwrite($f,"\$string['$stringname'] = '$value';\n");
567 if (LANG_KEEP_ORPHANS && isset($orphans[$stringname])) {
568 unset($orphans[$stringname]);
572 if (LANG_KEEP_ORPHANS) {
573 // let us add orphaned strings, i.e. already translated strings without the English referential source
574 foreach ($orphans as $key => $value) {
575 fwrite($f,"\$string['$key'] = '".lang_fix_value_before_save($value)."'; // ORPHANED\n");
578 fwrite($f,"\n?>\n");
579 fclose($f);
580 return true;
584 * Fix value of the translated string after it is load from the file.
586 * These modifications are typically necessary to work with the same string coming from two sources.
587 * We need to compare the content of these sources and we want to have e.g. "This string\r\n"
588 * to be the same as " This string\n".
590 * @param string $value Original string from the file
591 * @return string Fixed value
593 function lang_fix_value_from_file($value='') {
594 $value = str_replace("\r","",$value); // Bad character caused by Windows
595 $value = preg_replace("/\n{3,}/", "\n\n", $value); // Collapse runs of blank lines
596 $value = trim($value); // Delete leading/trailing white space
597 $value = str_replace("\\","",$value); // Delete all slashes
598 $value = str_replace("%%","%",$value);
599 $value = str_replace("&","&amp;",$value); // Fixes MDL-9248
600 $value = str_replace("<","&lt;",$value);
601 $value = str_replace(">","&gt;",$value);
602 $value = str_replace('"',"&quot;",$value);
603 return $value;
607 * Fix value of the translated string before it is saved into the file
609 * @uses $CFG
610 * @param string $value Raw string to be saved into the lang pack
611 * @return string Fixed value
613 function lang_fix_value_before_save($value='') {
614 global $CFG;
615 if ($CFG->lang != "zh_hk" and $CFG->lang != "zh_tw") { // Some MB languages include backslash bytes
616 $value = str_replace("\\","",$value); // Delete all slashes
618 if (ini_get_bool('magic_quotes_sybase')) { // Unescape escaped sybase quotes
619 $value = str_replace("''", "'", $value);
621 $value = str_replace("'", "\\'", $value); // Add slashes for '
622 $value = str_replace('"', "\\\"", $value); // Add slashes for "
623 $value = str_replace("%","%%",$value); // Escape % characters
624 $value = str_replace("\r", "",$value); // Remove linefeed characters
625 $value = trim($value); // Delete leading/trailing white space
626 return $value;
630 * Try and create a new language directory.
632 * @uses $CFG
633 * @param string $directory full path to the directory under $langbase
634 * @return string|false Returns full path to directory if successful, false if not
636 function lang_make_directory($dir, $shownotices=true) {
637 global $CFG;
638 umask(0000);
639 if (! file_exists($dir)) {
640 if (! @mkdir($dir, $CFG->directorypermissions)) {
641 return false;
643 //@chmod($dir, $CFG->directorypermissions); // Just in case mkdir didn't do it
645 return $dir;
649 * Return the string key name for use in HTML form.
651 * Required because '.' in form input names get replaced by '_' by PHP.
653 * @param string $keyfromfile The key name containing '.'
654 * @return string The key name without '.'
656 function lang_form_string_key($keyfromfile) {
657 return str_replace('.', '##46#', $keyfromfile); /// Derived from &#46, the ascii value for a period.
661 * Return the string key name for use in file.
663 * Required because '.' in form input names get replaced by '_' by PHP.
665 * @param string $keyfromfile The key name without '.'
666 * @return string The key name containing '.'
668 function lang_file_string_key($keyfromform) {
669 return str_replace('##46#', '.', $keyfromform);
673 * Return the substring of the string and take care of XHTML compliance.
675 * There was a problem with pure substr() which could possibly produce XHTML parsing error:
676 * substr('Marks &amp; Spencer', 0, 9) -> 'Marks &am' ... is not XHTML compliance
677 * This function takes care of these cases. Fixes MDL-8852.
679 * Thanks to kovacsendre, the author of the function at http://php.net/substr
681 * @param string $str The original string
682 * @param int $start Start position in the $value string
683 * @param int $length Optional length of the returned substring
684 * @return string The substring as returned by substr() with XHTML compliance
685 * @todo Seems the function does not work with negative $start together with $length being set
687 function lang_xhtml_save_substr($str, $start, $length = NULL) {
688 if ($length === 0) {
689 //stop wasting our time ;)
690 return "";
693 //check if we can simply use the built-in functions
694 if (strpos($str, '&') === false) {
695 // No entities. Use built-in functions
696 if ($length === NULL) {
697 return substr($str, $start);
698 } else {
699 return substr($str, $start, $length);
703 // create our array of characters and html entities
704 $chars = preg_split('/(&[^;\s]+;)|/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE);
705 $html_length = count($chars);
707 // check if we can predict the return value and save some processing time, i.e.:
708 // input string was empty OR
709 // $start is longer than the input string OR
710 // all characters would be omitted
711 if (($html_length === 0) or ($start >= $html_length) or (isset($length) and ($length <= -$html_length))) {
712 return '';
715 //calculate start position
716 if ($start >= 0) {
717 $real_start = $chars[$start][1];
718 } else {
719 //start'th character from the end of string
720 $start = max($start,-$html_length);
721 $real_start = $chars[$html_length+$start][1];
724 if (!isset($length)) {
725 // no $length argument passed, return all remaining characters
726 return substr($str, $real_start);
727 } elseif ($length > 0) {
728 // copy $length chars
729 if ($start+$length >= $html_length) {
730 // return all remaining characters
731 return substr($str, $real_start);
732 } else {
733 //return $length characters
734 return substr($str, $real_start, $chars[max($start,0)+$length][1] - $real_start);
736 } else {
737 //negative $length. Omit $length characters from end
738 return substr($str, $real_start, $chars[$html_length+$length][1] - $real_start);
743 * Finds all English string files in the standard lang/en_utf8 location.
745 * The English version of the file may be found in
746 * $CFG->dirroot/lang/en_utf8/filename
747 * The localised version of the found file should be saved into
748 * $CFG->dataroot/lang/current_lang[_local]/filename
749 * where "filename" is returned as a part of the file record.
751 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
753 function lang_standard_locations() {
754 global $CFG;
755 $files = array();
756 // Standard location of master English string files.
757 $places = array($CFG->dirroot.'/lang/en_utf8');
758 foreach ($places as $place) {
759 foreach (get_directory_list($place, '', false) as $file) {
760 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
761 $fullpath = $place.'/'.$file;
762 $files[$fullpath] = array(
763 'filename' => $file,
764 'location' => '',
765 'plugin' => '',
766 'prefix' => '',
771 return $files;
775 * Finds all English string files in non-standard location.
777 * Searches for lang/en_utf8/*.php in various types of plugins (blocks, database presets, question types,
778 * 3rd party modules etc.) and returns an array of found files details.
780 * The English version of the file may be found in
781 * $CFG->dirroot/location/plugin/lang/en_utf8/filename
782 * The localised version of the found file should be saved into
783 * $CFG->dataroot/lang/current_lang[_local]/prefix_plugin.php
784 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
786 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
788 function lang_extra_locations() {
789 global $CFG;
790 $files = array();
791 $places = places_to_search_for_lang_strings();
792 foreach ($places as $prefix => $directories) {
793 if ($prefix != '__exceptions') {
794 foreach ($directories as $directory) {
795 foreach (get_list_of_plugins($directory) as $plugin) {
796 $enlangdirlocation = $CFG->dirroot.'/'.$directory.'/'.$plugin.'/lang/en_utf8';
797 foreach (get_directory_list($enlangdirlocation, '', false) as $file) {
798 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
799 $fullpath = $enlangdirlocation.'/'.$file;
800 $files[$fullpath] = array(
801 'filename' => $file,
802 'location' => $directory,
803 'plugin' => $plugin,
804 'prefix' => $prefix,
812 return $files;