root/elgg/trunk/lib/elgglib.php

Revision 150, 137.1 kB (checked in by cedenoj, 1 year ago)

fixes # 2831
The problem was that the sso auth system was using elgg's validation code to check whether or not a username is valid. Elgg doesn't allow dashes in the username, but DINO accounts, use dashes. Because of this we couldn't create the elgg account for DINO users and login them into social

Line 
1 <?php
2
3 /**
4  * Library of functions for handling input validation
5  * and some HTML generation
6  *
7  * This library incorporates portions of bits of lib/weblib.php
8  * and lib/moodlelib.php from moodle
9  * http://moodle.org || http://sourceforge.net/projects/moodle
10  *
11  * @copyright Copyright (C) 2001-2003  Martin Dougiamas  http://dougiamas.com
12  * @author Curverider Ltd
13  * @author Martin Dougiamas and many others
14  * @link http://elgg.org/
15  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
16  * @package elgg
17  * @subpackage elgg.lib
18  */
19
20
21 /// PARAMETER HANDLING ////////////////////////////////////////////////////
22
23 /**
24  * Returns a particular value for the named variable, taken from
25  * POST or GET.  If the parameter doesn't exist then an error is
26  * thrown because we require this variable.
27  *
28  * This function should be used to initialise all required values
29  * in a script that are based on parameters.  Usually it will be
30  * used like this:
31  *    $id = required_param('id');
32  *
33  * @param string $varname the name of the parameter variable we want
34  * @param int $options a bit field that specifies any cleaning needed
35  * @return mixed
36  */
37 function required_param($varname, $options=PARAM_CLEAN) {
38
39     // detect_unchecked_vars addition
40     global $CFG;
41     if (!empty($CFG->detect_unchecked_vars)) {
42         global $UNCHECKED_VARS;
43         unset ($UNCHECKED_VARS->vars[$varname]);
44     }
45
46     if (isset($_POST[$varname])) {       // POST has precedence
47         $param = $_POST[$varname];
48     } else if (isset($_GET[$varname])) {
49         $param = $_GET[$varname];
50     } else {
51         error('A required parameter ('.$varname.') was missing');
52     }
53
54     return clean_param($param, $options);
55 }
56
57 /**
58  * Returns a particular value for the named variable, taken from
59  * POST or GET, otherwise returning a given default.
60  *
61  * This function should be used to initialise all optional values
62  * in a script that are based on parameters.  Usually it will be
63  * used like this:
64  *    $name = optional_param('name', 'Fred');
65  *
66  * @param string $varname the name of the parameter variable we want
67  * @param mixed  $default the default value to return if nothing is found
68  * @param int $options a bit field that specifies any cleaning needed
69  * @return mixed
70  */
71 function optional_param($varname, $default=NULL, $options=PARAM_CLEAN) {
72
73     // detect_unchecked_vars addition
74     global $CFG;
75     if (!empty($CFG->detect_unchecked_vars)) {
76         global $UNCHECKED_VARS;
77         unset ($UNCHECKED_VARS->vars[$varname]);
78     }
79
80     if (isset($_POST[$varname])) {       // POST has precedence
81         $param = $_POST[$varname];
82     } else if (isset($_GET[$varname])) {
83         $param = $_GET[$varname];
84     } else {
85         return $default;
86     }
87
88     return clean_param($param, $options);
89 }
90
91
92 /**
93  * Used by {@link optional_param()} and {@link required_param()} to
94  * clean the variables and/or cast to specific types, based on
95  * an options field.
96  * <code>
97  * $course->format = clean_param($course->format, PARAM_ALPHA);
98  * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
99  * </code>
100  *
101  * @uses $CFG
102  * @uses PARAM_CLEAN
103  * @uses PARAM_INT
104  * @uses PARAM_INTEGER
105  * @uses PARAM_ALPHA
106  * @uses PARAM_ALPHANUM
107  * @uses PARAM_NOTAGS
108  * @uses PARAM_ALPHATEXT
109  * @uses PARAM_BOOL
110  * @uses PARAM_SAFEDIR
111  * @uses PARAM_CLEANFILE
112  * @uses PARAM_FILE
113  * @uses PARAM_PATH
114  * @uses PARAM_HOST
115  * @uses PARAM_URL
116  * @uses PARAM_LOCALURL
117  * @uses PARAM_CLEANHTML
118  * @param mixed $param the variable we are cleaning
119  * @param int $options a bit field that specifies the cleaning needed. This field is specified by combining PARAM_* definitions together with a logical or.
120  * @return mixed
121  */
122 function clean_param($param, $options) {
123
124     global $CFG;
125
126     if (is_array($param)) {              // Let's loop
127         $newparam = array();
128         foreach ($param as $key => $value) {
129             $newparam[$key] = clean_param($value, $options);
130         }
131         return $newparam;
132     }
133
134     if (!$options) {
135         return $param;                   // Return raw value
136     }
137
138     //this corrupts data - Sven
139     //if ((string)$param == (string)(int)$param) {  // It's just an integer
140     //    return (int)$param;
141     //}
142
143     if ($options & PARAM_CLEAN) {
144 // this breaks backslashes in user input
145 //        $param = stripslashes($param);   // Needed by kses to work fine
146         $param = clean_text($param);     // Sweep for scripts, etc
147 // and this unnecessarily escapes quotes, etc in user input
148 //        $param = addslashes($param);     // Restore original request parameter slashes
149     }
150
151     if ($options & PARAM_INT) {
152         $param = (int)$param;            // Convert to integer
153     }
154
155     if ($options & PARAM_ALPHA) {        // Remove everything not a-z
156         $param = eregi_replace('[^a-zA-Z]', '', $param);
157     }
158
159     if ($options & PARAM_ALPHANUM) {     // Remove everything not a-zA-Z0-9
160         $param = eregi_replace('[^A-Za-z0-9]', '', $param);
161     }
162
163     if ($options & PARAM_ALPHAEXT) {     // Remove everything not a-zA-Z/_-
164         $param = eregi_replace('[^a-zA-Z/_-]', '', $param);
165     }
166
167     if ($options & PARAM_BOOL) {         // Convert to 1 or 0
168         $tempstr = strtolower($param);
169         if ($tempstr == 'on') {
170             $param = 1;
171         } else if ($tempstr == 'off') {
172             $param = 0;
173         } else {
174             $param = empty($param) ? 0 : 1;
175         }
176     }
177
178     if ($options & PARAM_NOTAGS) {       // Strip all tags completely
179         $param = strip_tags($param);
180     }
181
182     if ($options & PARAM_SAFEDIR) {     // Remove everything not a-zA-Z0-9_-
183         $param = eregi_replace('[^a-zA-Z0-9_-]', '', $param);
184     }
185
186     if ($options & PARAM_CLEANFILE) {    // allow only safe characters
187         $param = clean_filename($param);
188     }
189
190     if ($options & PARAM_FILE) {         // Strip all suspicious characters from filename
191         $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
192         $param = ereg_replace('\.\.+', '', $param);
193         if($param == '.') {
194             $param = '';
195         }
196     }
197
198     if ($options & PARAM_PATH) {         // Strip all suspicious characters from file path
199         $param = str_replace('\\\'', '\'', $param);
200         $param = str_replace('\\"', '"', $param);
201         $param = str_replace('\\', '/', $param);
202         $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
203         $param = ereg_replace('\.\.+', '', $param);
204         $param = ereg_replace('//+', '/', $param);
205         $param = ereg_replace('/(\./)+', '/', $param);
206     }
207
208     if ($options & PARAM_HOST) {         // allow FQDN or IPv4 dotted quad
209         preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
210         // match ipv4 dotted quad
211         if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
212             // confirm values are ok
213             if ( $match[0] > 255
214                  || $match[1] > 255
215                  || $match[3] > 255
216                  || $match[4] > 255 ) {
217                 // hmmm, what kind of dotted quad is this?
218                 $param = '';
219             }
220         } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
221                    && !preg_match('/^[\.-]/'$param) // no leading dots/hyphens
222                    && !preg_match('/[\.-]$/'$param) // no trailing dots/hyphens
223                    ) {
224             // all is ok - $param is respected
225         } else {
226             // all is not ok...
227             $param='';
228         }
229     }
230
231     if ($options & PARAM_URL) { // allow safe ftp, http, mailto urls
232
233         include_once($CFG->dirroot . 'lib/validateurlsyntax.php');
234
235         //
236         // Parameters to validateurlsyntax()
237         //
238         // s? scheme is optional
239         //   H? http optional
240         //   S? https optional
241         //   F? ftp   optional
242         //   E? mailto optional
243         // u- user section not allowed
244         //   P- password not allowed
245         // a? address optional
246         //   I? Numeric IP address optional (can use IP or domain)
247         //   p-  port not allowed -- restrict to default port
248         // f? "file" path section optional
249         //   q? query section optional
250         //   r? fragment (anchor) optional
251         //
252         if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p-f?q?r?')) {
253             // all is ok, param is respected
254         } else {
255             $param =''; // not really ok
256         }
257         $options ^= PARAM_URL; // Turn off the URL bit so that simple PARAM_URLs don't test true for PARAM_LOCALURL
258     }
259
260     if ($options & PARAM_LOCALURL) {
261         // assume we passed the PARAM_URL test...
262         // allow http absolute, root relative and relative URLs within wwwroot
263         if (!empty($param)) {
264             if (preg_match(':^/:', $param)) {
265                 // root-relative, ok!
266             } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
267                 // absolute, and matches our wwwroot
268             } else {
269                 // relative - let's make sure there are no tricks
270                 if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
271                     // looks ok.
272                 } else {
273                     $param = '';
274                 }
275             }
276         }
277     }
278
279     if ($options & PARAM_CLEANHTML) {
280 //        $param = stripslashes($param);         // Remove any slashes
281         $param = clean_text($param);           // Sweep for scripts, etc
282 //        $param = trim($param);                 // Sweep for scripts, etc
283     }
284
285     return $param;
286 }
287
288 /**
289  * Retrieves the list of plugins available in the $plugin
290  * directory. Defaults to 'mod'.
291  *
292  * NOTE: To get the list of enabled modules, do
293  * get_records('modules', 'enabled', true) instead.
294  *
295  * @return array
296  **/
297 function get_list_of_plugins($plugin='mod', $exclude='') {
298     
299     global $CFG;
300     if ($plugin == 'mod') {
301         $plugin = $CFG->dirroot . $plugin;
302     }
303     static $plugincache = array();
304     $plugincachename = $plugin . "_" . $exclude;
305     
306     if (isset($plugincache[$plugincachename])) {
307         $plugins = $plugincache[$plugincachename];
308     } else {
309         $plugins = array();
310         if ($basedir = opendir($plugin)) {
311             while (false !== ($dir = readdir($basedir))) {
312                 $firstchar = substr($dir, 0, 1);
313                 if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == $exclude) {
314                     continue;
315                 }
316                 if ((filetype($plugin .'/'. $dir) != 'dir') && ((filetype($plugin .'/'. $dir) != 'link'))) {
317                     continue;
318                 }
319                 $plugins[] = $dir;
320             }
321         }
322         if ($plugins) {
323             asort($plugins);
324         }
325         $plugincache[$plugincachename] = $plugins;
326     }
327     return $plugins;
328 }
329
330 // Adds a function to the variables used to cycle through plugin extensions
331 // to actions on objects
332 function listen_for_event($object_type, $event, $function) {
333     
334     global $CFG;
335     $CFG->event_hooks[$object_type][$event][] = $function;
336     
337 }
338
339 function plugin_hook($object_type,$event,$object = null) {
340     
341     global $CFG;
342     
343     if (!empty($CFG->event_hooks['all']['all']) && is_array($CFG->event_hooks['all']['all'])) {
344         foreach($CFG->event_hooks['all']['all'] as $hook) {
345             $object = $hook($object_type,$event,$object);
346         }
347     }
348     if (!empty($CFG->event_hooks[$object_type]['all']) && is_array($CFG->event_hooks[$object_type]['all'])) {
349         foreach($CFG->event_hooks[$object_type]['all'] as $hook) {
350             $object = $hook($object_type,$event,$object);
351         }
352     }
353     if (!empty($CFG->event_hooks['all'][$event]) && is_array($CFG->event_hooks['all'][$event])) {
354         foreach($CFG->event_hooks['all'][$event] as $hook) {
355             $object = $hook($object_type,$event,$object);
356         }
357     }
358     if (!empty($CFG->event_hooks[$object_type][$event]) && is_array($CFG->event_hooks[$object_type][$event])) {
359         foreach($CFG->event_hooks[$object_type][$event] as $hook) {
360             $object = $hook($object_type,$event,$object);
361         }
362     }
363     
364     return $object;
365     
366 }
367
368 function report_session_error() {
369     global $CFG, $FULLME;
370
371     //clear session cookies
372     setcookie('ElggSession'.$CFG->sessioncookie, '', time() - 3600, $CFG->cookiepath);
373     setcookie('ElggSessionTest'.$CFG->sessioncookie, '', time() - 3600, $CFG->cookiepath);
374     //increment database error counters
375     //if (isset($CFG->session_error_counter)) {
376     //    set_config('session_error_counter', 1 + $CFG->session_error_counter);
377     //} else {
378     //    set_config('session_error_counter', 1);
379     //}
380     //called from setup.php, so gettext module hasn't been loaded yet
381     redirect($FULLME, '', 1);
382 }
383
384 // never called
385 /**
386  * For security purposes, this function will check that the currently
387  * given sesskey (passed as a parameter to the script or this function)
388  * matches that of the current user.
389  *
390  * @param string $sesskey optionally provided sesskey
391  * @return bool
392  */
393 // function confirm_sesskey($sesskey=NULL) {
394 //     global $USER;
395
396 //     if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
397 //         return true;
398 //     }
399
400 //     if (empty($sesskey)) {
401 //         $sesskey = required_param('sesskey');  // Check script parameters
402 //     }
403
404 //     if (!isset($USER->sesskey)) {
405 //         return false;
406 //     }
407
408 //     return ($USER->sesskey === $sesskey);
409 // }
410
411
412 /**
413  * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
414  * if one does not already exist, but does not overwrite existing sesskeys. Returns the
415  * sesskey string if $USER exists, or boolean false if not.
416  *
417  * @uses $USER
418  * @return string
419  */
420 function sesskey() {
421     global $USER;
422
423     if(!isset($USER)) {
424         return false;
425     }
426
427     if (empty($USER->sesskey)) {
428         $USER->sesskey = random_string(10);
429     }
430
431     return $USER->sesskey;
432 }
433
434
435 /**
436  * Send an email to a specified user
437  *
438  * @uses $CFG
439  * @param user $user  A {@link $USER} object
440  * @param user $from A {@link $USER} object
441  * @param string $subject plain text subject line of the email
442  * @param string $messagetext plain text version of the message
443  * @param string $messagehtml complete html version of the message (optional)
444  * @param string $attachment a file on the filesystem
445  * @param string $attachname the name of the file (extension indicates MIME)
446  * @param bool $usetrueaddress determines whether $from email address should
447  *          be sent out. Will be overruled by user profile setting for maildisplay
448  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
449  *          was blocked by user and "false" if there was another sort of error.
450  */
451 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $replyto='', $replytoname='') {
452
453     global $CFG;
454     $textlib = textlib_get_instance();
455
456     include_once($CFG->libdir .'/phpmailer/class.phpmailer.php');
457
458     if (empty($user) || empty($user->email)) {
459         return false;
460     }
461
462     /*
463     if (over_bounce_threshold($user)) {
464         error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
465         return false;
466     }
467     */ // this doesn't exist right now, we may bring it in later though.
468
469     $mail = new phpmailer;
470
471     $mail->Version = 'Elgg ';           // mailer version (should have $CFG->version on here but we don't have it yet)
472     $mail->PluginDir = $CFG->libdir .'/phpmailer/';      // plugin directory (eg smtp plugin)
473
474
475     $mail->CharSet = 'UTF-8'; // everything is now uft8
476
477     if (empty($CFG->smtphosts)) {
478         $mail->IsMail();                               // use PHP mail() = sendmail
479     } else if ($CFG->smtphosts == 'qmail') {
480         $mail->IsQmail();                              // use Qmail system
481     } else {
482         $mail->IsSMTP();                               // use SMTP directly
483         if ($CFG->debug > 7) {
484             echo '<pre>' . "\n";
485             $mail->SMTPDebug = true;
486         }
487         $mail->Host = $CFG->smtphosts;               // specify main and backup servers
488
489         if ($CFG->smtpuser) {                          // Use SMTP authentication
490             $mail->SMTPAuth = true;
491             $mail->Username = $CFG->smtpuser;
492             $mail->Password = $CFG->smtppass;
493         }
494     }
495
496     /* not here yet, leave it in just in case.
497     // make up an email address for handling bounces
498     if (!empty($CFG->handlebounces)) {
499         $modargs = 'B'.base64_encode(pack('V',$user->ident)).substr(md5($user->email),0,16);
500         $mail->Sender = generate_email_processing_address(0,$modargs);
501     }
502     else {
503         $mail->Sender   =  $CFG->sysadminemail;
504     }
505     */
506     $mail->Sender = $CFG->sysadminemail; // for elgg. delete if we change the above.
507
508     // TODO add a preference for maildisplay
509     if (is_string($from)) { // So we can pass whatever we want if there is need
510         $mail->From     = $CFG->noreplyaddress;
511         $mail->FromName = $from;
512     } else if (empty($from)) { // make stuff up
513         $mail->From     = $CFG->sysadminemail;
514         $mail->FromName = $CFG->sitename.' '.__gettext('Administrator');
515     } else if ($usetrueaddress and !empty($from->maildisplay)) {
516         $mail->From     = $from->email;
517         $mail->FromName = $from->name;
518     } else {
519         $mail->From     = $CFG->noreplyaddress;
520         $mail->FromName = $from->name;
521         if (empty($replyto)) {
522             $mail->AddReplyTo($CFG->noreplyaddress,__gettext('Do not reply'));
523         }
524     }
525
526     if (!empty($replyto)) {
527         $mail->AddReplyTo($replyto,$replytoname);
528     }
529
530     $mail->Subject = $textlib->substr(stripslashes($subject), 0, 900);
531
532     $mail->AddAddress($user->email, $user->name);
533
534     $mail->WordWrap = 79;                               // set word wrap
535
536     if (!empty($from->customheaders)) {                 // Add custom headers
537         if (is_array($from->customheaders)) {
538             foreach ($from->customheaders as $customheader) {
539                 $mail->AddCustomHeader($customheader);
540             }
541         } else {
542             $mail->AddCustomHeader($from->customheaders);
543         }
544     }
545
546     if (!empty($from->priority)) {
547         $mail->Priority = $from->priority;
548     }
549
550     //TODO add a user preference for this. right now just send plaintext
551     $user->mailformat = 0;
552     if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
553         $mail->IsHTML(true);
554         $mail->Encoding = 'quoted-printable';           // Encoding to use
555         $mail->Body    $messagehtml;
556         $mail->AltBody "\n$messagetext\n";
557     } else {
558         $mail->IsHTML(false);
559         $mail->Body "\n$messagetext\n";
560     }
561
562     if ($attachment && $attachname) {
563         if (ereg( "\\.\\." ,$attachment )) {    // Security check for ".." in dir path
564             $mail->AddAddress($CFG->sysadminemail,$CFG->sitename.' '.__gettext('Administrator'));
565             $mail->AddStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
566         } else {
567             require_once($CFG->libdir.'/filelib.php');
568             $mimetype = mimeinfo('type', $attachname);
569             $mail->AddAttachment($attachment, $attachname, 'base64', $mimetype);
570         }
571     }
572
573     $mail = plugin_hook("mail","create",$mail);
574
575     if ($mail->Send()) {
576         //        set_send_count($user); // later
577         return true;
578     } else {
579         mtrace('ERROR: '. $mail->ErrorInfo);
580         return false;
581     }
582 }
583
584 /**
585  * Returns an array with all the filenames in
586  * all subdirectories, relative to the given rootdir.
587  * If excludefile is defined, then that file/directory is ignored
588  * If getdirs is true, then (sub)directories are included in the output
589  * If getfiles is true, then files are included in the output
590  * (at least one of these must be true!)
591  *
592  * @param string $rootdir  ?
593  * @param string $excludefile  If defined then the specified file/directory is ignored
594  * @param bool $descend  ?
595  * @param bool $getdirs  If true then (sub)directories are included in the output
596  * @param bool $getfiles  If true then files are included in the output
597  * @return array An array with all the filenames in
598  * all subdirectories, relative to the given rootdir
599  * @todo Finish documenting this function. Add examples of $excludefile usage.
600  */
601 function get_directory_list($rootdir, $excludefile='', $descend=true, $getdirs=false, $getfiles=true) {
602
603     $dirs = array();
604
605     if (!$getdirs and !$getfiles) {   // Nothing to show
606         return $dirs;
607     }
608
609     if (!is_dir($rootdir)) {          // Must be a directory
610         return $dirs;
611     }
612
613     if (!$dir = opendir($rootdir)) {  // Can't open it for some reason
614         return $dirs;
615     }
616
617     while (false !== ($file = readdir($dir))) {
618         $firstchar = substr($file, 0, 1);
619         if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
620             continue;
621         }
622         $fullfile = $rootdir .'/'. $file;
623         if (filetype($fullfile) == 'dir') {
624             if ($getdirs) {
625                 $dirs[] = $file;
626             }
627             if ($descend) {
628                 $subdirs = get_directory_list($fullfile, $excludefile, $descend, $getdirs, $getfiles);
629                 foreach ($subdirs as $subdir) {
630                     $dirs[] = $file .'/'. $subdir;
631                 }
632             }
633         } else if ($getfiles) {
634             $dirs[] = $file;
635         }
636     }
637     closedir($dir);
638
639     asort($dirs);
640
641     return $dirs;
642 }
643
644 /**
645  * handy function to loop through an array of files and resolve any filename conflicts
646  * both in the array of filenames and for what is already on disk.
647  */
648
649 function resolve_filename_collisions($destination,$files,$format='%s_%d.%s') {
650     foreach ($files as $k => $f) {
651         if (check_potential_filename($destination,$f,$files)) {
652             $bits = explode('.', $f);
653             for ($i = 1; true; $i++) {
654                 $try = sprintf($format, $bits[0], $i, $bits[1]);
655                 if (!check_potential_filename($destination,$try,$files)) {
656                     $files[$k] = $try;
657                     break;
658                 }
659             }
660         }
661     }
662     return $files;
663 }
664
665 /**
666  * @used by resolve_filename_collisions
667  */
668 function check_potential_filename($destination,$filename,$files) {
669     if (file_exists($destination.'/'.$filename)) {
670         return true;
671     }
672     if (count(array_keys($files,$filename)) > 1) {
673         return true;
674     }
675     return false;
676 }
677
678 /**
679  * Adds up all the files in a directory and works out the size.
680  *
681  * @param string $rootdir  ?
682  * @param string $excludefile  ?
683  * @return array
684  * @todo Finish documenting this function
685  */
686 function get_directory_size($rootdir, $excludefile='') {
687
688     global $CFG;
689     $textlib = textlib_get_instance();
690
691     // do it this way if we can, it's much faster
692     if (!empty($CFG->pathtodu) && is_executable(trim($CFG->pathtodu))) {
693         $command = trim($CFG->pathtodu).' -sk --apparent-size '.escapeshellarg($rootdir);
694         exec($command,$output,$return);
695         if (is_array($output)) {
696             return get_real_size(intval($output[0]).'k'); // we told it to return k.
697         }
698     }
699     
700     $size = 0;
701
702     if (!is_dir($rootdir)) {          // Must be a directory
703         return $dirs;
704     }
705
706     if (!$dir = @opendir($rootdir)) {  // Can't open it for some reason
707         return $dirs;
708     }
709
710     while (false !== ($file = readdir($dir))) {
711         $firstchar = $textlib->substr($file, 0, 1);
712         if ($firstchar == '.' or $file == 'CVS' or $file == $excludefile) {
713             continue;
714         }
715         $fullfile = $rootdir .'/'. $file;
716         if (filetype($fullfile) == 'dir') {
717             $size += get_directory_size($fullfile, $excludefile);
718         } else {
719             $size += filesize($fullfile);
720         }
721     }
722     closedir($dir);
723
724     return $size;
725 }
726
727 /**
728  * Converts numbers like 10M into bytes.
729  *
730  * @param mixed $size The size to be converted
731  * @return mixed
732  */
733 function get_real_size($size=0) {
734     if (!$size) {
735         return 0;
736     }
737     $scan['GB'] = 1073741824;
738     $scan['Gb'] = 1073741824;
739     $scan['G'] = 1073741824;
740     $scan['g'] = 1073741824;
741     $scan['MB'] = 1048576;
742     $scan['Mb'] = 1048576;
743     $scan['M'] = 1048576;
744     $scan['m'] = 1048576;
745     $scan['KB'] = 1024;
746     $scan['Kb'] = 1024;
747     $scan['K'] = 1024;
748     $scan['k'] = 1024;
749
750     while (list($key) = each($scan)) {
751         if ((strlen($size)>strlen($key))&&(substr($size, strlen($size) - strlen($key))==$key)) {
752             $size = substr($size, 0, strlen($size) - strlen($key)) * $scan[$key];
753             break;
754         }
755     }
756     return $size;
757 }
758
759 /**
760  * Converts bytes into display form
761  *
762  * @param string $size  ?
763  * @return string
764  * @staticvar string $gb Localized string for size in gigabytes
765  * @staticvar string $mb Localized string for size in megabytes
766  * @staticvar string $kb Localized string for size in kilobytes
767  * @staticvar string $b Localized string for size in bytes
768  * @todo Finish documenting this function. Verify return type.
769  */
770 function display_size($size) {
771
772     static $gb, $mb, $kb, $b;
773
774     if (empty($gb)) {
775         $gb = __gettext('GB');
776         $mb = __gettext('MB');
777         $kb = __gettext('KB');
778         $b  = __gettext('bytes');
779     }
780
781     if ($size >= 1073741824) {
782         $size = round($size / 1073741824 * 10) / 10 . $gb;
783     } else if ($size >= 1048576) {
784         $size = round($size / 1048576 * 10) / 10 . $mb;
785     } else if ($size >= 1024) {
786         $size = round($size / 1024 * 10) / 10 . $kb;
787     } else {
788         $size = $size .' '. $b;
789     }
790     return $size;
791 }
792
793 /*
794  * Convert high ascii characters into low ascii
795  * This code is from http://kalsey.com/2004/07/dirify_in_php/
796  *
797  */
798 function convert_high_ascii($s) {
799     $HighASCII = array(
800         "!\xc0!" => 'A',    # A`
801         "!\xe0!" => 'a',    # a`
802         "!\xc1!" => 'A',    # A'
803         "!\xe1!" => 'a',    # a'
804         "!\xc2!" => 'A',    # A^
805         "!\xe2!" => 'a',    # a^
806         "!\xc4!" => 'Ae',   # A:
807         "!\xe4!" => 'ae',   # a:
808         "!\xc3!" => 'A',    # A~
809         "!\xe3!" => 'a',    # a~
810         "!\xc8!" => 'E',    # E`
811         "!\xe8!" => 'e',    # e`
812         "!\xc9!" => 'E',    # E'
813         "!\xe9!" => 'e',    # e'
814         "!\xca!" => 'E',    # E^
815         "!\xea!" => 'e',    # e^
816         "!\xcb!" => 'Ee',   # E:
817         "!\xeb!" => 'ee',   # e:
818         "!\xcc!" => 'I',    # I`
819         "!\xec!" => 'i',    # i`
820         "!\xcd!" => 'I',    # I'
821         "!\xed!" => 'i',    # i'
822         "!\xce!" => 'I',    # I^
823         "!\xee!" => 'i',    # i^
824         "!\xcf!" => 'Ie',   # I:
825         "!\xef!" => 'ie',   # i:
826         "!\xd2!" => 'O',    # O`
827         "!\xf2!" => 'o',    # o`
828         "!\xd3!" => 'O',    # O'
829         "!\xf3!" => 'o',    # o'
830         "!\xd4!" => 'O',    # O^
831         "!\xf4!" => 'o',    # o^
832         "!\xd6!" => 'Oe',   # O:
833         "!\xf6!" => 'oe',   # o:
834         "!\xd5!" => 'O',    # O~
835         "!\xf5!" => 'o',    # o~
836         "!\xd8!" => 'Oe',   # O/
837         "!\xf8!" => 'oe',   # o/
838         "!\xd9!" => 'U',    # U`
839         "!\xf9!" => 'u',    # u`
840         "!\xda!" => 'U',    # U'
841         "!\xfa!" => 'u',    # u'
842         "!\xdb!" => 'U',    # U^
843         "!\xfb!" => 'u',    # u^
844         "!\xdc!" => 'Ue',   # U:
845         "!\xfc!" => 'ue',   # u:
846         "!\xc7!" => 'C',    # ,C
847         "!\xe7!" => 'c',    # ,c
848         "!\xd1!" => 'N',    # N~
849         "!\xf1!" => 'n',    # n~
850         "!\xdf!" => 'ss'
851     );
852     $find = array_keys($HighASCII);
853     $replace = array_values($HighASCII);
854     $s = preg_replace($find,$replace,$s);
855     return $s;
856 }
857
858 /*
859  * Cleans a given filename by removing suspicious or troublesome characters
860  * Only these are allowed:
861  *    alphanumeric _ - .
862  *
863  * @param string $string  ?
864  * @return string
865  */
866 function clean_filename($string) {
867     $string = convert_high_ascii($string);
868     $string = eregi_replace("\.\.+", '', $string);
869     $string = preg_replace('/[^\.a-zA-Z\d\_-]/','_', $string ); // only allowed chars
870     $string = eregi_replace("_+", '_', $string);
871     return $string;
872 }
873
874
875
876 /**
877  * Function to raise the memory limit to a new value.
878  * Will respect the memory limit if it is higher, thus allowing
879  * settings in php.ini, apache conf or command line switches
880  * to override it
881  *
882  * The memory limit should be expressed with a string (eg:'64M')
883  *
884  * @param string $newlimit the new memory limit
885  * @return bool
886  */
887 function raise_memory_limit ($newlimit) {
888
889     if (empty($newlimit)) {
890         return false;
891     }
892
893     $cur = @ini_get('memory_limit');
894     if (empty($cur)) {
895         // if php is compiled without --enable-memory-limits
896         // apparently memory_limit is set to ''
897         $cur=0;
898     } else {
899         if ($cur == -1){
900             return true; // unlimited mem!
901         }
902       $cur = get_real_size($cur);
903     }
904
905     $new = get_real_size($newlimit);
906     if ($new > $cur) {
907         ini_set('memory_limit', $newlimit);
908         return true;
909     }
910     return false;
911 }
912
913 /**
914  * Converts string to lowercase using most compatible function available.
915  *
916  * @param string $string The string to convert to all lowercase characters.
917  * @param string $encoding The encoding on the string.
918  * @return string
919  * @todo Add examples of calling this function with/without encoding types
920  */
921 function elgg_strtolower ($string, $encoding='') {
922     $textlib = textlib_get_instance();
923     return $textlib->strtolower($string, $encoding?$encoding:'utf-8');
924 }
925
926
927
928 /**
929  * Given a simple array, this shuffles it up just like shuffle()
930  * Unlike PHP's shuffle() ihis function works on any machine.
931  *
932  * @param array $array The array to be rearranged
933  * @return array
934  */
935 function swapshuffle($array) {
936
937     srand ((double) microtime() * 10000000);
938     $last = count($array) - 1;
939     for ($i=0;$i<=$last;$i++) {
940         $from = rand(0,$last);
941         $curr = $array[$i];
942         $array[$i] = $array[$from];
943         $array[$from] = $curr;
944     }
945     return $array;
946 }
947
948 /**
949  * Like {@link swapshuffle()}, but works on associative arrays
950  *
951  * @param array $array The associative array to be rearranged
952  * @return array
953  */
954 function swapshuffle_assoc($array) {
955 ///
956
957     $newkeys = swapshuffle(array_keys($array));
958     foreach ($newkeys as $newkey) {
959         $newarray[$newkey] = $array[$newkey];
960     }
961     return $newarray;
962 }
963
964 /**
965  * Given an arbitrary array, and a number of draws,
966  * this function returns an array with that amount
967  * of items.  The indexes are retained.
968  *
969  * @param array $array ?
970  * @param ? $draws ?
971  * @return ?
972  * @todo Finish documenting this function
973  */
974 function draw_rand_array($array, $draws) {
975     srand ((double) microtime() * 10000000);
976
977     $return = array();
978
979     $last = count($array);
980
981     if ($draws > $last) {
982         $draws = $last;
983     }
984
985     while ($draws > 0) {
986         $last--;
987
988         $keys = array_keys($array);
989         $rand = rand(0, $last);
990
991         $return[$keys[$rand]] = $array[$keys[$rand]];
992         unset($array[$keys[$rand]]);
993
994         $draws--;
995     }
996
997     return $return;
998 }
999
1000
1001 /**
1002  * Function to check the passed address is within the passed subnet
1003  *
1004  * The parameter is a comma separated string of subnet definitions.
1005  * Subnet strings can be in one of two formats:
1006  *   1: xxx.xxx.xxx.xxx/xx
1007  *   2: xxx.xxx
1008  * Code for type 1 modified from user posted comments by mediator at
1009  * {@link http://au.php.net/manual/en/function.ip2long.php}
1010  *
1011  * @param string $addr    The address you are checking
1012  * @param string $subnetstr    The string of subnet addresses
1013  * @return bool
1014  */
1015 function address_in_subnet($addr, $subnetstr) {
1016
1017     $subnets = explode(',', $subnetstr);
1018     $found = false;
1019     $addr = trim($addr);
1020
1021     foreach ($subnets as $subnet) {
1022         $subnet = trim($subnet);
1023         if (strpos($subnet, '/') !== false) { /// type 1
1024
1025             list($ip, $mask) = explode('/', $subnet);
1026             $mask = 0xffffffff << (32 - $mask);
1027             $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
1028
1029         } else { /// type 2
1030             $found = (strpos($addr, $subnet) === 0);
1031         }
1032
1033         if ($found) {
1034             break;
1035         }
1036     }
1037
1038     return $found;
1039 }
1040
1041 /**
1042  * For outputting debugging info
1043  *
1044  * @uses STDOUT
1045  * @param string $string ?
1046  * @param string $eol ?
1047  * @todo Finish documenting this function
1048  */
1049 function mtrace($string, $eol="\n", $sleep=0) {
1050
1051     if (defined('STDOUT')) {
1052         fwrite(STDOUT, $string.$eol);
1053     } else {
1054         echo $string . $eol;
1055     }
1056
1057     flush();
1058
1059     //delay to keep message on user's screen in case of subsequent redirect
1060     if ($sleep) {
1061         sleep($sleep);
1062     }
1063 }
1064
1065 //Replace 1 or more slashes or backslashes to 1 slash
1066 function cleardoubleslashes ($path) {
1067     return preg_replace('/(\/|\\\){1,}/','/',$path);
1068 }
1069
1070
1071
1072 /**
1073  * Returns most reliable client address
1074  *
1075  * @return string The remote IP address
1076  */
1077 function getremoteaddr() {
1078     if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
1079         return cleanremoteaddr($_SERVER['HTTP_CLIENT_IP']);
1080     }
1081     if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1082         return cleanremoteaddr($_SERVER['HTTP_X_FORWARDED_FOR']);
1083     }
1084     if (!empty($_SERVER['REMOTE_ADDR'])) {
1085         return cleanremoteaddr($_SERVER['REMOTE_ADDR']);
1086     }
1087     return '';
1088 }
1089
1090 /**
1091  * Cleans a remote address ready to put into the log table
1092  */
1093 function cleanremoteaddr($addr) {
1094     $originaladdr = $addr;
1095     $matches = array();
1096     // first get all things that look like IP addresses.
1097     if (!preg_match_all('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/',$addr,$matches,PREG_SET_ORDER)) {
1098         return '';
1099     }
1100     $goodmatches = array();
1101     $lanmatches = array();
1102     foreach ($matches as $match) {
1103         //        print_r($match);
1104         // check to make sure it's not an internal address.
1105         // the following are reserved for private lans...
1106         // 10.0.0.0 - 10.255.255.255
1107         // 172.16.0.0 - 172.31.255.255
1108         // 192.168.0.0 - 192.168.255.255
1109         // 169.254.0.0 -169.254.255.255
1110         $bits = explode('.',$match[0]);
1111         if (count($bits) != 4) {
1112             // weird, preg match shouldn't give us it.
1113             continue;
1114         }
1115         if (($bits[0] == 10)
1116             || ($bits[0] == 172 && $bits[1] >= 16 && $bits[1] <= 31)
1117             || ($bits[0] == 192 && $bits[1] == 168)
1118             || ($bits[0] == 169 && $bits[1] == 254)) {
1119             $lanmatches[] = $match[0];
1120             continue;
1121         }
1122         // finally, it's ok
1123         $goodmatches[] = $match[0];
1124     }
1125     if (!count($goodmatches)) {
1126         // perhaps we have a lan match, it's probably better to return that.
1127         if (!count($lanmatches)) {
1128             return '';
1129         } else {
1130             return array_pop($lanmatches);
1131         }
1132     }
1133     if (count($goodmatches) == 1) {
1134         return $goodmatches[0];
1135     }
1136     error_log("NOTICE: cleanremoteaddr gives us something funny: $originaladdr had ".count($goodmatches)." matches");
1137     // we need to return something, so
1138     return array_pop($goodmatches);
1139 }
1140
1141 /**
1142  * html_entity_decode is only supported by php 4.3.0 and higher
1143  * so if it is not predefined, define it here
1144  *
1145  * @param string $string ?
1146  * @return string
1147  * @todo Finish documenting this function
1148  */
1149 if(!function_exists('html_entity_decode')) {
1150      function html_entity_decode($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') {
1151         $trans_tbl = get_html_translation_table(HTML_ENTITIES, $quote_style);
1152         $trans_tbl = array_flip($trans_tbl);
1153         return strtr($string, $trans_tbl);
1154     }
1155 }
1156
1157 /**
1158  * The clone keyword is only supported from PHP 5 onwards.
1159  * The behaviour of $obj2 = $obj1 differs fundamentally
1160  * between PHP 4 and PHP 5. In PHP 4 a copy of $obj1 was
1161  * created, in PHP 5 $obj1 is referenced. To create a copy
1162  * in PHP 5 the clone keyword was introduced. This function
1163  * simulates this behaviour for PHP < 5.0.0.
1164  * See also: http://mjtsai.com/blog/2004/07/15/php-5-object-references/
1165  *
1166  * Modified 2005-09-29 by Eloy (from Julian Sedding proposal)
1167  * Found a better implementation (more checks and possibilities) from PEAR:
1168  * http://cvs.php.net/co.php/pear/PHP_Compat/Compat/Function/clone.php
1169  *
1170  * @param object $obj
1171  * @return object
1172  */
1173 if(!check_php_version('5.0.0')) {
1174 // the eval is needed to prevent PHP 5 from getting a parse error!
1175 eval('
1176     function clone($obj) {
1177     /// Sanity check
1178         if (!is_object($obj)) {
1179             user_error(\'clone() __clone method called on non-object\', E_USER_WARNING);
1180             return;
1181         }
1182
1183     /// Use serialize/unserialize trick to deep copy the object
1184         $obj = unserialize(serialize($obj));
1185
1186     /// If there is a __clone method call it on the "new" class
1187         if (method_exists($obj, \'__clone\')) {
1188             $obj->__clone();
1189         }
1190
1191         return $obj;
1192     }
1193 ');
1194 }
1195
1196
1197 /**
1198  * microtime_diff
1199  *
1200  * @param string $a ?
1201  * @param string $b ?
1202  * @return string
1203  * @todo Finish documenting this function
1204  */
1205 function microtime_diff($a, $b) {
1206     list($a_dec, $a_sec) = explode(' ', $a);
1207     list($b_dec, $b_sec) = explode(' ', $b);
1208     return $b_sec - $a_sec + $b_dec - $a_dec;
1209 }
1210
1211
1212 /**
1213  *** get_performance_info() pairs up with init_performance_info()
1214  *** loaded in setup.php. Returns an array with 'html' and 'txt'
1215  *** values ready for use, and each of the individual stats provided
1216  *** separately as well.
1217  ***
1218  **/
1219 function get_performance_info() {
1220     global $CFG, $PERF;
1221
1222     $info = array();
1223     $info['html'] = '';         // holds userfriendly HTML representation
1224     $info['txt']  = me() . ' '; // holds log-friendly representation
1225
1226     $info['realtime'] = microtime_diff($PERF->starttime, microtime());
1227     
1228     $info['html'] .= '<span class="timeused">'.$info['realtime'].' secs</span> ';
1229     $info['txt'] .= 'time: '.$info['realtime'].'s ';
1230
1231     if (function_exists('memory_get_usage')) {
1232         $info['memory_total'] = memory_get_usage();
1233         $info['memory_growth'] = memory_get_usage() - $PERF->startmemory;
1234         $info['html'] .= '<span class="memoryused">RAM: '.display_size($info['memory_total']).'</span> ';
1235         $info['txt']  .= 'memory_total: '.$info['memory_total'].'B (' . display_size($info['memory_total']).') memory_growth: '.$info['memory_growth'].'B ('.display_size($info['memory_growth']).') ';
1236     }
1237
1238     $inc = get_included_files();
1239     //error_log(print_r($inc,1));
1240     $info['includecount'] = count($inc);
1241     $info['html'] .= '<span class="included">Included '.$info['includecount'].' files</span> ';
1242     $info['txt']  .= 'includecount: '.$info['includecount'].' ';
1243
1244     if (!empty($PERF->dbqueries)) {
1245         $info['dbqueries'] = $PERF->dbqueries;
1246         $info['html'] .= '<span class="dbqueries">DB queries '.$info['dbqueries'].'</span> ';
1247         $info['txt'] .= 'dbqueries: '.$info['dbqueries'].' ';
1248     }
1249
1250     if (!empty($PERF->logwrites)) {
1251         $info['logwrites'] = $PERF->logwrites;
1252         $info['html'] .= '<span class="logwrites">Log writes '.$info['logwrites'].'</span> ';
1253         $info['txt'] .= 'logwrites: '.$info['logwrites'].' ';
1254     }
1255
1256     if (function_exists('posix_times')) {
1257         $ptimes = posix_times();
1258         if ($ptimes) {
1259             foreach ($ptimes as $key => $val) {
1260                 $info[$key] = $ptimes[$key] -  $PERF->startposixtimes[$key];           
1261             }
1262             $info['html'] .= "<span class=\"posixtimes\">ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime]</span> ";
1263             $info['txt'] .= "ticks: $info[ticks] user: $info[utime] sys: $info[stime] cuser: $info[cutime] csys: $info[cstime] ";
1264         }
1265     }
1266
1267     // Grab the load average for the last minute
1268     // /proc will only work under some linux configurations
1269     // while uptime is there under MacOSX/Darwin and other unices
1270     if (!@ini_get('open_basedir') && is_readable('/proc/loadavg') && $loadavg = @file('/proc/loadavg')) {
1271         list($server_load) = explode(' ', $loadavg[0]);
1272         unset($loadavg);
1273     } else if (!@ini_get('open_basedir') && function_exists('is_executable') && is_executable('/usr/bin/uptime') && $loadavg = `/usr/bin/uptime` ) {
1274         if (preg_match('/load averages?: (\d+[\.:]\d+)/', $loadavg, $matches)) {
1275             $server_load = $matches[1];
1276         } else {
1277             trigger_error('Could not parse uptime output!');
1278         }
1279     }
1280     if (!empty($server_load)) {
1281         $info['serverload'] = $server_load;
1282         $info['html'] .= '<span class="serverload">Load average: '.$info['serverload'].'</span> ';
1283         $info['txt'] .= 'serverload: '.$info['serverload'];
1284     }
1285     
1286
1287     $info['html'] = '<div class="performanceinfo">'.$info['html'].'</div>';
1288     return $info;
1289 }
1290
1291 if (!function_exists('file_get_contents')) {
1292    function file_get_contents($file) {
1293        $file = file($file);
1294        return $file ? implode('', $file) : false;
1295    }
1296 }
1297
1298
1299
1300
1301 /**
1302  * Detect if an object or a class contains a given property
1303  * will take an actual object or the name of a class
1304  * @param mix $obj Name of class or real object to test
1305  * @param string $property name of property to find
1306  * @return bool true if property exists
1307  */
1308 function object_property_exists( $obj, $property ) {
1309     if (is_string( $obj )) {
1310         $properties = get_class_vars( $obj );
1311     }
1312     else {
1313         $properties = get_object_vars( $obj );
1314     }
1315     return array_key_exists( $property, $properties );
1316 }
1317
1318
1319 /**
1320  * Add quotes to HTML characters
1321  *
1322  * Returns $var with HTML characters (like "<", ">", etc.) properly quoted.
1323  * This function is very similar to {@link p()}
1324  *
1325  * @param string $var the string potentially containing HTML characters
1326  * @return string
1327  */
1328 function s($var) {
1329     if ($var == '0') {  // for integer 0, boolean false, string '0'
1330         return '0';
1331     }
1332     return preg_replace("/&amp;(#\d+);/iu", '&$1;', htmlspecialchars(stripslashes_safe($var), ENT_COMPAT, 'utf-8'));
1333 }
1334
1335 /**
1336  * Add quotes to HTML characters
1337  *
1338  * Prints $var with HTML characters (like "<", ">", etc.) properly quoted.
1339  * This function is very similar to {@link s()}
1340  *
1341  * @param string $var the string potentially containing HTML characters
1342  * @return string
1343  */
1344 function p($var) {
1345     echo s($var);
1346 }
1347
1348
1349 /**
1350  * Ensure that a variable is set
1351  *
1352  * Return $var if it is defined, otherwise return $default,
1353  * This function is very similar to {@link optional_variable()}
1354  *
1355  * @param    mixed $var the variable which may be unset
1356  * @param    mixed $default the value to return if $var is unset
1357  * @return   mixed
1358  */
1359 function nvl(&$var, $default='') {
1360
1361     return isset($var) ? $var : $default;
1362 }
1363
1364 /**
1365  * Remove query string from url
1366  *
1367  * Takes in a URL and returns it without the querystring portion
1368  *
1369  * @param string $url the url which may have a query string attached
1370  * @return string
1371  */
1372  function strip_querystring($url) {
1373      $textlib = textlib_get_instance();
1374
1375     if ($commapos = $textlib->strpos($url, '?')) {
1376         return $textlib->substr($url, 0, $commapos);
1377     } else {
1378         return $url;
1379     }
1380 }
1381
1382 /**
1383  * Returns the URL of the HTTP_REFERER, less the querystring portion
1384  * @return string
1385  */
1386 function get_referer() {
1387
1388     return strip_querystring(nvl($_SERVER['HTTP_REFERER']));
1389 }
1390
1391
1392 /**
1393  * Returns the name of the current script, WITH the querystring portion.
1394  * this function is necessary because PHP_SELF and REQUEST_URI and SCRIPT_NAME
1395  * return different things depending on a lot of things like your OS, Web
1396  * server, and the way PHP is compiled (ie. as a CGI, module, ISAPI, etc.)
1397  * <b>NOTE:</b> This function returns false if the global variables needed are not set.
1398  *
1399  * @return string
1400  */
1401  function me() {
1402
1403     if (!empty($_SERVER['REQUEST_URI'])) {
1404         return $_SERVER['REQUEST_URI'];
1405
1406     } else if (!empty($_SERVER['PHP_SELF'])) {
1407         if (!empty($_SERVER['QUERY_STRING'])) {
1408             return $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
1409         }
1410         return $_SERVER['PHP_SELF'];
1411
1412     } else if (!empty($_SERVER['SCRIPT_NAME'])) {
1413         if (!empty($_SERVER['QUERY_STRING'])) {
1414             return $_SERVER['SCRIPT_NAME'] .'?'. $_SERVER['QUERY_STRING'];
1415         }
1416         return $_SERVER['SCRIPT_NAME'];
1417
1418     } else if (!empty($_SERVER['URL'])) {     // May help IIS (not well tested)
1419         if (!empty($_SERVER['QUERY_STRING'])) {
1420             return $_SERVER['URL'] .'?'. $_SERVER['QUERY_STRING'];
1421         }
1422         return $_SERVER['URL'];
1423
1424     } else {
1425         notify('Warning: Could not find any of these web server variables: $REQUEST_URI, $PHP_SELF, $SCRIPT_NAME or $URL');
1426         return false;
1427     }
1428 }
1429
1430 /**
1431  * Like {@link me()} but returns a full URL
1432  * @see me()
1433  * @return string
1434  */
1435 function qualified_me() {
1436
1437     global $CFG;
1438
1439     if (!empty($CFG->wwwroot)) {
1440         $url = parse_url($CFG->wwwroot);
1441     }
1442
1443     if (!empty($url['host'])) {
1444         $hostname = $url['host'];
1445     } else if (!empty($_SERVER['SERVER_NAME'])) {
1446         $hostname = $_SERVER['SERVER_NAME'];
1447     } else if (!empty($_ENV['SERVER_NAME'])) {
1448         $hostname = $_ENV['SERVER_NAME'];
1449     } else if (!empty($_SERVER['HTTP_HOST'])) {
1450         $hostname = $_SERVER['HTTP_HOST'];
1451     } else if (!empty($_ENV['HTTP_HOST'])) {
1452         $hostname = $_ENV['HTTP_HOST'];
1453     } else {
1454         notify('Warning: could not find the name of this server!');
1455         return false;
1456     }
1457
1458     if (!empty($url['port'])) {
1459         $hostname .= ':'.$url['port'];
1460     } else if (!empty($_SERVER['SERVER_PORT'])) {
1461         if ($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
1462             $hostname .= ':'.$_SERVER['SERVER_PORT'];
1463         }
1464     }
1465
1466     if (isset($_SERVER['HTTPS'])) {
1467         $protocol = ($_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
1468     } else if (isset($_SERVER['SERVER_PORT'])) { # Apache2 does not export $_SERVER['HTTPS']
1469         $protocol = ($_SERVER['SERVER_PORT'] == '443') ? 'https://' : 'http://';
1470     } else {
1471         $protocol = 'http://';
1472     }
1473
1474     $url_prefix = $protocol.$hostname;
1475     return $url_prefix . me();
1476 }
1477
1478 /**
1479  * Determine if a web referer is valid
1480  *
1481  * Returns true if the referer is the same as the goodreferer. If
1482  * the referer to test is not specified, use {@link qualified_me()}.
1483  * If the admin has not set secure forms ($CFG->secureforms) then
1484  * this function returns true regardless of a match.
1485  *
1486  * @uses $CFG
1487  * @param string $goodreferer the url to compare to referer
1488  * @return boolean
1489  */
1490 function match_referer($goodreferer = '') {
1491     global $CFG;
1492
1493     if (empty($CFG->secureforms)) {    // Don't bother checking referer
1494         return true;
1495     }
1496
1497     if ($goodreferer == 'nomatch') {   // Don't bother checking referer
1498         return true;
1499     }
1500
1501     if (empty($goodreferer)) {
1502         $goodreferer = qualified_me();
1503     }
1504
1505     $referer = get_referer();
1506
1507     return (($referer == $goodreferer) or ($referer == $CFG->wwwroot) or ($referer == $CFG->wwwroot .'index.php'));
1508 }
1509
1510 /**
1511  * Determine if there is data waiting to be processed from a form
1512  *
1513  * Used on most forms in Moodle to check for data
1514  * Returns the data as an object, if it's found.
1515  * This object can be used in foreach loops without
1516  * casting because it's cast to (array) automatically
1517  *
1518  * Checks that submitted POST data exists, and also
1519  * checks the referer against the given url (it uses
1520  * the current page if none was specified.
1521  *
1522  * @uses $CFG
1523  * @param string $url the url to compare to referer for secure forms
1524  * @return boolean
1525  */
1526 function data_submitted($url='') {
1527
1528
1529     global $CFG;
1530
1531     if (empty($_POST)) {
1532         return false;
1533
1534     } else {
1535         if (match_referer($url)) {
1536             return (object)$_POST;
1537         } else {
1538             if ($CFG->debug > 10) {
1539                 notice('The form did not come from this page! (referer = '. get_referer() .')');
1540             }
1541             return false;
1542         }
1543     }
1544 }
1545
1546 /**
1547  * Moodle replacement for php stripslashes() function
1548  *
1549  * The standard php stripslashes() removes ALL backslashes
1550  * even from strings - so  C:\temp becomes C:temp - this isn't good.
1551  * This function should work as a fairly safe replacement
1552  * to be called on quoted AND unquoted strings (to be sure)
1553  *
1554  * @param string the string to remove unsafe slashes from
1555  * @return string
1556  */
1557 function stripslashes_safe($string) {
1558
1559     $string = str_replace("\\'", "'", $string);
1560     $string = str_replace('\\"', '"', $string);
1561     $string = str_replace('\\\\', '\\', $string);
1562     return $string;
1563 }
1564
1565 /**
1566  * Recursive implementation of stripslashes()
1567  *
1568  * This function will allow you to strip the slashes from a variable.
1569  * If the variable is an array or object, slashes will be stripped
1570  * from the items (or properties) it contains, even if they are arrays
1571  * or objects themselves.
1572  *
1573  * @param mixed the variable to remove slashes from
1574  * @return mixed
1575  */
1576 function stripslashes_recursive($var) {
1577     if(is_object($var)) {
1578         $properties = get_object_vars($var);
1579         foreach($properties as $property => $value) {
1580             $var->$property = stripslashes_recursive($value);
1581         }
1582     }
1583     else if(is_array($var)) {
1584         foreach($var as $property => $value) {
1585             $var[$property] = stripslashes_recursive($value);
1586         }
1587     }
1588     else if(is_string($var)) {
1589         $var = stripslashes($var);
1590     }
1591     return $var;
1592 }
1593
1594 /**
1595  * This does a search and replace, ignoring case
1596  * This function is only used for versions of PHP older than version 5
1597  * which do not have a native version of this function.
1598  * Taken from the PHP manual, by bradhuizenga @ softhome.net
1599  *
1600  * @param string $find the string to search for
1601  * @param string $replace the string to replace $find with
1602  * @param string $string the string to search through
1603  * return string
1604  */
1605 if (!function_exists('str_ireplace')) {    /// Only exists in PHP 5
1606     function str_ireplace($find, $replace, $string) {
1607         $textlib = textlib_get_instance();
1608
1609         if (!is_array($find)) {
1610             $find = array($find);
1611         }
1612
1613         if(!is_array($replace)) {
1614             if (!is_array($find)) {
1615                 $replace = array($replace);
1616             } else {
1617                 // this will duplicate the string into an array the size of $find
1618                 $c = count($find);
1619                 $rString = $replace;
1620                 unset($replace);
1621                 for ($i = 0; $i < $c; $i++) {
1622                     $replace[$i] = $rString;
1623                 }
1624             }
1625         }
1626
1627         foreach ($find as $fKey => $fItem) {
1628             $between = explode($textlib->strtolower($fItem),$textlib->strtolower($string));
1629             $pos = 0;
1630             foreach($between as $bKey => $bItem) {
1631                 $between[$bKey] = $textlib->substr($string,$pos,$textlib->strlen($bItem));
1632                 $pos += $textlib->strlen($bItem) + $textlib->strlen($fItem);
1633             }
1634             $string = implode($replace[$fKey],$between);
1635         }
1636         return ($string);
1637     }
1638 }
1639
1640 /**
1641  * Locate the position of a string in another string
1642  *
1643  * This function is only used for versions of PHP older than version 5
1644  * which do not have a native version of this function.
1645  * Taken from the PHP manual, by dmarsh @ spscc.ctc.edu
1646  *
1647  * @param string $haystack The string to be searched
1648  * @param string $needle The string to search for
1649  * @param int $offset The position in $haystack where the search should begin.
1650  */
1651 if (!function_exists('stripos')) {    /// Only exists in PHP 5
1652     function stripos($haystack, $needle, $offset=0) {
1653         $textlib = textlib_get_instance();
1654
1655         return $textlib->strpos($textlib->strtoupper($haystack), $textlib->strtoupper($needle), $offset);
1656     }
1657 }
1658
1659
1660 /**
1661  * Returns true if the current version of PHP is greater that the specified one.
1662  *
1663  * @param string $version The version of php being tested.
1664  * @return boolean
1665  * @todo Finish documenting this function
1666  */
1667 function check_php_version($version='4.1.0') {
1668     return (version_compare(phpversion(), $version) >= 0);
1669 }
1670
1671
1672 /**
1673  * Checks to see if is a browser matches the specified
1674  * brand and is equal or better version.
1675  *
1676  * @uses $_SERVER
1677  * @param string $brand The browser identifier being tested
1678  * @param int $version The version of the browser
1679  * @return boolean
1680  * @todo Finish documenting this function
1681  */
1682  function check_browser_version($brand='MSIE', $version=5.5) {
1683     $agent = $_SERVER['HTTP_USER_AGENT'];
1684
1685     if (empty($agent)) {
1686         return false;
1687     }
1688
1689     switch ($brand) {
1690
1691       case 'Gecko':   /// Gecko based browsers
1692
1693           if (substr_count($agent, 'Camino')) {
1694               // MacOS X Camino support
1695               $version = 20041110;
1696           }
1697
1698           // the proper string - Gecko/CCYYMMDD Vendor/Version
1699           // Faster version and work-a-round No IDN problem.
1700           if (preg_match("/Gecko\/([0-9]+)/i", $agent, $match)) {
1701               if ($match[1] > $version) {
1702                       return true;
1703                   }
1704               }
1705           break;
1706
1707
1708       case 'MSIE':   /// Internet Explorer
1709
1710           if (strpos($agent, 'Opera')) {     // Reject Opera
1711               return false;
1712           }
1713           $string = explode(';', $agent);
1714           if (!isset($string[1])) {
1715               return false;
1716           }
1717           $string = explode(' ', trim($string[1]));
1718           if (!isset($string[0]) and !isset($string[1])) {
1719               return false;
1720           }
1721           if ($string[0] == $brand and (float)$string[1] >= $version ) {
1722               return true;
1723           }
1724           break;
1725
1726     }
1727
1728     return false;
1729 }
1730
1731
1732 /**
1733  * Set a variable's value depending on whether or not it already has a value.
1734  *
1735  * If variable is set, set it to the set_value otherwise set it to the
1736  * unset_value.  used to handle checkboxes when you are expecting them from
1737  * a form
1738  *
1739  * @param mixed $var Passed in by reference. The variable to check.
1740  * @param mixed $set_value The value to set $var to if $var already has a value.
1741  * @param mixed $unset_value The value to set $var to if $var does not already have a value.
1742  */
1743 function checked(&$var, $set_value = 1, $unset_value = 0) {
1744
1745     if (empty($var)) {
1746         $var = $unset_value;
1747     } else {
1748         $var = $set_value;
1749     }
1750 }
1751
1752 /**
1753  * Prints the word "checked" if a variable is true, otherwise prints nothing,
1754  * used for printing the word "checked" in a checkbox form element.
1755  *
1756  * @param boolean $var Variable to be checked for true value
1757  * @param string $true_value Value to be printed if $var is true
1758  * @param string $false_value Value to be printed if $var is false
1759  */
1760 function frmchecked(&$var, $true_value = 'checked', $false_value = '') {
1761
1762     if ($var) {
1763         echo $true_value;
1764     } else {
1765         echo $false_value;
1766     }
1767 }
1768
1769 /**
1770  * Prints a simple button to close a window
1771  */
1772 function close_window_button($name='closewindow') {
1773
1774     echo '<div style="text-align: center;">' . "\n";
1775     echo '<script type="text/javascript">' . "\n";
1776     echo '<!--' . "\n";
1777     echo "document.write('<form>');\n";
1778     echo "document.write('<input type=\"button\" onclick=\"self.close();\" value=\"".__gettext("Close this window")."\" />');\n";
1779     echo "document.write('<\/form>');\n";
1780     echo '-->' . "\n";
1781     echo '</script>' . "\n";
1782     echo '<noscript>' . "\n";
1783     print_string($name);
1784     echo '</noscript>' . "\n";
1785     echo '</div>' . "\n";
1786 }
1787
1788 /*
1789  * Try and close the current window immediately using Javascript
1790  */
1791 function close_window($delay=0) {
1792     echo '<script language="JavaScript" type="text/javascript">'."\n";
1793     echo '<!--'."\n";
1794     if ($delay) {
1795         sleep($delay);
1796     }
1797     echo 'self.close();'."\n";
1798     echo '-->'."\n";
1799     echo '</script>'."\n";
1800     exit;
1801 }
1802
1803
1804 /**
1805  * Given an array of value, creates a popup menu to be part of a form
1806  * $options["value"]["label"]
1807  *
1808  * @param    type description
1809  * @todo Finish documenting this function
1810  */
1811 function choose_from_menu ($options, $name, $selected='', $nothing='choose', $script='',
1812                            $nothingvalue='0', $return=false, $disabled=false, $tabindex=0) {
1813
1814     if ($nothing == 'choose') {
1815         $nothing = __gettext('Choose') .'...';
1816     }
1817
1818     $attributes = ($script) ? 'onchange="'. $script .'"' : '';
1819     if ($disabled) {
1820         $attributes .= ' disabled="disabled"';
1821     }
1822
1823     if ($tabindex) {
1824         $attributes .= ' tabindex="'.$tabindex.'"';
1825     }
1826
1827     $output = '<select id="menu'.$name.'" name="'. $name .'" '. $attributes .'>' . "\n";
1828     if ($nothing) {
1829         $output .= '   <option value="'. $nothingvalue .'"'. "\n";
1830         if ($nothingvalue === $selected) {
1831             $output .= ' selected="selected"';
1832         }
1833         $output .= '>'. $nothing .'</option>' . "\n";
1834     }
1835     if (!empty($options)) {
1836         foreach ($options as $value => $label) {
1837             $output .= '   <option value="'. $value .'"';
1838             if ($value === $selected) {
1839                 $output .= ' selected="selected"';
1840             }
1841             if ($label === '') {
1842                 $output .= '>'. $value .'</option>' . "\n";
1843             } else {
1844                 $output .= '>'. $label .'</option>' . "\n";
1845             }
1846         }
1847     }
1848     $output .= '</select>' . "\n";
1849
1850     if ($return) {
1851         return $output;
1852     } else {
1853         echo $output;
1854     }
1855 }
1856
1857 /**
1858  * Just like choose_from_menu, but takes a nested array (2 levels) and makes a dropdown menu
1859  * including option headings with the first level.
1860  */
1861 function choose_from_menu_nested($options,$name,$selected='',$nothing='choose',$script = '',
1862                                  $nothingvalue=0,$return=false,$disabled=false,$tabindex=0) {
1863
1864    if ($nothing == 'choose') {
1865         $nothing = __gettext('Choose') .'...';
1866     }
1867
1868     $attributes = ($script) ? 'onchange="'. $script .'"' : '';
1869     if ($disabled) {
1870         $attributes .= ' disabled="disabled"';
1871     }
1872
1873     if ($tabindex) {
1874         $attributes .= ' tabindex="'.$tabindex.'"';
1875     }
1876
1877     $output = '<select id="menu'.$name.'" name="'. $name .'" '. $attributes .'>' . "\n";
1878     if ($nothing) {
1879         $output .= '   <option value="'. $nothingvalue .'"'. "\n";
1880         if ($nothingvalue === $selected) {
1881             $output .= ' selected="selected"';
1882         }
1883         $output .= '>'. $nothing .'</option>' . "\n";
1884     }
1885     if (!empty($options)) {
1886         foreach ($options as $section => $values) {
1887             $output .= '   <optgroup label="'.$section.'">'."\n";
1888             foreach ($values as $value => $label) {
1889                 $output .= '   <option value="'. $value .'"';
1890                 if ($value === $selected) {
1891                     $output .= ' selected="selected"';
1892                 }
1893                 if ($label === '') {
1894                     $output .= '>'. $value .'</option>' . "\n";
1895                 } else {
1896                     $output .= '>'. $label .'</option>' . "\n";
1897                 }
1898             }
1899             $output .= '   </optgroup>'."\n";
1900         }
1901     }
1902     $output .= '</select>' . "\n";
1903
1904     if ($return) {
1905         return $output;
1906     } else {
1907         echo $output;
1908     }
1909 }
1910
1911
1912 /**
1913  * Given an array of values, creates a group of radio buttons to be part of a form
1914  *
1915  * @param array  $options  An array of value-label pairs for the radio group (values as keys)
1916  * @param string $name     Name of the radiogroup (unique in the form)
1917  * @param string $checked  The value that is already checked
1918  */
1919 function choose_from_radio ($options, $name, $checked='') {
1920
1921     static $idcounter = 0;
1922
1923     if (!$name) {
1924         $name = 'unnamed';
1925     }
1926
1927     $output = '<span class="radiogroup '.$name."\">\n";
1928
1929     if (!empty($options)) {
1930         $currentradio = 0;
1931         foreach ($options as $value => $label) {
1932             $htmlid = 'auto-rb'.sprintf('%04d', ++$idcounter);
1933             $output .= ' <span class="radioelement '.$name.' rb'.$currentradio."\">";
1934             $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="radio" value="'.$value.'"';
1935             if ($value == $checked) {
1936                 $output .= ' checked="checked"';
1937             }
1938             if ($label === '') {
1939                 $output .= ' /> <label for="'.$htmlid.'">'$value .'</label></span>' "\n";
1940             } else {
1941                 $output .= ' /> <label for="'.$htmlid.'">'$label .'</label></span>' "\n";
1942             }
1943             $currentradio = ($currentradio + 1) % 2;
1944         }
1945     }
1946
1947     $output .= '</span>' "\n";
1948
1949     echo $output;
1950 }
1951
1952 /** Display an standard html checkbox with an optional label
1953  *
1954  * @param string  $name    The name of the checkbox
1955  * @param string  $value   The valus that the checkbox will pass when checked
1956  * @param boolean $checked The flag to tell the checkbox initial state
1957  * @param string  $label   The label to be showed near the checkbox
1958  * @param string  $alt     The info to be inserted in the alt tag
1959  */
1960 function print_checkbox ($name, $value, $checked = true, $label = '', $alt = '', $script='',$return=false) {
1961
1962     static $idcounter = 0;
1963
1964     if (!$name) {
1965         $name = 'unnamed';
1966     }
1967
1968     if (!$alt) {
1969         $alt = 'checkbox';
1970     }
1971
1972     if ($checked) {
1973         $strchecked = ' checked="checked"';
1974     }
1975
1976     $htmlid = 'auto-cb'.sprintf('%04d', ++$idcounter);
1977     $output  = '<span class="checkbox '.$name."\">";
1978     $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="checkbox" value="'.$value.'" alt="'.$alt.'"'.$strchecked.' '.((!empty($script)) ? ' onclick="'.$script.'" ' : '').' />';
1979     if(!empty($label)) {
1980         $output .= ' <label for="'.$htmlid.'">'.$label.'</label>';
1981     }
1982     $output .= '</span>'."\n";
1983
1984     if (empty($return)) {
1985         echo $output;
1986     } else {
1987         return $output;
1988     }
1989
1990 }
1991
1992 /** Display an standard html text field with an optional label
1993  *
1994  * @param string  $name    The name of the text field
1995  * @param string  $value   The value of the text field
1996  * @param string  $label   The label to be showed near the text field
1997  * @param string  $alt     The info to be inserted in the alt tag
1998  */
1999 function print_textfield ($name, $value, $alt = '',$size=50,$maxlength= 0,$return=false) {
2000
2001     static $idcounter = 0;
2002
2003     if (empty($name)) {
2004         $name = 'unnamed';
2005     }
2006
2007     if (empty($alt)) {
2008         $alt = 'textfield';
2009     }
2010
2011     if (!empty($maxlength)) {
2012         $maxlength = ' maxlength="'.$maxlength.'" ';
2013     }
2014
2015     $htmlid = 'auto-cb'.sprintf('%04d', ++$idcounter);
2016     $output  = '<span class="textfield '.$name."\">";
2017     $output .= '<input name="'.$name.'" id="'.$htmlid.'" type="text" value="'.$value.'" size="'.$size.'" '.$maxlength.' alt="'.$alt.'" />';
2018  
2019     $output .= '</span>'."\n";
2020
2021     if (empty($return)) {
2022         echo $output;
2023     } else {
2024         return $output;
2025     }
2026
2027 }
2028
2029 /**
2030  * Check if system reached account limit
2031  * @return boolean
2032  */
2033 function maxusers_limit() {
2034     global $CFG;
2035
2036     $limit = isset($CFG->maxusers) ? intval($CFG->maxusers) : 0;
2037
2038     if ($limit > 0 && count_users('person') >= $limit) {
2039         return true;
2040     }
2041     
2042     return false;
2043 }
2044
2045 /**
2046  * Validates username
2047  * @param string $username The username to validate
2048  * @return boolean
2049  */
2050 function validate_username($username) {
2051     global $CFG;
2052
2053     $minchars = isset($CFG->username_minchars) ? intval($CFG->username_minchars) : 3;
2054     $maxchars = isset($CFG->username_maxchars) ? intval($CFG->username_maxchars) : 12;
2055
2056     // TODO: modifying regex needs update rewrite rules
2057     $regex = sprintf('/^[A-Za-z0-9_]{%d,%d}$/i', $minchars, $maxchars); // letters and numbers only
2058
2059     // TODO: should allow plugins to extend validation?
2060
2061     if (!preg_match($regex, $username)) {
2062         return false;
2063     }
2064     
2065     return true;
2066 }
2067
2068 /**
2069  * Check if username is available
2070  * @param string $username The username to check availability
2071  * @return boolean
2072  */
2073 function username_is_available($username) {
2074
2075     if (!validate_username($username)) {
2076         return false;
2077     }
2078
2079     if (record_exists('users', 'username', $username)) {
2080         return false;
2081     }
2082
2083     return true;
2084 }
2085
2086 /**
2087  * Validates password
2088  * @param string $password1 The password to validate
2089  * @param string $password2 The 2nd password to verify that match
2090  * @return boolean
2091  */
2092 function validate_password($password1, $password2=null) {
2093     global $CFG;
2094
2095     $minchars = isset($CFG->password_minchars) ? intval($CFG->password_minchars) : 6;
2096     $maxchars = isset($CFG->password_maxchars) ? intval($CFG->password_maxchars) : 32;
2097
2098     // TODO: should allow plugins to extend validate?
2099     // could help to enforce passwords
2100
2101     if (empty($password1) || (empty($password2) || $password1 != $password2)) {
2102         return false;
2103     }
2104
2105     // TODO: from units/users/userdetails_actions.php
2106     /*
2107     if (!preg_match('/^[a-z0-9]*$/i', $password1)) { // only allow letters and numbers
2108         return false;
2109     }
2110      */
2111
2112     $len = strlen($password1);
2113
2114     if ($len < $minchars || $len > $maxchars) {
2115         return  false;
2116     }
2117     
2118     return true;
2119 }
2120
2121 /**
2122  * Validates an email to make sure it makes sense and adheres
2123  * to the email filter if it's set.
2124  *
2125  * @param string $address The email address to validate.
2126  * @return boolean
2127  */
2128 function validate_email($address) {
2129
2130     global $CFG;
2131     
2132     if (ereg('^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'.
2133                   '@'.
2134                   '[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.
2135                   '[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$',
2136                   $address)) {
2137                       
2138                       if ($CFG->emailfilter != "") {
2139                           $domain = substr($address,strpos($address,"@")+1);
2140                           if (substr_count($CFG->emailfilter, $domain) == 0) {
2141                               return false;
2142                           }
2143                       }
2144                       
2145                       return true;
2146                       
2147                   } else {
2148                       return false;
2149                   }
2150 }
2151
2152 /**
2153  * Check for bad characters ?
2154  *
2155  * @param string $string ?
2156  * @param int $allowdots ?
2157  * @todo Finish documenting this function - more detail needed in description as well as details on arguments
2158  */
2159 function detect_munged_arguments($string, $allowdots=1) {
2160     if (substr_count($string, '..') > $allowdots) {   // Sometimes we allow dots in references
2161         return true;
2162     }
2163     if (ereg('[\|\`]', $string)) {  // check for other bad characters
2164         return true;
2165     }
2166     if (empty($string) or $string == '/') {
2167         return true;
2168     }
2169
2170     return false;
2171 }
2172
2173
2174
2175 /**
2176  * Just returns an array of text formats suitable for a popup menu
2177  *
2178  * @uses FORMAT_MOODLE
2179  * @uses FORMAT_HTML
2180  * @uses FORMAT_PLAIN
2181  * @uses FORMAT_MARKDOWN
2182  * @return array
2183  */
2184 function format_text_menu() {
2185
2186     return array (FORMAT_MOODLE => __gettext('Elgg auto-format'),
2187                   FORMAT_HTML   => __gettext('HTML format'),
2188                   FORMAT_PLAIN  => __gettext('Plain text format'),
2189                   FORMAT_MARKDOWN  => __gettext('Markdown format'));
2190 }
2191
2192 /*
2193  * Given text in a variety of format codings, this function returns
2194  * the text as safe HTML.
2195  *
2196  * @uses $CFG
2197  * @uses FORMAT_MOODLE
2198  * @uses FORMAT_HTML
2199  * @uses FORMAT_PLAIN
2200  * @uses FORMAT_WIKI
2201  * @uses FORMAT_MARKDOWN
2202  * @param string $text The text to be formatted. This is raw text originally from user input.
2203  * @param int $format Identifier of the text format to be used
2204  *            (FORMAT_MOODLE, FORMAT_HTML, FORMAT_PLAIN, FORMAT_WIKI, FORMAT_MARKDOWN)
2205  * @param  array $options ?
2206  * @param int $courseid ?
2207  * @return string
2208  * @todo Finish documenting this function
2209  */
2210 function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL ) {
2211
2212     global $CFG, $course;
2213
2214     if (!isset($options->noclean)) {
2215         $options->noclean=false;
2216     }
2217     if (!isset($options->smiley)) {
2218         $options->smiley=true;
2219     }
2220     if (!isset($options->filter)) {
2221         $options->filter=true;
2222     }
2223     if (!isset($options->para)) {
2224         $options->para=true;
2225     }
2226     if (!isset($options->newlines)) {
2227         $options->newlines=true;
2228     }
2229
2230     if (empty($courseid)) {
2231         if (!empty($course->id)) {         // An ugly hack for better compatibility
2232             $courseid = $course->id;
2233         }
2234     }
2235
2236     /*
2237     if (!empty($CFG->cachetext)) {
2238         $time = time() - $CFG->cachetext;
2239         $md5key = md5($text.'-'.$courseid.$options->noclean.$options->smiley.$options->filter.$options->para.$options->newlines);
2240         if ($cacheitem = get_record_select('cache_text', "md5key = '$md5key' AND timemodified > '$time'")) {
2241             return $cacheitem->formattedtext;
2242         }
2243     }
2244     */ // DISABLED - there is no cache_text - Penny
2245
2246     $CFG->currenttextiscacheable = true;   // Default status - can be changed by any filter
2247
2248     switch ($format) {
2249         case FORMAT_HTML:
2250
2251             if (!empty($options->smiley)) {
2252                 replace_smilies($text);
2253             }
2254
2255             if (!isset($options->noclean)) {
2256                 $text = clean_text($text, $format, !empty($options->cleanuserfile));
2257             }
2258
2259             if (!empty($options->filter)) {
2260                 $text = filter_text($text, $courseid);
2261             }
2262             break;
2263
2264         case FORMAT_PLAIN:
2265             $text = s($text);
2266             $text = rebuildnolinktag($text);
2267             $text = str_replace('  ', '&nbsp; ', $text);
2268             $text = nl2br($text);
2269             break;
2270
2271         case FORMAT_WIKI:
2272             // this format is deprecated
2273             $text = '<p>NOTICE: Wiki-like formatting has been removed from Moodle.  You should not be seeing
2274                      this message as all texts should have been converted to Markdown format instead.
2275                      Please post a bug report to http://moodle.org/bugs with information about where you
2276                      saw this message.</p>'.s($text);
2277             break;
2278
2279         case FORMAT_MARKDOWN:
2280             $text = markdown_to_html($text);
2281             if (!empty($options->smiley)) {
2282                 replace_smilies($text);
2283             }
2284             if (empty($options->noclean)) {
2285                 $text = clean_text($text, $format);
2286             }
2287             if (!empty($options->filter)) {
2288                 $text = filter_text($text, $courseid);
2289             }
2290             break;
2291
2292         default:  // FORMAT_MOODLE or anything else
2293             $text = text_to_html($text, $options->smiley, $options->para, $options->newlines);
2294             if (empty($options->noclean)) {
2295                 $text = clean_text($text, $format);
2296             }
2297             if (!empty($options->filter)) {
2298                 $text = filter_text($text, $courseid);
2299             }
2300             break;
2301     }
2302
2303     if (!empty($CFG->cachetext) and $CFG->currenttextiscacheable) {
2304         $newrecord->md5key = $md5key;
2305         $newrecord->formattedtext = $text;
2306         $newrecord->timemodified = time();
2307         @insert_record('cache_text', $newrecord);
2308     }
2309
2310     return