root/elgg/trunk/lib/elgglib.php

Revision 37, 137.1 kB (checked in by hopsonro, 2 years ago)

Add cedenoj's SSO patch for Elgg.

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 $text;
2311 }
2312
2313