PageRenderTime 82ms CodeModel.GetById 47ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 1ms

/sloodle/lib/general.php

http://sloodle.googlecode.com/
PHP | 1123 lines | 560 code | 127 blank | 436 comment | 184 complexity | 288458d456f3001d17449c45e161f439 MD5 | raw file
   1<?php
   2    
   3    /**
   4    * Sloodle general library.
   5    *
   6    * Provides various utility functionality for general Sloodle purposes.
   7    *
   8    * @package sloodle
   9    * @copyright Copyright (c) 2007-8 Sloodle (various contributors)
  10    * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
  11    *
  12    * @contributor Edmund Edgar
  13    * @contributor Peter R. Bloomfield
  14    *
  15    */
  16    
  17    // This library expects that the Sloodle config file has already been included
  18    //  (along with the Moodle libraries)
  19    
  20    /** Include our email functionality. */
  21    require_once(SLOODLE_LIBROOT.'/mail.php');
  22
  23
  24    /**
  25    * Force the user to login, but reject guest logins.
  26    * This function exists to workaround some Moodle 1.8 bugs.
  27    * @return void
  28    */
  29    function sloodle_require_login_no_guest()
  30    {
  31        global $CFG, $SESSION, $FULLME;
  32        // Attempt a direct login initially
  33        require_login(0, false);
  34        // Has the user been logged-in as a guest?
  35        if (isguestuser()) {
  36            // Make sure we can come back here after login
  37            $SESSION->wantsurl = $FULLME;
  38            // Redirect to the appropriate login page
  39            if (empty($CFG->loginhttps)) {
  40                redirect($CFG->wwwroot .'/login/index.php');
  41            } else {
  42                $wwwroot = str_replace('http:','https:', $CFG->wwwroot);
  43                redirect($wwwroot .'/login/index.php');
  44            }
  45            exit();
  46        }
  47    }    
  48    
  49    /**
  50    * Sets a Sloodle configuration value.
  51    * This data will be stored in Moodle's "config" table, so it will persist even after Sloodle is uninstalled.
  52    * After being set, it will be available (read-only) as a named member of Moodle's $CFG variable.
  53    * <b>NOTE:</b> in Sloodle debug mode, this function will terminate the script with an error if the name is not prefixed with "sloodle_".
  54    * @param string $name The name of the value to be stored (should be prefixed with "sloodle_")
  55    * @param string $value The string representation of the value to be stored
  56    * @return bool True on success, or false on failure (may fail if database query encountered an error)
  57    * @see sloodle_get_config()
  58    */
  59    function sloodle_set_config($name, $value)
  60    {
  61        // If in debug mode, ensure the name is prefixed appropriately for Sloodle
  62        if (defined('SLOODLE_DEBUG') && SLOODLE_DEBUG) {
  63            if (substr_count($name, 'sloodle_') < 1) {
  64                exit ("ERROR: sloodle_set_config(..) called with invalid value name \"$name\". Expected \"sloodle_\" prefix.");
  65            }
  66        }
  67        // Use the standard Moodle config function, ignoring the 3rd parameter ("plugin", which defaults to NULL)
  68        return set_config(strtolower($name), $value);
  69	}
  70
  71    /**
  72    * Gets a Sloodle configuration value from Moodle's "config" table.
  73    * This function does not necessarily need to be used.
  74    * All configuration data is available as named members of Moodle's $CFG global variable.
  75    * <b>NOTE:</b> in Sloodle debug mode, this function will terminate the script with an error if the name is not prefixed with "sloodle_".
  76    * @param string $name The name of the value to be stored (should be prefixed with "sloodle_")
  77    * @return mixed A string containing the configuration value, or false if the query failed (e.g. if the named value didn't exist)
  78    * @see sloodle_set_config()
  79    */
  80	function sloodle_get_config($name)
  81    {
  82        // If in debug mode, ensure the name is prefixed appropriately for Sloodle
  83        if (defined('SLOODLE_DEBUG') && SLOODLE_DEBUG) {
  84            if (substr_count($name, 'sloodle_') < 1) {
  85                exit ("ERROR: sloodle_get_config(..) called with invalid value name \"$name\". Expected \"sloodle_\" prefix.");
  86            }
  87        }
  88        // Use the Moodle config function, ignoring the plugin parameter
  89        $val = get_config(NULL, strtolower($name));
  90        // Older Moodle versions return a database record object instead of the value itself
  91        // Workaround:
  92        if (is_object($val)) return $val->value;
  93        return $val;
  94	}
  95    
  96    /**
  97    * Determines whether or not auto-registration is allowed for the site.
  98    * @return bool True if auto-reg is allowed on the site, or false otherwise.
  99    */
 100    function sloodle_autoreg_enabled_site()
 101    {
 102        return (bool)sloodle_get_config('sloodle_allow_autoreg');
 103    }
 104    
 105    /**
 106    * Determines whether or not auto-enrolment is allowed for the site.
 107    * @return bool True if auto-enrolment is allowed on the site, or false otherwise.
 108    */
 109    function sloodle_autoenrol_enabled_site()
 110    {
 111        return (bool)sloodle_get_config('sloodle_allow_autoenrol');
 112    }
 113
 114    /**
 115    * Sends an XMLRPC message into Second Life.
 116    * @param string $channel A string containing a UUID identifying the XMLRPC channel in SL to be used
 117    * @param int $intval An integer value to be sent in the message
 118    * @param string $strval A string value to be sent in the message
 119    * @return bool True if successful, or false if an error occurs
 120    */
 121    function sloodle_send_xmlrpc_message($channel,$intval,$strval)
 122    {
 123        // Include our XMLRPC library
 124        require_once(SLOODLE_DIRROOT.'/lib/xmlrpc.inc');
 125        // Instantiate a new client object for communicating with Second Life
 126        $client = new xmlrpc_client("http://xmlrpc.secondlife.com/cgi-bin/xmlrpc.cgi");
 127        // Construct the content of the RPC
 128        $content = '<?xml version="1.0"?><methodCall><methodName>llRemoteData</methodName><params><param><value><struct><member><name>Channel</name><value><string>'.$channel.'</string></value></member><member><name>IntValue</name><value><int>'.$intval.'</int></value></member><member><name>StringValue</name><value><string>'.$strval.'</string></value></member></struct></value></param></params></methodCall>';
 129        
 130        // Attempt to send the data via http
 131        $response = $client->send(
 132            $content,
 133            60,
 134            'http'
 135        );
 136        
 137        //var_dump($response); // Debug output
 138        // Make sure we got a response value
 139        if (!isset($response->val) || empty($response->val) || is_null($response->val)) {
 140            // Report an error if we are in debug mode
 141            if (defined('SLOODLE_DEBUG') && SLOODLE_DEBUG) {
 142                print '<p align="left">Not getting the expected XMLRPC response. Is Second Life broken again?<br/>';
 143                if (isset($response->errstr)) print "XMLRPC Error - ".$response->errstr;
 144                print '</p>';
 145            }
 146            return FALSE;
 147        }
 148        
 149        // Check the contents of the response value
 150        //if (defined('SLOODLE_DEBUG') && SLOODLE_DEBUG) {
 151        //    print_r($response->val);
 152        //}
 153        
 154        //TODO: Check the details of the response to see if this was successful or not...
 155        return TRUE;
 156    
 157    }
 158
 159    /**
 160    * Old logging function
 161    * @todo <b>May require update?</b>
 162    */
 163    function sloodle_add_to_log($courseid = null, $module = null, $action = null, $url = null, $cmid = null, $info = null)
 164    {
 165
 166       global $CFG;
 167
 168       // TODO: Make sure we set this in the calling function, then remove this bit
 169       if ($courseid == null) {
 170          $courseid = optional_param('sloodle_courseid',0,PARAM_RAW);
 171       }
 172
 173       // if no action is specified, use the object name
 174       if ($action == null) {
 175          $action = $_SERVER['X-SecondLife-Object-Name'];
 176       }
 177
 178       $region = $_SERVER['X-SecondLife-Region'];
 179       if ($info == null) {
 180          $info = $region;
 181       }
 182
 183       $slurl = '';
 184       if (preg_match('/^(.*)\(.*?\)$/',$region,$matches)) { // strip the coordinates, eg. Cicero (123,123)
 185          $region = $matches[1];
 186       }
 187
 188       $xyz = $_SERVER['X-SecondLife-Local-Position'];
 189       if (preg_match('/^\((.*?),(.*?),(.*?)\)$/',$xyz,$matches)) {
 190          $xyz = $matches[1].'/'.$matches[2].'/'.$matches[3];
 191       }
 192
 193       return add_to_log($courseid, null, $action, $CFG->wwwroot.'/mod/sloodle/toslurl.php?region='.urlencode($region).'&xyz='.$xyz, $userid, $info );
 194       //return add_to_log($courseid, null, "ok", "ok", $userid, "ok");
 195
 196    }
 197
 198    /**
 199    * Determines whether or not Sloodle is installed.
 200    * Queries Moodle's modules table for a Sloodle entry.
 201    * <b>NOTE:</b> does not check for the presence of the Sloodle files.
 202    * @return bool True if Sloodle is installed, or false otherwise.
 203    */
 204    function sloodle_is_installed()
 205    {
 206        // Is there a Sloodle entry in the modules table?
 207        return record_exists('modules', 'name', 'sloodle');
 208    }
 209    
 210    /**
 211    * Generates a random login security token.
 212    * Uses mixed-case letters and numbers to generate a random 16-character string.
 213    * @return string
 214    * @see sloodle_random_web_password()
 215    */
 216    function sloodle_random_security_token()
 217    {
 218        // Define the characters we can use in our token, and get the length of it
 219        $str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 220        $strlen = strlen($str) - 1;
 221        // Prepare the token variable
 222        $token = '';
 223        // Loop once for each output character
 224        for($length = 0; $length < 16; $length++) {
 225            // Shuffle the string, then pick and store a random character
 226            $str = str_shuffle($str);
 227            $char = mt_rand(0, $strlen);
 228            $token .= $str[$char];
 229        }
 230        
 231        return $token;
 232    }
 233    
 234    /**
 235    * Generates a random web password
 236    * Uses mixed-case letters and numbers to generate a random 8-character string.
 237    * @return string
 238    * @see sloodle_random_security_token()
 239    */
 240    function sloodle_random_web_password()
 241    {
 242        // Define the characters we can use in our token, and get the length of it
 243        $str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 244        $strlen = strlen($str) - 1;
 245        // Prepare the password string
 246        $pwd = '';
 247        // Loop once for each output character
 248        for($length = 0; $length < 8; $length++) {
 249            // Shuffle the string, then pick and store a random character
 250            $str = str_shuffle($str);
 251            $char = mt_rand(0, $strlen);
 252            $pwd .= $str[$char];
 253        }
 254        
 255        return $pwd;
 256    }
 257    
 258    /**
 259    * Generates a random prim password (7 to 9 digit number).
 260    * @return string The password as a string
 261    */
 262    function sloodle_random_prim_password()
 263    {
 264        return (string)mt_rand(1000000, 999999999);
 265    }
 266    
 267    /**
 268    * Converts a string vector to an array vector.
 269    * String vector should be of format "<x,y,z>".
 270    * Converts to associative array with members 'x', 'y', and 'z'.
 271    * Returns false if input parameter was not of correct format.
 272    * @param string $vector A string vector of format "<x,y,z>".
 273    * @return mixed
 274    * @see sloodle_array_to_vector()
 275    * @see sloodle_round_vector()
 276    */
 277    function sloodle_vector_to_array($vector)
 278    {
 279        if (preg_match('/<(.*?),(.*?),(.*?)>/',$vector,$vectorbits)) {
 280            $arr = array();
 281            $arr['x'] = $vectorbits[1];
 282            $arr['y'] = $vectorbits[2];
 283            $arr['z'] = $vectorbits[3];
 284            return $arr;
 285        }
 286        return false;
 287    }
 288    
 289    /**
 290    * Converts an array vector to a string vector.
 291    * Array vector should be associative, containing elements 'x', 'y', and 'z'.
 292    * Converts to a string vector of format "<x,y,z>".
 293    * @return string
 294    * @see sloodle_vector_to_array()
 295    * @see sloodle_round_vector()
 296    */
 297    function sloodle_array_to_vector($arr)
 298    {
 299        $ret = '<'.$arr['x'].','.$arr['y'].','.$arr['z'].'>';
 300        return $ret;
 301    }
 302    
 303    /**
 304    * Obtains the identified course module instance database record.
 305    * @param int $id The integer ID of a course module instance
 306    * @return mixed  A database record if successful, or false if it could not be found
 307    */
 308    function sloodle_get_course_module_instance($id)
 309    {
 310        return get_record('course_modules', 'id', $id);
 311    }
 312    
 313    /**
 314    * Determines whether or not the specified course module instance is visible.
 315    * Checks that the instance itself and the course section are both valid.
 316    * @param int $id The integer ID of a course module instance.
 317    * @return bool True if visible, or false if invisible or not found
 318    */
 319    function sloodle_is_course_module_instance_visible($id)
 320    {
 321        // Get the course module instance record, whether directly from the parameter, or from the database
 322        if (is_object($id)) {
 323            $course_module_instance = $id;
 324        } else if (is_int($id)) {
 325            if (!($course_module_instance = get_record('course_modules', 'id', $id))) return FALSE;
 326        } else return FALSE;
 327        
 328        // Make sure the instance itself is visible
 329        if ((int)$course_module_instance->visible == 0) return FALSE;
 330        // Find out which section it is in, and if that section is valid
 331        if (!($section = get_record('course_sections', 'id', $course_module_instance->section))) return FALSE;
 332        if ((int)$section->visible == 0) return FALSE;
 333        
 334        // Looks like the module is visible
 335        return TRUE;
 336    }
 337    
 338    /**
 339    * Determines if the specified course module instance is of the named type.
 340    * For example, this can check if a particular instance is a "forum" or a "chat".
 341    * @param int $id The integer ID of a course module instance
 342    * @param string $module_name Module type to check (must be the exact name of an installed module, e.g. 'sloodle' or 'quiz')
 343    * @return bool True if the module is of the specified type, or false otherwise
 344    */
 345    function sloodle_check_course_module_instance_type($id, $module_name)
 346    {
 347        // Get the record for the module type
 348        if (!($module_record = get_record('modules', 'name', $module_name))) return FALSE;
 349
 350        // Get the course module instance record, whether directly from the parameter, or from the database
 351        if (is_object($id)) {
 352            $course_module_instance = $id;
 353        } else if (is_int($id)) {
 354            if (!($course_module_instance = get_record('course_modules', 'id', $id))) return FALSE;
 355        } else return FALSE;
 356        
 357        // Check the type of the instance
 358        return ($course_module_instance->module == $module_record->id);
 359    }
 360    
 361    /**
 362    * Obtains the ID number of the specified module (type not instance).
 363    * @param string $name The name of the module type to check, e.g. 'sloodle' or 'forum'
 364    * @return mixed Integer containing module ID, or false if it is not installed
 365    */
 366    function sloodle_get_module_id($name)
 367    {
 368        // Ensure the name is a non-empty string
 369        if (!is_string($name) || empty($name)) return FALSE;
 370        // Obtain the module record
 371        if (!($module_record = get_record('modules', 'name', $module_name))) return FALSE;
 372        
 373        return $module_record->id;
 374    }
 375    
 376    /**
 377    * Checks if the specified position is in the current (site-wide) loginzone.
 378    * @param mixed $pos A string vector or an associated array vector
 379    * @return bool True if position is in LoginZone, or false if not
 380    * @see sloodle_login_zone_coordinates()
 381    * @todo Update or remove... no longer valid
 382    */
 383    function sloodle_position_is_in_login_zone($pos)
 384    {
 385        // Get a position array from the parameter
 386        $posarr = NULL;
 387        if (is_array($pos) && count($pos) == 3) {
 388            $posarr = $pos;
 389        } else if (is_string($pos)) {
 390            $posarr = sloodle_vector_to_array($pos);
 391        } else {
 392            return FALSE;
 393        }
 394        // Fetch the loginzone boundaries
 395        list($maxarr,$minarr) = sloodle_login_zone_coordinates();
 396
 397        // Make sure the position is not past the maximum bounds
 398        if ( ($posarr['x'] > $maxarr['x']) || ($posarr['y'] > $maxarr['y']) || ($posarr['z'] > $maxarr['z']) ) {
 399            return FALSE;
 400        }
 401        // Make sure the position is not past the minimum bounds
 402        if ( ($posarr['x'] < $minarr['x']) || ($posarr['y'] < $minarr['y']) || ($posarr['z'] < $minarr['z']) ) {
 403            return FALSE;
 404        }
 405
 406        return TRUE;
 407    }
 408    
 409    /**
 410    * Generates teleport coordinates for a user who has already finished the LoginZone process.
 411    * @param string $pos A string vector giving the position of the LoginZone
 412    * @param string $size A string vector giving the size of the LoginZone
 413    * @return array, bool An associative array vector containing a teleport location, or false if the operation fails.
 414    */
 415    function sloodle_finished_login_coordinates($pos, $size)
 416    {
 417        // Make sure the parameters are valid types
 418        if (!is_string($pos) || !is_string($size)) {
 419            return FALSE;
 420        }
 421        // Convert both to arrays
 422        $posarr = sloodle_vector_to_array($pos);
 423        $sizearr = sloodle_vector_to_array($size);
 424        // Calculate a position just below the loginzone
 425        $coord = array();
 426        $coord['x'] = round($posarr['x'],0);
 427        $coord['y'] = round($posarr['y'],0);
 428        $coord['z'] = round(($posarr['z']-(($sizearr['z'])/2)-2),0);
 429        return $coord;
 430    }
 431    
 432    /**
 433    * Generates a random position within a cuboid zone of the specified size.
 434    * (Note: leaves a 2 metre margin round the outside)
 435    * @param array $size Associative array giving the size of the zone
 436    * @return array An associative vector array
 437    */
 438    function sloodle_random_position_in_zone($size)
 439    {
 440        // Construct the half-size array
 441        $halfsize = array('x'=>($size['x'] / 2.0) - 2.0, 'y'=>($size['y'] / 2.0) - 2.0, 'z'=>($size['z'] / 2.0) - 2.0);
 442    
 443        $pos = array();
 444        $pos['x'] = mt_rand(0.0, $size['x'] - 4.0) - $halfsize['x'];
 445        $pos['y'] = mt_rand(0.0, $size['y'] - 4.0) - $halfsize['y'];
 446        $pos['z'] = mt_rand(0.0, $size['z'] - 4.0) - $halfsize['z'];
 447        return $pos;
 448    }
 449
 450    // Round the specified 3d vector to integer values
 451    // $pos should be a vector string "<x,y,z>" or an associative array {x,y,z}
 452    // Return is the same as the type passed-in
 453    // If the input type is unrecognised, it simply returns it back out unchanged
 454    /**
 455    * Rounds the specified 3d vector integer values.
 456    * Can handle/return a string vector, or an array vector.
 457    * (Output type matches input type).
 458    * @param mixed $pos Either a string vector or an array vector
 459    * @return mixed
 460    */
 461    function sloodle_round_vector($pos)
 462    {
 463        // We will work with an array, but allow for conversion to/from string
 464        $arrayvec = $pos;
 465        $returnstring = FALSE;
 466        // Is it a string?
 467        if (is_string($pos)) {
 468            $arrayvec = sloodle_vector_to_array($pos);
 469            $returnstring = TRUE;
 470        } else if (!is_array($pos)) {
 471            return $pos;
 472        }
 473    
 474        // Construct an output array
 475        $output = array();
 476        foreach ($arrayvec as $key => $val) {
 477            $output[$key] = round($val, 0);
 478        }
 479        
 480        // If we need to convert it back to a string, then do so
 481        if ($returnstring) {
 482            return sloodle_array_to_vector($output);
 483        }
 484        
 485        return $output;
 486    }
 487    
 488    /**
 489    * Calculates the maximum and minimum bounds of the specified LoginZone
 490    * Returns the bounds as a numeric array of two associate array vectors: ($max, $min).
 491    * (Or returns false if no LoginZone position/size could be found in the Moodle configuration table).
 492    * @param string $pos A string vector giving the position of the LoginZone
 493    * @param string $size A string vector giving the size of the LoginZone
 494    * @return array
 495    */
 496    function sloodle_login_zone_bounds($pos, $size)
 497    {
 498        // Make sure the parameters are valid types
 499        if (($pos == FALSE) || ($size == FALSE)) {
 500            return FALSE;
 501        }
 502        // Convert both to arrays
 503        $posarr = sloodle_vector_to_array($pos);
 504        $sizearr = sloodle_vector_to_array($size);
 505        // Calculate the bounds
 506        $max = array();
 507        $max['x'] = $posarr['x']+(($sizearr['x'])/2)-2;
 508        $max['y'] = $posarr['y']+(($sizearr['y'])/2)-2;
 509        $max['z'] = $posarr['z']+(($sizearr['z'])/2)-2;
 510        $min = array();
 511        $min['x'] = $posarr['x']-(($sizearr['x'])/2)+2;
 512        $min['y'] = $posarr['y']-(($sizearr['y'])/2)+2;
 513        $min['z'] = $posarr['z']-(($sizearr['z'])/2)+2;
 514        
 515        return array($max,$min);
 516    }
 517    
 518    
 519    /**
 520    * Checks if the given prim password is valid.
 521    * @param string $password The password string to check
 522    * @return bool True if it is valid, or false otherwise.
 523    */
 524    function sloodle_validate_prim_password($password)
 525    {
 526        // Check that it's a string
 527        if (!is_string($password)) return false;
 528        // Check the length
 529        $len = strlen($password);
 530        if ($len < 5 || $len > 9) return false;
 531        // Check that it's all numbers
 532        if (!ctype_digit($password)) return false;
 533        // Check that it doesn't start with a 0
 534        if ($password[0] == '0') return false;
 535        
 536        // It all seems fine
 537        return true;
 538    }
 539    
 540    /**
 541    * Checks if the given prim password is valid, and provides feedback.
 542    * An array is written to by reference, each element containing error codes.
 543    * Each error code is a word. The full text of the error message may be obtained
 544    *  from the string file by looking for "primpass:errorcode".
 545    *
 546    * @param string $password The password to validate
 547    * @param array &$errors An array (passed by reference) which will contain any error messages
 548    * @return bool True if the prim password is valid, or false otherwise
 549    */
 550    function sloodle_validate_prim_password_verbose($password, &$errors)
 551    {
 552        // Initialise variables
 553        $errors = array();
 554        $result = true;
 555        
 556        // Check that it's a string
 557        if (!is_string($password)) {
 558            $errors[] = 'invalidtype';
 559            $result = false;
 560        }
 561        // Check the length
 562        $len = strlen($password);
 563        if ($len < 5) {
 564            $errors[] = 'tooshort';
 565            $result = false;
 566        }
 567        if ($len > 9) {
 568            $errors[] = 'toolong';
 569            $result = false;
 570        }
 571        
 572        // Check that it's all numbers
 573        if (!ctype_digit($password)) {
 574            $errors[] = 'numonly';
 575            $result = false;
 576        }
 577        
 578        // Check that it doesn't start with a 0
 579        if ($password[0] == '0') {
 580            $errors[] = 'leadingzero';
 581            $result = false;
 582        }
 583        
 584        return $result;
 585    }
 586    
 587    
 588    /**
 589    * Stores a pending login notification for an auto-registered user.
 590    * A cron job will process the pending notification queue.
 591    * @param string $destination Identifies the destination of the notification (for SL, this will be the object UUID. The send function will construct the email address)
 592    * @param string $avatar Identifier for the avatar being notified
 593    * @param string $username The username to notify the user of
 594    * @param string $password The (plaintext) password to notify the user of
 595    * @return bool True if successful, or false otherwise
 596    */
 597    function sloodle_login_notification($destination, $avatar, $username, $password)
 598    {
 599        // If another pending notification already exists for the same username, then delete it
 600        delete_records('sloodle_login_notifications', 'username', $username);
 601        
 602        // Add the new details
 603        $notification = new stdClass();
 604        $notification->destination = $destination;
 605        $notification->avatar = $avatar;
 606        $notification->username = $username;
 607        $notification->password = $password;
 608
 609        return (bool)insert_record('sloodle_login_notifications', $notification);
 610    }
 611    
 612    /**
 613    * Send a login notification.
 614    * @param string $destination Identifies the destination of the notification (for SL, this will be the object UUID. The target email address will be constructed)
 615    * @param string $avatar Identifier for the avatar being notified
 616    * @param string $username The username to notify the user of
 617    * @param string $password The (plaintext) password to notify the user of
 618    * @return bool True if successful, or false otherwise
 619    */
 620    function sloodle_send_login_notification($destination, $avatar, $username, $password)
 621    {
 622        global $CFG;
 623        return sloodle_text_email_sl($destination, 'SLOODLE_LOGIN', "$avatar|{$CFG->wwwroot}|$username|$password");
 624    }
 625    
 626    /**
 627    * Processes pending login notifications, up to a certain limit.
 628    * Retrieves the requests one-at-a-time for processing.
 629    * This is slower, but ensures minimal damage if the process is terminated, e.g. due to server timeout.
 630    * @param int $limit The maximum number of pending requests to process.
 631    * @return void
 632    */
 633    function sloodle_process_login_notifications($limit = 25)
 634    {
 635        global $CFG;
 636        
 637        // Validate the limit
 638        $limit = (int)$limit;
 639        if ($limit < 1) return;
 640        
 641        // Go through each one
 642        for ($i = 0; $i < $limit; $i++) {
 643            // Obtain the first record
 644            $recs = get_records('sloodle_login_notifications', '', '', 'id', '*', 0, $limit);
 645            if (!$recs) return false;
 646            reset($recs);
 647            $rec = current($recs);
 648            
 649            // Determine the user ID of the person who requested this
 650            $userid = 0;
 651            if (!($sloodleuser = get_record('sloodle_users', 'uuid', $rec->avatar))) {
 652                // Failed to the user - get the guest user instead
 653                $guestdata = guest_user();
 654                $userid = $guestdata->id;
 655            } else {
 656                // Got the data - store the user ID
 657                $userid = $sloodleuser->userid;
 658            }
 659            
 660            // Send the notification
 661            if (sloodle_send_login_notification($rec->destination, $rec->avatar, $rec->username, $rec->password)) {
 662                // Log the notification
 663                 add_to_log(SITEID, 'sloodle', 'view', '', 'Sent login details by email to avatar in-world', 0, $userid);
 664            } else {
 665                // Log the failed notification (but don't keep trying the same one)
 666                add_to_log(SITEID, 'sloodle', 'view failed', '', 'Failed to send login details by email to avatar in-world', 0, $userid);
 667            }
 668            
 669            // Delete the record from the data
 670            delete_records('sloodle_login_notifications', 'id', $rec->id);
 671        }
 672    }
 673    
 674    
 675    /**
 676    * Extracts a value from a name-value associative array if it is set.
 677    * (The array should associate name to value).
 678    * @param array $settings The array of names and values
 679    * @param string $name The name of the value to retrieve
 680    * @param mixed $default The default value to return if the specified value was not found
 681    * @return mixed The value from the input array, or the $default parameter
 682    */
 683    function sloodle_get_value($settings, $name, $default = null)
 684    {
 685        if (is_array($settings) && isset($settings[$name])) return $settings[$name];
 686        return $default;
 687    }
 688    
 689    
 690    /**
 691    * Outputs the standard form elements for access levels in object configuration.
 692    * Each part can be optionally hidden, and default values can be provided.
 693    * (Note: the server access level must be communicated from the object back to Moodle... rubbish implementation, but it works!)
 694    * @param array $current_config An associative array of setting names to values, containing defaults. (Ignored if null).
 695    * @param bool $show_use_object Determines whether or not the "Use Object" setting is shown
 696    * @param bool $show_control_object Determines whether or not the "Control Object" setting is shown
 697    * @param bool $show_server Determines whether or not the server access setting is shown
 698    * @return void
 699    */
 700    function sloodle_print_access_level_options($current_config, $show_use_object = true, $show_control_object = true, $show_server = true)
 701    {
 702        // Quick-escape: if everything is being suppressed, then do nothing
 703        if (!($show_use_object || $show_control_object || $show_server)) return;
 704        
 705        // Fetch default values from the configuration, if possible
 706        $sloodleobjectaccessleveluse = sloodle_get_value($current_config, 'sloodleobjectaccessleveluse', SLOODLE_OBJECT_ACCESS_LEVEL_PUBLIC);
 707        $sloodleobjectaccesslevelctrl = sloodle_get_value($current_config, 'sloodleobjectaccesslevelctrl', SLOODLE_OBJECT_ACCESS_LEVEL_OWNER);
 708        $sloodleserveraccesslevel = sloodle_get_value($current_config, 'sloodleserveraccesslevel', SLOODLE_SERVER_ACCESS_LEVEL_PUBLIC);
 709        
 710        // Define our object access level array
 711        $object_access_levels = array(  SLOODLE_OBJECT_ACCESS_LEVEL_PUBLIC => get_string('accesslevel:public','sloodle'),
 712                                        SLOODLE_OBJECT_ACCESS_LEVEL_GROUP => get_string('accesslevel:group','sloodle'),
 713                                        SLOODLE_OBJECT_ACCESS_LEVEL_OWNER => get_string('accesslevel:owner','sloodle') );
 714        // Define our server access level array
 715        $server_access_levels = array(  SLOODLE_SERVER_ACCESS_LEVEL_PUBLIC => get_string('accesslevel:public','sloodle'),
 716                                        SLOODLE_SERVER_ACCESS_LEVEL_COURSE => get_string('accesslevel:course','sloodle'),
 717                                        SLOODLE_SERVER_ACCESS_LEVEL_SITE => get_string('accesslevel:site','sloodle'),
 718                                        SLOODLE_SERVER_ACCESS_LEVEL_STAFF => get_string('accesslevel:staff','sloodle') );
 719    
 720        // Display box and a heading
 721        print_box_start('generalbox boxaligncenter');
 722        echo '<h3>'.get_string('accesslevel','sloodle').'</h3>';
 723    
 724        // Print the object settings
 725        if ($show_use_object || $show_control_object) {
 726            
 727            // Object access
 728            echo '<b>'.get_string('accesslevelobject','sloodle').'</b><br><i>'.get_string('accesslevelobject:desc','sloodle').'</i><br><br>';
 729            // Use object
 730            if ($show_use_object) {
 731                echo get_string('accesslevelobject:use','sloodle').': ';
 732                choose_from_menu($object_access_levels, 'sloodleobjectaccessleveluse', $sloodleobjectaccessleveluse, '');
 733                echo '<br><br>';
 734            }
 735            // Control object
 736            if ($show_control_object) {
 737                echo get_string('accesslevelobject:control','sloodle').': ';
 738                choose_from_menu($object_access_levels, 'sloodleobjectaccesslevelctrl', $sloodleobjectaccesslevelctrl, '');
 739                echo '<br><br>';
 740            }
 741        }
 742        
 743        // Print the server settings
 744        if ($show_server) {
 745            // Server access
 746            echo '<b>'.get_string('accesslevelserver','sloodle').'</b><br><i>'.get_string('accesslevelserver:desc','sloodle').'</i><br><br>';
 747            echo get_string('accesslevel','sloodle').': ';
 748            choose_from_menu($server_access_levels, 'sloodleserveraccesslevel', $sloodleserveraccesslevel, '');
 749            echo '<br>';
 750        }        
 751        
 752        print_box_end();
 753    }
 754
 755    function sloodle_access_level_option_choice($option, $current_config, $show, $prefix = '', $suffix = '') {
 756
 757	$access_levels = array();
 758        if ($option == 'sloodleserveraccesslevel') {
 759            $access_levels = array( SLOODLE_SERVER_ACCESS_LEVEL_PUBLIC => get_string('accesslevel:public','sloodle'),
 760                                    SLOODLE_SERVER_ACCESS_LEVEL_COURSE => get_string('accesslevel:course','sloodle'),
 761                                    SLOODLE_SERVER_ACCESS_LEVEL_SITE => get_string('accesslevel:site','sloodle'),
 762                                    SLOODLE_SERVER_ACCESS_LEVEL_STAFF => get_string('accesslevel:staff','sloodle') 
 763                                  );
 764        } else {
 765            $access_levels = array( SLOODLE_OBJECT_ACCESS_LEVEL_PUBLIC => get_string('accesslevel:public','sloodle'),
 766                                    SLOODLE_OBJECT_ACCESS_LEVEL_GROUP => get_string('accesslevel:group','sloodle'),
 767                                    SLOODLE_OBJECT_ACCESS_LEVEL_OWNER => get_string('accesslevel:owner','sloodle') 
 768                                  );
 769        }
 770
 771        $defaults = array(
 772            'sloodleobjectaccessleveluse' => SLOODLE_OBJECT_ACCESS_LEVEL_PUBLIC,
 773            'sloodleobjectaccesslevelctrl' => SLOODLE_OBJECT_ACCESS_LEVEL_OWNER,
 774            'sloodleserveraccesslevel' => SLOODLE_SERVER_ACCESS_LEVEL_PUBLIC
 775        );
 776
 777        // Fetch default values from the configuration, if possible
 778        $selected_value = sloodle_get_value($current_config, $option, $defaults[$option]);
 779        
 780        if ($show) {
 781            return choose_from_menu($access_levels, $prefix.$option.$suffix, $selected_value, '', '', 0, $return = true);
 782        } else {
 783            return '&nbsp;';
 784        } 
 785        
 786    }
 787
 788
 789    /**
 790    * Returns a very approximate natural language description of a period of time (in minutes, hours, days, or weeks).
 791    * Can also be used to describe how long ago something happened, in which case anything less than 1 minute is treated as 'now'.
 792    * @param int $secs Number of seconds in period of time
 793    * @param bool $ago If true (not default), then the time will be described in past tense, e.g. "3 days ago", as opposed to simply "3 days".
 794    * @return string
 795    */
 796    function sloodle_describe_approx_time($secs, $ago = false)
 797    {
 798        // Make sure the time is a positive integer
 799        $secs = (int)$secs;
 800        if ($secs < 0) $secs *= -1;
 801        
 802        // Less than a minute
 803        if ($secs < 60) {
 804            // If we are describing a past time, then approximate to 'now'
 805            if ($ago) return ucwords(get_string('now', 'sloodle'));
 806            // Give the number of seconds
 807            if ($secs == 1) return '1 '. get_string('second', 'sloodle');
 808            return $secs.' '. get_string('seconds', 'sloodle');
 809        }
 810        
 811        // This variable will hold the time description
 812        $desc = '';
 813        
 814        // Roughly 1 minute
 815        if ($secs < 120) $desc = '1 '. get_string('minute', 'sloodle');
 816        // Several minutes (up to 1 hour)
 817        else if ($secs < 3600) $desc = ((string)(int)($secs / 60)).' '. get_string('minutes', 'sloodle');
 818        // Roughly 1 hour
 819        else if ($secs < 7200) $desc = '1 '. get_string('hour', 'sloodle');
 820        // Several hours (up to 1 day)
 821        else if ($secs < 86400) $desc = ((string)(int)($secs / 3600)).' '. get_string('hours', 'sloodle');
 822        // Roughly 1 day
 823        else if ($secs < 172800) $desc = '1 '. get_string('day', 'sloodle');
 824        // Several days (up to 1 week)
 825        else if ($secs < 604800) $desc = ((string)(int)($secs / 86400)).' '. get_string('days', 'sloodle');
 826        // Roughly 1 week
 827        else if ($secs < 1209600) $desc = '1 '. get_string('week', 'sloodle');
 828        // Several weeks (up to 2 months)
 829        else if ($secs < 5184000) $desc = ((string)(int)($secs / 604800)).' '. get_string('weeks', 'sloodle');
 830        // Several months (up to 11 months)
 831        else if ($secs < 29462400) $desc = ((string)(int)($secs / 2592000)).' '. get_string('months', 'sloodle');
 832        // 1 year
 833        else if ($secs < 63072000) $desc = '1 '. get_string('year', 'sloodle');
 834        // Several years
 835        else $desc = ((string)(int)($secs / 31536000)).' '. get_string('years', 'sloodle');
 836        
 837        // Add 'ago' if necessary
 838        if ($ago) return get_string('timeago', 'sloodle', $desc);
 839        return $desc;
 840    }
 841    
 842    /**
 843    * Gets the basic URL of the current web-page being accessed.
 844    * Includes the protocol, hostname, and script path/name.
 845    * @return string
 846    */
 847    function sloodle_get_web_path()
 848    {
 849        // Check for the protocol
 850        if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off') $protocol = "http";
 851        else $protocol = "https";
 852        // Get the host name (e.g. domain)
 853        $host = $_SERVER['SERVER_NAME'];
 854        // Finally, get the script path/name
 855        $file = $_SERVER['SCRIPT_NAME'];
 856        
 857        return $protocol.'://'.$host.$file;
 858    }
 859    
 860    /**
 861    * Gets an array of subdirectories within the given directory.
 862    * Ignores anything which starts with a .
 863    * @param string $dir The directory to search WITHOUT a trailing slash. (Note: cannot search the current directory or higher in the file hierarchy)
 864    * @param bool $relative If TRUE (default) the array of results will be relative to the input directory. Otherwise, they will include the input directory path.
 865    * @return array|false A numeric array of subdirectory names sorted alphabetically, or false if an error occurred (such as the input value not being a directory)
 866    */
 867    function sloodle_get_subdirectories($dir, $relative = true)
 868    {
 869        // Make sure we have a valid directory
 870        if (empty($dir)) return false;
 871        // Open the directory
 872        if (!is_dir($dir)) return false;
 873        if (!$dh = opendir($dir)) return false;
 874        
 875        // Go through each item
 876        $output = array();
 877        while (($file = readdir($dh)) !== false) {
 878            // Ignore anything starting with a . and anything which isn't a directory
 879            if (strpos($file, '.') == 0) continue;
 880            $filetype = @filetype($dir.'/'.$file);
 881            if (empty($filetype) || $filetype != 'dir') continue;
 882            
 883            // Store it
 884            if ($relative) $output[] = $file;
 885            else $output[] = $dir.'/'.$file;
 886        }
 887        closedir($dh);
 888        natcasesort($output);
 889        return $output;
 890    }
 891    
 892    /**
 893    * Gets an array of files within the given directory.
 894    * Ignores anything which starts with a .
 895    * @param string $dir The directory to search WITHOUT a trailing slash. (Note: cannot search the current directory or higher in the file hierarchy)
 896    * @param bool $relative If TRUE (default) the array of results will be relative to the input directory. Otherwise, they will include the input directory path.
 897    * @return array|false A numeric array of file names sorted alphabetically, or false if an error occurred (such as the input value not being a directory)
 898    */
 899    function sloodle_get_files($dir, $relative = true)
 900    {
 901        // Make sure we have a valid directory
 902        if (empty($dir)) return false;
 903        // Open the directory
 904        if (!is_dir($dir)) return false;
 905        if (!$dh = opendir($dir)) return false;
 906        
 907        // Go through each item
 908        $output = array();
 909        while (($file = readdir($dh)) !== false) {
 910            // Ignore anything starting with a . and anything which isn't a file
 911            if (strpos($file, '.') == 0) continue;
 912            $filetype = @filetype($dir.'/'.$file);
 913            if (empty($filetype) || $filetype != 'file') continue;
 914            
 915            // Store it
 916            if ($relative) $output[] = $file;
 917            else $output[] = $dir.'/'.$file;
 918        }
 919        closedir($dh);
 920        natcasesort($output);
 921        return $output;
 922    }
 923    
 924    /**
 925    * Extracts the object name and version number from an object identifier.
 926    * @param string $objid An object identifier, such as "chat-1.0"
 927    * @return array A numeric array of name then version number.
 928    */
 929    function sloodle_parse_object_identifier($objid)
 930    {
 931        // Find the last dash character, and split the string around it.
 932        $lastpos = strrpos($objid, '-');
 933        // Check for common problems
 934        if ($lastpos === false) return array($objid, ''); // No dash
 935        if ($lastpos == 0) return array('', substr($objid, 1)); // Dash at start
 936        if ($lastpos == (strlen($objid) - 1)) return array(substr($objid, 0, -1), ''); // Dash at end
 937        // Split up the values
 938        $name = substr($objid, 0, $lastpos);
 939        $version = substr($objid, $lastpos + 1, strlen($objid) - $lastpos - 1);
 940        return array($name, $version);
 941    }
 942    
 943    /**
 944    * Gets all object types and versions available in this installation.
 945    * Creates a 2-dimensional associative array.
 946    * The top level is the object name, and the second is the object version (both as strings).
 947    * The associated value is the path to the configuration form script, or boolean false
 948    *  if the object has no configuration options.
 949    * @return array|false Returns a 2d associative array if successful, or false if an error occurs
 950    */
 951    function sloodle_get_installed_object_types()
 952    {
 953        // Fetch all sub-directories of the "mod" directory
 954        $MODPATH = SLOODLE_DIRROOT.'/mod';
 955        $dirs = sloodle_get_subdirectories($MODPATH, true);
 956        if (!$dirs) return false;
 957        
 958        // Go through each object to parse names and version numbers.
 959        // Object names should have format "name-version" (e.g. "chat-1.0").
 960        // We will skip anything that does not match this format.
 961        // We will also skip anything with a "noshow" file in the folder.
 962        $mods = array();
 963        foreach ($dirs as $d) {
 964            if (empty($d)) continue;
 965            
 966            // Parse the object identifier
 967            list($name, $version) = sloodle_parse_object_identifier($d);
 968            if (empty($name) || empty($version)) continue;
 969
 970            // Check if there's a noshow file
 971            if (file_exists("{$MODPATH}/{$d}/noshow")) continue;
 972            
 973            // Check if this object has a configuration script
 974            $cfgscript = "$MODPATH/$d/object_config.php";
 975            if (file_exists($cfgscript)) {
 976                $mods[$name][$version] = $cfgscript;
 977            } else {
 978                $mods[$name][$version] = false;
 979            }
 980        }
 981        
 982        // Sort the array by name of the object
 983        ksort($mods);        
 984        return $mods;
 985    }
 986   
 987
 988    /**
 989    * Render a page viewing a particular feature, or a SLOODLE module.
 990    * Outputs error text in SLOODLE debug mode.
 991    * @param string $feature The name of a feature to view ("course", "user", "users"), or "module" to indicate that we are viewing some kind of module. Note: features should contain only alphanumric characters.
 992    * @return bool True if successful, or false if not.
 993    */
 994    function sloodle_view($feature)
 995    {
 996        global $CFG, $USER;
 997        // Make sure the parameter is safe -- nothing but alphanumeric characters.
 998        if (!ctype_alnum($feature)) {
 999            sloodle_debug('sloodle_view(..): Invalid characters in view feature, "'.$feature.'"');
1000            return false;
1001        }
1002        if (empty($feature)) {
1003            sloodle_debug('sloodle_view(..): No feature name specified.');
1004            return false;
1005        }
1006        $feature = trim($feature);
1007
1008        // Has a module been requested?
1009        if (strcasecmp($feature, 'module') == 0) {
1010            // We should have an ID parameter, indicating which module has been requested
1011            $id = required_param('id', PARAM_INT);
1012            // Query the database for the SLOODLE module sub-type
1013            $instanceid = get_field('course_modules', 'instance', 'id', $id);
1014            if ($instanceid === false) error('Course module instance '.$id.' not found.');
1015            $type = get_field('sloodle', 'type', 'id', $instanceid);
1016            if ($type === false) error('SLOODLE module instance '.$instanceid.' not found.');
1017            // We will just use the type as a feature name now.
1018            // This means the following words are unavailable as module sub-types: course, user, users
1019            $feature = $type;
1020        }
1021
1022        // Attempt to include the relevant viewing class
1023        $filename = SLOODLE_DIRROOT."/view/{$feature}.php";
1024        if (!file_exists($filename)) {
1025            error("SLOODLE file not found: view/{$feature}.php");
1026            exit();
1027        }
1028        require_once($filename);
1029
1030        // Create and execute the viewing instance
1031        $classname = 'sloodle_view_'.$feature;
1032        if (!class_exists($classname)) {
1033            error("SLOODLE class missing: {$classname}");
1034            exit();
1035        }
1036        $viewer = new $classname();
1037        $viewer->view();
1038
1039        return true;
1040    } 
1041
1042    /**
1043    * Returns the given string, 'cleaned' and ready for output to SL as UTF-8.
1044    * Removes tags and slash-characters.
1045    * @param string str The string to clean.
1046    * @return string
1047    */
1048    function sloodle_clean_for_output($str)
1049    {
1050        return strip_tags(stripcslashes(@html_entity_decode($str, ENT_QUOTES, 'UTF-8')));
1051    }
1052
1053    /**
1054    * Returns the given string, 'cleaned' and ready for storage in the database.
1055    * Note: removes tags and slash-characters.
1056    * @param string str The string to clean.
1057    * @return string
1058    */
1059    function sloodle_clean_for_db($str)
1060    {
1061        return htmlentities($str, ENT_QUOTES, 'UTF-8');
1062    }
1063
1064    /**
1065    * Converts a shorthand file size to a number of bytes, if necessary.
1066    * This follows PHP shorthand, with K for Kilobytes, M for Megabytes, and G for Gigabytes.
1067    * @param string size The shorthand size to conert
1068    * @return integer The size specified in bytes
1069    */
1070    function sloodle_convert_file_size_shorthand($size)
1071    {
1072        $size = trim($size);
1073        $num = (int)$size;
1074        $char = strtolower($size{strlen($size)-1});
1075        switch ($char)
1076        {
1077        case 'g': $num *= 1024;
1078        case 'm': $num *= 1024;
1079        case 'k': $num *= 1024;
1080        }
1081
1082        return $num;
1083    }
1084
1085    /**
1086    * Converts a file size to plain text.
1087    * For example, will convert "1024" to "1 kilobyte".
1088    * @param integer|string size If an integer, then it is the number of bytes. If a string, then it can be PHP shorthand, such as "1M" for 1 megabyte.
1089    * @return string A text string describing the specified size.
1090    */
1091    function sloodle_get_size_description($size)
1092    {
1093        // Make sure we have a number of bytes
1094        $bytes = 0;
1095        if (is_int($size)) $bytes = $size;
1096        else $bytes = sloodle_convert_file_size_shorthand($size);
1097        $desc = '';
1098
1099        // Keep the number small by going with the largest possible units
1100        if ($bytes >= 1073741824) $desc = ($bytes / 1073741824)." GB";
1101        else if ($bytes >= 1048576) $desc = ($bytes / 1048576). " MB";
1102        else if ($bytes >= 1024) $desc = ($bytes / 1024). " KB";
1103        else $desc = $bytes . " bytes";
1104
1105        return $desc;
1106    }
1107
1108    /**
1109    * Gets the maximum size of a file (in bytes) that can be uploaded using POST.
1110    * @return integer
1111    */
1112    function sloodle_get_max_post_upload()
1113    {
1114        // Get the sizes of the relevant limits
1115        $upload_max_filesize = sloodle_convert_file_size_shorthand(ini_get('upload_max_filesize'));
1116        $post_max_size = sloodle_convert_file_size_shorthand(ini_get('post_max_size'));
1117
1118        // Use the smaller limit
1119        return min($upload_max_filesize, $post_max_size);
1120    }
1121
1122
1123?>