" . get_string('pleasedonotreplytothismessage') . "
\n"; } } else { if (empty($mail->Sender)) { $mail->Sender = $userfrom->email; } $mail->From = $userfrom->email; $mail->FromName = display_name($userfrom, $userto); } $replytoset = false; if (!empty($customheaders) && is_array($customheaders)) { foreach ($customheaders as $customheader) { // To prevent duplicated declaration of the field "Message-ID", // don't add it into the $mail->CustomHeader[]. if (false === stripos($customheader, 'message-id')) { // Hack the fields "In-Reply-To" and "References": // add touser' . hsc($notice) . '
' . $messagehtml; } } else { $usertoname = display_name($userto, $userto); $mail->AddAddress($userto->email, $usertoname ); $to = $userto->email; } if (!$replytoset) { $mail->AddReplyTo($mail->From, $mail->FromName); } } catch (phpmailerException $e) { // If there's a phpmailer error already, assume it's an invalid address throw new InvalidEmailException("Cannot send email to $usertoname with subject $subject. Error from phpmailer was: " . $mail->ErrorInfo); } $mail->WordWrap = 79; if ($messagehtml) { $mail->IsHTML(true); $mail->Encoding = 'quoted-printable'; $mail->Body = $messagehtml; $mail->AltBody = $messagetext; } else { $mail->IsHTML(false); $mail->Body = $messagetext; } try { $sent = $mail->Send(); } catch (phpmailerException $e) { $sent = false; } if ($sent) { if ($logfile = get_config('emaillog')) { $docroot = get_config('docroot'); @$client = (string) $_SERVER['REMOTE_ADDR']; @$script = (string) $_SERVER['SCRIPT_FILENAME']; if (strpos($script, $docroot) === 0) { $script = substr($script, strlen($docroot)); } $line = "$to <- $mail->From - " . str_shorten_text($mail->Subject, 200); @error_log('[' . date("Y-m-d h:i:s") . "] [$client] [$script] $line\n", 3, $logfile); } if (get_config('bounces_handle')) { // Update the count of sent mail update_send_count($userto); } return true; } throw new EmailException("Couldn't send email to $usertoname with subject $subject. " . "Error from phpmailer was: " . $mail->ErrorInfo ); } /** * Generate an email processing address for VERP handling of email * * @param int $userid the ID of the user sending the mail * @param object $userto an object containing the email address * @param char $type The type of address to generate * * The type of address is typically a Bounce. These are processed by the * process_email function. */ function generate_email_processing_address($userid, $userto, $type='B') { $mailprefix = get_config('bounceprefix'); $maildomain = get_config('bouncedomain'); $installation_key = get_config('installation_key'); // Postfix and other smtp servers don't like the use of / in the extension part of an email // Replace it with another valid email character that isn't in base64, like '-' $args = $type . preg_replace('/\//', '-', base64_encode(pack('V', $userid))) . substr(md5($userto->email), 0, 16); return $mailprefix . $args . substr(md5($mailprefix . $userto->email . $installation_key), 0, 16) . '@' . $maildomain; } /** * Check whether an email account is over the site-wide bounce threshold. * If the user is over threshold, then e-mail is disabled for their * account, and they are sent a notification to notify them of the change. * * @param object $mailinfo The row from artefact_internal_profile_email for * the user being processed. * @return boolean false if the user is not over threshold, true if they * are. */ function check_overcount($mailinfo) { // if we don't handle bounce e-mails, then we can't be over threshold if (!get_config('bounces_handle')) { return false; } $minbounces = get_config('bounces_min'); $bounceratio = get_config('bounces_ratio'); // If they haven't set a minbounces value, then we can't proceed if (!$minbounces) { return false; } if ($mailinfo->mailssent == 0) { return false; } // If the bouncecount is larger than the allowed amount // and the bounce count ratio (bounces/total sent) is larger than the // bounceratio, then disable email $overlimit = ($mailinfo->mailsbounced >= $minbounces) && ($mailinfo->mailsbounced/$mailinfo->mailssent >= $bounceratio); if ($overlimit) { if (get_account_preference($mailinfo->owner,'maildisabled') != 1) { // Disable the e-mail account db_begin(); set_account_preference($mailinfo->owner, 'maildisabled', 1); $lang = get_user_language($mailinfo->owner); // Send a notification that e-mail has been disabled $message = new StdClass; $message->users = array($mailinfo->owner); $message->subject = get_string_from_language($lang, 'maildisabled', 'account'); $message->message = get_string_from_language($lang, 'maildisabledbounce', 'account', get_config('wwwroot') . 'account/'); require_once('activity.php'); activity_occurred('maharamessage', $message); db_commit(); } return true; } return false; } /** * Update the send count for the specified e-mail address * * @param object $userto object to update count for. Must contain email and * user id * @param boolean reset Reset the sent mail count to 0 (optional). */ function update_send_count($userto, $reset=false) { if (!$userto->id) { // We need a user id to update the send count. return false; } if ($mailinfo = get_record_select('artefact_internal_profile_email', '"owner" = ? AND email = ? AND principal = 1', array($userto->id, $userto->email))) { $mailinfo->mailssent = (!empty($reset)) ? 0 : $mailinfo->mailssent+1; update_record('artefact_internal_profile_email', $mailinfo, array('email' => $userto->email, 'owner' => $userto->id)); } } /** * Update the bounce count for the specified e-mail address * * @param object $userto object to update count for. Must contain email and * user id * @param boolean reset Reset the sent mail count to 0 (optional). */ function update_bounce_count($userto, $reset=false) { if (!$userto->id) { // We need a user id to update the bounce count. return false; } if ($mailinfo = get_record_select('artefact_internal_profile_email', '"owner" = ? AND email = ? AND principal = 1', array($userto->id, $userto->email))) { $mailinfo->mailsbounced = (!empty($reset)) ? 0 : $mailinfo->mailsbounced+1; update_record('artefact_internal_profile_email', $mailinfo, array('email' => $userto->email, 'owner' => $userto->id)); } } /** * Process an incoming email * * @param string $address the email address to process */ function process_email($address) { $email = new StdClass; if (strlen($address) <= 30) { log_debug ('-- Email address not long enough to contain valid data.'); return $email; } if (!strstr($address, '@')) { log_debug ('-- Email address does not contain @.'); return $email; } $mailprefix = get_config('bounceprefix'); $prefixlength = strlen($mailprefix); list($email->localpart,$email->domain) = explode('@',$address); // The prefix is stored in the first characters denoted by $prefixlength $email->prefix = substr($email->localpart, 0, $prefixlength); // The type of message received is a one letter code $email->type = substr($email->localpart, $prefixlength, 1); // The userid should be available immediately afterwards // Postfix and other smtp servers don't like the use of / in the extension part of an email // We may of replaced it with another valid email character which isn't in base64, namely '-' // If we didn't, then the preg_replace won't do anything list(,$email->userid) = unpack('V',base64_decode(preg_replace('/-/', '/', substr($email->localpart, $prefixlength + 1, 8)))); // Any additional arguments $email->args = substr($email->localpart, $prefixlength + 9,-16); // And a hash of the intended recipient for authentication $email->addresshash = substr($email->localpart,-16); if (!$email->userid) { log_debug('-- no userid associated with this email address'); return $email; } switch ($email->type) { case 'B': // E-mail bounces if ($user = get_record_select('artefact_internal_profile_email', '"owner" = ? AND principal = 1', array($email->userid))) { $maildomain = get_config('bouncedomain'); $installation_key = get_config('installation_key'); // check the half md5 of their email $md5check = substr(md5($mailprefix . $user->email . $installation_key), 0, 16); $user->id = $user->owner; if ($md5check == substr($email->addresshash, -16)) { update_bounce_count($user); check_overcount($user); } // else maybe they've already changed their email address } break; // No more cases yet } return $email; } /** * Check an imap mailbox for new mailbounces */ function check_imap_for_bounces() { $imapserver = get_config('imapserver'); if (!$imapserver) { return; } if (!extension_loaded('imap')) { log_debug('php imap extension not loaded, can\'t continue'); return; } $imapport = get_config('imapport'); $imap = imap_open("{" . $imapserver . ($imapport ? ':' . $imapport : '') . get_config('imapflags') . '}' . get_config('imapmailbox'), get_config('imapuser'), get_config('imappass')); $check = imap_check($imap); if ($check->Nmsgs == 0) { imap_close($imap); return; } $emails = imap_fetch_overview($imap, "1:" . $check->Nmsgs); foreach ($emails as $email) { if ($email->deleted) { continue; } $address = $email->to; log_debug('---------- started processing email at ' . date('r', time()) . ' ----------'); log_debug('-- mail from ' . $address . ' -- delivered ' . $email->date); $ret = process_email($address); log_debug('---------- finished processing email at ' . date('r', time()) . ' ----------'); imap_delete($imap, $email->msgno); } imap_expunge($imap); imap_close($imap); } /** * converts a user object to a string representation of the user suitable for * the current user (or specified user) to see * * Both parameters should be objects containing id, preferredname, firstname, * lastname, admin * * @param object $user the user that you're trying to format to a string * @param object $userto the user that is looking at the string representation (if left * blank, will default to the currently logged in user). * @param boolean $nameonly do not append the user's username even if $userto can see it. * @param boolean $realname show the user's real name even if preferredname exists * @param boolean $username show the user's username even if the viewer is not an admin * * @returns string name to display */ function display_name($user, $userto=null, $nameonly=false, $realname=false, $username=false) { global $USER; static $tutorcache = array(); if ($nameonly) { return display_default_name($user); } $userto = get_user_for_display($userto); $user = get_user_for_display($user); $addusername = $username || !empty($userto->admin) || !empty($userto->staff); // if they don't have a preferred name set, just return here if (empty($user->preferredname)) { $firstlast = full_name($user); if ($addusername) { return $firstlast . ' (' . display_username($user) . ')'; } return $firstlast; } else if ($user->id == $userto->id) { // If viewing our own name, show it how we like it return $user->preferredname; } // Preferred name is set $addrealname = $realname || !empty($userto->admin) || !empty($userto->staff); if (!$addrealname) { // Tutors can always see the user's real name, so we need to check if the // viewer is a tutor of the user whose name is being displayed if (!isset($tutorcache[$user->id][$userto->id])) { $tutorcache[$user->id][$userto->id] = record_exists_sql(' SELECT s.member FROM {group_member} s JOIN {group_member} t ON s.group = t.group JOIN {group} g ON (g.id = s.group AND g.deleted = 0 AND g.submittableto = 1) JOIN {grouptype_roles} gtr ON (g.grouptype = gtr.grouptype AND gtr.role = t.role AND gtr.see_submitted_views = 1) WHERE s.member = ? AND t.member = ?', array($user->id, $userto->id) ); } $addrealname = $tutorcache[$user->id][$userto->id]; } if ($addrealname) { $firstlast = full_name($user); if ($addusername) { return $user->preferredname . ' (' . $firstlast . ' - ' . display_username($user) . ')'; } return $user->preferredname . ' (' . $firstlast . ')'; } if ($addusername) { return $user->preferredname . ' (' . display_username($user) . ')'; } return $user->preferredname; } /** * function to format a users name when there is no user to look at them * ie when display_name is not going to work.. */ function display_default_name($user) { $user = get_user_for_display($user); return empty($user->preferredname) ? full_name($user) : $user->preferredname; } /** * Converts a user object to a full name representation, honouring the language * setting. * * Currently a stub, will need to be improved and completed as demand arises. * * @param object $user The user object to make a full name out of. If empty, * the global $USER object is used*/ function full_name($user=null) { global $USER; if ($user === null) { $user = new StdClass; $user->firstname = $USER->get('firstname'); $user->lastname = $USER->get('lastname'); $user->deleted = $USER->get('deleted'); } return isset($user->deleted) && $user->deleted ? get_string('deleteduser') : $user->firstname . ' ' . $user->lastname; } /** * Takes an array, object or integer identifying a user and returns an object with * the properties needed for display_name, display_default_name, or profile_icon_url. */ function get_user_for_display($user=null) { global $USER; static $usercache = array(); $fields = array( 'id', 'username', 'preferredname', 'firstname', 'lastname', 'admin', 'staff', 'profileicon', 'email', 'deleted', 'urlid', 'suspendedctime', ); if (is_numeric($user) && isset($usercache[$user])) { return $usercache[$user]; } if (is_array($user)) { $user = (object)$user; } else if (is_null($user) || (is_numeric($user) && $user == $USER->get('id'))) { $user = new StdClass; foreach ($fields as $f) { $user->$f = $USER->get($f); } $user->admin = $user->admin || $USER->is_institutional_admin(); $user->staff = $user->staff || $USER->is_institutional_staff(); } else if ($user instanceof User) { $userObj = $user; $user = new StdClass; foreach ($fields as $f) { $user->$f = $userObj->get($f); } } else if (is_numeric($user)) { $user = get_record('usr', 'id', $user); } if (!is_object($user)) { throw new InvalidArgumentException("Invalid user passed to get_user_for_display"); } if (!isset($user->id)) { $user->id = null; } if (is_numeric($user->id)) { if (!isset($usercache[$user->id])) { return $usercache[$user->id] = $user; } foreach ($fields as $f) { if (!isset($usercache[$user->id]->$f) && isset($user->$f)) { $usercache[$user->id]->$f = $user->$f; } } return $usercache[$user->id]; } return $user; } /** * Creates a string containing a displayable username. * * If the username is longer than 30 characters (bug #548165), then print * the first 30 characters followed by '...' * * @param object $user The user object to display the username of. If empty, * the global $USER object is used */ function display_username($user=null) { global $USER; if ($user === null) { $user = new StdClass; $user->username = $USER->get('username'); } if (strlen($user->username) > MAX_USERNAME_DISPLAY) { return substr($user->username, 0, MAX_USERNAME_DISPLAY).'...'; } else { return $user->username; } } /** * helper function to default to currently * logged in user if there isn't an id specified * @throws InvalidArgumentException if there is no user and no $USER */ function optional_userid($userid) { if (!empty($userid)) { return $userid; } if (!is_logged_in()) { throw new InvalidArgumentException("optional_userid no userid and no logged in user"); } global $USER; return $USER->get('id'); } /** * helper function to default to currently * logged in user if there isn't an id specified * @throws InvalidArgumentException if there is no user and no $USER */ function optional_userobj($user) { if (!empty($user) && is_object($user)) { return $user; } if (!empty($user) && is_numeric($user)) { if ($user = get_record('usr', 'id', $user)) { return $user; } throw new InvalidArgumentException("optional_userobj given id $id no db match found"); } if (!is_logged_in()) { throw new InvalidArgumentException("optional_userobj no userid and no logged in user"); } global $USER; return $USER->to_stdclass(); } /** * helper function for testing logins */ function is_logged_in() { global $USER; if (empty($USER)) { return false; } return $USER->is_logged_in(); } /** * is there a friend relationship between these two users? * * @param int $userid1 * @param int $userid2 */ function is_friend($userid1, $userid2) { return record_exists_select('usr_friend', '(usr1 = ? AND usr2 = ?) OR (usr2 = ? AND usr1 = ?)', array($userid1, $userid2, $userid1, $userid2)); } /** * has there been a request between these two users? * * @param int $userid1 * @param int $userid2 */ function get_friend_request($userid1, $userid2) { return get_record_select('usr_friend_request', '("owner" = ? AND requester = ?) OR (requester = ? AND "owner" = ?)', array($userid1, $userid2, $userid1, $userid2)); } /** * Returns an object containing information about a user, including account * and activity preferences * * @param int $userid The ID of the user to retrieve information about * @return object The user object. Note this is not in the same form as * the $USER object used to denote the current user - * the object returned by this method is a simple object. */ function get_user($userid) { if (!$user = get_record('usr', 'id', $userid, null, null, null, null, '*, ' . db_format_tsfield('expiry') . ', ' . db_format_tsfield('lastlogin') . ', ' . db_format_tsfield('lastlastlogin') . ', ' . db_format_tsfield('lastaccess') . ', ' . db_format_tsfield('suspendedctime') . ', ' . db_format_tsfield('ctime'))) { throw new InvalidArgumentException('Unknown user ' . $userid); } $user->activityprefs = load_activity_preferences($userid); $user->accountprefs = load_account_preferences($userid); return $user; } /** * Suspends a user * * @param int $suspendeduserid The ID of the user to suspend * @param string $reason The reason why the user is being suspended * @param int $suspendinguserid The ID of the user who is performing the suspension */ function suspend_user($suspendeduserid, $reason, $suspendinguserid=null) { if ($suspendinguserid === null) { global $USER; $suspendinguserid = $USER->get('id'); } $suspendrec = new StdClass; $suspendrec->id = $suspendeduserid; $suspendrec->suspendedcusr = $suspendinguserid; $suspendrec->suspendedreason = $reason; $suspendrec->suspendedctime = db_format_timestamp(time()); update_record('usr', $suspendrec, 'id'); // Try to kick the user from any active login session. require_once(get_config('docroot') . 'auth/session.php'); remove_user_sessions($suspendeduserid); $lang = get_user_language($suspendeduserid); $message = new StdClass; $message->users = array($suspendeduserid); $message->subject = get_string_from_language($lang, 'youraccounthasbeensuspended'); if ($reason == '') { $message->message = get_string_from_language($lang, 'youraccounthasbeensuspendedtext2', 'mahara', get_config('sitename'), display_name($suspendinguserid, $suspendeduserid)); } else { $message->message = get_string_from_language($lang, 'youraccounthasbeensuspendedreasontext', 'mahara', get_config('sitename'), display_name($suspendinguserid, $suspendeduserid), $reason); } require_once('activity.php'); activity_occurred('maharamessage', $message); handle_event('suspenduser', $suspendeduserid); } /** * Unsuspends a user * * @param int $userid The ID of the user to unsuspend */ function unsuspend_user($userid) { $suspendedrec = new StdClass; $suspendedrec->id = $userid; $suspendedrec->suspendedcusr = null; $suspendedrec->suspendedreason = null; $suspendedrec->suspendedctime = null; update_record('usr', $suspendedrec); $lang = get_user_language($userid); $message = new StdClass; $message->users = array($userid); $message->subject = get_string_from_language($lang, 'youraccounthasbeenunsuspended'); $message->message = get_string_from_language($lang, 'youraccounthasbeenunsuspendedtext2', 'mahara', get_config('sitename')); require_once('activity.php'); activity_occurred('maharamessage', $message); handle_event('unsuspenduser', $userid); } /** * Deletes a user * * This function ensures that a user is deleted according to how Mahara wants a * deleted user to be. You can call it multiple times on the same user without * harm. * * @param int $userid The ID of the user to delete */ function delete_user($userid) { db_begin(); // We want to append 'deleted.timestamp' to some unique fields in the usr // table, so they can be reused by new accounts $fieldstomunge = array('username', 'email'); $datasuffix = '.deleted.' . microtime(true); $user = get_record('usr', 'id', $userid, null, null, null, null, implode(', ', $fieldstomunge)); $deleterec = new StdClass; $deleterec->id = $userid; $deleterec->deleted = 1; foreach ($fieldstomunge as $field) { if (!preg_match('/\.deleted\.\d+$/', $user->$field)) { $deleterec->$field = $user->$field . $datasuffix; } } // Set authinstance to default internal, otherwise the old authinstance can be blocked from deletion // by deleted users. $authinst = get_field('auth_instance', 'id', 'institution', 'mahara', 'authname', 'internal'); if ($authinst) { $deleterec->authinstance = $authinst; } // Free the urlid for another user to use $deleterec->urlid = null; update_record('usr', $deleterec); // Remove user from any groups they're in, invited to or want to be in $groupids = get_column('group_member', '"group"', 'member', $userid); if ($groupids) { require_once(get_config('libroot') . 'group.php'); foreach ($groupids as $groupid) { group_remove_user($groupid, $userid, true); } } delete_records('group_member_request', 'member', $userid); delete_records('group_member_invite', 'member', $userid); // Remove any friend relationships the user is in execute_sql('DELETE FROM {usr_friend} WHERE usr1 = ? OR usr2 = ?', array($userid, $userid)); execute_sql('DELETE FROM {usr_friend_request} WHERE owner = ? OR requester = ?', array($userid, $userid)); // Delete the user from others' favourites lists delete_records('favorite_usr', 'usr', $userid); // Delete favourites lists owned by the user execute_sql('DELETE FROM {favorite_usr} WHERE favorite IN (SELECT id FROM {favorite} WHERE owner = ?)', array($userid)); delete_records('favorite', 'owner', $userid); delete_records('artefact_access_usr', 'usr', $userid); delete_records('auth_remote_user', 'localusr', $userid); delete_records('import_queue', 'usr', $userid); delete_records('usr_account_preference', 'usr', $userid); delete_records('usr_activity_preference', 'usr', $userid); delete_records('usr_infectedupload', 'usr', $userid); delete_records('usr_institution', 'usr', $userid); delete_records('usr_institution_request', 'usr', $userid); delete_records('usr_password_request', 'usr', $userid); delete_records('usr_watchlist_view', 'usr', $userid); delete_records('view_access', 'usr', $userid); // Remove the user's views & artefacts $viewids = get_column('view', 'id', 'owner', $userid); if ($viewids) { require_once(get_config('libroot') . 'view.php'); foreach ($viewids as $viewid) { $view = new View($viewid); $view->delete(); } } $artefactids = get_column('artefact', 'id', 'owner', $userid); // @todo: test all artefact bulk_delete stuff, then replace the one-by-one // artefact deletion below with ArtefactType::delete_by_artefacttype($artefactids); if ($artefactids) { foreach ($artefactids as $artefactid) { try { $a = artefact_instance_from_id($artefactid, true); if ($a) { $a->delete(); } } catch (ArtefactNotFoundException $e) { // Awesome, it's already gone. } } } // Remove the user's collections $collectionids = get_column('collection', 'id', 'owner', $userid); if ($collectionids) { require_once(get_config('libroot') . 'collection.php'); foreach ($collectionids as $collectionid) { $collection = new Collection($collectionid); $collection->delete(); } } handle_event('deleteuser', $userid); // Destroy all active sessions of the deleted user require_once(get_config('docroot') . 'auth/session.php'); remove_user_sessions($userid); db_commit(); } /** * Undeletes a user * * NOTE: changing their email addresses to remove the .deleted.timestamp part * has not been implemented yet! This function is not actually used anywhere in * Mahara, so hasn't really been tested because of this. It's a simple enough * job for the first person who gets there - see how delete_user works to see * what you must undo. * * @param int $userid The ID of the user to undelete */ function undelete_user($userid) { $deleterec = new StdClass; $deleterec->id = $userid; $deleterec->deleted = 0; update_record('usr', $deleterec); handle_event('undeleteuser', $userid); } /** * Expires a user * * Nothing amazing needs to happen here, but this function is here for * consistency. * * This function is called when a user account is detected to be expired. * It is assumed that the account actually is expired. * * @param int $userid The ID of user to expire */ function expire_user($userid) { handle_event('expireuser', $userid); } /** * Unexpires a user * * @param int $userid The ID of user to unexpire */ function unexpire_user($userid) { $lifetime = get_config('defaultaccountlifetime'); $now = time(); $dbnow = db_format_timestamp($now); $values = array($dbnow, $userid, $dbnow); if ($lifetime) { $newexpiry = '?'; array_unshift($values, db_format_timestamp($now + $lifetime)); } else { $newexpiry = 'NULL'; } // Update the lastaccess time here to stop users who are currently // inactive from expiring again on the next cron run. We can leave // inactivemailsent turned on until the user logs in again. execute_sql(" UPDATE {usr} SET expiry = $newexpiry, expirymailsent = 0, lastaccess = ? WHERE id = ? AND expiry IS NOT NULL AND expiry < ?", $values ); handle_event('unexpireuser', $userid); } /** * Marks a user as inactive * * Sets the account expiry to the current time to disable login. * * This function is called when a user account is detected to be inactive. * It is assumed that the account actually is inactive. * * @param int $userid The ID of user to mark inactive */ function deactivate_user($userid) { execute_sql(' UPDATE {usr} SET expiry = current_timestamp WHERE id = ? AND (expiry IS NULL OR expiry > current_timestamp)', array($userid) ); handle_event('deactivateuser', $userid); } /** * Activates a user * * @param int $userid The ID of user to reactivate */ function activate_user($userid) { handle_event('activateuser', $userid); } /** * Get the thread of message up to this point, given the id of * the message being replied to. */ function get_message_thread($replyto) { $message = get_record('notification_internal_activity', 'id', $replyto); if (empty($message->parent)) { return array($message); } return array_merge(get_message_thread($message->parent), array($message)); } /** * Sends a message from one user to another * * @param object $to User to send the message to * @param string $message The message to send * @param object $from Who to send the message from. If not set, defaults to * the currently logged in user * @throws AccessDeniedException if the message is not allowed to be sent (as * configured by the 'to' user's settings) */ function send_user_message($to, $message, $parent, $from=null) { // FIXME: permission checking! if ($from === null) { global $USER; $from = $USER; } $messagepref = get_account_preference($to->id, 'messages'); if ($messagepref == 'allow' || ($messagepref == 'friends' && is_friend($from->id, $to->id)) || $from->get('admin')) { require_once('activity.php'); activity_occurred('usermessage', array( 'userto' => $to->id, 'userfrom' => $from->id, 'message' => $message, 'parent' => $parent, ) ); } else { throw new AccessDeniedException('Cannot send messages between ' . display_name($from) . ' and ' . display_name($to)); } } /** * can a user send a message to another? * * @param int/object from the user to send the message * @param int/object to the user to receive the message * @return boolean whether userfrom is allowed to send messages to userto */ function can_send_message($from, $to) { if (empty($from)) { return false; // not logged in } if (!is_object($from)) { $from = get_record('usr', 'id', $from); } if (is_object($to)) { $to = $to->id; } $messagepref = get_account_preference($to, 'messages'); return (is_friend($from->id, $to) && $messagepref == 'friends') || $messagepref == 'allow' || $from->admin; } function load_user_institutions($userid) { if (!is_numeric($userid) || $userid < 0) { throw new InvalidArgumentException("couldn't load institutions, no user id specified"); } if ($institutions = get_records_sql_assoc(' SELECT u.institution,'.db_format_tsfield('ctime').','.db_format_tsfield('u.expiry', 'membership_expiry').',u.studentid,u.staff,u.admin,i.displayname,i.theme,i.registerallowed, i.showonlineusers,i.allowinstitutionpublicviews, i.logo, i.style, i.licensemandatory, i.licensedefault, i.dropdownmenu, i.skins, i.suspended FROM {usr_institution} u INNER JOIN {institution} i ON u.institution = i.name WHERE u.usr = ? ORDER BY i.priority DESC', array($userid))) { return $institutions; } return array(); } /** * Return a username which isn't taken and which is similar to a desired username * * @param string $desired * @param string $maxlen Maximum length of desired username */ function get_new_username($desired, $maxlen=30) { if (function_exists('mb_strtolower')) { $desired = mb_strtolower(mb_substr($desired, 0, $maxlen, 'UTF-8'), 'UTF-8'); } else { $desired = strtolower(substr($desired, 0, $maxlen)); } if (function_exists('mb_substr')) { $taken = get_column_sql(' SELECT LOWER(username) FROM {usr} WHERE username ' . db_ilike() . " ?", array(mb_substr($desired, 0, $maxlen - 6, 'UTF-8') . '%')); } else { $taken = get_column_sql(' SELECT LOWER(username) FROM {usr} WHERE username ' . db_ilike() . " ?", array(substr($desired, 0, $maxlen - 6) . '%')); } if (!$taken) { return $desired; } $taken = array_flip($taken); $i = ''; if (function_exists('mb_substr')) { $newname = mb_substr($desired, 0, $maxlen - 1, 'UTF-8') . $i; } else { $newname = substr($desired, 0, $maxlen - 1) . $i; } while (isset($taken[$newname])) { $i++; if (function_exists('mb_substr')) { $newname = mb_substr($desired, 0, $maxlen - strlen($i), 'UTF-8') . $i; } else { $newname = substr($desired, 0, $maxlen - strlen($i)) . $i; } } return $newname; } /** * Get a unique profile urlid * * @param string $desired */ function get_new_profile_urlid($desired) { $maxlen = 30; $desired = strtolower(substr($desired, 0, $maxlen)); $taken = get_column_sql('SELECT urlid FROM {usr} WHERE urlid LIKE ?', array(substr($desired, 0, $maxlen - 6) . '%')); if (!$taken) { return $desired; } $i = 1; $newname = substr($desired, 0, $maxlen - 2) . '-1'; while (in_array($newname, $taken)) { $i++; $newname = substr($desired, 0, $maxlen - strlen($i) - 1) . '-' . $i; } return $newname; } /** * Get the profile url for a user * * @param object $user * @param boolean $full return a full url * @param boolean $useid Override the cleanurls setting and use a view id in the link * * @return string */ function profile_url($user, $full=true, $useid=false) { $wantclean = !$useid && get_config('cleanurls'); if ($user instanceof User) { $id = $user->get('id'); $urlid = $wantclean ? $user->get('urlid') : null; } else if (is_array($user)) { $id = $user['id']; $urlid = $user['urlid']; } else if (is_numeric($user)) { $id = $user; $user = get_user_for_display($id); $urlid = ($wantclean && !empty($user->urlid)) ? $user->urlid : null; } else if (isset($user->id)) { $id = $user->id; $urlid = isset($user->urlid) ? $user->urlid : null; } if ($wantclean && !is_null($urlid)) { // If the host part of the url is not being returned, the user subdomain // can't be added here, so ignore the subdomain setting when !$full. if ($full && get_config('cleanurlusersubdomains')) { list($proto, $rest) = explode('://', get_config('wwwroot')); return $proto . '://' . $urlid . '.' . substr($rest, 0, -1); } $url = get_config('cleanurluserdefault') . '/' . $urlid; } else if (!is_null($id)) { $url = 'user/view.php?id=' . (int) $id; } else { throw new SystemException("profile_url called with no user id"); } if ($full) { $url = get_config('wwwroot') . $url; } return $url; } /** * used by user/myfriends.php and user/find.php to get the data (including pieforms etc) for display * @param array $userids * @return array containing the users in the order from $userids */ function get_users_data($userids, $getviews=true) { global $USER; $userids = array_map('intval', $userids); $sql = 'SELECT u.id, u.username, u.preferredname, u.firstname, u.lastname, u.admin, u.staff, u.deleted, u.profileicon, u.email, u.urlid, fp.requester AS pending, ap.value AS hidenamepref, COALESCE((SELECT ap.value FROM {usr_account_preference} ap WHERE ap.usr = u.id AND ap.field = \'messages\'), \'allow\') AS messages, COALESCE((SELECT ap.value FROM {usr_account_preference} ap WHERE ap.usr = u.id AND ap.field = \'friendscontrol\'), \'auth\') AS friendscontrol, (SELECT 1 FROM {usr_friend} WHERE ((usr1 = ? AND usr2 = u.id) OR (usr2 = ? AND usr1 = u.id))) AS friend, (SELECT 1 FROM {usr_friend_request} fr WHERE fr.requester = ? AND fr.owner = u.id) AS requestedfriendship, (SELECT title FROM {artefact} WHERE artefacttype = \'introduction\' AND owner = u.id) AS introduction, fp.message FROM {usr} u LEFT JOIN {usr_account_preference} ap ON (u.id = ap.usr AND ap.field = \'hiderealname\') LEFT JOIN {usr_friend_request} fp ON fp.owner = ? AND fp.requester = u.id WHERE u.id IN (' . join(',', array_fill(0, count($userids), '?')) . ')'; $userid = $USER->get('id'); $data = get_records_sql_assoc($sql, array_merge(array($userid, $userid, $userid, $userid), $userids)); $allowhidename = get_config('userscanhiderealnames'); $showusername = get_config('searchusernames'); $institutionstrings = get_institution_strings_for_users($userids); foreach ($data as &$record) { $record->messages = ($record->messages == 'allow' || $record->friend && $record->messages == 'friends' || $USER->get('admin')) ? 1 : 0; if (isset($institutionstrings[$record->id])) { $record->institutions = $institutionstrings[$record->id]; } $record->display_name = display_name($record, null, false, !$allowhidename || !$record->hidenamepref, $showusername); } if (!$data || !$getviews || !$views = get_views(array_keys($data), null, null)) { $views = array(); } if ($getviews) { $viewcount = array_map('count', $views); // since php is so special and inconsistent, we can't use array_map for this because it breaks the top level indexes. $cleanviews = array(); foreach ($views as $userindex => $viewarray) { $cleanviews[$userindex] = array_slice($viewarray, 0, 5); // Don't reveal any more about the view than necessary foreach ($cleanviews as $userviews) { foreach ($userviews as &$view) { foreach (array_keys(get_object_vars($view)) as $key) { if ($key != 'id' && $key != 'title' && $key != 'url' && $key != 'fullurl') { unset($view->$key); } } } } } } foreach ($data as $friend) { if ($getviews && isset($cleanviews[$friend->id])) { $friend->views = $cleanviews[$friend->id]; } if ($friend->pending) { $friend->accept = acceptfriend_form($friend->id); } if (!$friend->friend && !$friend->pending && !$friend->requestedfriendship && $friend->friendscontrol == 'auto') { $friend->makefriend = addfriend_form($friend->id); } } $ordereddata = array(); foreach ($userids as $id) { if (isset($data[$id])) { $ordereddata[] = $data[$id]; } } return $ordereddata; } function build_userlist_html(&$data, $page, $admingroups) { if ($data['data']) { $userlist = array_map(create_function('$u','return (int)$u[\'id\'];'), $data['data']); $userdata = get_users_data($userlist, $page == 'myfriends'); } $smarty = smarty_core(); $smarty->assign('data', isset($userdata) ? $userdata : null); $smarty->assign('page', $page); $smarty->assign('offset', $data['offset']); $params = array(); if (isset($data['query'])) { $smarty->assign('query', 1); $params['query'] = $data['query']; } if (isset($data['filter'])) { $params['filter'] = $data['filter']; } if ($page == 'myfriends') { $resultcounttextsingular = get_string('friend', 'group'); $resultcounttextplural = get_string('friends', 'group'); } else { $resultcounttextsingular = get_string('user', 'group'); $resultcounttextplural = get_string('users', 'group'); } $smarty->assign('admingroups', $admingroups); $data['tablerows'] = $smarty->fetch('user/userresults.tpl'); $pagination = build_pagination(array( 'id' => 'friendslist_pagination', 'url' => get_config('wwwroot') . 'user/' . $page . '.php?' . http_build_query($params), 'jsonscript' => 'json/friendsearch.php', 'datatable' => 'friendslist', 'searchresultsheading' => 'searchresultsheading', 'count' => $data['count'], 'setlimit' => true, 'limit' => $data['limit'], 'offset' => $data['offset'], 'jumplinks' => 6, 'numbersincludeprevnext' => 2, 'resultcounttextsingular' => $resultcounttextsingular, 'resultcounttextplural' => $resultcounttextplural, 'extradata' => array('page' => $page), )); $data['pagination'] = $pagination['html']; $data['pagination_js'] = $pagination['javascript']; } function build_onlinelist_html(&$data, $page) { if ($data['data']) { $userdata = get_users_data($data['data'], false); } $smarty = smarty_core(); $smarty->assign('data', isset($userdata) ? $userdata : null); $smarty->assign('page', $page); $resultcounttextsingular = get_string('user', 'group'); $resultcounttextplural = get_string('users', 'group'); $data['tablerows'] = $smarty->fetch('user/onlineuserresults.tpl'); $pagination = build_pagination(array( 'id' => 'onlinelist_pagination', 'url' => get_config('wwwroot') . 'user/' . $page . '.php', 'datatable' => 'onlinelist', 'count' => $data['count'], 'limit' => $data['limit'], 'offset' => $data['offset'], 'resultcounttextsingular' => $resultcounttextsingular, 'resultcounttextplural' => $resultcounttextplural, 'extradata' => array('page' => $page), )); $data['pagination'] = $pagination['html']; $data['pagination_js'] = $pagination['javascript']; } /** * Build the html for a list of staff information * * @param object $data * @param string $page * @param string $listtype * @param string $institution */ function build_stafflist_html(&$data, $page, $listtype, $inst='mahara') { global $USER; if ($data) { $data = get_users_data($data, false); } $smarty = smarty_core(); $smarty->assign('page', $page); $smarty->assign('listtype', $listtype); $smarty->assign('inst', $inst); $smarty->assign('USER', $USER); if (count($data) > 5) { $split = ceil(count($data) / 2); $columns = array_chunk($data, $split); } if (isset($columns) && count($columns) == 2) { $smarty->assign('columnleft', $columns[0]); $smarty->assign('columnright', $columns[1]); } else { $smarty->assign('data', isset($data) ? $data : null); } $data['tablerows'] = $smarty->fetch('institution/stafflist.tpl'); } function get_institution_strings_for_users($userids) { $userlist = join(',', $userids); if (!$records = get_records_sql_array(' SELECT ui.usr, i.displayname, ui.staff, ui.admin, i.name FROM {usr_institution} ui JOIN {institution} i ON ui.institution = i.name WHERE ui.usr IN (' . $userlist . ')', array())) { return array(); } $institutions = array(); foreach ($records as &$ui) { if (!isset($institutions[$ui->usr])) { $institutions[$ui->usr] = array(); } $key = ($ui->admin ? 'admin' : ($ui->staff ? 'staff' : 'member')); $institutions[$ui->usr][$key][$ui->name] = $ui->displayname; } foreach ($institutions as &$userinst) { foreach ($userinst as $key => &$value) { $links = array(); foreach ($value as $k => $v) { $url = get_config('wwwroot').'institution/index.php?institution='.$k; $links[] = get_string('institutionlink', 'mahara', $url, hsc($v)); } switch ($key) { case 'admin': $value = get_string('adminofinstitutions', 'mahara', join(', ', $links)); break; case 'staff': $value = get_string('staffofinstitutions', 'mahara', join(', ', $links)); break; default: $value = get_string('memberofinstitutions', 'mahara', join(', ', $links)); break; } } } foreach ($institutions as &$userinst) { $userinst = join('. ',$userinst); } return $institutions; } function get_institution_string_for_user($userid) { $strings = get_institution_strings_for_users(array($userid)); if (empty($strings[$userid])) { return ''; } return $strings[$userid]; } function friends_control_sideblock($returnto='myfriends') { global $USER; $form = array( 'name' => 'friendscontrol', 'plugintype' => 'core', 'pluginname' => 'account', 'autofocus' => false, 'elements' => array( 'friendscontrol' => array( 'type' => 'radio', 'defaultvalue' => $USER->get_account_preference('friendscontrol'), 'separator' => '