/[drupal]/contributions/modules/temporary_invitation/temporary_invitation_api.inc
ViewVC logotype

Contents of /contributions/modules/temporary_invitation/temporary_invitation_api.inc

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.13 - (show annotations) (download) (as text)
Thu Feb 5 13:30:48 2009 UTC (9 months, 3 weeks ago) by jpetso
Branch: MAIN
CVS Tags: DRUPAL-5--2-4, DRUPAL-5--2-3, HEAD
Changes since 1.12: +2 -4 lines
File MIME type: text/x-php
* Make it possible for modules to alter the email template and body.
* Streamline message template retrieval, and make it pluggable too.
* Update the copyright notices, and remove the amateur GPL header
  (drupal.org now explicitely states GPLv2+ anyways).
1 <?php
2 // $Id: temporary_invitation_api.inc,v 1.12 2009/02/02 17:19:25 jpetso Exp $
3 /**
4 * @file
5 * Temporary Invitation - Invite guests for a limited timespan.
6 * This file contains the "reusable" parts of the Temporary Invitation module.
7 *
8 * Copyright 2007 by Jakob Petsovits ("jpetso", http://drupal.org/user/56020)
9 * Copyright 2006 by D R Pratten <http://www.davidpratten.com>
10 */
11
12
13 /**
14 * Invokes hook_temporary_invitation() for all modules that implemented it.
15 *
16 * @param $op
17 * What kind of action is being performed. Possible values:
18 * - 'insert': The invitation has been created and is readily accessible.
19 * - 'login': The user associated with the invitation has just logged in
20 * by using the login ticket.
21 * - 'delete': The invitation is about to be deleted from the database, either
22 * because it has expired or because the host user has been
23 * deleted.
24 * @param $invitation
25 * An invitation object, as returned by temporary_invitation_load().
26 * @param $passcode
27 * If $op == 'insert' and $op == 'login', this contains the passcode
28 * as plaintext. For all other hook invocations this is NULL.
29 * @param $mail
30 * If $op == 'insert', this contains a object with the attributes
31 * 'to' (mail address), 'subject' (string), 'body' (string) and
32 * 'copy_to_self' (boolean). This mail will be sent out to the invited user
33 * and (if 'copy_to_self' is TRUE) to the host user as a BCC copy.
34 * For all other hook invocations this is NULL.
35 */
36 function temporary_invitation_invoke_all($op, $invitation, $passcode = NULL, $mail = NULL) {
37 module_invoke_all('temporary_invitation', $op, $invitation, $passcode, $mail);
38 }
39
40
41 // array_fill_keys() (which is used in here) is only available in PHP 5.2
42 // or higher, so define it here for previous versions. This implementation
43 // depends on array_combine() which still requires PHP 5 or higher,
44 // so define that one as well.
45
46 if (!function_exists('array_combine')) {
47 function array_combine($array1, $array2)
48 {
49 for ($i = 0; $i < count($array1); $i++) {
50 $return_array[$array1[$i]] = $array2[$i];
51 }
52 if (isset($return_array)) {
53 return $return_array;
54 }
55 return FALSE;
56 }
57 }
58
59 if (!function_exists('array_fill_keys')) {
60 function array_fill_keys($keys, $value) {
61 if (count($keys) == 0) {
62 return array();
63 }
64 return array_combine($keys, array_fill(0, count($keys), $value));
65 }
66 }
67
68
69
70 /**
71 * Retrieve all invitation objects for a given host user.
72 *
73 * @param $host_uid
74 * User ID of the user whose invitations shall be retrieved.
75 * @param $include_expired
76 * If TRUE, this function also considers invitations that are already expired.
77 * Otherwise (by default), only valid invitations are included in the result.
78 *
79 * @returns An array of invitations for the host user. The invitations
80 * themselves are objects with the attributes 'host_uid',
81 * 'invited_uid', 'name', 'created' and 'ticket' (where 'ticket' is a
82 * complete ticket object as returned by loginticket_load()).
83 */
84 function temporary_invitation_load_array($host_uid, $include_expired = FALSE) {
85 $result = db_query('SELECT * FROM {temporary_invitation}
86 WHERE host_uid = %d
87 ORDER BY created DESC', $host_uid);
88 $invitations = array();
89
90 while ($invitation = db_fetch_object($result)) {
91 $ticket = loginticket_load('temporary_invitation',
92 array('passcode_md5' => $invitation->passcode_md5), $include_expired
93 );
94 if ($ticket !== FALSE) {
95 $invitation->ticket = $ticket;
96 $invitation->invited_uid = $ticket->uid;
97 $invitations[] = $invitation;
98 }
99 }
100 return $invitations;
101 }
102
103 /**
104 * Retrieve the invitation object for a given passcode.
105 *
106 * @param $passcode_md5
107 * The MD5 hash of the invitation's passcode.
108 * @param $include_expired
109 * If TRUE, this function also considers invitations that are already expired.
110 * Otherwise (by default), only a valid invitation is included in the result.
111 *
112 * @returns An invitation object with attributes named 'host_uid',
113 * 'invited_uid', 'name', 'created' and 'ticket' (where 'ticket' is a
114 * complete ticket object as returned by loginticket_load()).
115 */
116 function temporary_invitation_load($passcode_md5, $include_expired = FALSE) {
117 $result = db_query("SELECT * FROM {temporary_invitation}
118 WHERE passcode_md5 = '%s'", $passcode_md5);
119
120 while ($invitation = db_fetch_object($result)) {
121 $ticket = loginticket_load('temporary_invitation',
122 array('passcode_md5' => $invitation->passcode_md5), $include_expired
123 );
124 if ($ticket !== FALSE) {
125 $invitation->ticket = $ticket;
126 $invitation->invited_uid = $ticket->uid;
127 return $invitation;
128 }
129 }
130 return FALSE;
131 }
132
133 /**
134 * Create a temporary invitation, that is, create a new user and relate this
135 * user with the given host user, and then create a login ticket that
136 * the generated user can use to login.
137 * When the ticket is created, hook_temporary_invitation($op='insert') is
138 * called (which in turn is used by Temporary Invitation to notify the user
139 * by sending login code and the link to the login form.
140 *
141 * @param $host_user
142 * The user that manages the invitation.
143 * @param $invitation_name
144 * Short description for the invitation.
145 * @param $mail
146 * An object with the attributes 'to' (mail address), 'subject' (string),
147 * 'body' (string) and 'copy_to_self' (boolean). This mail will be sent out
148 * to the invited user and (if 'copy_to_self' is TRUE) to the host user
149 * as a BCC copy.
150 * @param $expiration_time
151 * A Unix timestamp specifying when the invitation will expire.
152 *
153 * @return An array, with the following values given for these keys:
154 * 'invitation': An object containing the newly created invitation,
155 * as returned by temporary_invitation_load().
156 * 'passcode': The passcode of the invitation as plaintext.
157 */
158 function temporary_invitation_create($host_user, $invitation_name, $mail, $expiration_time) {
159 $invited_user = temporary_invitation_create_user($host_user, $mail->to);
160 $passcode = loginticket_create('temporary_invitation', $invited_user, $expiration_time);
161 $passcode_md5 = md5($passcode);
162
163 db_query("INSERT INTO {temporary_invitation}
164 (passcode_md5, host_uid, name, created)
165 VALUES ('%s', %d, '%s', %d)",
166 $passcode_md5, $host_user->uid, $invitation_name, time());
167
168 $invitation = temporary_invitation_load($passcode_md5);
169 temporary_invitation_invoke_all('insert', $invitation, $passcode, $mail);
170
171 return array(
172 'invitation' => $invitation,
173 'passcode' => $passcode,
174 );
175 }
176
177 /**
178 * Create a user with a random password and username.
179 * If a user with the same mail address already exists, this user will instead
180 * be suitably prepared so that she's able to log in with the passcode.
181 *
182 * @param $host_user
183 * The user object of the user who invites the guest.
184 * @param $invited_user_mail
185 * The mail address of the invited user.
186 *
187 * @return The user object as delivered by user_save().
188 */
189 function temporary_invitation_create_user($host_user, $invited_user_mail) {
190 $roles = array_fill_keys(_temporary_invitation_get_user_roles_default(), 1);
191
192 if ($user = user_load(array('mail' => $invited_user_mail))) {
193 // The user already exists, prepare that user to receive an invitation.
194 $userinfo = array(
195 'init' => $invited_user_mail,
196 'status' => 1,
197 );
198 if (_temporary_invitation_existing_user_gets_roles()) {
199 // array_merge() destroys the indexes, so we use "+" for merging
200 $userinfo['roles'] = $user->roles + $roles;
201 }
202 }
203 else {
204 // The user doesn't exist yet, create a new one to invite.
205 do {
206 $suffix = isset($username) ? ('-'. user_password(4)) : '';
207 $username = $invited_user_mail . $suffix;
208 $user = user_load(array('name' => $username));
209 } while ($user); // loop until we have a username that doesn't exist
210
211 // Likewise, we need a unique email address.
212 // We want to avoid that invited people can't create their own accounts
213 // anymore, so instead of using the real mail address, we use fake one.
214 // Not totally clean, but better than anything else I can think of.
215 do {
216 $fake_mail = 'temporary-user-'. user_password(8) .'@localhost';
217 $user = user_load(array('mail' => $fake_mail));
218 } while ($user); // loop until we have a mail address that doesn't exist
219
220 $user = NULL;
221 $userinfo = array(
222 'name' => $username,
223 'pass' => user_password(8),
224 'mail' => $fake_mail,
225 'init' => $invited_user_mail,
226 'status' => 1,
227 'roles' => $roles,
228 // Only users with the following property will be blocked or deleted.
229 // We really want to keep current users untouched.
230 'temporary_invitation_remove_user' => TRUE,
231 );
232 }
233 $user = user_save($user, $userinfo);
234
235 watchdog('user',
236 t('New temporarily invited user: %username.', array('%username' => $user->name)),
237 WATCHDOG_NOTICE,
238 l(t('edit'), 'user/'. $user->uid .'/edit')
239 );
240 return $user;
241 }
242
243 /**
244 * Update all roles of invited users with a new set of roles.
245 * In order to make sure no other roles are affected (in case the user
246 * has other, unrelated roles as well) only those will be deleted that
247 * have been selected before.
248 *
249 * @param $old_roles
250 * A plain list of old role ids for invited users.
251 * @param $new_roles
252 * A plain list of new role ids for invited users.
253 */
254 function temporary_invitation_update_user_roles($old_roles, $new_roles) {
255 $new_roles = array_fill_keys($new_roles, 1);
256 $tickets = loginticket_load_array('temporary_invitation', array(), TRUE);
257
258 foreach ($tickets as $ticket) {
259 $user = user_load(array('uid' => $ticket->uid));
260
261 if ($user !== FALSE) {
262 if ($user->temporary_invitation_remove_user /* we created that user */
263 || _temporary_invitation_existing_user_gets_roles()) {
264 $roles = $user->roles + $new_roles; // array_merge() destroys the indexes
265 foreach ($old_roles as $rid) {
266 if (!isset($new_roles[$rid])) {
267 unset($roles[$rid]);
268 }
269 }
270 user_save($user, array('roles' => $roles));
271 }
272 }
273 }
274 }
275
276 /**
277 * Delete the invitation for the given user that had been invited, and call
278 * hook_temporary_invitation($op='delete', $invitation, NULL) before that.
279 *
280 * @param $passcode_md5
281 * The MD5 hash of the passcode for the invitation that should be deleted
282 * @param $redirect
283 * If TRUE, drupal_goto() is called, which uses the 'destination' parameter
284 * for redirecting to the page that is given in the delete link.
285 * If FALSE, drupal_goto() is not called, and you can continue execution.
286 */
287 function temporary_invitation_delete($passcode_md5, $redirect = FALSE) {
288 global $user;
289 $invitation = temporary_invitation_load($passcode_md5, TRUE);
290
291 // safety check: only the user possessing the invitation may delete it
292 if ($invitation !== FALSE
293 && (($user->uid == $invitation->host_uid && user_access('manage own temporary invitations'))
294 || user_access('manage temporary invitations')))
295 {
296 loginticket_delete($invitation->ticket);
297
298 if ($redirect) {
299 drupal_goto();
300 }
301 }
302 else {
303 watchdog('user',
304 t('Detected malicious attempt to delete an invitation.'),
305 WATCHDOG_WARNING,
306 l(t('view'), 'user/'. $user->uid)
307 );
308 if ($redirect) {
309 drupal_access_denied();
310 }
311 }
312 }
313
314
315 /**
316 * Retrieve the invited user for a given invitation.
317 * Note that the mail address doesn't generally contain the address that was
318 * given for the invitation mail, as this would prevent that user from
319 * registering a regular account on this site. Instead, the 'init' property
320 * of the user object contains the invited mail address.
321 */
322 function temporary_invitation_get_invited_user($invitation) {
323 global $user;
324
325 if ($invitation->invited_uid == $user->uid) {
326 return drupal_clone($user);
327 }
328 else {
329 return user_load(array('uid' => $invitation->invited_uid));
330 }
331 }
332
333 /**
334 * Retrieve the host user for a given invitation.
335 */
336 function temporary_invitation_get_host_user($invitation) {
337 global $user;
338
339 if ($invitation->host_uid == $user->uid) {
340 return drupal_clone($user);
341 }
342 else {
343 return user_load(array('uid' => $invitation->host_uid));
344 }
345 }
346
347
348
349 /**
350 * Implementation of hook_user():
351 * If a host user is deleted, also do away with the associated invitations.
352 * hook_temporary_invitation($op='delete', $invitation, NULL) is called
353 * for each deleted invitation, before the delete action is performed.
354 */
355 function temporary_invitation_user($op, &$edit, &$account, $category = NULL) {
356 if ($op == 'delete') {
357 $invitations = temporary_invitation_load_array($account->uid, TRUE);
358 $tickets = array();
359
360 foreach ($invitations as $invitation) {
361 $tickets[] = $invitation->ticket;
362 }
363 // let loginticket_delete() invoke all the cleanup operations by itself
364 loginticket_delete($tickets);
365 }
366 }
367
368 /**
369 * Implementation of hook_loginticket():
370 * When a ticket is deleted, also delete the associated user
371 * and invitation entry from the database, and call
372 * hook_temporary_invitation($op='delete', $invitation, NULL)
373 * before performing the actual delete actions.
374 */
375 function temporary_invitation_loginticket($op, $ticket, $passcode) {
376 if ($ticket->purpose != 'temporary_invitation') {
377 return;
378 }
379
380 switch ($op) {
381 case 'delete':
382 $invitation = temporary_invitation_load($ticket->passcode_md5, TRUE);
383 if ($invitation !== FALSE) {
384 temporary_invitation_invoke_all('delete', $invitation, NULL, NULL);
385 }
386
387 // Delete the invitation entry.
388 db_query("DELETE FROM {temporary_invitation} WHERE passcode_md5 = '%s'",
389 $ticket->passcode_md5);
390
391 // Delete the user if (s)he still exists at all.
392 $user = user_load(array('uid' => $ticket->uid));
393
394 if ($user && $user->temporary_invitation_remove_user) {
395 // Check if any other valid invitations remain for this user.
396 // After all, we don't want to remove him when another invitation
397 // is still available.
398 $remaining_tickets = loginticket_load_array(
399 'temporary_invitation', array('uid' => $ticket->uid)
400 );
401 foreach ($remaining_tickets as $key => $remaining_ticket) {
402 if ($remaining_ticket->passcode_md5 == $ticket->passcode_md5) {
403 unset($remaining_tickets[$key]); // ignore the just deleted one.
404 }
405 }
406
407 if (empty($remaining_tickets)) {
408 // No more invitations left for this user: get rid of the sucker.
409 switch (_temporary_invitation_get_delete_action()) {
410 case 'block':
411 if ($user->status == 1) {
412 user_save($user, array('status' => 0));
413 }
414 break;
415
416 case 'delete':
417 // Workaround to disable the "<username> has been deleted." message.
418 $messages = drupal_get_messages();
419
420 user_delete(array(), $ticket->uid);
421
422 // Write back the old messages.
423 $_SESSION['messages'] = $messages;
424 break;
425 }
426 }
427 }
428 return;
429
430 case 'login':
431 $invitation = temporary_invitation_load($ticket->passcode_md5);
432 if ($invitation !== FALSE) {
433 temporary_invitation_invoke_all('login', $invitation, $passcode, NULL);
434 }
435
436 default:
437 return;
438 }
439 }
440
441
442
443 /**
444 * Implementation of hook_elements():
445 * Register the duration widget with the Forms API and set default values.
446 */
447 function temporary_invitation_elements() {
448 $type['duration'] = array(
449 // the $form_values elements will be named '#name'_metric and '#name'_value
450 '#name' => 'duration',
451 '#default_metric' => 'mon',
452 '#default_value' => 1,
453 '#process' => array('_temporary_invitation_duration_process' => array()),
454 );
455 return $type;
456 }
457
458 /**
459 * The process hook for 'duration' type widgets.
460 * Called after defining the form and while building it.
461 */
462 function _temporary_invitation_duration_process($element) {
463 $name = $element['#name'];
464 $element[$name .'_value'] = array(
465 '#type' => 'textfield',
466 '#default_value' => $element['#default_value'],
467 '#size' => 3,
468 '#maxlength' => 2,
469 '#required' => TRUE,
470 );
471 $element[$name .'_metric'] = array(
472 '#type' => 'select',
473 '#default_value' => $element['#default_metric'],
474 '#options' => _temporary_invitation_duration_metric_options(),
475 );
476 return $element;
477 }
478
479 function _temporary_invitation_duration_metric_options() {
480 return array(
481 'hours' => t('hours'),
482 'mday' => t('days'),
483 'mon' => t('months'),
484 );
485 }
486
487 function theme_duration($element) {
488 // class="container-inline" makes child widgets align horizontally
489 return theme('form_element', $element,
490 '<div class="container-inline">'. $element['#children']. '</div>'
491 );
492 }
493
494 /**
495 * Add the timespan of a given duration widget to a given point in time.
496 *
497 * @param $name
498 * The '#name' property of the duration widget.
499 * @param $form_values
500 * The $form_values array of the returned form, as received in form_submit().
501 * @param $timestamp
502 * The time to which the duration is added. Defaults to the current time.
503 */
504 function temporary_invitation_duration_widget_add($name, $form_values, $timestamp = NULL) {
505 return temporary_invitation_duration_add(
506 $form_values[$name .'_metric'], $form_values[$name .'_value'], $timestamp
507 );
508 }
509
510 /**
511 * Add a timespan to a given point in time.
512 *
513 * @param $metric
514 * What kind of value should be added to the timestamp. This may be one of
515 * 'hours', 'minutes', 'seconds', 'mon', 'mday' or 'year', as used in the
516 * associative array returned by getdate().
517 * @param $value
518 * How much of the time metric should be added.
519 * @param $timestamp
520 * The time to which the duration is added. Defaults to the current time.
521 */
522 function temporary_invitation_duration_add($metric, $value, $timestamp = NULL) {
523 if (!isset($timestamp)) {
524 $timestamp = time();
525 }
526 $date = getdate($timestamp);
527 $date[$metric] += $value;
528
529 return mktime(
530 $date['hours'], $date['minutes'], $date['seconds'],
531 $date['mon'], $date['mday'], $date['year']
532 );
533 }
534
535 /**
536 * Subtract the timespan of a given duration widget from a given point in time.
537 *
538 * @param $name
539 * The '#name' property of the duration widget.
540 * @param $form_values
541 * The $form_values array of the returned form, as received in form_submit().
542 * @param $timestamp
543 * The time from which the duration is subtracted.
544 * Defaults to the current time.
545 */
546 function temporary_invitation_duration_widget_subtract($name, $form_values, $timestamp = NULL) {
547 return temporary_invitation_duration_subtract(
548 $form_values[$name .'_metric'], $form_values[$name .'_value'], $timestamp
549 );
550 }
551
552 /**
553 * Subtract a timespan to a given point in time.
554 *
555 * @param $metric
556 * What kind of value should be subtracted from the timestamp. This may be
557 * one of 'hours', 'minutes', 'seconds', 'mon', 'mday' or 'year', as used
558 * in the associative array returned by getdate().
559 * @param $value
560 * How much of the time metric should be subtracted.
561 * @param $timestamp
562 * The time from which the duration is subtracted.
563 * Defaults to the current time.
564 */
565 function temporary_invitation_duration_subtract($metric, $value, $timestamp = NULL) {
566 if (!isset($timestamp)) {
567 $timestamp = time();
568 }
569 temporary_invitation_duration_add($metric, $value * -1, $timestamp);
570 }
571
572
573
574 /**
575 * Retrieve the roles that will be assigned to newly invited users.
576 */
577 function _temporary_invitation_get_user_roles_default() {
578 return variable_get('temporary_invitation_user_roles_default', array());
579 }
580
581 /**
582 * Retrieve the roles that will be assigned to newly invited users.
583 */
584 function _temporary_invitation_existing_user_gets_roles() {
585 return !variable_get('temporary_invitation_no_roles_for_existing_user', TRUE);
586 }
587
588 /**
589 * Find out what shall be done with the user belonging to an invitation
590 * if the invitation is deleted.
591 *
592 * @param $which
593 * If set to 'all', an associative array consisting of all available
594 * delete action keys and descriptions is returned.
595 * If set to 'selected', the function returns the currently selected key,
596 * which is either 'delete' or 'block'.
597 */
598 function _temporary_invitation_get_delete_action($which = 'selected') {
599 if ($which == 'selected') {
600 return variable_get('temporary_invitation_delete_action', 'delete');
601 }
602 else if ($which == 'all') {
603 return array(
604 'delete' => t('Delete the associated user'),
605 'block' => t('Block the associated user'),
606 );
607 }
608 }

  ViewVC Help
Powered by ViewVC 1.1.2