root/releases/0.6rc2/lib/elgglib.php

Revision 339, 123.8 kB (checked in by sven, 3 years ago)

minor HTML validation fixes

  • Property svn:eol-style set to native
Line 
1 <?php
2
3 /**
4  * Library of functions for handling input validation
5  * and some HTML generation
6  * This library is a combination of bits of lib/weblib.php
7  * and lib/moodlelib.php from moodle
8  * http://moodle.org || http://sourceforge.net/projects/moodle
9  * Copyright (C) 2001-2003  Martin Dougiamas  http://dougiamas.com
10  * @author Martin Dougiamas and many others
11  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
12  */
13
14
15 /// PARAMETER HANDLING ////////////////////////////////////////////////////
16
17 /**
18  * Returns a particular value for the named variable, taken from
19  * POST or GET.  If the parameter doesn't exist then an error is
20  * thrown because we require this variable.
21  *
22  * This function should be used to initialise all required values
23  * in a script that are based on parameters.  Usually it will be
24  * used like this:
25  *    $id = required_param('id');
26  *
27  * @param string $varname the name of the parameter variable we want
28  * @param int $options a bit field that specifies any cleaning needed
29  * @return mixed
30  */
31 function required_param($varname, $options=PARAM_CLEAN) {
32
33     // detect_unchecked_vars addition
34     global $CFG;
35     if (!empty($CFG->detect_unchecked_vars)) {
36         global $UNCHECKED_VARS;
37         unset ($UNCHECKED_VARS->vars[$varname]);
38     }
39
40     if (isset($_POST[$varname])) {       // POST has precedence
41         $param = $_POST[$varname];
42     } else if (isset($_GET[$varname])) {
43         $param = $_GET[$varname];
44     } else {
45         error('A required parameter ('.$varname.') was missing');
46     }
47
48     return clean_param($param, $options);
49 }
50
51 /**
52  * Returns a particular value for the named variable, taken from
53  * POST or GET, otherwise returning a given default.
54  *
55  * This function should be used to initialise all optional values
56  * in a script that are based on parameters.  Usually it will be
57  * used like this:
58  *    $name = optional_param('name', 'Fred');
59  *
60  * @param string $varname the name of the parameter variable we want
61  * @param mixed  $default the default value to return if nothing is found
62  * @param int $options a bit field that specifies any cleaning needed
63  * @return mixed
64  */
65 function optional_param($varname, $default=NULL, $options=PARAM_CLEAN) {
66
67     // detect_unchecked_vars addition
68     global $CFG;
69     if (!empty($CFG->detect_unchecked_vars)) {
70         global $UNCHECKED_VARS;
71         unset ($UNCHECKED_VARS->vars[$varname]);
72     }
73
74     if (isset($_POST[$varname])) {       // POST has precedence
75         $param = $_POST[$varname];
76     } else if (isset($_GET[$varname])) {
77         $param = $_GET[$varname];
78     } else {
79         return $default;
80     }
81
82     return clean_param($param, $options);
83 }
84
85
86 /**
87  * Used by {@link optional_param()} and {@link required_param()} to
88  * clean the variables and/or cast to specific types, based on
89  * an options field.
90  * <code>
91  * $course->format = clean_param($course->format, PARAM_ALPHA);
92  * $selectedgrade_item = clean_param($selectedgrade_item, PARAM_CLEAN);
93  * </code>
94  *
95  * @uses $CFG
96  * @uses PARAM_CLEAN
97  * @uses PARAM_INT
98  * @uses PARAM_INTEGER
99  * @uses PARAM_ALPHA
100  * @uses PARAM_ALPHANUM
101  * @uses PARAM_NOTAGS
102  * @uses PARAM_ALPHATEXT
103  * @uses PARAM_BOOL
104  * @uses PARAM_SAFEDIR
105  * @uses PARAM_CLEANFILE
106  * @uses PARAM_FILE
107  * @uses PARAM_PATH
108  * @uses PARAM_HOST
109  * @uses PARAM_URL
110  * @uses PARAM_LOCALURL
111  * @uses PARAM_CLEANHTML
112  * @param mixed $param the variable we are cleaning
113  * @param int $options a bit field that specifies the cleaning needed. This field is specified by combining PARAM_* definitions together with a logical or.
114  * @return mixed
115  */
116 function clean_param($param, $options) {
117
118     global $CFG;
119
120     if (is_array($param)) {              // Let's loop
121         $newparam = array();
122         foreach ($param as $key => $value) {
123             $newparam[$key] = clean_param($value, $options);
124         }
125         return $newparam;
126     }
127
128     if (!$options) {
129         return $param;                   // Return raw value
130     }
131
132     if ((string)$param == (string)(int)$param) {  // It's just an integer
133         return (int)$param;
134     }
135
136     if ($options & PARAM_CLEAN) {
137         $param = stripslashes($param);   // Needed by kses to work fine
138         $param = clean_text($param);     // Sweep for scripts, etc
139         $param = addslashes($param);     // Restore original request parameter slashes
140     }
141
142     if ($options & PARAM_INT) {
143         $param = (int)$param;            // Convert to integer
144     }
145
146     if ($options & PARAM_ALPHA) {        // Remove everything not a-z
147         $param = eregi_replace('[^a-zA-Z]', '', $param);
148     }
149
150     if ($options & PARAM_ALPHANUM) {     // Remove everything not a-zA-Z0-9
151         $param = eregi_replace('[^A-Za-z0-9]', '', $param);
152     }
153
154     if ($options & PARAM_ALPHAEXT) {     // Remove everything not a-zA-Z/_-
155         $param = eregi_replace('[^a-zA-Z/_-]', '', $param);
156     }
157
158     if ($options & PARAM_BOOL) {         // Convert to 1 or 0
159         $tempstr = strtolower($param);
160         if ($tempstr == 'on') {
161             $param = 1;
162         } else if ($tempstr == 'off') {
163             $param = 0;
164         } else {
165             $param = empty($param) ? 0 : 1;
166         }
167     }
168
169     if ($options & PARAM_NOTAGS) {       // Strip all tags completely
170         $param = strip_tags($param);
171     }
172
173     if ($options & PARAM_SAFEDIR) {     // Remove everything not a-zA-Z0-9_-
174         $param = eregi_replace('[^a-zA-Z0-9_-]', '', $param);
175     }
176
177     if ($options & PARAM_CLEANFILE) {    // allow only safe characters
178         $param = clean_filename($param);
179     }
180
181     if ($options & PARAM_FILE) {         // Strip all suspicious characters from filename
182         $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':\\/]', '', $param);
183         $param = ereg_replace('\.\.+', '', $param);
184         if($param == '.') {
185             $param = '';
186         }
187     }
188
189     if ($options & PARAM_PATH) {         // Strip all suspicious characters from file path
190         $param = str_replace('\\\'', '\'', $param);
191         $param = str_replace('\\"', '"', $param);
192         $param = str_replace('\\', '/', $param);
193         $param = ereg_replace('[[:cntrl:]]|[<>"`\|\':]', '', $param);
194         $param = ereg_replace('\.\.+', '', $param);
195         $param = ereg_replace('//+', '/', $param);
196         $param = ereg_replace('/(\./)+', '/', $param);
197     }
198
199     if ($options & PARAM_HOST) {         // allow FQDN or IPv4 dotted quad
200         preg_replace('/[^\.\d\w-]/','', $param ); // only allowed chars
201         // match ipv4 dotted quad
202         if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/',$param, $match)){
203             // confirm values are ok
204             if ( $match[0] > 255
205                  || $match[1] > 255
206                  || $match[3] > 255
207                  || $match[4] > 255 ) {
208                 // hmmm, what kind of dotted quad is this?
209                 $param = '';
210             }
211         } elseif ( preg_match('/^[\w\d\.-]+$/', $param) // dots, hyphens, numbers
212                    && !preg_match('/^[\.-]/'$param) // no leading dots/hyphens
213                    && !preg_match('/[\.-]$/'$param) // no trailing dots/hyphens
214                    ) {
215             // all is ok - $param is respected
216         } else {
217             // all is not ok...
218             $param='';
219         }
220     }
221
222     if ($options & PARAM_URL) { // allow safe ftp, http, mailto urls
223
224         include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
225
226         //
227         // Parameters to validateurlsyntax()
228         //
229         // s? scheme is optional
230         //   H? http optional
231         //   S? https optional
232         //   F? ftp   optional
233         //   E? mailto optional
234         // u- user section not allowed
235         //   P- password not allowed
236         // a? address optional
237         //   I? Numeric IP address optional (can use IP or domain)
238         //   p-  port not allowed -- restrict to default port
239         // f? "file" path section optional
240         //   q? query section optional
241         //   r? fragment (anchor) optional
242         //
243         if (!empty($param) && validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p-f?q?r?')) {
244             // all is ok, param is respected
245         } else {
246             $param =''; // not really ok
247         }
248         $options ^= PARAM_URL; // Turn off the URL bit so that simple PARAM_URLs don't test true for PARAM_LOCALURL
249     }
250
251     if ($options & PARAM_LOCALURL) {
252         // assume we passed the PARAM_URL test...
253         // allow http absolute, root relative and relative URLs within wwwroot
254         if (!empty($param)) {
255             if (preg_match(':^/:', $param)) {
256                 // root-relative, ok!
257             } elseif (preg_match('/^'.preg_quote($CFG->wwwroot, '/').'/i',$param)) {
258                 // absolute, and matches our wwwroot
259             } else {
260                 // relative - let's make sure there are no tricks
261                 if (validateUrlSyntax($param, 's-u-P-a-p-f+q?r?')) {
262                     // looks ok.
263                 } else {
264                     $param = '';
265                 }
266             }
267         }
268     }
269
270     if ($options & PARAM_CLEANHTML) {
271         $param = stripslashes($param);         // Remove any slashes
272         $param = clean_text($param);           // Sweep for scripts, etc
273         $param = trim($param);                 // Sweep for scripts, etc
274     }
275
276     return $param;
277 }
278
279 /**
280  * Retrieves the list of plugins available in the $plugin
281  * directory. Defaults to 'mod'.
282  *
283  * NOTE: To get the list of enabled modules, do
284  * get_records('modules', 'enabled', true) instead.
285  *
286  * @return array
287  **/
288 function get_list_of_plugins($plugin='mod', $exclude='') {
289
290     global $CFG;
291
292     $basedir = opendir($CFG->dirroot .'/'. $plugin);
293     while (false !== ($dir = readdir($basedir))) {
294         $firstchar = substr($dir, 0, 1);
295         if ($firstchar == '.' or $dir == 'CVS' or $dir == '_vti_cnf' or $dir == $exclude) {
296             continue;
297         }
298         if (filetype($CFG->dirroot .'/'. $plugin .'/'. $dir) != 'dir') {
299             continue;
300         }
301         $plugins[] = $dir;
302     }
303     if ($plugins) {
304         asort($plugins);
305     }
306     return $plugins;
307 }
308
309 function report_session_error() {
310     global $CFG, $FULLME;
311     if (empty($CFG->lang)) {
312         $CFG->lang = "en";
313     }
314
315     //clear session cookies
316     setcookie('ElggSession'.$CFG->sessioncookie, '', time() - 3600, '/');
317     setcookie('ElggSessionTest'.$CFG->sessioncookie, '', time() - 3600, '/');
318     //increment database error counters
319     if (isset($CFG->session_error_counter)) {
320         set_config('session_error_counter', 1 + $CFG->session_error_counter);
321     } else {
322         set_config('session_error_counter', 1);
323     }
324     redirect($FULLME, gettext('A server error that affects your login session was detected. Please login again or restart your browser.'), 5);
325 }
326
327 /**
328  * For security purposes, this function will check that the currently
329  * given sesskey (passed as a parameter to the script or this function)
330  * matches that of the current user.
331  *
332  * @param string $sesskey optionally provided sesskey
333  * @return bool
334  */
335 function confirm_sesskey($sesskey=NULL) {
336     global $USER;
337
338     if (!empty($USER->ignoresesskey) || !empty($CFG->ignoresesskey)) {
339         return true;
340     }
341
342     if (empty($sesskey)) {
343         $sesskey = required_param('sesskey');  // Check script parameters
344     }
345
346     if (!isset($USER->sesskey)) {
347         return false;
348     }
349
350     return ($USER->sesskey === $sesskey);
351 }
352
353
354 /**
355  * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
356  * if one does not already exist, but does not overwrite existing sesskeys. Returns the
357  * sesskey string if $USER exists, or boolean false if not.
358  *
359  * @uses $USER
360  * @return string
361  */
362 function sesskey() {
363     global $USER;
364
365     if(!isset($USER)) {
366         return false;
367     }
368
369     if (empty($USER->sesskey)) {
370         $USER->sesskey = random_string(10);
371     }
372
373     return $USER->sesskey;
374 }
375
376
377 /**
378  * Send an email to a specified user
379  *
380  * @uses $CFG
381  * @param user $user  A {@link $USER} object
382  * @param user $from A {@link $USER} object
383  * @param string $subject plain text subject line of the email
384  * @param string $messagetext plain text version of the message
385  * @param string $messagehtml complete html version of the message (optional)
386  * @param string $attachment a file on the filesystem
387  * @param string $attachname the name of the file (extension indicates MIME)
388  * @param bool $usetrueaddress determines whether $from email address should
389  *          be sent out. Will be overruled by user profile setting for maildisplay
390  * @return bool|string Returns "true" if mail was sent OK, "emailstop" if email
391  *          was blocked by user and "false" if there was another sort of error.
392  */
393 function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $attachment='', $attachname='', $usetrueaddress=true, $repyto='', $replytoname='') {
394
395     global $CFG;
396     $textlib = textlib_get_instance();
397
398     include_once($CFG->libdir .'/phpmailer/class.phpmailer.php');
399
400     if (empty($user)) {
401         return false;
402     }
403
404     /*
405     if (over_bounce_threshold($user)) {
406         error_log("User $user->id (".fullname($user).") is over bounce threshold! Not sending.");
407         return false;
408     }
409     */ // this doesn't exist right now, we may bring it in later though.
410
411     $mail = new phpmailer;
412
413     $mail->Version = 'Elgg ';           // mailer version (should have $CFG->version on here but we don't have it yet)
414     $mail->PluginDir = $CFG->libdir .'/phpmailer/';      // plugin directory (eg smtp plugin)
415
416
417     $mail->CharSet = 'UTF-8'; // everything is now uft8
418
419     if (empty($CFG->smtphosts)) {
420         $mail->IsMail();                               // use PHP mail() = sendmail
421     } else if ($CFG->smtphosts == 'qmail') {
422         $mail->IsQmail();                              // use Qmail system
423     } else {
424         $mail->IsSMTP();                               // use SMTP directly
425         if ($CFG->debug > 7) {
426             echo '<pre>' . "\n";
427             $mail->SMTPDebug = true;
428         }
429         $mail->Host = $CFG->smtphosts;               // specify main and backup servers
430
431         if ($CFG->smtpuser) {                          // Use SMTP authentication
432             $mail->SMTPAuth = true;
433             $mail->Username = $CFG->smtpuser;
434             $mail->Password = $CFG->smtppass;
435         }
436     }
437
438     /* not here yet, leave it in just in case.
439     // make up an email address for handling bounces
440     if (!empty($CFG->handlebounces)) {
441         $modargs = 'B'.base64_encode(pack('V',$user->ident)).substr(md5($user->email),0,16);
442         $mail->Sender = generate_email_processing_address(0,$modargs);
443     }
444     else {
445         $mail->Sender   =  $CFG->sysadminemail;
446     }
447     */
448     $mail->Sender = $CFG->sysadminemail; // for elgg. delete if we change the above.
449
450     // TODO add a preference for maildisplay
451     if (is_string($from)) { // So we can pass whatever we want if there is need
452         $mail->From     = $CFG->noreplyaddress;
453         $mail->FromName = $from;
454     } else if (empty($from)) { // make stuff up
455         $mail->From     = $CFG->sysadminemail;
456         $mail->FromName = $CFG->sitename.' '.gettext('Administrator');
457     } else if ($usetrueaddress and !empty($from->maildisplay)) {
458         $mail->From     = $from->email;
459         $mail->FromName = $from->name;
460     } else {
461         $mail->From     = $CFG->noreplyaddress;
462         $mail->FromName = $from->name;
463         if (empty($replyto)) {
464             $mail->AddReplyTo($CFG->noreplyaddress,gettext('Do not reply'));
465         }
466     }
467
468     if (!empty($replyto)) {
469         $mail->AddReplyTo($replyto,$replytoname);
470     }
471
472     $mail->Subject = $textlib->substr(stripslashes($subject), 0, 900);
473
474     $mail->AddAddress($user->email, $user->name);
475
476     $mail->WordWrap = 79;                               // set word wrap
477
478     if (!empty($from->customheaders)) {                 // Add custom headers
479         if (is_array($from->customheaders)) {
480             foreach ($from->customheaders as $customheader) {
481                 $mail->AddCustomHeader($customheader);
482             }
483         } else {
484             $mail->AddCustomHeader($from->customheaders);
485         }
486     }
487
488     if (!empty($from->priority)) {
489         $mail->Priority = $from->priority;
490     }
491
492     //TODO add a user preference for this. right now just send plaintext
493     $user->mailformat = 0;
494     if ($messagehtml && $user->mailformat == 1) { // Don't ever send HTML to users who don't want it
495         $mail->IsHTML(true);
496         $mail->Encoding = 'quoted-printable';           // Encoding to use
497         $mail->Body    $messagehtml;
498         $mail->AltBody "\n$messagetext\n";
499     } else {
500         $mail->IsHTML(false);
501         $mail->Body "\n$messagetext\n";
502     }
503
504     if ($attachment && $attachname) {
505         if (ereg( "\\.\\." ,$attachment )) {    // Security check for ".." in dir path
506             $mail->AddAddress($CFG->sysadminemail,$CFG->sitename.' '.gettext('Administrator'));
507             $mail->AddStringAttachment('Error in attachment.  User attempted to attach a filename with a unsafe name.', 'error.txt', '8bit', 'text/plain');
508         } else {
509             require_once($CFG->libdir.'/filelib.php');
510             $mimetype = mimeinfo('type', $attachname);
511             $mail->AddAttachment($attachment, $attachname, 'base64', $mimetype);
512         }
513     }
514
515     if ($mail->Send()) {
516         //        set_send_count($user); // later
517         return true;
518     } else {
519         mtrace('ERROR: '. $mail->ErrorInfo);
520         return false;
521     }
522 }
523
524 /**
525  * Returns an array with all the filenames in
526  * all subdirectories, relative to the given rootdir.
527  * If excludefile is defined, then that file/directory is ignored
528  * If getdirs is true, then (sub)directories are included in the output
529  * If getfiles is true, then files are included in the output
530  * (at least one of these must be true!)
531  *
532  * @param string $rootdir  ?
533  * @param string $excludefile  If defined then the specified file/directory is ignored
534  * @param bool $descend  ?
535  * @param bool $getdirs  If true then (sub)directories are included in the output
536  * @param bool $getfiles  If true then files are included in the output
537  * @return array An array with all the filenames in
538  * all subdirectories, relative to the given rootdir
539  * @todo Finish documenting this function. Add examples of $excludefile usage.
540  */
541 function get_directory_list($rootdir, $excludefile='', $descend=true, $getdirs=false, $getfiles=true) {
542
543     $dirs = array();
544
545     if (!$getdirs and !$getfiles) {   // Nothing to show
546         return $dirs;
547     }
548
549     if (!is_dir($rootdir)) {          // Must be a directory
550         return $dirs;
551     }
552
553     if (!$dir = opendir($rootdir)) {