/[drupal]/contributions/modules/signup/signup.module
ViewVC logotype

Contents of /contributions/modules/signup/signup.module

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


Revision 1.242 - (show annotations) (download) (as text)
Thu Oct 8 00:01:24 2009 UTC (6 weeks, 6 days ago) by dww
Branch: MAIN
CVS Tags: HEAD
Changes since 1.241: +3 -1 lines
File MIME type: text/x-php
#597808 by dww: Fixed bug that was causing signup limits to be ignored
unless the signup_status module is enabled (broken in rc6 by #581652).
1 <?php
2 // $Id: signup.module,v 1.241 2009/10/03 00:48:24 dww Exp $
3
4
5 /**
6 * @file
7 * The Signup module (http://drupal.org/project/signup) manages replies to
8 * nodes. In particular, it's good for event management. Signup supports
9 * sending reminder emails and automatically closing signups for nodes with
10 * a start time, via the Event module (http://drupal.org/project/event) or
11 * with a CCK date field (http://drupal.org/project/date). Signup provides
12 * extensive Views integration (http://drupal.org/project/views). For more
13 * information, see the README.txt and INSTALL.txt files in this directory.
14 */
15
16 /**
17 * @defgroup signup_core Core drupal hooks
18 */
19
20 /**
21 * Implementation of hook_theme(), the theme registry.
22 */
23 function signup_theme() {
24 $path = drupal_get_path('module', 'signup') .'/theme';
25 return array(
26 'signup_admin_page' => array(
27 'file' => 'admin.inc',
28 'path' => $path,
29 'arguments' => array(
30 'filter_status_form' => NULL,
31 'signup_admin_form' => NULL,
32 ),
33 ),
34 'signup_filter_status_form' => array(
35 'file' => 'admin.inc',
36 'path' => $path,
37 'arguments' => array(
38 'form' => NULL,
39 ),
40 ),
41 'signup_admin_form' => array(
42 'file' => 'admin.inc',
43 'path' => $path,
44 'arguments' => array(
45 'form' => NULL,
46 ),
47 ),
48 'signup_email_token_custom_data' => array(
49 'file' => 'email.inc',
50 'path' => $path,
51 'arguments' => array(
52 'signup_data' => NULL,
53 ),
54 ),
55 'signup_custom_data_email' => array(
56 'file' => 'email.inc',
57 'path' => $path,
58 'arguments' => array(
59 'data' => NULL,
60 ),
61 ),
62 'signup_custom_data_field_text' => array(
63 'file' => 'email.inc',
64 'path' => $path,
65 'arguments' => array(
66 'key' => NULL,
67 'value' => NULL,
68 ),
69 ),
70 'signup_broadcast_sender_copy' => array(
71 'file' => 'email.inc',
72 'path' => $path,
73 'arguments' => array(
74 'raw_message' => NULL,
75 'cooked_message' => NULL,
76 ),
77 ),
78 'signup_user_list' => array(
79 'file' => 'no_views.inc',
80 'path' => $path,
81 'arguments' => array(
82 'node' => NULL,
83 'registered_signups' => NULL,
84 'anon_signups' => NULL,
85 ),
86 ),
87 'signup_user_schedule' => array(
88 'file' => 'no_views.inc',
89 'path' => $path,
90 'arguments' => array(
91 'node' => NULL,
92 ),
93 ),
94 'signup_node_admin_page' => array(
95 'file' => 'node.admin.inc',
96 'path' => $path,
97 'arguments' => array(
98 'node' => NULL,
99 'signup_node_admin_summary_form' => NULL,
100 'signup_node_admin_details_form' => NULL,
101 'signup_form' => NULL,
102 ),
103 ),
104 'signup_node_admin_summary_form' => array(
105 'file' => 'node.admin.inc',
106 'path' => $path,
107 'arguments' => array(
108 'form' => NULL,
109 ),
110 ),
111 'signup_node_admin_details_form' => array(
112 'file' => 'node.admin.inc',
113 'path' => $path,
114 'arguments' => array(
115 'form' => NULL,
116 ),
117 ),
118 'signup_custom_data' => array(
119 'file' => 'node.admin.inc',
120 'path' => $path,
121 'arguments' => array(
122 'data' => NULL,
123 ),
124 ),
125 'signup_attended_text' => array(
126 'file' => 'node.admin.inc',
127 'path' => $path,
128 'arguments' => array(
129 'attended' => NULL,
130 ),
131 ),
132 'signup_signups_closed' => array(
133 'file' => 'node.inc',
134 'path' => $path,
135 'arguments' => array(
136 'node' => NULL,
137 'current_signup' => '',
138 ),
139 ),
140 'signup_anonymous_user_login_text' => array(
141 'file' => 'node.inc',
142 'path' => $path,
143 'arguments' => array(
144 'anon_login_text' => NULL,
145 ),
146 ),
147 'signup_node_output_header' => array(
148 'file' => 'node.inc',
149 'path' => $path,
150 'arguments' => array(
151 'node' => NULL,
152 ),
153 ),
154 'signup_user_form' => array(
155 'file' => 'signup_form.inc',
156 'path' => $path,
157 'arguments' => array(
158 'node' => NULL,
159 ),
160 ),
161 'signup_anonymous_username' => array(
162 'file' => 'signup_form.inc',
163 'path' => $path,
164 'arguments' => array(
165 'form_data' => NULL,
166 'email' => NULL,
167 ),
168 ),
169 'signup_settings_view_label' => array(
170 'file' => 'admin.settings.inc',
171 'path' => drupal_get_path('module', 'signup') .'/includes',
172 'arguments' => array(
173 'view' => NULL,
174 ),
175 ),
176 );
177 }
178
179 /**
180 * Implementation of hook_init().
181 */
182 function signup_init() {
183 _signup_initialize_scheduler_backend();
184 }
185
186 /**
187 * Implementation of hook_cron().
188 *
189 * There are two cron-based tasks that can be performed by the signup module:
190 * sending reminder emails to nodes that will begin soon, and auto-closing
191 * signup on nodes that have already started (depending on settings). Each of
192 * these tasks is rather complicated and depends on the specific date-based
193 * backend module that's currently installed (if any), so each one is handled
194 * in a separate helper function.
195 *
196 * @ingroup signup_core
197 * @see _signup_cron_send_reminders()
198 * @see _signup_cron_autoclose()
199 */
200 function signup_cron() {
201 module_load_include('inc', 'signup', 'includes/cron');
202 _signup_initialize_scheduler_backend();
203 _signup_cron_send_reminders();
204 _signup_cron_autoclose();
205 }
206
207 /**
208 * Private query builder helper function.
209 *
210 * @param $common_sql
211 * Nested array of shared query fragments that are common to all date-based
212 * backends. The supported keys are:
213 * 'primary': the query's primary table and its alias (required).
214 * 'fields': array of fields to SELECT.
215 * 'joins': array of JOIN statements for other tables.
216 * 'where': array of WHERE clauses.
217 * 'group_by': array of GROUP BY fields.
218 *
219 * @param $backend_sql
220 * Similar nested array provided by the date-based backend include file,
221 * except that 'primary' is not allowed.
222 *
223 * @return
224 * Complete SQL statement based on the given query fragments.
225 */
226 function _signup_build_query($common_sql, $type_sql) {
227 // Combine type-specific sql with common_sql.
228 $full_sql = array_merge_recursive($common_sql, $type_sql);
229
230 $sql = 'SELECT '. implode(', ', $full_sql['fields']);
231 $sql .= ' FROM '. $common_sql['primary'] .' ';
232 if (!empty($full_sql['joins'])) {
233 $sql .= implode(' ', $full_sql['joins']);
234 }
235 if (!empty($full_sql['where'])) {
236 $sql .= ' WHERE ('. implode(') AND (', $full_sql['where']) .')';
237 }
238 if (!empty($full_sql['group_by'])) {
239 $sql .= ' GROUP BY '. implode(', ', $full_sql['group_by']);
240 }
241 return $sql;
242 }
243
244 /**
245 * Implementation of hook_help().
246 *
247 * @ingroup signup_core
248 */
249 function signup_help($path, $arg) {
250 switch ($path) {
251 case 'admin/help#signup':
252 return '<p>'.
253 t('Signup allows users to sign up (in other words, register) for content of any type. The most common use is for events, where users indicate they are planning to attend. This module includes options for sending a notification email to a selected email address upon a new user signup (good for notifying event coordinators, etc.) and a confirmation email to users who sign up. Each of these options are controlled per node. When used on event nodes (with <a href="@event_url">event.module</a> installed) or nodes that have a date field (with <a href="@date_url">date.module</a>) and regular cron runs, it can also send out reminder emails to all signups a configurable number of days before the start of the event (also controlled per node) and to automatically close signups 1 hour before their start (general setting). Settings exist for resticting signups to selected roles and content types.', array('@event_url' => url('http://drupal.org/project/event'), '@date_url' => url('http://drupal.org/project/date')))
254 .'</p><p>'.
255 t('To use signup, you must enable which content types should allow signups in administer->settings->content types, and you must also grant the %sign_up_for_content permission to any user role that should be able to sign up in administer->access control. Each signup-enabled node will now have a place for users to sign up.', array('%sign_up_for_content' => 'sign up for content'))
256 .'</p><p>'.
257 t('There are two ways for a user to have administrative access to the signup information about a given node: either the user has the %administer_signups_for_own_content permission and they are viewing a node they created, or the user has the global %administer_all_signups permission. Administrative access allows a user to view all the users who have signed up for the node, along with whatever information they included when they signed up. Signup administrators can also cancel other user\'s signups for the node, and can close signups on the node entirely (meaning no one else is allowed to sign up).', array('%administer_signups_for_own_content' => 'administer signups for own content', '%administer all signups' => 'administer all signups'))
258 .'</p><p>'.
259 t('Default settings for notification email address, reminder emails and confirmation emails are located in administer->settings->signup. These will be the default values used for a signup node unless otherwise specified (to configure these options per node, visit \'edit\' for that node and make the adjustments in the \'Sign up settings\' section).')
260 .'</p><p>'.
261 t('Signups can be manually closed for any node at the %signup_overview page, or on the \'signups\' tab on each node.', array('%signup_overview' => t('Signup overview')))
262 .'</p><p>'.
263 t('The user signup form is fully themable -- form fields may be added or deleted. For more details see the instructions in signup.theme, where a sample user form is included.')
264 .'</p>';
265 }
266
267 // If we're still here, consider the URL for help on various menu tabs.
268 if (($node = menu_get_object()) && arg(2) == 'signups') {
269 switch (arg(3)) {
270 case 'broadcast':
271 return '<p>'. t('This page allows you to send an email message to every user who signed up for this %node_type.', array('%node_type' => node_get_types('name', $node->type))) .'</p>';
272
273 case 'add':
274 return '<p>'. t('This page allows you to sign up another user for this %node_type.', array('%node_type' => node_get_types('name', $node->type))) .'</p>';
275
276 }
277 }
278
279 // See if we need to add our extra checking and validation while configuring
280 // CCK node types for signup. We only want to do this if the $path
281 // doesn't contain 'help#' since hook_help() is invoked twice on admin
282 // pages.
283 if (!empty($arg[0]) && $arg[0] == 'admin' && $arg[1] == 'content'
284 && function_exists('signup_date_check_node_types')
285 ) {
286 if ($arg[2] == 'types' && $arg[3] != 'add') {
287 signup_date_check_node_types();
288 }
289 elseif ($arg[2] == 'node-type') {
290 // Thanks to stupid core handling of node types with underscores, we
291 // need to convert this back to the actual machine-readable type name.
292 signup_date_check_node_types(str_replace('-', '_', $arg[3]));
293 }
294 }
295 }
296
297 /**
298 * Implementation of hook_menu().
299 *
300 * @ingroup signup_core
301 */
302 function signup_menu() {
303 $path = drupal_get_path('module', 'signup') .'/includes';
304 $items = array();
305 $items['admin/settings/signup'] = array(
306 'description' => 'Configure settings for signups.',
307 'access arguments' => array('administer all signups'),
308 'page callback' => 'drupal_get_form',
309 'page arguments' => array('signup_settings_form'),
310 'title' => user_access('access administration pages') ? 'Signup' : 'Signup settings',
311 'file' => 'admin.settings.inc',
312 'file path' => $path,
313 );
314
315 $items['signup/cancel/%signup_menu/%'] = array(
316 'description' => 'View all signup-enabled posts, and open or close signups on them.',
317 'type' => MENU_CALLBACK,
318 'access callback' => '_signup_menu_signup_access',
319 'access arguments' => array(2, 'cancel'),
320 'page callback' => 'signup_cancel_signup_page',
321 'page arguments' => array(2, 3),
322 'file' => 'signup_cancel.inc',
323 'file path' => $path,
324 );
325 $items['signup/edit/%signup_menu'] = array(
326 'title' => 'Edit signup',
327 'page callback' => 'signup_edit_page',
328 'page arguments' => array(2),
329 'access callback' => '_signup_menu_signup_access',
330 'access arguments' => array(2, 'edit'),
331 'type' => MENU_LOCAL_TASK,
332 'file' => 'signup_edit_form.inc',
333 'file path' => $path,
334 );
335
336 $items['admin/content/signup'] = array(
337 'description' => 'View all signup-enabled posts, and open or close signups on them.',
338 'access arguments' => array('administer all signups'),
339 'page callback' => 'signup_admin_page',
340 'title' => 'Signup administration',
341 'file' => 'admin.signup_administration.inc',
342 'file path' => $path,
343 );
344
345 // Conditionally add any available signup-related tabs to nodes.
346 $items['node/%node/signups'] = array(
347 'title' => 'Signups',
348 'page callback' => 'signup_node_tab_page',
349 'page arguments' => array(1),
350 'access callback' => '_signup_menu_access',
351 'access arguments' => array(1, 'any'),
352 'type' => MENU_LOCAL_TASK,
353 'weight' => 20,
354 );
355 $items['node/%node/signups/signup'] = array(
356 'title' => 'Sign up',
357 'type' => MENU_DEFAULT_LOCAL_TASK,
358 'access callback' => '_signup_menu_access',
359 'access arguments' => array(1, 'signup'),
360 'weight' => -10,
361 );
362 $items['node/%node/signups/list'] = array(
363 'title' => 'List',
364 'page callback' => 'signup_user_list_output',
365 'page arguments' => array(1),
366 'access callback' => '_signup_menu_access',
367 'access arguments' => array(1, 'list-tab'),
368 'type' => MENU_LOCAL_TASK,
369 'weight' => -5,
370 'file' => 'node_output.inc',
371 'file path' => $path,
372 );
373 $items['node/%node/signups/admin'] = array(
374 'title' => 'Administer',
375 'page callback' => 'signup_node_admin_page',
376 'page arguments' => array(1),
377 'access callback' => '_signup_menu_access',
378 'access arguments' => array(1, 'admin'),
379 'type' => MENU_LOCAL_TASK,
380 'weight' => 0,
381 'file' => 'node_admin.inc',
382 'file path' => $path,
383 );
384 $items['node/%node/signups/settings'] = array(
385 'title' => 'Settings',
386 'page callback' => 'signup_node_settings_page',
387 'page arguments' => array(1),
388 'access callback' => '_signup_menu_access',
389 'access arguments' => array(1, 'admin'),
390 'type' => MENU_LOCAL_TASK,
391 'weight' => 1,
392 'file' => 'node_settings.inc',
393 'file path' => $path,
394 );
395 $items['node/%node/signups/confirm'] = array(
396 'page callback' => 'drupal_get_form',
397 'page arguments' => array('signup_cancel_multiple_confirm', 1),
398 'access callback' => '_signup_menu_access',
399 'access arguments' => array(1, 'admin'),
400 'type' => MENU_CALLBACK,
401 'file' => 'node_admin.inc',
402 'file path' => $path,
403 );
404 $items['node/%node/signups/add'] = array(
405 'title' => 'Add',
406 'page callback' => 'signup_node_admin_add_user_page',
407 'page arguments' => array(1),
408 'access callback' => '_signup_menu_access',
409 'access arguments' => array(1, 'add'),
410 'type' => MENU_LOCAL_TASK,
411 'weight' => 5,
412 'file' => 'signup_form.inc',
413 'file path' => $path,
414 );
415 $items['node/%node/signups/broadcast'] = array(
416 'title' => 'Signup broadcast',
417 'page callback' => 'drupal_get_form',
418 'page arguments' => array('signup_broadcast_form', 1),
419 'access callback' => '_signup_menu_access',
420 'access arguments' => array(1, 'broadcast'),
421 'type' => MENU_LOCAL_TASK,
422 'weight' => 10,
423 'file' => 'broadcast.inc',
424 'file path' => $path,
425 );
426
427 // Add extra menu items if we're not using views.
428 if (!module_exists('views')) {
429 module_load_include('inc', 'signup', 'includes/no_views');
430 signup_no_views_menu($items);
431 }
432
433 return $items;
434 }
435
436 /**
437 * Determine menu access for a given type of signup menu item.
438 *
439 * This ensures that the node is signup enabled, and that that the current
440 * user should have permission to view the requested menu item type.
441 *
442 * @param $node
443 * The fully loaded node object from the menu autoloader.
444 * @param $menu_type
445 * String specifying what kind of menu item to test access for. Can be:
446 * 'signup': the signup form
447 * 'list': the signup attendee listing
448 * 'list-tab': the signup attendee listing tab
449 * 'admin': the signup administration tab
450 * 'add': the signup administration tab to add other users (requires
451 * that signups are currently open on the given node).
452 * 'broadcast': for the broadcast tab
453 * 'any': if the user has permission to see any of these
454 *
455 * @return
456 * TRUE if the current node is signup enabled and the current user has
457 * permisison to access to requested menu item, otherwise FALSE.
458 *
459 * @see signup_menu()
460 */
461 function _signup_menu_access($node, $menu_type = 'node') {
462 global $user;
463 // If the node isn't signup enabled, immediately return failure.
464 if (empty($node->signup)) {
465 return FALSE;
466 }
467
468 // For certain menu types, invoke a hook to allow other modules to alter the
469 // access behavior for signup menu items. Just relying on hook_menu_alter()
470 // for this won't work, since there are places outside of the menu system,
471 // where we call this function to decide if a user should have access to
472 // something. If multiple modules return a value, the logical OR is used, so
473 // if anyone returns TRUE, access is granted.
474 if (in_array($menu_type, array('signup', 'list', 'admin', 'add', 'broadcast'))) {
475 $access_array = module_invoke_all('signup_menu_access', $node, $menu_type);
476 if (!empty($access_array)) {
477 // Return TRUE if any values are TRUE, otherwise, FALSE.
478 return in_array(TRUE, $access_array);
479 }
480 }
481
482 // No module returned a value in hook_signup_menu_access, so continue with
483 // the main logic.
484 switch ($menu_type) {
485 case 'signup':
486 // See if this user can signup, if the node is configured to display the
487 // signup form on a separate tab, and if the node has signup output.
488 return (user_access('sign up for content') && variable_get('signup_form_location', 'node') == 'tab') && _signup_needs_output($node);
489
490 case 'list':
491 return user_access('view all signups') || _signup_menu_access($node, 'admin');
492
493 case 'list-tab':
494 // See if this user can view signups, and if the site is configured to
495 // display the signup user list as a tab.
496 $user_list = variable_get('signup_display_signup_user_list', 'signup');
497 if ($user_list == 'signup-tab' || $user_list == 'embed-view-tab') {
498 $user_list_tab = TRUE;
499 }
500 else {
501 $user_list_tab = FALSE;
502 }
503 $list_access = _signup_menu_access($node, 'list');
504 return $list_access && $user_list_tab && _signup_needs_output($node);
505
506 case 'admin':
507 $admin_all = user_access('administer all signups');
508 $admin_own = user_access('administer signups for own content') && ($user->uid == $node->uid);
509 return $admin_all || $admin_own;
510
511 case 'add':
512 return ($node->signup_status && _signup_menu_access($node, 'admin'));
513
514 case 'broadcast':
515 $email_all = user_access('email all signed up users');
516 $email_own = user_access('email users signed up for own content') && ($user->uid == $node->uid);
517 return $email_all || $email_own;
518
519 case 'any':
520 $signup = _signup_menu_access($node, 'signup');
521 $list = _signup_menu_access($node, 'list-tab');
522 $admin = _signup_menu_access($node, 'admin');
523 $email = _signup_menu_access($node, 'broadcast');
524 return $signup || $list || $admin || $email;
525 }
526 }
527
528 function _signup_user_menu_access($account) {
529 global $user;
530 return user_access('administer all signups') || $account->uid == $user->uid;
531 }
532
533 /**
534 * Menu loader callback to load a project node.
535 */
536 function signup_menu_load($sid) {
537 if (!is_numeric($sid)) {
538 return FALSE;
539 }
540 $signup = signup_load_signup($sid);
541 if (empty($signup)) {
542 return FALSE;
543 }
544 return $signup;
545 }
546
547 /**
548 * Determine menu access callback for a specific signup.
549 *
550 * @param $signup
551 * The fully-loaded signup object that would be affected.
552 * @param $op
553 * The operation the menu item would perform. Can be 'edit' or 'cancel'.
554 *
555 * @return
556 * TRUE if the operation should be permitted, otherwise FALSE.
557 */
558 function _signup_menu_signup_access($signup, $op) {
559 global $user;
560 $node = node_load($signup->nid);
561 // Ensure the user still has access to view the node they signed up for.
562 if (!node_access('view', $node)) {
563 return FALSE;
564 }
565 // See if the user is allowed to perform the operation on their own signup.
566 $permission = "$op own signups";
567 if (user_access($permission) && ($user->uid == $signup->uid)) {
568 return TRUE;
569 }
570 // Check admin powers for this signup.
571 if (_signup_menu_access($node, 'admin')) {
572 return TRUE;
573 }
574 return FALSE;
575 }
576
577 /**
578 * Menu callback to handle the default tab at node/N/signups
579 *
580 * This tests the user's permission to see what tab they should really see.
581 * If they have permission to signup for nodes and the site is configured to
582 * put the signup form on a separate tab from the node itself, display the
583 * current user's signup info (either a form to signup, or their current
584 * signup). If not, see if they can view a signup user list, and redirect
585 * there. If not, see if they can administer signups and redirect there.
586 * Finally, if they can at least send a signup broadcast, go there.
587 *
588 * @param $node
589 * Fully loaded node object to generate the node/N/signup menu handler for.
590 *
591 * @return
592 * Either the output of the default task ("Sign up") or redirect to a tab.
593 *
594 * @see signup_menu()
595 * @see _signup_menu_access()
596 * @see _signup_current_user_signup();
597 */
598 function signup_node_tab_page($node) {
599 $signup = _signup_menu_access($node, 'signup');
600 $list = _signup_menu_access($node, 'list-tab');
601 $admin = _signup_menu_access($node, 'admin');
602 $broadcast = _signup_menu_access($node, 'broadcast');
603 if ($signup) {
604 module_load_include('inc', 'signup', 'includes/node_output');
605 return _signup_current_user_signup($node, 'tab');
606 }
607 elseif ($list) {
608 drupal_goto("node/$node->nid/signups/list");
609 }
610 elseif ($admin) {
611 drupal_goto("node/$node->nid/signups/admin");
612 }
613 elseif ($broadcast) {
614 drupal_goto("node/$node->nid/signups/broadcast");
615 }
616 }
617
618 /**
619 * Initialize the necessary scheduler backend(s).
620 */
621 function _signup_initialize_scheduler_backend() {
622 module_load_include('inc', 'signup', '/includes/scheduler');
623 _signup_load_scheduler_includes();
624 }
625
626 /**
627 * Implementation of hook_perm().
628 *
629 * @ingroup signup_core
630 */
631 function signup_perm() {
632 return array(
633 'sign up for content',
634 'cancel signups',
635 'cancel own signups',
636 'edit own signups',
637 'view all signups',
638 'administer all signups',
639 'administer signups for own content',
640 'email users signed up for own content',
641 'email all signed up users',
642 );
643 }
644
645 /**
646 * Implementation of hook_user().
647 *
648 * When a user is deleted, cancel all of that user's signups to remove all
649 * instances of that user from the {signup_log} table, free up space in nodes
650 * with signup limits, etc.
651 *
652 * @ingroup signup_core
653 */
654 function signup_user($type, &$edit, &$user, $category = NULL) {
655 switch ($type) {
656 case 'delete':
657 $uids = array();
658 if (is_array($edit['accounts'])) {
659 // A multi-user delete from Admin > User management > Users.
660 $uids = $edit['accounts'];
661 }
662 else {
663 // A single-user delete from the edit tab on the user's profile.
664 $uids[] = $edit['_account']->uid;
665 }
666 foreach ($uids as $uid) {
667 $query = db_query("SELECT * FROM {signup_log} WHERE uid = %d", $uid);
668 while ($signup = db_fetch_object($query)) {
669 signup_cancel_signup($signup);
670 }
671 }
672 break;
673 }
674
675 // If we're not using views, we need to support additional user
676 // operations, for example, to add the user's current signup
677 // schedule to their user profile page.
678 if (!module_exists('views')) {
679 module_load_include('inc', 'signup', 'includes/no_views');
680 return _signup_user_no_views($type, $edit, $user, $category);
681 }
682 }
683
684 /**
685 * Implementation of hook_form_alter().
686 *
687 * @ingroup signup_core
688 */
689 function signup_form_alter(&$form, &$form_state, $form_id) {
690 if (!empty($form['type']['#value'])) {
691 if ($form_id == $form['type']['#value'] .'_node_form') {
692 module_load_include('inc', 'signup', 'includes/node_form');
693 signup_alter_node_form($form, $form_state, $form_id);
694 }
695 }
696 }
697
698 /**
699 * Alters the form for administrator settings per node type.
700 * (admin/content/types)
701 */
702 function signup_form_node_type_form_alter(&$form, &$form_state) {
703 $type = $form['old_type']['#value'];
704 $form['signup'] = array(
705 '#type' => 'fieldset',
706 '#title' => t('Signup settings'),
707 '#collapsible' => TRUE,
708 '#collapsed' => TRUE,
709 );
710 $form['signup']['signup_node_default_state'] = array(
711 '#type' => 'radios',
712 '#title' => t('Signup options'),
713 '#options' => array(
714 'disabled' => t('Disabled'),
715 'allowed_off' => t('Allowed (off by default)'),
716 'enabled_on' => t('Enabled (on by default)'),
717 ),
718 '#default_value' => variable_get('signup_node_default_state_'. $type, 'disabled'),
719 '#description' => t('If %disabled is selected, signups will not be possible for this content type. If %allowed_off is selected, signups will be off by default, but users with the %admin_all_signups permission will be able to allow signups for specific posts of this content type. If %enabled_on is selected, users will be allowed to signup for this content type unless an administrator disbles signups on specific posts.', array('%disabled' => t('Disabled'), '%allowed_off' => t('Allowed (off by default)'), '%enabled_on' => t('Enabled (on by default)'), '%admin_all_signups' => t('administer all signups'))),
720 );
721
722 if (!empty($type) && function_exists('_signup_date_alter_node_type_form')) {
723 _signup_date_alter_node_type_form($form, $form_state);
724 }
725 }
726
727 /**
728 * @defgroup signup_nodeapi Functions for nodeapi integration
729 */
730
731 /**
732 * Implementation of hook_nodeapi().
733 *
734 * @ingroup signup_nodeapi
735 */
736 function signup_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
737 switch ($op) {
738 case 'insert':
739 case 'update':
740 module_load_include('inc', 'signup', 'includes/node_form');
741 signup_save_node($node, $op);
742 break;
743
744 case 'delete':
745 // Clean up the signup tables for the deleted node.
746 db_query("DELETE FROM {signup} WHERE nid = %d", $node->nid);
747 db_query("DELETE FROM {signup_log} WHERE nid = %d", $node->nid);
748 break;
749
750 case 'load':
751 // Check for a signup for this node.
752 // If it's a new node, load the defaults.
753 $result = db_query("SELECT * FROM {signup} WHERE nid = %d", ($node->nid ? $node->nid : 0));
754 $signup = db_fetch_object($result);
755 // Load signup data for both new nodes w/ enabled node types,
756 // and any existing nodes that are already signup enabled.
757 if ((!$node->nid && variable_get('signup_node_default_state_'. $node->type, 'disabled') == 'enabled_on') || ($node->nid && !empty($signup))) {
758 $node->signup = 1;
759 $node->signup_forwarding_email = $signup->forwarding_email;
760 $node->signup_send_confirmation = $signup->send_confirmation;
761 $node->signup_confirmation_email = $signup->confirmation_email;
762 $node->signup_send_reminder = $signup->send_reminder ;
763 $node->signup_reminder_days_before = $signup->reminder_days_before;
764 $node->signup_reminder_email = $signup->reminder_email;
765 $node->signup_close_signup_limit = $signup->close_signup_limit;
766 $node->signup_status = $signup->status;
767 if ($node->nid) {
768 $node->signup_total = db_result(db_query("SELECT COUNT(*) FROM {signup_log} WHERE nid = %d", $node->nid));
769 $node->signup_effective_total = db_result(db_query("SELECT SUM(count_towards_limit) FROM {signup_log} WHERE nid = %d", $node->nid));
770 }
771 }
772 else {
773 $node->signup = 0;
774 }
775 break;
776
777 case 'view':
778 // If this is a signup node, figure out what (if anything) to print.
779 // Only include any of this if we're trying to view the node as
780 // a page, not during the view from comment validation, etc.
781 if ($page && _signup_needs_output($node)) {
782 $info_location = variable_get('signup_form_location', 'node');
783 $list_location = variable_get('signup_display_signup_user_list', 'signup');
784 if ($info_location == 'node' || $list_location == 'signup' || $list_location == 'embed-view') {
785 module_load_include('inc', 'signup', 'includes/node_output');
786 }
787 if ($info_location == 'node') {
788 $signup_info = _signup_node_output($node);
789 if (!empty($signup_info)) {
790 if (module_exists('content')) {
791 // Due to a bug in CCK (http://drupal.org/node/363456), we need
792 // to call this function twice to ensure we get the real value.
793 // The bug is present when you first enable the setting to
794 // display in the node instead of a separate tab, or when you
795 // first upgrade to the version that contains this code.
796 content_extra_field_weight($node->type, 'signup_node_info');
797 $weight = content_extra_field_weight($node->type, 'signup_node_info');
798 }
799 else {
800 $weight = variable_get('signup_info_node_weight_'. $node->type, 10);
801 }
802 $node->content['signup'] = array(
803 '#value' => $signup_info,
804 '#weight' => $weight,
805 );
806 }
807 }
808 if ($list_location == 'signup' || $list_location == 'embed-view') {
809 $signup_list = signup_user_list_output($node);
810 if (!empty($signup_list)) {
811 if (module_exists('content')) {
812 // Call this twice to work-around a bug in CCK (#363456).
813 content_extra_field_weight($node->type, 'signup_node_list');
814 $weight = content_extra_field_weight($node->type, 'signup_node_list');
815 }
816 else {
817 $weight = variable_get('signup_list_node_weight_'. $node->type, 11);
818 }
819 $node->content['signup_list'] = array(
820 '#value' => $signup_list,
821 '#weight' => $weight,
822 );
823 }
824 }
825 }
826 break;
827
828 }
829 }
830
831 /**
832 * Helper function that determines if a given node should have any
833 * signup-related output.
834 *
835 * @param $node A fully loaded node object.
836 *
837 * @return TRUE if this node should have signup output, FALSE if not.
838 *
839 * @see signup_nodeapi()
840 */
841 function _signup_needs_output($node) {
842 if (!$node->signup) {
843 // Not signup enabled at all.
844 return FALSE;
845 }
846 $suppress = module_invoke_all('signup_suppress', $node);
847 if (in_array(TRUE, $suppress)) {
848 // Someone doesn't want signup details printed.
849 return FALSE;
850 }
851 return TRUE;
852 }
853
854 /**
855 * Implementation of hook_action_info().
856 */
857 function signup_action_info() {
858 return array(
859 'signup_cancel_action' => array(
860 'type' => 'signup',
861 'description' => t('Cancel signup'),
862 'configurable' => FALSE,
863 ),
864 'signup_mark_attended_action' => array(
865 'type' => 'signup',
866 'description' => t('Mark signup attended'),
867 'configurable' => FALSE,
868 ),
869 'signup_mark_not_attended_action' => array(
870 'type' => 'signup',
871 'description' => t('Mark signup did not attend'),
872 'configurable' => FALSE,
873 ),
874 );
875 }
876
877 /**
878 * Action callback to cancel a given signup.
879 *
880 * @param $signup
881 * Reference to a fully-loaded signup object to cancel.
882 *
883 * @see signup_load_signup()
884 * @see signup_cancel_signup()
885 */
886 function signup_cancel_action($signup) {
887 signup_cancel_signup($signup);
888 watchdog('action', 'Canceled signup @signup_id.', array('@signup_id' => $signup->sid));
889 }
890
891 /**
892 * Action callback to mark a given signup that the user attended the node.
893 *
894 * @param $signup
895 * Reference to a fully-loaded signup object to record attendance on.
896 *
897 * @return
898 * Nothing: $signup object is modified by reference and the {signup_log}
899 * table is directly UPDATE'ed in the database.
900 */
901 function signup_mark_attended_action(&$signup) {
902 db_query("UPDATE {signup_log} SET attended = %d WHERE sid = %d", TRUE, $signup->sid);
903 $signup->attended = TRUE;
904 watchdog('action', 'Marked signup @signup_id attended.', array('@signup_id' => $signup->sid));
905 }
906
907 /**
908 * Action callback to mark a given signup that the user didn't attend the node.
909 *
910 * @param $signup
911 * Reference to a fully-loaded signup object to record attendance on.
912 *
913 * @return
914 * Nothing: $signup object is modified by reference and the {signup_log}
915 * table is directly UPDATE'ed in the database.
916 */
917 function signup_mark_not_attended_action(&$signup) {
918 db_query("UPDATE {signup_log} SET attended = %d WHERE sid = %d", FALSE, $signup->sid);
919 $signup->attended = FALSE;
920 watchdog('action', 'Marked signup @signup_id did not attend.', array('@signup_id' => $signup->sid));
921 }
922
923 /**
924 * Implementation of hook_views_bulk_operations_object_info().
925 *
926 * Exports information to VBO about what kinds of objects to do operations on.
927 */
928 function signup_views_bulk_operations_object_info() {
929 return array(
930 'signup' => array(
931 'type' => 'signup',
932 'base_table' => 'signup_log',
933 'load' => 'signup_load_signup',
934 'title' => 'label',
935 ),
936 );
937 }
938
939 /**
940 * Deprecated implmentation of hook_object_info().
941 *
942 * This is the old name for hook_views_bulk_operations_object_info(). It was
943 * renamed in VBO at http://drupal.org/node/362534 to prevent possible
944 * namespace colisions. However, it means that modules supporting VBO need to
945 * have both implementations during the transition while users upgrade to VBO
946 * version 6.x-1.4. Once 6.x-1.4 is ubiquitous, this can be removed.
947 */
948 function signup_object_info() {
949 return signup_views_bulk_operations_object_info();
950 }
951
952 /**
953 * Load a $signup object from the given Signup ID (sid).
954 *
955 * In addition to pulling all the fields from the {signup_log} table, this
956 * method also adds a "label" member to the object which is used by Views bulk
957 * operations (VBO) in various parts of its UI.
958 *
959 * @param $sid
960 * Signup ID to load.
961 *
962 * @return
963 * Fully loaded $signup object corresponding to the given ID.
964 */
965 function signup_load_signup($sid) {
966 $signup = db_fetch_object(db_query("SELECT sl.*, n.title, u.name, u.mail FROM {signup_log} sl INNER JOIN {node} n ON sl.nid = n.nid INNER JOIN {users} u ON sl.uid = u.uid WHERE sl.sid = %d", $sid));
967 if (!empty($signup)) {
968 // This label is escaped by VBO, so it needs to be plain text, not HTML.
969 $signup->label = t("!user signup for '!title'", array('!user' => $signup->name, '!title' => $signup->title));
970 }
971 return $signup;
972 }
973
974 /**
975 * Save a $signup object to the database.
976 *
977 * @param $signup
978 * Fully-loaded signup object to save.
979 *
980 * @return
981 * The return value from drupal_write_record().
982 *
983 * @see signup_load_signup()
984 * @see drupal_write_record()
985 */
986 function signup_save_signup(&$signup) {
987 $rval = FALSE;
988 if (is_array($signup->form_data)) {
989 $form_data_array = $signup->form_data;
990 $signup->form_data = serialize($form_data_array);
991 }
992 if (empty($signup->sid)) {
993 $hook = 'signup_insert';
994 $update = array();
995 }
996 else {
997 $hook = 'signup_update';
998 $update = array('sid');
999 }
1000 $rval = drupal_write_record('signup_log', $signup, $update);
1001
1002 // Restore $signup->form_data if we had to serialized it.
1003 if (isset($form_data_array)) {
1004 $signup->form_data = $form_data_array;
1005 }
1006
1007 if (!empty($rval)) {
1008 // If we successfully wrote a record, invoke the appropriate hook.
1009 module_invoke_all($hook, $signup);
1010 }
1011
1012 // Propagate the return value from drupal_write_record().
1013 return $rval;
1014 }
1015
1016 /**
1017 * Retrieve a list of all users who have signed up for a node.
1018 *
1019 * @param $nid
1020 * The node ID to retrieve signups for.
1021 *
1022 * @return
1023 * An array of $signup objects containing all columns from {signup_log}
1024 * along with the 'name', 'mail', and 'language' columns from {users}.
1025 */
1026 function signup_get_signups($nid) {
1027 $signups = array();
1028 $query = db_query("SELECT u.uid, u.name, u.mail, u.language, s_l.* FROM {signup_log} s_l INNER JOIN {users} u ON u.uid = s_l.uid WHERE s_l.nid = %d", $nid);
1029 while ($signup = db_fetch_object($query)) {
1030 $signups[] = $signup;
1031 }
1032 return $signups;
1033 }
1034
1035 /**
1036 * Implementation of hook_content_extra_fields().
1037 */
1038 function signup_content_extra_fields($type_name) {
1039 $extra = array();
1040 if (variable_get('signup_node_default_state_'. $type_name, 'disabled') != 'disabled') {
1041 if (variable_get('signup_form_location', 'node') == 'node') {
1042 $extra['signup_node_info'] = array(
1043 'label' => t('Signup information'),
1044 'description' => t('Signup form or current signup information.'),
1045 'weight' => 10,
1046 );
1047 }
1048 $user_list = variable_get('signup_display_signup_user_list', 'signup');
1049 if ($user_list == 'signup' || $user_list == 'embed-view') {
1050 $extra['signup_node_list'] = array(
1051 'label' => t('Signup user list'),
1052 'description' => t('List of users currently signed up.'),
1053 'weight' => 11,
1054 );
1055 }
1056 }
1057 return $extra;
1058 }
1059
1060 /**
1061 * Cancel the given signup.
1062 *
1063 * @param $signup
1064 * Information about the signup to cancel. Can be either the integer signup
1065 * ID (sid), or a full object of data about the signup (a complete row from
1066 * the {signup_log} table.
1067 * @param $notify_user
1068 * When set to TRUE, a confirmation message is displayed via
1069 * drupal_set_message().
1070 */
1071 function signup_cancel_signup($signup, $notify_user = TRUE) {
1072 // If we only have a numeric sid argument, load the full signup object.
1073 if (is_numeric($signup)) {
1074 $query = db_query('SELECT * FROM {signup_log} WHERE sid = %d', $signup);
1075 $signup = db_fetch_object($query);
1076 }
1077
1078 $node = node_load($signup->nid);
1079
1080 $effective_total_changed = FALSE;
1081 $node->signup_total--;
1082 if (!empty($signup->count_towards_limit)) {
1083 $node->signup_effective_total -= $signup->count_towards_limit;
1084 $effective_total_changed = TRUE;
1085 }
1086
1087 // Invoke hook_signup_cancel().
1088 module_invoke_all('signup_cancel', $signup, $node);
1089
1090 // Delete the record from the {signup_log} table.
1091 db_query('DELETE FROM {signup_log} WHERE sid = %d', $signup->sid);
1092 if ($notify_user) {
1093 drupal_set_message(t('Signup to !title cancelled.', array('!title' => l($node->title, "node/$node->nid"))));
1094 }
1095
1096 if ($effective_total_changed) {
1097 // See if signups should be re-opened if the total dropped below the limit.
1098 _signup_check_limit($node, 'total');
1099 }
1100 }
1101
1102 /**
1103 * Generate a token for the given signup ID.
1104 */
1105 function signup_get_token($sid, $operation) {
1106 $private_key = drupal_get_private_key();
1107 return md5("signup_token:$sid:$operation:$private_key");
1108 }
1109
1110 /**
1111 * Ensure that the given token is valid for the given sid.
1112 */
1113 function signup_valid_token($token, $sid, $operation) {
1114 $private_key = drupal_get_private_key();
1115 return $token == md5("signup_token:$sid:$operation:$private_key");
1116 }
1117
1118 /**
1119 * Callback function for closing signups
1120 * @ingroup signup_callback
1121 */
1122 function signup_close_signup($nid, $cron = 'no') {
1123 db_query("UPDATE {signup} SET status = 0 WHERE nid = %d", $nid);
1124 if ($cron == 'no') {
1125 $node = node_load($nid);
1126 foreach (module_implements('signup_close') as $module) {
1127 $function = $module .'_signup_close';
1128 $function($node);
1129 }
1130 watchdog('signup', 'Signups closed for %title.', array('%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), 'node/'. $nid));
1131 }
1132 }
1133
1134 /**
1135 * Callback function for reopening signups
1136 * @ingroup signup_callback
1137 */
1138 function signup_open_signup($nid, $cron = 'no') {
1139 db_query("UPDATE {signup} SET status = 1 WHERE nid = %d", $nid);
1140 if ($cron == 'no') {
1141 $node = node_load($nid);
1142 foreach (module_implements('signup_open') as $module) {
1143 $function = $module .'_signup_open';
1144 $function($node);
1145 }
1146 watchdog('signup', 'Signups reopened for %title.', array('%title' => $node->title), WATCHDOG_NOTICE, l(t('view'), 'node/'. $nid));
1147 }
1148 }
1149
1150 /**
1151 * Returns a list of content types that have signups enabled
1152 */
1153 function signup_content_types() {
1154 $signup_content_types = array();
1155 foreach (node_get_types('names') as $content_type => $content_name) {
1156 if (variable_get('signup_node_default_state_'. $content_type, 'disabled') != 'disabled') {
1157 $signup_content_types[] = $content_type;
1158 }
1159 }
1160 return $signup_content_types;
1161 }
1162
1163 /**
1164 * Signs up a user to a node.
1165 *
1166 * Caution: This function does not perform any access checking.
1167 *
1168 * NOTE: other modules can call this function. To do so, $signup_form
1169 * must be as follows:
1170 *
1171 * $signup_form['nid'] : nid of the node to which the user will be signed up
1172 * $signup_form['uid'] : uid of the user to sign up
1173 * $signup_form['signup_anon_mail'] : Optional. An email address of an
1174 * anonymous user to sign up. Only include this if the user is not
1175 * already registered with the site. $signup_form['uid'] will
1176 * automatically be set to 0 if this element is passed in. NOTE: It's
1177 * highly recommended to call the signup_validate_anon_email
1178 * function in the external module's validation cycle or perform
1179 * that function's validation logic prior to passing in this element!
1180 * $signup_form['signup_form_data'] : an array of key/value pairs --
1181 * key is the data category, value is the user input
1182 *
1183 * @param $notify_user
1184 * When set to TRUE, confirmation messages are displayed via
1185 * drupal_set_message() and confirmation/forwarding emails are sent if
1186 * enabled for the current node.
1187 *
1188 * @return
1189 * The signup id (SID) if the user was successfully signed up, FALSE if the
1190 * user is already signed up or signups are not allowed on the given node.
1191 */
1192 function signup_sign_up_user($signup_form, $notify_user = TRUE) {
1193 $node = node_load($signup_form['nid']);
1194
1195 // Since this is an API call, we need to validate that there are no
1196 // duplicate signups being generated, even though through the usual
1197 // UI, there's no way to reach this function if it's a duplicate.
1198 // How to find duplicates is different for anonymous and
1199 // authenticated signups.
1200 $signup_anon_mail = '';
1201 if (!empty($signup_form['signup_anon_mail'])) {
1202 $signup_anon_mail = $signup_form['signup_anon_mail'];
1203 // Ensure the uid is 0 for anonymous signups, even if it's not duplicate.
1204 $signup_form['uid'] = 0;
1205 // Now, see if this email is already signed-up.
1206 if (db_result(db_query("SELECT COUNT(*) FROM {signup_log} WHERE anon_mail = '%s' AND nid = %d", $signup_anon_mail, $node->nid))) {
1207 drupal_set_message(t('Anonymous user %email is already signed up for %title', array('%email' => $signup_anon_mail, '%title' => $node->title), 'error'));
1208 return FALSE;
1209 }
1210 }
1211 else {
1212 // This query does the JOIN on {users} so we can avoid a full
1213 // user_load() just so theme('username') can have the data it
1214 // needs for the error message we might print out.
1215 $result = db_query("SELECT sl.uid, u.name FROM {signup_log} sl INNER JOIN {users} u ON sl.uid = u.uid WHERE sl.uid = %d AND sl.nid = %d", $signup_form['uid'], $signup_form['nid']);
1216 $account = db_fetch_object($result);
1217 if (!empty($account)) {
1218 drupal_set_message(t('User !user is already signed up for %title', array('!user' => theme('username', $account), '%title' => $node->title)), 'error');
1219 return FALSE;
1220 }
1221 }
1222
1223 // If we made it this far, we're going to need the full user object.
1224 $account = user_load(array('uid' => $signup_form['uid']));
1225
1226 if ($node->signup_status) {
1227 // Grab the current time once, since we need it in a few places.
1228 $current_time = time();
1229
1230 // Allow other modules to inject data into the user's signup data.
1231 $extra = module_invoke_all('signup_sign_up', $node, $account);
1232 $signup_info = array();
1233 if (!empty($signup_form['signup_form_data'])) {