3 * Display the admin/language menu and process strings translation.
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
;
30 $uselocal = LANG_DEFAULT_USELOCAL
;
33 $SESSION->langtranslateintolocal
= $uselocal;
36 if (!has_capability('moodle/site:langeditmaster', $context, $USER->id
, false)) {
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 = 'Local string customization'; // TODO / FIXME
65 $strlangpackmaintaining = 'Language pack maintaining'; // TODO / FIXME
66 $strnomissingstrings = 'No missing strings'; // TODO / FIXME
68 // TODO / FIXME add into en_utf8/error.php
69 $string['cannoteditmasterlang'] = 'You do not have permission to edit master language package. This
70 permission is controlled by the capability "moodle/site:langeditmaster". Set this capability
71 to allow you to edit master language packages in case you are the maintainer of a package.';
72 $string['cannotcustomizelocallang'] = 'You do not have permission to customize the strings translation.
73 This permission is controlled by the capability "moodle/site:langeditlocal". Set this capability
74 to allow you to edit local language packages in case you want to modify translations for your site.';
76 // TODO/FIXME add into en_utf8/admin.php:
77 // $string['numberofmissingstrings'] = 'Number of missing strings: $a';
79 $currentlang = current_language();
83 // Missing array keys are not bugs here but missing strings
84 error_reporting(E_ALL ^ E_NOTICE
);
85 $title = $strmissingstrings;
88 $title = $streditstrings;
91 $title = $strlanguage;
94 $navigation = "<a href=\"lang.php\">$strlanguage</a> -> $title";
96 $crumbs[] = array('name' => $strlanguage, 'link' => "$CFG->wwwroot/admin/lang.php");
97 $navigation = build_navigation($crumbs);
99 admin_externalpage_print_header();
101 // Prepare and render menu tabs
103 $secondrow = array();
108 $inactive = array('uselocal');
109 $activated = array('uselocal');
111 $inactive = array('usemaster');
112 $activated = array('usemaster');
114 if (has_capability('moodle/site:langeditlocal', $context, $USER->id
, false)) {
115 $firstrow[] = new tabobject('uselocal',
116 $CFG->wwwroot
."/admin/lang.php?mode=$mode&currentfile=$currentfile&uselocal=1",
117 $strlocalstringcustomization );
119 if (has_capability('moodle/site:langeditmaster', $context, $USER->id
, false)) {
120 $firstrow[] = new tabobject('usemaster',
121 $CFG->wwwroot
."/admin/lang.php?mode=$mode&currentfile=$currentfile&uselocal=0",
122 $strlangpackmaintaining );
124 $secondrow[] = new tabobject('missing', $CFG->wwwroot
.'/admin/lang.php?mode=missing', $strmissingstrings );
125 $secondrow[] = new tabobject('compare', $CFG->wwwroot
.'/admin/lang.php?mode=compare', $streditstrings );
127 // langdoc.php functionality is planned to be merged into lang.php
128 $secondrow[] = new tabobject('langdoc', $CFG->wwwroot
.'/admin/langdoc.php', $stredithelpdocs );
129 $tabs = array($firstrow, $secondrow);
130 print_tabs($tabs, $currenttab, $inactive, $activated);
135 $currlang = current_language();
136 $langs = get_list_of_languages(false, true);
137 popup_form ("$CFG->wwwroot/$CFG->admin/lang.php?lang=", $langs, "chooselang", $currlang, "", "", "", false, 'self', $strcurrentlanguage.':');
139 admin_externalpage_print_footer();
143 // Get a list of all the root files in the English directory
145 $langbase = $CFG->dataroot
. '/lang';
146 $enlangdir = "$CFG->dirroot/lang/en_utf8";
147 if ($currentlang == 'en_utf8') {
148 $langdir = $enlangdir;
150 $langdir = "$langbase/$currentlang";
152 $locallangdir = "$langbase/{$currentlang}_local";
154 if (! $stringfiles = get_directory_list($enlangdir, "", false)) {
155 error("Could not find English language pack!");
158 foreach ($stringfiles as $key => $file) {
159 if (substr($file, -4) != ".php") { //Avoid non php files to be showed
160 unset($stringfiles[$key]);
162 if ($file == "langconfig.php") { //Avoid langconfig.php to be showed
163 unset($stringfiles[$key]);
167 if ($mode == "missing") {
168 if (!file_exists($langdir)) {
169 error ('to edit this language pack, you need to put it in '.$CFG->dataroot
.'/lang');
172 // Following variables store the HTML output to be echo-ed
176 // For each file, check that a counterpart exists, then check all the strings
177 foreach ($stringfiles as $file) {
179 include("$enlangdir/$file");
186 if (file_exists("$langdir/$file")) {
187 include("$langdir/$file");
191 // notify(get_string("filemissing", "", "$langdir/$file"));
192 $o .= '<div class="notifyproblem">'.get_string("filemissing", "", "$langdir/$file").'</div><br />';
199 foreach ($enstring as $key => $value) {
200 if (empty($string[$key]) and $string[$key] != "0") { //bug fix 4735 mits
201 $value = htmlspecialchars($value);
202 $value = str_replace("$"."a", "\\$"."a", $value);
203 $value = str_replace("%%","%",$value);
205 $m .= "<a href=\"lang.php?mode=missing#$file\">$file";
206 $m .= $fileismissing ?
'*' : '';
207 $m .= '</a> ';
208 $o .= "<p><a name=\"$file\"></a><b>".get_string("stringsnotset","","$langdir/$file")."</b></p><pre>";
210 $somethingfound = true;
213 if (LANG_LINK_MISSING_STRINGS
) {
214 $missinglinkstart = "<a href=\"lang.php?mode=compare&currentfile=$file#missing$missingcounter\">";
215 $missinglinkend = '</a>';
217 $missinglinkstart = '';
218 $missinglinkend = '';
220 if (strlen($value) > LANG_MISSING_TEXT_MAX_LEN
) {
221 $value = lang_xhtml_save_substr($value, 0, LANG_MISSING_TEXT_MAX_LEN
) . ' ...'; // MDL-8852
223 $o .= "$"."string['".$missinglinkstart.$key.$missinglinkend."'] = \"$value\";<br />";
227 $o .= '</pre><hr />';
232 print_box($m, 'filenames');
236 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/help", "CVS")) {
237 error("Could not find English language help files!");
240 foreach ($files as $filekey => $file) { // check all the help files.
241 if (!file_exists("$langdir/help/$file")) {
242 echo "<p><font color=\"red\">".get_string("filemissing", "", "$langdir/help/$file")."</font></p>";
243 $somethingfound = true;
248 if (! $files = get_directory_list("$CFG->dirroot/lang/en_utf8/docs", "CVS")) {
249 error("Could not find English language docs files!");
251 foreach ($files as $filekey => $file) { // check all the docs files.
252 if (!file_exists("$langdir/docs/$file")) {
253 echo "<p><font color=\"red\">".get_string("filemissing", "", "$langdir/docs/$file")."</font></p>";
254 $somethingfound = true;
259 if (!empty($somethingfound)) {
260 print_continue("lang.php");
262 notice(get_string("languagegood"), "lang.php" );
265 } else if ($mode == "compare") {
267 if (!file_exists($langbase) ){
268 if (!lang_make_directory($langbase) ){
269 error('ERROR: Could not create base lang directory ' . $langbase);
271 echo '<div class="notifysuccess">Created directory '.
272 $langbase .'</div>'."<br />\n";
275 if (!$uselocal && !file_exists($langdir)) {
276 if (!lang_make_directory($langdir)) {
277 error('ERROR: Could not create directory '.$langdir);
279 echo '<div class="notifysuccess">Created directory '.
280 $langdir .'</div>'."<br />\n";
283 if ($uselocal && !file_exists($locallangdir)) {
284 if (!lang_make_directory($locallangdir)) {
285 echo '<div class="notifyproblem">ERROR: Could not create directory '.
286 $locallangdir .'</div>'."<br />\n";
289 echo '<div class="notifysuccess">Created directory '.
290 $locallangdir .'</div>'."<br />\n";
294 if (isset($_POST['currentfile'])){ // Save a file
295 if (!confirm_sesskey()) {
296 error(get_string('confirmsesskeybad', 'error'));
299 $newstrings = array();
301 foreach ($_POST as $postkey => $postval) {
302 $stringkey = lang_file_string_key($postkey);
303 $newstrings[$stringkey] = $postval;
306 unset($newstrings['currentfile']);
309 include("$langdir/$currentfile");
310 if (isset($string)) {
311 $packstring = $string;
313 $packstring = array();
316 $saveinto = $locallangdir;
318 $packstring = array();
319 $saveinto = $langdir;
322 if (lang_save_file($saveinto, $currentfile, $newstrings, $uselocal, $packstring)) {
323 notify(get_string("changessaved")." ($saveinto/$currentfile)", "green");
325 error("Could not save the file '$saveinto/$currentfile'!", "lang.php?mode=compare&currentfile=$currentfile");
330 print_box_start('generalbox editstrings');
331 $menufiles = array();
332 foreach ($stringfiles as $file) {
333 $menufiles[$file] = $file;
335 popup_form("$CFG->wwwroot/$CFG->admin/lang.php?mode=compare&currentfile=", $menufiles, "choosefile",
336 $currentfile, $strchoosefiletoedit);
338 echo '<div class="filestorageinfobox">';
339 echo $strfilestoredin;
340 echo '<code class="path">';
341 echo $uselocal ?
"{$currentlang}_local" : $currentlang;
343 helpbutton('langswitchstorage', $strfilestoredinhelp, 'moodle');
347 if ($currentfile <> '') {
348 $saveto = $uselocal ?
$locallangdir : $langdir;
350 if (!file_exists("$saveto/$currentfile")) {
351 if (!@touch
("$saveto/$currentfile")) {
352 print_heading(get_string("filemissing", "", "$saveto/$currentfile"), '', 4, 'error');
354 print_heading($strfilecreated, '', 4, 'notifysuccess');
357 if ($currentlang == "en_utf8" && !$uselocal) {
359 print_heading($streditennotallowed, '', 4);
360 } elseif ($f = fopen("$saveto/$currentfile","r+")) {
365 echo "<p><font size=\"1\">".get_string("makeeditable", "", "$saveto/$currentfile")."</font></p>";
367 error_reporting($CFG->debug
);
369 $o = ''; // stores the HTML output to be echo-ed
371 include("$enlangdir/$currentfile");
373 if ($currentlang != 'en' and $currentfile == 'moodle.php') {
374 $enstring['thislanguage'] = "<< TRANSLATORS: Specify the name of your language here. If possible use Unicode Numeric Character References >>";
375 $enstring['thischarset'] = "<< TRANSLATORS: Charset encoding - always use utf-8 >>";
376 $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. >>";
377 $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 >>";
383 @include
("$locallangdir/$currentfile");
384 $localstring = isset($string) ?
$string : array();
387 @include
("$langdir/$currentfile");
390 $o .= "<form id=\"$currentfile\" action=\"lang.php\" method=\"post\">";
393 $o .= "<table summary=\"\" width=\"100%\" class=\"translator\">";
396 foreach ($enstring as $key => $envalue) {
398 if (LANG_SUBMIT_REPEAT
&& $editable && $linescounter % LANG_SUBMIT_REPEAT_EVERY
== 0) {
399 $o .= '<tr><td> </td><td><br />';
400 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
401 $o .= '<br /> </td></tr>';
403 $envalue = nl2br(htmlspecialchars($envalue));
404 $envalue = preg_replace('/(\$a\-\>[a-zA-Z0-9]*|\$a)/', '<b>$0</b>', $envalue); // Make variables bold.
405 $envalue = str_replace("%%","%",$envalue);
406 $envalue = str_replace("\\","",$envalue); // Delete all slashes
408 $o .= "\n\n".'<tr class="';
409 if ($linescounter %
2 == 0) {
415 $o .= '<td dir="ltr" lang="en">';
416 $o .= '<span class="stren">'.$envalue.'</span>';
418 $o .= '<span class="strkey">'.$key.'</span>';
421 // Missing array keys are not bugs here but missing strings
422 error_reporting(E_ALL ^ E_NOTICE
);
424 $value = lang_fix_value_from_file($localstring[$key]);
425 $value2 = lang_fix_value_from_file($string[$key]);
430 $value = lang_fix_value_from_file($string[$key]);
431 $value2 = lang_fix_value_from_file($localstring[$key]);
433 error_reporting($CFG->debug
);
435 // Color highlighting:
436 // red #ef6868 - translation missing in both system and local pack
437 // yellow #feff7f - translation missing in system pack but is translated in local
438 // green #AAFFAA - translation present in both system and local but is different
441 $cellcolour = 'class="bothmissing"';
443 $cellcolour = 'class="mastermissing"';
446 if (LANG_DISPLAY_MISSING_LINKS
) {
447 $missingtarget = '<a name="missing'.$missingcounter.'"></a>';
448 $missingnext = '<a href="#missing'.($missingcounter+
1).'">'.
449 '<img src="' . $CFG->pixpath
. '/t/down.gif" class="iconsmall" alt="'.$strnext.'" /></a>';
450 $missingprev = '<a href="#missing'.($missingcounter-1).'">'.
451 '<img src="' . $CFG->pixpath
. '/t/up.gif" class="iconsmall" alt="'.$strprev.'" /></a>';
458 if ($value <> $value2 && $value2 <> '') {
459 $cellcolour = 'class="localdifferent"';
469 $o .= '<td '.$cellcolour.' valign="top">'. $missingprev . $missingtarget."\n";
470 if (isset($string[$key])) {
471 $valuelen = strlen($value);
473 $valuelen = strlen($envalue);
476 if (strstr($value, "\r") or strstr($value, "\n") or $valuelen > $cols) {
477 $rows = ceil($valuelen / $cols);
478 $o .= '<textarea name="stringXXX'.lang_form_string_key($key).'" cols="'.$cols.'" rows="'.$rows.'">'.$value.'</textarea>'."\n";
481 $cols = $valuelen +
5;
483 $o .= '<input type="text" name="stringXXX'.lang_form_string_key($key).'" value="'.$value.'" size="'.$cols.'" />';
485 if ($value2 <> '' && $value <> $value2) {
486 $o .= '<br /><span style="font-size:small">'.$value2.'</span>';
488 $o .= $missingnext . '</td>';
491 $o .= '<td '.$cellcolour.' valign="top">'.$value.'</td>';
496 $o .= '<tr><td> </td><td><br />';
497 $o .= '<input type="hidden" name="sesskey" value="'.$USER->sesskey
.'" />';
498 $o .= '<input type="hidden" name="currentfile" value="'.$currentfile.'" />';
499 $o .= '<input type="hidden" name="mode" value="compare" />';
500 $o .= '<input type="submit" name="update" value="'.get_string('savechanges').': '.$currentfile.'" />';
509 if (LANG_DISPLAY_MISSING_LINKS
) {
510 if ($missingcounter > 0) {
511 print_heading(get_string('numberofmissingstrings', 'admin', $missingcounter), '', 4);
513 print_heading('<a href="#missing1">'.$strgotofirst.'</a>', "", 4);
516 print_heading($strnomissingstrings, '', 4, 'notifysuccess');
522 // no $currentfile specified
523 // no useful information to display - maybe some help? instructions?
527 admin_externalpage_print_footer();
529 //////////////////////////////////////////////////////////////////////
532 * Save language translation file.
534 * Thanks to Petri Asikainen for the original version of code
535 * used to save language files.
539 * @param string $path Full pathname to the directory to use
540 * @param string $file File to overwrite
541 * @param array $strings Array of strings to write
542 * @param bool $local Should *_local version be saved?
543 * @param array $packstrings Array of default langpack strings (needed if $local)
544 * @return bool Created successfully?
546 function lang_save_file($path, $file, $strings, $local, $packstrings) {
548 if (LANG_KEEP_ORPHANS
) {
549 // let us load the current content of the file
551 @include
("$path/$file");
552 if (isset($string)) {
559 // let us rewrite the file
560 if (!$f = @fopen
("$path/$file","w")) {
564 fwrite($f, "<?PHP // \$Id\$ \n");
565 fwrite($f, " // $file - created with Moodle $CFG->release ($CFG->version)\n");
567 fwrite($f, " // local modifications from $CFG->wwwroot\n");
571 foreach ($strings as $key => $value) {
572 @list
($id, $stringname) = explode('XXX',$key);
573 $value = lang_fix_value_before_save($value);
574 if ($id == "string" and $value != ""){
575 if ((!$local) ||
(lang_fix_value_from_file($packstrings[$stringname]) <> lang_fix_value_from_file($value))) {
576 // Either we are saving the master language pack
577 // or we are saving local language pack and the strings differ.
578 fwrite($f,"\$string['$stringname'] = '$value';\n");
580 if (LANG_KEEP_ORPHANS
&& isset($orphans[$stringname])) {
581 unset($orphans[$stringname]);
585 if (LANG_KEEP_ORPHANS
) {
586 // let us add orphaned strings, i.e. already translated strings without the English referential source
587 foreach ($orphans as $key => $value) {
588 fwrite($f,"\$string['$key'] = '".lang_fix_value_before_save($value)."'; // ORPHANED\n");
597 * Fix value of the translated string after it is load from the file.
599 * These modifications are typically necessary to work with the same string coming from two sources.
600 * We need to compare the content of these sources and we want to have e.g. "This string\r\n"
601 * to be the same as " This string\n".
603 * @param string $value Original string from the file
604 * @return string Fixed value
606 function lang_fix_value_from_file($value='') {
607 $value = str_replace("\r","",$value); // Bad character caused by Windows
608 $value = preg_replace("/\n{3,}/", "\n\n", $value); // Collapse runs of blank lines
609 $value = trim($value); // Delete leading/trailing white space
610 $value = str_replace("\\","",$value); // Delete all slashes
611 $value = str_replace("%%","%",$value);
612 $value = str_replace("&","&",$value); // Fixes MDL-9248
613 $value = str_replace("<","<",$value);
614 $value = str_replace(">",">",$value);
615 $value = str_replace('"',""",$value);
620 * Fix value of the translated string before it is saved into the file
623 * @param string $value Raw string to be saved into the lang pack
624 * @return string Fixed value
626 function lang_fix_value_before_save($value='') {
628 if ($CFG->lang
!= "zh_hk" and $CFG->lang
!= "zh_tw") { // Some MB languages include backslash bytes
629 $value = str_replace("\\","",$value); // Delete all slashes
631 $value = str_replace("'", "\\'", $value); // Add slashes for '
632 $value = str_replace('"', "\\\"", $value); // Add slashes for "
633 $value = str_replace("%","%%",$value); // Escape % characters
634 $value = str_replace("\r", "",$value); // Remove linefeed characters
635 $value = trim($value); // Delete leading/trailing white space
640 * Try and create a new language directory.
643 * @param string $directory full path to the directory under $langbase
644 * @return string|false Returns full path to directory if successful, false if not
646 function lang_make_directory($dir, $shownotices=true) {
649 if (! file_exists($dir)) {
650 if (! @mkdir
($dir, $CFG->directorypermissions
)) {
653 //@chmod($dir, $CFG->directorypermissions); // Just in case mkdir didn't do it
659 * Return the string key name for use in HTML form.
661 * Required because '.' in form input names get replaced by '_' by PHP.
663 * @param string $keyfromfile The key name containing '.'
664 * @return string The key name without '.'
666 function lang_form_string_key($keyfromfile) {
667 return str_replace('.', '##46#', $keyfromfile); /// Derived from ., the ascii value for a period.
671 * Return the string key name for use in file.
673 * Required because '.' in form input names get replaced by '_' by PHP.
675 * @param string $keyfromfile The key name without '.'
676 * @return string The key name containing '.'
678 function lang_file_string_key($keyfromform) {
679 return str_replace('##46#', '.', $keyfromform);
683 * Return the substring of the string and take care of XHTML compliance.
685 * There was a problem with pure substr() which could possibly produce XHTML parsing error:
686 * substr('Marks & Spencer', 0, 9) -> 'Marks &am' ... is not XHTML compliance
687 * This function takes care of these cases. Fixes MDL-8852.
689 * Thanks to kovacsendre, the author of the function at http://php.net/substr
691 * @param string $str The original string
692 * @param int $start Start position in the $value string
693 * @param int $length Optional length of the returned substring
694 * @return string The substring as returned by substr() with XHTML compliance
695 * @todo Seems the function does not work with negative $start together with $length being set
697 function lang_xhtml_save_substr($str, $start, $length = NULL) {
699 //stop wasting our time ;)
703 //check if we can simply use the built-in functions
704 if (strpos($str, '&') === false) {
705 // No entities. Use built-in functions
706 if ($length === NULL) {
707 return substr($str, $start);
709 return substr($str, $start, $length);
713 // create our array of characters and html entities
714 $chars = preg_split('/(&[^;\s]+;)|/', $str, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE
);
715 $html_length = count($chars);
717 // check if we can predict the return value and save some processing time, i.e.:
718 // input string was empty OR
719 // $start is longer than the input string OR
720 // all characters would be omitted
721 if (($html_length === 0) or ($start >= $html_length) or (isset($length) and ($length <= -$html_length))) {
725 //calculate start position
727 $real_start = $chars[$start][1];
729 //start'th character from the end of string
730 $start = max($start,-$html_length);
731 $real_start = $chars[$html_length+
$start][1];
734 if (!isset($length)) {
735 // no $length argument passed, return all remaining characters
736 return substr($str, $real_start);
737 } elseif ($length > 0) {
738 // copy $length chars
739 if ($start+
$length >= $html_length) {
740 // return all remaining characters
741 return substr($str, $real_start);
743 //return $length characters
744 return substr($str, $real_start, $chars[max($start,0)+
$length][1] - $real_start);
747 //negative $length. Omit $length characters from end
748 return substr($str, $real_start, $chars[$html_length+
$length][1] - $real_start);
753 * Finds all English string files in the standard lang/en_utf8 location.
755 * The English version of the file may be found in
756 * $CFG->dirroot/lang/en_utf8/filename
757 * The localised version of the found file should be saved into
758 * $CFG->dataroot/lang/current_lang[_local]/filename
759 * where "filename" is returned as a part of the file record.
761 * @return array Array of a file information. Compatible format with {@link lang_extra_locations()}
763 function lang_standard_locations() {
766 // Standard location of master English string files.
767 $places = array($CFG->dirroot
.'/lang/en_utf8');
768 foreach ($places as $place) {
769 foreach (get_directory_list($place, '', false) as $file) {
770 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
771 $fullpath = $place.'/'.$file;
772 $files[$fullpath] = array(
785 * Finds all English string files in non-standard location.
787 * Searches for lang/en_utf8/*.php in various types of plugins (blocks, database presets, question types,
788 * 3rd party modules etc.) and returns an array of found files details.
790 * The English version of the file may be found in
791 * $CFG->dirroot/location/plugin/lang/en_utf8/filename
792 * The localised version of the found file should be saved into
793 * $CFG->dataroot/lang/current_lang[_local]/prefix_plugin.php
794 * where "location", "plugin", "prefix" and "filename" are returned as a part of the file record.
796 * @return array Array of a file information. Compatible format with {@link lang_standard_locations()}
798 function lang_extra_locations() {
801 $places = places_to_search_for_lang_strings();
802 foreach ($places as $prefix => $directories) {
803 if ($prefix != '__exceptions') {
804 foreach ($directories as $directory) {
805 foreach (get_list_of_plugins($directory) as $plugin) {
806 $enlangdirlocation = $CFG->dirroot
.'/'.$directory.'/'.$plugin.'/lang/en_utf8';
807 foreach (get_directory_list($enlangdirlocation, '', false) as $file) {
808 if ((substr($file, -4) == ".php") && ($file != "langconfig.php")) {
809 $fullpath = $enlangdirlocation.'/'.$file;
810 $files[$fullpath] = array(
812 'location' => $directory,