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

Contents of /contributions/modules/promotion/promotion.module

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


Revision 1.15 - (show annotations) (download) (as text)
Tue Feb 19 22:27:35 2008 UTC (21 months ago) by hanenkamp
Branch: MAIN
CVS Tags: HEAD
Changes since 1.14: +2 -2 lines
File MIME type: text/x-php
Fixed a problem with the way the string was formatted on insert in the previous patch.
1 <?php
2 // $Id: promotion.module,v 1.14 2008/02/19 22:24:30 hanenkamp Exp $
3
4 // Size of the pager on the Promotional Codes screen
5 define('PROMO_CODES_PER_PAGE', 20);
6 define('PROMO_STAT_USERS_PER_PAGE', 50);
7
8 // Match types
9 define('PROMO_TYPE_EXACT', 1);
10 define('PROMO_TYPE_SIMPLE', 2);
11 define('PROMO_TYPE_ADVANCED', 3);
12
13 // Activation action
14 define('PROMO_ACT_IMMEDIATE', 1);
15 define('PROMO_ACT_ROLES_WAIT', 2);
16 define('PROMO_ACT_ALL_WAIT', 3);
17
18 // Expiraiton actions
19 define('PROMO_EXP_ACTION_DEMOTE', 1);
20 define('PROMO_EXP_ACTION_DEMOTE_AND_BLOCK', 2);
21 define('PROMO_EXP_ACTION_BLOCK', 3);
22 define('PROMO_EXP_ACTION_DELETE', 4);
23
24 /**
25 * Implements hook_help().
26 */
27 function promotion_help($section) {
28 switch ($section) {
29 case 'admin/settings/promotion':
30 $output = t('Use this settings screen to set the general settings affecting all promotions on your web site.');
31 break;
32 }
33
34 return $output;
35 }
36
37 /**
38 * Implements hook_perm().
39 */
40 function promotion_perm() {
41 return array('administer promotion', 'manage promotional codes', 'access promotion statistics');
42 }
43
44 /**
45 * Provides the main settings page.
46 */
47 function promotion_settings() {
48 $form = array();
49
50 $user_register = variable_get('user_register', 1);
51
52 // Disabled unless user_register == 1 or 2. I do this to avoid allowing
53 // modules like Invite causing problems (Invite sets user_register = 3 to mean
54 // by invitation only, which isn't compatible with this anyway)
55 if ($user_register != 1 && $user_register != 2) {
56 $form['disabled_message'] = array(
57 '#value' => '<div class="error">'.t('Disabled. Cannot customize registration unless public registrations are allowed by visitors on the <a href="!user_settings">User settings</a> screen.', array( '!user_settings' => url('admin/user/settings') )).'</div>',
58 );
59 }
60
61 // Enabled.
62 else {
63
64 // Is promotion required? Default is no.
65 $form['promotion_required'] = array(
66 '#type' => 'checkbox',
67 '#title' => t('Require a valid promotional code to register for an account.'),
68 '#default_value' => variable_get('promotion_required', 0),
69 );
70 }
71
72 return system_settings_form($form);
73 }
74
75 /**
76 * Implements hook_menu().
77 */
78 function promotion_menu($may_cache) {
79 $items = array();
80
81 if ($may_cache) {
82 // General settings page
83 $items[] = array(
84 'path' => 'admin/settings/promotion',
85 'title' => t('Promotion settings'),
86 'description' => t('Configure the general settings for the promotional code system.'),
87 'callback' => 'drupal_get_form',
88 'callback arguments' => 'promotion_settings',
89 'access' => user_access('administer promotion'),
90 'type' => MENU_NORMAL_ITEM,
91 );
92
93 // Configure available promotions
94 $items[] = array(
95 'path' => 'admin/user/promotion',
96 'title' => t('Promotional codes'),
97 'description' => t('Configure the promotional codes available for web site registration.'),
98 'callback' => 'promotion_list',
99 'access' => user_access('manage promotional codes'),
100 'type' => MENU_NORMAL_ITEM,
101 );
102
103 // The default task of listing the promotions
104 $items[] = array(
105 'path' => 'admin/user/promotion/list',
106 'title' => t('List'),
107 'type' => MENU_DEFAULT_LOCAL_TASK,
108 );
109
110 $items[] = array(
111 'path' => 'admin/user/promotion/add',
112 'title' => t('New code'),
113 'callback' => 'promotion_add',
114 'access' => user_access('manage promotional codes'),
115 'type' => MENU_LOCAL_TASK,
116 );
117
118 $items[] = array(
119 'path' => 'admin/logs/promotion',
120 'title' => t('Promotion Statistics'),
121 'description' => t('View how popular your sign-up promotions have been.'),
122 'callback' => 'promotion_statistics',
123 'access' => user_access('access promotion statistics'),
124 'type' => MENU_NORMAL_ITEM,
125 );
126 }
127
128 else {
129 if (arg(0) == 'admin' && arg(1) == 'user' && arg(2) == 'promotion' && is_numeric(arg(3))) {
130 $pid = arg(3);
131 $items[] = array(
132 'path' => 'admin/user/promotion/' . $pid,
133 'title' => t('Edit'),
134 'callback' => 'promotion_edit',
135 'callback arguments' => array($pid),
136 'access' => user_access('manage promotional codes'),
137 'type' => MENU_CALLBACK,
138 );
139 $items[] = array(
140 'path' => 'admin/user/promotion/' . $pid . '/delete',
141 'title' => t('Delete'),
142 'callback' => 'drupal_get_form',
143 'callback arguments' => array('promotion_delete_confirm', $pid),
144 'access' => user_access('manage promotional codes'),
145 'type' => MENU_CALLBACK,
146 );
147 }
148
149 if (arg(0) == 'admin' && arg(1) == 'logs' && arg(2) == 'promotion' && is_numeric(arg(3))) {
150 $pid = arg(3);
151 $detail = arg(4);
152
153 $items[] = array(
154 'path' => 'admin/logs/promotion/' . $pid,
155 'title' => t('Promotion Statistics'),
156 'callback' => 'promotion_statistics',
157 'callback arguments' => array($pid, $detail),
158 'access' => user_access('access promotion statistics'),
159 'type' => MENU_CALLBACK,
160 );
161 }
162 }
163
164 return $items;
165 }
166
167 function promotion_list() {
168 $header = array(
169 array('data' => t('Name'), 'field' => 'name'),
170 array('data' => t('Active'), 'field' => 'active_promotion'),
171 array('data' => t('Code'), 'field' => 'simple_code'),
172 array('data' => t('Start Date'), 'field' => 'start_date'),
173 array('data' => t('End Date'), 'field' => 'end_date'),
174 array('data' => t('Expiration'), 'field' => 'expiration_date'),
175 );
176
177 $sql = 'SELECT pid, name, active_promotion, simple_code, start_date, end_date, expiration_date FROM {promotion_codes}';
178 $sql .= tablesort_sql($header);
179 $result = pager_query($sql, PROMO_CODES_PER_PAGE);
180
181 while ($promo_code = db_fetch_object($result)) {
182 $rows[] = array(
183 l($promo_code->name, 'admin/user/promotion/'.$promo_code->pid),
184 $promo_code->active_promotion ? 'yes' : 'no',
185 $promo_code->simple_code,
186 $promo_code->start_date ? format_date($promo_code->start_date) : 'none',
187 $promo_code->end_date ? format_date($promo_code->end_date) : 'none',
188 $promo_code->expiration_date ? format_date($promo_code->expiration_date) : 'none',
189 );
190 }
191
192 if (count($rows)) {
193 $output = theme('table', $header, $rows);
194
195 $pager = theme('pager', NULL, PROMO_CODES_PER_PAGE, 0);
196 if (!empty($pager)) {
197 $output .= $pager;
198 }
199 }
200
201 else {
202 $output .= '<p>'.t('No promotional codes have been created.').'</p>';
203 }
204
205 return $output;
206 }
207
208 function promotion_form($promotion, $form_values = NULL) {
209 if ($promotion->pid) {
210 $form['pid'] = array(
211 '#type' => 'value',
212 '#value' => $promotion->pid,
213 );
214 }
215
216 $form['name'] = array(
217 '#type' => 'textfield',
218 '#title' => t('Name'),
219 '#default_value' => $promotion->name,
220 '#size' => 60,
221 '#maxlength' => 128,
222 '#required' => TRUE,
223 );
224
225 $form['description'] = array(
226 '#type' => 'textarea',
227 '#title' => t('Description'),
228 '#default_value' => $promotion->description,
229 );
230
231 // Get the roles, but exclude anonymous and authenticated
232 $roles = user_roles(1);
233 $keys = array_keys($roles, 'authenticated user');
234 unset($roles[$keys[0]]);
235
236 $form['code'] = array(
237 '#type' => 'fieldset',
238 '#title' => t('Promotion Codes'),
239 '#description' => t('These settings describe the code that is actually accepted by this promotion.'),
240 '#collapsible' => TRUE,
241 '#collapsed' => FALSE,
242 );
243
244 $form['code']['code_type'] = array(
245 '#type' => 'radios',
246 '#title' => t('Select a format for the promotion code'),
247 '#default_value' => $promotion->code_type,
248 '#options' => array(
249 1 => t('<strong>Exact code.</strong> Use when you want all registrants to use the exact same code.'),
250 2 => t('<strong>Simple format.</strong> Use when you have a range of codes with numbers or letters. For example, if you want to accept codes %promo_000 through %promo_999, you could use the format %promo_123.', array('%promo_000' => 'PROMO-000', '%promo_999' => 'PROMO-999', '%promo_123' => 'PROMO-[123]')),
251 # 3 => t('<strong>Advanced.</strong> Use custom PHP code to validate the code. Only do this if you have a complex code you want to use and know how to write PHP code.'),
252 ),
253 '#required' => TRUE,
254 );
255
256 $form['code']['simple_code'] = array(
257 '#type' => 'textfield',
258 '#title' => t('Exact or Simple code'),
259 '#description' => t('If you selected <strong>Exact code</strong> above, enter that code here. If you chose <strong>Simple format</strong>, you may use [] containing letters or numbers to indicate that you accept any letter or number at that position in the code. All other letters, numbers, and symbols will be matched exactly. For example, "PROMO-[000]" would accept "PROMO-123" and "promo-123," but not "PRO-123" and not "PROMO-ABC." Promotional codes are not case sensitive.'),
260 '#default_value' => $promotion->simple_code,
261 '#size' => 30,
262 '#maxlength' => 30,
263 '#required' => TRUE,
264 );
265
266 # $form['code']['advanced_validator'] = array(
267 # '#type' => 'textarea',
268 # '#title' => t('Advanced Validation Code'),
269 # '#description' => t('If you selected <strong>Advanced</strong> above, enter the PHP code to validate the promotional code here. The variable $code will be set to the text of the code and $user will contain the user information submitted with the user registration form.'),
270 # '#default_value' => $promotion->advanced_validator,
271 # );
272
273 $form['code']['max_use_count'] = array(
274 '#type' => 'select',
275 '#title' => t('Select the number of times a single code may be used'),
276 '#description' => t('This is the number of times a code may be used to register for an account. If you use an exact code, this allows you to have a limited number of individuals sign up under that code. If you use a range of codes using the simple code format or a custom validator, you can choose to allow each code to be used only once or more than once.'),
277 '#options' => array(
278 1 => 1,
279 2 => 2,
280 3 => 3,
281 4 => 4,
282 5 => 5,
283 10 => 10,
284 50 => 50,
285 100 => 100,
286 500 => 500,
287 1000 => 1000,
288 0 => t('unlimited'),
289 ),
290 '#default_value' => $promotion->max_use_count,
291 '#required' => TRUE,
292 );
293
294 $form['availability'] = array(
295 '#type' => 'fieldset',
296 '#title' => t('Availability'),
297 '#collapsible' => TRUE,
298 '#collapsed' => TRUE,
299 );
300
301 $form['availability']['active_promotion'] = array(
302 '#type' => 'checkbox',
303 '#title' => t('Is this promotion currently available?'),
304 '#description' => t('By unchecking the box, the promotion is completely deactivated. The promotion will not be available regardless of any other setting. If checked, the promotion is available as long as the current date is between the start date and the end date. <strong>WARNING:</strong> If delayed activation or account expiration are checked, any accounts that are pending activation or would expire will not while this is checked. If you want to make sure accounts expire properly, do not uncheck this box until all accounts have expired.'),
305 '#default_value' => $promotion->active_promotion,
306 );
307
308 $form['availability']['start_date'] = array(
309 '#type' => 'textfield',
310 '#title' => t('Start date'),
311 '#default_value' => $promotion->start_date ? format_date($promotion->start_date, 'custom', 'Y-m-d H:i:s') : '',
312 '#description' => t('If set, specifies the first day this promotion will be accepted. If not set, the promotion is immediately available. Must be in YYYY-MM-DD format.'),
313 '#attributes' => array('class' => 'jscalendar'),
314 );
315
316 $form['availability']['end_date'] = array(
317 '#type' => 'textfield',
318 '#title' => t('End date'),
319 '#default_value' => $promotion->end_date ? format_date($promotion->end_date, 'custom', 'Y-m-d H:i:s') : '',
320 '#description' => t('If set, specifies the first day this promotion is no longer accepted (i.e., it will be available until the day before). If not set, the promotion is available indefinitely. Must be in YYYY-MM-DD format.'),
321 '#attributes' => array('class' => 'jscalendar'),
322 );
323
324 $form['activation'] = array(
325 '#type' => 'fieldset',
326 '#title' => t('Activation'),
327 '#collapsible' => TRUE,
328 '#collapsed' => TRUE,
329 );
330
331 $activate_when_choices = array(
332 1 => t('<strong>Immediately.</strong> Other than waiting for email validation and administrator approval, the acount should be immediately activated.'),
333 2 => t('<strong>Immediately, but postpone promotion.</strong> The account is activated immediately, but the roles granted by this promotion will not be granted until the activation date.'),
334 3 => t('<strong>Delayed account activation.</strong> The account will not be created until after the activation date.'),
335 );
336
337 if (count($roles) == 0) {
338 unset($activate_when_choices[2]);
339 }
340
341 $form['activation']['activate_when'] = array(
342 '#type' => 'radios',
343 '#title' => t('Accounts registered with this promotion are activated when'),
344 '#options' => $activate_when_choices,
345 '#default_value' => $promotion->activate_when,
346 );
347
348 $form['activation']['activation_date'] = array(
349 '#type' => 'textfield',
350 '#title' => t('Activation date'),
351 '#default_value' => $promotion->activation_date ? format_date($promotion->activation_date, 'custom', 'Y-m-d H:i:s') : '',
352 '#attributes' => array('class' => 'jscalendar'),
353 );
354
355 if (count($roles) > 0) {
356 $form['activation']['promotion_roles'] = array(
357 '#type' => 'checkboxes',
358 '#title' => t('Select the roles registrants under this promotion receive'),
359 '#description' => t('These are the roles that should be assigned to any user registering under this promotion.'),
360 '#default_value' => $promotion->promotion_roles,
361 '#options' => $roles,
362 );
363 }
364
365 $form['expiration'] = array(
366 '#type' => 'fieldset',
367 '#title' => t('Expiration'),
368 '#description' => t('These settings determine if accounts registered under a promotion expire and what happens to an account after the promotion expires.'),
369 '#collapsible' => TRUE,
370 '#collapsed' => TRUE,
371 );
372
373 $form['expiration']['accounts_expire'] = array(
374 '#type' => 'checkbox',
375 '#title' => t('Accounts registered with this promotion will expire.'),
376 '#description' => t('If checked, accounts will be demoted, blocked, or deleted after the date given.'),
377 '#default_value' => $promotion->accounts_expire,
378 );
379
380 $form['expiration']['expiration_date'] = array(
381 '#type' => 'textfield',
382 '#title' => t('Expiration date'),
383 '#default_value' => $promotion->expiration_date ? format_date($promotion->expiration_date, 'custom', 'Y-m-d H:i:s') : '',
384 '#attributes' => array('class' => 'jscalendar'),
385 );
386
387 $expiration_actions = array();
388 if (count($roles) > 0) {
389 $expiration_actions[1] = t('Demote the accounts');
390 $expiration_actions[2] = t('Demote and block the accounts');
391 }
392 $expiration_actions[3] = t('Block the accounts');
393 $expiration_actions[4] = t('Delete the accounts');
394
395 $form['expiration']['expiration_action'] = array(
396 '#type' => 'radios',
397 '#title' => t('After expiration, take which action'),
398 '#options' => $expiration_actions,
399 '#default_value' => $promotion->expiration_action,
400 );
401
402 // Only show if there are custom roles available to use
403 if (count($roles) > 0) {
404 $form['expiration']['demotion_roles'] = array(
405 '#type' => 'checkboxes',
406 '#title' => t('Demotion Roles'),
407 '#description' => t('These are the roles users registered with this promotion will lose if you have chosen to perform demotion on expiration.'),
408 '#default_value' => $promotion->demotion_roles,
409 '#options' => $roles,
410 );
411 }
412 #
413 # $form['advanced'] = array(
414 # '#type' => 'fieldset',
415 # '#title' => t('Advanced Options'),
416 # '#collapsible' => TRUE,
417 # '#collapsed' => TRUE,
418 # );
419 #
420 # $form['advanced']['bypass_verification'] = array(
421 # '#type' => 'checkbox',
422 # '#title' => t('Registrants using this promotional code bypass email verification.'),
423 # '#description' => t('If checked, an extra email field will be added to the registration form for confirmation and the user will receive a welcome message rather than a confirmation message to their inbox.'),
424 # '#default_value' => $promotion->bypass_verification,
425 # );
426 #
427 # $form['advanced']['bypass_administration'] = array(
428 # '#type' => 'checkbox',
429 # '#title' => t('Registrants using this promotional code bypass administrator approval.'),
430 # '#description' => t('If registration normally requires administrator approval, checking this box will cause registrants using this code to skip that step.'),
431 # '#default_value' => $promotion->bypass_administrator,
432 # );
433 #
434 # $form['advanced']['linked_code'] = array(
435 # '#type' => 'checkbox',
436 # '#title' => t('Allow this promotional code to be given as a link.'),
437 # '#default_value' => $promotion->linked_code,
438 # );
439 #
440 # $form['advanced']['code_link'] = array(
441 # '#type' => 'textfield',
442 # '#title' => t('Promotional code link'),
443 # '#description' => t('A final slash and then the code must be added to the URL when sending out the links.'),
444 # '#default_value' => $promotion->code_link,
445 # '#field_prefix' => url(NULL, NULL, NULL, TRUE) . (variable_get('clean_url', 0) ? '' : '?q='),
446 # );
447
448 $form['submit'] = array(
449 '#type' => 'submit',
450 '#value' => $promotion->pid ? t('Update') : t('Create'),
451 );
452
453 if ($promotion->pid) {
454 $form['delete'] = array(
455 '#type' => 'submit',
456 '#value' => t('Delete'),
457 );
458 }
459
460 return $form;
461 }
462
463 function promotion_form_validate($form_id, $form_values) {
464 $op = 'create';
465 $pid_clause = '';
466 if (isset($form_values['pid'])) {
467 $pid_clause = 'AND pid <> %d';
468 $op = 'update';
469 }
470
471 // Make sure that the name is not already taken
472 $result = db_query("SELECT pid FROM {promotion_codes} WHERE name = '%s' $pid_clause", $form_values['name'], $form_values['pid']);
473 if (db_num_rows($result) > 0) {
474 form_set_error('name', t('A promotion with that name already exists.'));
475 }
476
477 // Make sure the code is valid
478 if ($form_values['code_type'] == PROMO_TYPE_SIMPLE) {
479 if (!preg_match('/^(?:[^\[\]]+|\[[0-9a-zA-Z]*\])+$/', $form_values['simple_code'])) {
480 form_set_error('simple_code', t('The simple code format given is not valid. Make sure you do not have an umatched "[" or "]" and that any brackets contain only letters and numbers.'));
481 }
482 }
483
484 // Tests for delayed activation
485 if ($form_values['activate_when'] == PROMO_ACT_ROLES_WAIT
486 || $form_values['activate_when'] == PROMO_ACT_ALL_WAIT) {
487
488 // If delayed activation is used, there must be a date
489 if (!$form_values['activation_date']) {
490 form_set_error('activation_date', t('Activation date is required if you have chosen to delay promotion or activation.'));
491 }
492 }
493
494 // Tests for when roles are assigned late
495 if ($form_values['activate_when'] == PROMO_ACT_ROLES_WAIT) {
496
497 // If roles are going to be assigned late, there must be some roles checked
498 if (array_reduce($form_values['promotion_roles'], create_function('$a,$b', 'return $a + $b;')) == 0) {
499 form_set_error('promotion_roles', t('You have asked that the roles assigned be delayed. However, you have not selected any roles. Please do so before continuing.'));
500 }
501 }
502
503 // Tests for when accounts expire
504 if ($form_values['accounts_expire']) {
505
506 // If accounts may expire, the expiration must be set
507 if (!$form_values['expiration_date']) {
508 form_set_error('expiration_date', t('Expiration date is required if accounts may expire.'));
509 }
510
511 // If accounts expire, we need to know what action to take
512 if (!$form_values['expiration_action']) {
513 form_set_error('expiration_action', t('The expiration action must be selected if accounts may expire.'));
514 }
515
516 // Tests for when expiration involves stripping roles from accounts
517 if ($form_values['expiration_action'] == PROMO_EXP_ACTION_DEMOTE
518 || $form_values['expiration_action'] == PROMO_EXP_ACTION_DEMOTE_AND_BLOCK) {
519
520 // If roles are stripped, we must set some roles to strip
521 if (array_reduce($form_values['demotion_roles'], create_function('$a,$b', 'return $a + $b;')) == 0) {
522 form_set_error('demotion_roles', t('You have asked that members registered during a promotion be demoted, but have not selected the roles they lose. Please check one or more roles before continuing.'));
523 }
524 }
525 }
526
527 // Make sure these are dates, if they are set
528 $field_names = array(
529 'start_date' => t('Start date'),
530 'end_date' => t('End date'),
531 'activation_date' => t('Activation date'),
532 'expiration_date' => t('Expiration date'),
533 );
534
535 $now = time();
536 foreach ($field_names as $date => $title) {
537
538 // Don't bother to validate the date if it isn't even set
539 if ($form_values[$date]) {
540 $parsed_date = strtotime($form_values[$date]);
541
542 // It's not a valid date, complain
543 if (!$parsed_date) {
544 form_set_error($date, t('The %field given is not recognized.', array( '%field' => $title )));
545 }
546
547 // The end_date and expiration_date should be in the future
548 elseif ($parsed_date < $now && ($date == 'end_date' || $date == 'expiration_date')) {
549
550 // Don't let them create this thing!
551 if ($op == 'create') {
552 form_set_error($date, t('The %field given is in the past and it must be in the future.', array('%field' => $title)));
553 }
554
555 // This is just a warning!
556 else {
557 drupal_set_message(t('The %field given is in the past and it should be in the future. You may want to go back and !edit.', array('%field' => $title, '!edit' => l(t('edit the promotion'), 'admin/user/promotion/'.$form_values['pid']))), 'error');
558 }
559 }
560 }
561 }
562
563 // Make sure that that the expiration date is after the activation date
564 if ($form_values['activate_when'] == PROMO_ACT_ROLES_WAIT
565 || $form_values['activate_when'] == PROMO_ACT_ALL_WAIT) {
566
567 $activation_date = strtotime($form_values['activation_date']);
568 $expiration_date = strtotime($form_values['expiration_date']);
569
570 if ($form_values['accounts_expire'] && $activation_date && $expiration_date) {
571 if ($activation_date >= $expiration_date) {
572 form_set_error('activation_date', t('The activation date must be sooner than the expiration date.'));
573 }
574 }
575 }
576
577 // Make sure that the end date is after the start date
578 if ($form_values['start_date'] && $form_values['end_date']) {
579 $start_date = strtotime($form_values['start_date']);
580 $end_date = strtotime($form_values['end_date']);
581
582 if ($start_date && $end_date && $start_date >= $end_date) {
583 form_set_error('start_date', t('The start date must be sooner than the end date.'));
584 }
585 }
586
587 // Make sure that the activation date comes on or after the start date
588 if ($form_values['start_date'] && ($form_values['activate_when'] == PROMO_ACT_ROLES_WAIT || $form_values['activate_when'] == PROMO_ACT_ALL_WAIT) && $form_values['activation_date']) {
589 $start_date = strtotime($form_values['start_date']);
590 $activation_date = strtotime($form_values['activation_date']);
591
592 if ($start_date && $activation_date && $start_date > $activation_date) {
593 form_set_error('start_date', t('The start date must not be after the activation date.'));
594 }
595 }
596
597 // Make sure that the expiration date comes on or after the end date
598 if ($form_values['end_date'] && $form_values['accounts_expire'] && $form_values['expiration_date']) {
599 $end_date = strtotime($form_values['end_date']);
600 $expiration_date = strtotime($form_values['expiration_date']);
601
602 if ($end_date && $expiration_date && $end_date > $expiration_date) {
603 form_set_error('end_date', t('The end date must not be after the expiration date.'));
604 }
605 }
606 }
607
608 /**
609 * Internal function for setting up the {promotion_codes_roles} table.
610 */
611 function _promotion_update_roles($pid, $roles, $promotion) {
612 db_query("DELETE FROM {promotion_codes_roles} WHERE pid = %d AND promotion = %d", $pid, $promotion);
613
614 if (isset($roles)) {
615 foreach ($roles as $role) {
616 if ($role) {
617 db_query("INSERT INTO {promotion_codes_roles} (pid, rid, promotion) VALUES (%d, %d, %d)", $pid, $role, $promotion);
618 }
619 }
620 }
621 }
622
623 function promotion_form_submit($form_id, $form_values) {
624 // Convert dates from the submitted values into seconds since the epoch
625 foreach (array('start_date', 'end_date', 'expiration_date', 'activation_date') as $date) {
626 if ($form_values[$date]) {
627 $form_values[$date] = strtotime($form_values[$date]);
628 }
629 else {
630 $form_values[$date] = 0;
631 }
632 }
633
634 // update
635 if ($form_values['pid']) {
636 db_query("
637 UPDATE {promotion_codes}
638 SET name = '%s', description = '%s', active_promotion = %d,
639 start_date = %d, end_date = %d, code_type = %d, simple_code = '%s',
640 max_use_count = %d, accounts_expire = %d, expiration_date = %d,
641 expiration_action = %d, activate_when = %d, activation_date = %d
642 WHERE pid = %d",
643 $form_values['name'],
644 $form_values['description'],
645 $form_values['active_promotion'],
646 $form_values['start_date'],
647 $form_values['end_date'],
648 $form_values['code_type'],
649 $form_values['simple_code'],
650 $form_values['max_use_count'],
651 $form_values['accounts_expire'],
652 $form_values['expiration_date'],
653 $form_values['expiration_action'],
654 $form_values['activate_when'],
655 $form_values['activation_date'],
656 $form_values['pid']
657 );
658
659 _promotion_update_roles($form_values['pid'], $form_values['promotion_roles'], 1);
660 _promotion_update_roles($form_values['pid'], $form_values['demotion_roles'], 0);
661
662 drupal_set_message(t('Updated a promotion.'));
663 }
664
665 // insert
666 else {
667 $pid = db_next_id('{promotion_codes}_pid');
668
669 db_query("
670 INSERT INTO {promotion_codes} (pid, name, description, active_promotion,
671 start_date, end_date, code_type, simple_code, max_use_count,
672 accounts_expire, expiration_date, expiration_action, activate_when,
673 activation_date)
674 VALUES (%d, '%s', '%s', %d, %d, %d, %d, '%s', %d, %d, %d, %d, %d, %d)",
675 $pid,
676 $form_values['name'],
677 $form_values['description'],
678 $form_values['active_promotion'],
679 $form_values['start_date'],
680 $form_values['end_date'],
681 $form_values['code_type'],
682 $form_values['simple_code'],
683 $form_values['max_use_count'],
684 $form_values['accounts_expire'],
685 $form_values['expiration_date'],
686 $form_values['expiration_action'],
687 $form_values['activate_when'],
688 $form_values['activation_date']
689 );
690
691 _promotion_update_roles($pid, $form_values['promotion_roles'], 1);
692 _promotion_update_roles($pid, $form_values['demotion_roles'], 0);
693
694 drupal_set_message(t('Created a new promotion.'));
695 }
696
697 return 'admin/user/promotion';
698 }
699
700 function promotion_add() {
701 // Setup the defaults
702 $promotion['active_promotion'] = 1;
703 $promotion['activate_when'] = 1;
704
705 $output = drupal_get_form('promotion_form', (object) $promotion);
706 return $output;
707 }
708
709 function promotion_edit($pid) {
710 // Check for delete
711 if ($_POST['op'] == t('Delete')) {
712 drupal_goto('admin/user/promotion/'.$pid.'/delete');
713 }
714
715 // Load the promotion
716 if ($promotion = promotion_get_promotion($pid)) {
717 return drupal_get_form('promotion_form', $promotion);
718 }
719
720 else {
721 return drupal_not_found();
722 }
723 }
724
725 function promotion_delete_confirm($pid) {
726 $promotion = promotion_get_promotion($pid);
727
728 $form['pid'] = array('#type' => 'value', '#value' => $pid);
729 return confirm_form($form,
730 t('Are you sure you want to delete %name?', array('%name' => $promotion->name)),
731 'admin/user/promotion/' . $pid,
732 t('<p>This action cannot be undone. You will no longer be able to see statistics related to this promotion.</p><p><strong>CAUTION:</strong> You may wish to mark this promotion as inactive instead.</p>'),
733 t('Delete'), t('Cancel'));
734 }
735
736 function promotion_delete_confirm_submit($form_id, $form_values) {
737 if ($form_values['confirm']) {
738 db_query('DELETE FROM {promotion_codes} WHERE pid = %d', $form_values['pid']);
739 }
740
741 return 'admin/user/promotion';
742 }
743
744 function promotion_get_promotion($pid) {
745 if ($result = db_query("SELECT * FROM {promotion_codes} WHERE pid = %d", $pid)) {
746 $promotion = db_fetch_object($result);
747
748 $result = db_query("SELECT * FROM {promotion_codes_roles} WHERE pid = %d", $pid);
749 while ($code_role = db_fetch_array($result)) {
750 if ($code_role['promotion']) {
751 $promotion->promotion_roles[$code_role['rid']] = $code_role['rid'];
752 }
753 else {
754 $promotion->demotion_roles[$code_role['rid']] = $code_role['rid'];
755 }
756 }
757
758 return $promotion;
759 }
760
761 return NULL;
762 }
763
764 /**
765 * Implementation of hook_user().
766 */
767 function promotion_user($op, &$edit, &$account, $category = NULL) {
768 if ($op == 'register') {
769 if (arg(0) == 'user' && arg(1) == 'register') {
770 $url_code = arg(2); // /user/register/CODE
771 }
772 $required = variable_get('promotion_required', 0);
773
774 $form['promotion'] = array(
775 '#type' => 'fieldset',
776 '#title' => t('Promotional code'),
777 );
778 $form['promotion']['promotional_code'] = array(
779 '#type' => $url_code ? 'item' : 'textfield',
780 '#title' => t('Promotional code'),
781 '#description' =>
782 ($url_code ? t('You have been given a direct link to this promotion. If you do not want to use this promotion or would like to use another, !click_here.', array('!click_here' => l('click here', 'user/register'))) :
783 ($required ? t('You must enter a promotional code to register for an account.')
784 : t('Please enter a promotional code, if you have one.'))),
785 '#required' => $required,
786 '#size' => 30,
787 '#maxlength' => 30,
788 );
789 if ($url_code) {
790 $form['promotional_code']['#value'] = $url_code;
791 $form['promotional_code'] = array(
792 '#type' => 'value',
793 '#value' => $url_code,
794 );
795 }
796
797 return $form;
798 }
799
800 elseif ($op == 'validate' && $category == 'account') {
801 // We perform this validation if the promotional code is present, otherwise
802 // we assume that this is just editing the existing user, which we don't
803 // care about.
804 if (isset($edit['promotional_code']) && $edit['promotional_code']) {
805 $promotion = promotion_find_match($edit['promotional_code']);
806
807 if (!$promotion->valid_match) {
808 form_set_error('promotional_code', $promotion->validation_error);
809 }
810 }
811 }
812
813 elseif ($op == 'insert') {
814 if (isset($edit['promotional_code']) && $edit['promotional_code']) {
815 $promotion = promotion_find_match($edit['promotional_code']);
816
817 $now = time();
818 $status = 1;
819
820 // Delay role activation if the promotion requests it and the activation
821 // date is yet future
822 if ($promotion->activate_when == PROMO_ACT_ROLES_WAIT && $promotion->activation_date > $now) {
823 $user_register = variable_get('user_register', 1);
824 if ($user_register == 1) {
825 drupal_set_message(t('Your account will be activated after verifying your email address. However, some features of this site may not available until after %date, which is the promotion you signed up with officially starts.', array('%date' => format_date($promotion->activation_date, 'large'))));
826 }
827 else {
828 drupal_set_message(t('Once your account has been approved and your email address verified, you account will be activated. However, some features of this site may not be available until after %date, which is when the promotion you signed up with officially starts.', array('%date' => format_date($promotion->activation_date, 'large'))));
829 }
830
831 watchdog('promotion',
832 t('Delaying role activation of account %user because the promotion date of %promo is in the future.', array('%promo' => $promotion->title, '%user' => $edit['name'])),
833 WATCHDOG_NOTICE, l('edit', '/user/'.$edit['uid']));
834
835 $status = 0;
836 }
837
838 // Delay account activation if the promotion requests it and the
839 // activation date is yet future
840 elseif ($promotion->activate_when == PROMO_ACT_ALL_WAIT && $promotion->activation_date > $now) {
841 drupal_set_message(t('Your account will not be available after verifying your email address. The promotion you have signed up with has not yet started. Your account will become active after %date.', array('%date' => format_date($promotion->activation_date, 'large'))));
842
843 // Make sure the account is blocked
844 db_query("
845 UPDATE {users} SET status = %d WHERE uid = %d",
846 0, $edit['uid']);
847
848 watchdog('promotion',
849 t('Delaying account activation of account %user because the promotion date of %promo is in the future.', array('%promo' => $promotion->title, '%user' => $edit['name'])),
850 WATCHDOG_NOTICE, l('edit', '/user/'.$edit['uid']));
851
852 $status = 0;
853 }
854
855 db_query("
856 INSERT INTO {promotion_codes_users} (pid, uid, code_used, status)
857 VALUES (%d, %d, '%s', %d)",
858 $promotion->pid, $account->uid, $edit['promotional_code'], $status);
859
860 $edit['promotional_code'] = NULL;
861
862 if ($status) {
863 // Add initial role promotion
864 $edit['roles'] = $promotion->promotion_roles;
865
866 watchdog('promotion',
867 t('Activating roles of account %user using %promo.', array('%promo' => $promotion->title, '%user' => $edit['name'])),
868 WATCHDOG_NOTICE, l('edit', '/user/'.$edit['uid']));
869 }
870 }
871 }
872
873 // $op == 'delete' is not implemented on purpose. Registered codes remain
874 // used even if the user that used that code no longer exists.
875 }
876
877 /**
878 * This method searches through the available and active promotions to find a
879 * matching promotion record. It then loads and returns that record. In
880 * addition, it sets the valid_match value to TRUE.
881 *
882 * On failure, it returns an object that has the valid_match value set to FALSE
883 * and the validation_error set to a localized message to return to the user. No
884 * other values will be set on the returned object.
885 */
886 function promotion_find_match($code) {
887 // Search for active promotions
888 $now = time();
889 $result = db_query("
890 SELECT pid
891 FROM {promotion_codes}
892 WHERE active_promotion = 1
893 AND (start_date IS NULL OR start_date = 0 OR start_date <= %d)
894 AND (end_date IS NULL OR end_date = 0 OR end_date >= %d)",
895 $now, $now);
896
897 // Iterate through the active promotions found
898 while ($promo = db_fetch_array($result)) {
899
900 // Load the promotion object
901 $promotion = promotion_get_promotion($promo['pid']);
902
903 // Check to see if the given promotion matches the code
904 if (promotion_match($promotion, $code)) {
905 // Appears to match, but make sure it has not been already used too
906 // many times...
907 if ($promotion->max_use_count > 0) {
908 $result = db_query("
909 SELECT count(*) AS user_count
910 FROM {promotion_codes_users}
911 WHERE pid = %d AND lower(code_used) = lower('%s')",
912 $promotion->pid, $code);
913
914 // fetch the count
915 if ($counter = db_fetch_array($result)) {
916 if ($counter['user_count'] >= $promotion->max_use_count) {
917 // Too bad, already used up
918 $used_up_promotion = $promotion;
919 continue;
920 // Keep searching, just in case it matches another promotion
921 }
922 }
923 }
924
925 // Success! Found a matching promotion that it can consume
926 $promotion->valid_match = TRUE;
927 return $promotion;
928 }
929 }
930
931 // We looked, but all we found was a used promotion
932 if ($used_up_promotion) {
933 $used_up_promotion->valid_match = FALSE;
934 $used_up_promotion->validation_error = t('Sorry, the promotional code you have given has already been used.');
935 return $used_up_promotion;
936 }
937
938 // They have an invalid code, but let's try to be specific with the error
939 // message in case it's an old imperial--er promotional code.
940 $result = db_query("
941 SELECT pid
942 FROM {promotion_codes}
943 WHERE active_promotion = 0 OR start_date > %d OR end_date < %d",
944 $now, $now);
945
946 // iterate through all codes to find any possible match
947 while ($promo = db_fetch_array($result)) {
948 $promotion = promotion_get_promotion($promo['pid']);
949
950 // Attempt to match this code against this inactive promotion
951 if (promotion_match($promotion, $code)) {
952
953 // The promotion hasn't yet started
954 if ($promotion->start_date && $promotion->start_date > $now) {
955 $promotion->valid_match = FALSE;
956 $promotion->validation_error = t('The promotional code you have given is not yet available.');
957 return $promotion;
958 }
959
960 // The promotion has ended
961 if ($promotion->end_date && $promotion->end_date < $now) {
962 $promotion->valid_match = FALSE;
963 $promotion->validation_error = t('The promotional code you have entered has expired and is no longer available.');
964 return $promotion;
965 }
966
967 // The promotion just isn't active
968 if (!$promotion->active_promotion) {
969 $promotion->valid_match = FALSE;
970 $promotion->validation_error = t('The promotional code you have entered is not currently active.');
971 return $promotion;
972 }
973 }
974 }
975
976 // Complete non-match, return a generic error
977 $promotion = array(
978 'valid_match' => FALSE,
979 'validation_error' => t('Sorry, but the code you have given does not match any current promotion. Please check that it is correct and try again.'),
980 );
981 return (object) $promotion;
982 }
983
984 /**
985 * Matches a specific code against a promotion. This matches the code against
986 * the promotion.
987 *
988 * Returns TRUE if the code matches or FALSE if it does not.
989 */
990 function promotion_match($promotion, $code) {
991 // Perform an exact match (ignoring case)
992 if ($promotion->code_type == PROMO_TYPE_EXACT) {
993 return strcasecmp($promotion->simple_code, $code) == 0;
994 }
995
996 // Convert the promotion code into a regulary expression and match
997 elseif ($promotion->code_type == PROMO_TYPE_SIMPLE) {
998
999 // Process the code matcher into a regular expression
1000 $state = 'exact';
1001 $simple_match = '';
1002 foreach (str_split($promotion->simple_code) as $char) {
1003 if ($state == 'exact') {
1004 if ($char == '[') {
1005 $state = 'match';
1006 }
1007 else {
1008 $simple_match .= preg_quote($char);
1009 }
1010 }
1011 else {
1012 if ($char == ']') {
1013 $state = 'exact';
1014 }
1015 elseif (ctype_alpha($char)) {
1016 $simple_match .= '\p{L}'; // match any unicode letter
1017 }
1018 elseif (ctype_digit($char)) {
1019 $simple_match .= '\p{N}'; // match any unicode number
1020 }
1021 else {
1022 $simple_match .= preg_quote($char); // uh? what?
1023 }
1024 }
1025 }
1026
1027 // Perform the comparison
1028 return preg_match("/^$simple_match\$/i", $code);
1029 }
1030
1031 // Shouldn't happen
1032 else {
1033 watchdog('promotion',
1034 t('Unknown promotional code type %code_type', array('%code_type' => $promotion->code_type)),
1035 WATCHDOG_ERROR, l('edit', 'admin/user/promotion/'.$promotion->pid));
1036 return FALSE;
1037 }
1038 }
1039
1040 /**
1041 * Implements hook_cron().
1042 *
1043 * This performs the work of expiring an unexpired promotion account.
1044 */
1045 function promotion_cron() {
1046 $now = time();
1047 _promotion_process_activation($now);
1048 _promotion_process_expiration($now);
1049 }
1050
1051 /**
1052 * Performs the work during cron of activating accounts when a promotion has
1053 * become active.
1054 */
1055 function _promotion_process_activation($now) {
1056 $result = db_query("
1057 SELECT p.pid AS pid, c.uid AS uid, c.code_used AS code_used
1058 FROM {promotion_codes_users} c INNER JOIN {promotion_codes} p
1059 ON c.pid = p.pid
1060 WHERE p.active_promotion = 1
1061 AND (p.activate_when = 2 OR p.activate_when = 3)
1062 AND p.activation_date < %d AND c.status = 0
1063 AND (p.accounts_expire = 0 OR p.expiration_date > %d)",
1064 $now, $now);
1065
1066 while ($pid_uid = db_fetch_array($result)) {
1067 $promotion = promotion_get_promotion($pid_uid['pid']);
1068 $account = user_load(array(uid => $pid_uid['uid']));
1069
1070 if ($promotion && $account) {
1071 $edit['roles'] = $account->roles;
1072 foreach ($promotion->promotion_roles as $rid) {
1073 $edit['roles'][$rid] = $rid;
1074 }
1075
1076 if ($promotion->activate_when == PROMO_ACT_ALL_WAIT) {
1077 $edit['status'] = 1;
1078 }
1079
1080 watchdog('promotion',
1081 t('Activating %name with email address %email with promotion %promotion (%code).', array('%name' => $account->name, '%email' => $account->mail, '%promotion' => $promotion->name, '%code' => $pid_uid['code_used'])),
1082 WATCHDOG_NOTICE);
1083
1084 user_save($account, $edit);
1085 }
1086
1087 db_query("
1088 UPDATE {promotion_codes_users}
1089 SET status = 1
1090 WHERE pid = %d AND uid = %d
1091 ", $pid_uid['pid'], $pid_uid['uid']);
1092 }
1093 }
1094
1095 /**
1096 * Performs the work during cron of actually deactivating accounts when a
1097 * promotion has expired.
1098 */
1099 function _promotion_process_expiration($now) {
1100 $result = db_query("
1101 SELECT p.pid AS pid, c.uid AS uid, c.code_used AS code_used
1102 FROM {promotion_codes_users} c INNER JOIN {promotion_codes} p
1103 ON c.pid = p.pid
1104 WHERE p.active_promotion = 1 AND p.accounts_expire = 1
1105 AND p.expiration_date < %d AND c.status = 1
1106 ", $now);
1107
1108 while ($pid_uid = db_fetch_array($result)) {
1109 $promotion = promotion_get_promotion($pid_uid['pid']);
1110 $account = user_load(array(uid => $pid_uid['uid']));
1111
1112 if ($promotion && $account) {
1113 if ($promotion->expiration_action == PROMO_EXP_ACTION_DELETE) {
1114 user_delete(NULL, $account->uid);
1115
1116 watchdog('promotion',
1117 t('Deleted %name with email address %email because promotion %promotion (%code) expired.', array('%name' => $account->name, '%email' => $account->mail, '%promotion' => $promotion->name, '%code' => $pid_uid['code_used'])),
1118 WATCHDOG_NOTICE);
1119 }
1120
1121 else {
1122 $edit = array();
1123
1124 if ($promotion->expiration_action == PROMO_EXP_ACTION_BLOCK
1125 || $promotion->expiration_action == PROMO_EXP_ACTION_DEMOTE_AND_BLOCK) {
1126
1127 $edit['status'] = 0;
1128
1129 watchdog('promotion',
1130 t('Blocking %name with email address %email because promotion %promotion (%code) expired.', array('%name' => $account->name, '%email' => $account->mail, '%promotion' => $promotion->name, '%code' => $pid_uid['code_used'])),
1131 WATCHDOG_NOTICE);
1132 }
1133
1134 if ($promotion->expiration_action == PROMO_EXP_ACTION_DEMOTE
1135 || $promotion->expiration_action == PROMO_EXP_ACTION_DEMOTE_AND_BLOCK) {
1136
1137 $edit['roles'] = $account->roles;
1138 foreach ($promotion->demotion_roles as $rid) {
1139 unset($edit['roles'][$rid]);
1140 }
1141
1142 watchdog('promotion',
1143 t('Demoting %name with email address %email because promotion %promotion (%code) expired.', array('%name' => $account->name, '%email' => $account->mail, '%promotion' => $promotion->name, '%code' => $pid_uid['code_used'])),
1144 WATCHDOG_NOTICE);
1145 }
1146
1147 user_save($account, $edit);
1148 }
1149 }
1150
1151 db_query("
1152 UPDATE {promotion_codes_users}
1153 SET status = 0
1154 WHERE pid = %d AND uid = %d
1155 ", $pid_uid['pid'], $pid_uid['uid']);
1156 }
1157 }
1158
1159 function promotion_statistics($pid = NULL, $detail = NULL) {
1160 if (!isset($pid)) {
1161 return _promotion_statistics_general();
1162 }
1163