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

Contents of /contributions/modules/uc_coupon/uc_coupon.module

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


Revision 1.41 - (show annotations) (download) (as text)
Fri Oct 16 12:55:11 2009 UTC (6 weeks, 1 day ago) by longwave
Branch: MAIN
Changes since 1.40: +17 -3 lines
File MIME type: text/x-php
#605816: Warning when PayPal settings are incompatible with coupons.
1 <?php
2
3 // $Id: uc_coupon.module,v 1.40 2009/09/17 19:17:28 longwave Exp $
4
5 /**
6 * @file
7 * Provides discount coupons for Ubercart.
8 *
9 * Original code by Blake Lucchesi (www.boldsource.com)
10 * Maintained by David Long (dave@longwaveconsulting.com)
11 *
12 * Send any suggestions and feedback to the above address.
13 */
14
15 /**
16 * Implementation of hook_menu().
17 */
18 function uc_coupon_menu() {
19 $items = array();
20
21 $items['admin/store/customers/coupon'] = array(
22 'title' => 'Coupons',
23 'description' => 'Manage store discount coupons.',
24 'page callback' => 'uc_coupon_display',
25 'page arguments' => array('active'),
26 'access arguments' => array('view store coupons'),
27 'type' => MENU_NORMAL_ITEM,
28 );
29 $items['admin/store/customers/coupon/list'] = array(
30 'title' => 'Active coupons',
31 'description' => 'View active coupons.',
32 'page callback' => 'uc_coupon_display',
33 'page arguments' => array('active'),
34 'access arguments' => array('view store coupons'),
35 'type' => MENU_DEFAULT_LOCAL_TASK,
36 );
37 $items['admin/store/customers/coupon/inactive'] = array(
38 'title' => 'Inactive coupons',
39 'description' => 'View inactive coupons.',
40 'page callback' => 'uc_coupon_display',
41 'page arguments' => array('inactive'),
42 'access arguments' => array('view store coupons'),
43 'type' => MENU_LOCAL_TASK,
44 );
45 $items['admin/store/customers/coupon/add'] = array(
46 'title' => 'Add new coupon',
47 'description' => 'Add a new coupon.',
48 'page callback' => 'drupal_get_form',
49 'page arguments' => array('uc_coupon_add_form', 'add'),
50 'access arguments' => array('manage store coupons'),
51 'type' => MENU_LOCAL_TASK,
52 'weight' => 10,
53 );
54 $items['admin/store/reports/coupon'] = array(
55 'title' => 'Coupon usage reports',
56 'description' => 'View coupon usage reports.',
57 'page callback' => 'uc_coupon_reports',
58 'access arguments' => array('view reports'),
59 'type' => MENU_NORMAL_ITEM,
60 );
61 $items['admin/store/customers/coupon/%uc_coupon/edit'] = array(
62 'title' => 'Edit coupon',
63 'description' => 'Edit an existing coupon.',
64 'page callback' => 'drupal_get_form',
65 'page arguments' => array('uc_coupon_add_form', 'edit', 4),
66 'access arguments' => array('manage store coupons'),
67 'type' => MENU_CALLBACK,
68 );
69 $items['admin/store/customers/coupon/%uc_coupon/delete'] = array(
70 'title' => 'Delete coupon',
71 'description' => 'Delete a coupon.',
72 'page callback' => 'drupal_get_form',
73 'page arguments' => array('uc_coupon_delete_confirm', 4),
74 'access arguments' => array('manage store coupons'),
75 'type' => MENU_CALLBACK,
76 );
77 $items['admin/store/customers/coupon/%uc_coupon/codes'] = array(
78 'title' => 'Download bulk coupon codes',
79 'description' => 'Download the list of bulk coupon codes as a CSV file.',
80 'page callback' => 'uc_coupon_codes_csv',
81 'page arguments' => array(4),
82 'access arguments' => array('manage store coupons'),
83 'type' => MENU_CALLBACK,
84 );
85
86 $items['cart/checkout/coupon'] = array(
87 'title' => 'Apply coupon',
88 'page callback' => 'uc_coupon_checkout_apply',
89 'access arguments' => array('access content'),
90 'type' => MENU_CALLBACK,
91 );
92
93 $items['uc_coupon/autocomplete/node'] = array(
94 'title' => 'Node autocomplete',
95 'page callback' => 'uc_coupon_autocomplete_node',
96 'access arguments' => array('manage store coupons'),
97 'type' => MENU_CALLBACK,
98 );
99 $items['uc_coupon/autocomplete/term'] = array(
100 'title' => 'Term autocomplete',
101 'page callback' => 'uc_coupon_autocomplete_term',
102 'access arguments' => array('manage store coupons'),
103 'type' => MENU_CALLBACK,
104 );
105 $items['uc_coupon/autocomplete/user'] = array(
106 'title' => 'User autocomplete',
107 'page callback' => 'uc_coupon_autocomplete_user',
108 'access arguments' => array('manage store coupons'),
109 'type' => MENU_CALLBACK,
110 );
111 $items['uc_coupon/autocomplete/role'] = array(
112 'title' => 'Role autocomplete',
113 'page callback' => 'uc_coupon_autocomplete_role',
114 'access arguments' => array('manage store coupons'),
115 'type' => MENU_CALLBACK,
116 );
117
118 return $items;
119 }
120
121 /**
122 * Implementation of hook_perm().
123 */
124 function uc_coupon_perm() {
125 return array('view store coupons', 'manage store coupons', 'coupon wholesale pricing');
126 }
127
128
129 /**
130 * Display a brief over view of system coupons
131 *
132 * @param $view_type
133 * pass in an argument to filter out active/inactive coupons
134 */
135 function uc_coupon_display($view_type = 'active') {
136 _uc_coupon_paypal_check();
137
138 $header[] = array('data' => t('Name'), 'field' => 'name');
139 $header[] = array('data' => t('Code'), 'field' => 'code', 'sort' => 'asc');
140 $header[] = array('data' => t('Value'), 'field' => 'value');
141 $header[] = array('data' => t('Valid from'), 'field' => 'valid_from');
142 $header[] = array('data' => t('Valid until'), 'field' => 'valid_until');
143 $header[] = array('data' => t('Actions'));
144
145 $result = pager_query('SELECT cid, name, value, code, type, valid_from, valid_until, bulk FROM {uc_coupons} WHERE status = %d'. tablesort_sql($header), 20, 0, NULL, $view_type == 'inactive' ? 0 : 1);
146 $rows = array();
147 while ($row = db_fetch_object($result)) {
148 if ($row->type == 'percentage') {
149 $value = $row->value .'%';
150 }
151 else {
152 $value = uc_currency_format($row->value);
153 }
154
155 $code = $row->code;
156 $actions = l(t('edit'), "admin/store/customers/coupon/$row->cid/edit");
157 if ($row->bulk) {
158 $code .= '* '. t('(bulk)');
159 $actions .= ' '. l(t('codes'), "admin/store/customers/coupon/$row->cid/codes");
160 }
161 $actions .= ' '. l(t('delete'), "admin/store/customers/coupon/$row->cid/delete");
162
163 $valid_from = format_date($row->valid_from, 'custom', variable_get('uc_date_format_default', 'm/d/Y'));
164 $valid_until = format_date($row->valid_until, 'custom', variable_get('uc_date_format_default', 'm/d/Y'));
165 $rows[] = array($row->name, $code, $value, $valid_from, $valid_until, $actions);
166 }
167
168 if (count($rows)) {
169 $output = theme('table', $header, $rows, array('width' => '100%'));
170 $output .= theme('pager', NULL, 20);
171 }
172 else {
173 $output = '<p>'. t('There are currently no !type coupons in the system.', array('!type' => $view_type)) .'</p>';
174 }
175
176 return $output;
177 }
178
179
180
181 /**
182 * Form builder for product attributes.
183 *
184 * @param $action string
185 * Form action, edit or add. 'edit' loads default values.
186 *
187 * @param $coupon object
188 * Coupon, used to load defaults when $action = 'edit'
189 */
190 function uc_coupon_add_form($form_state, $action, $coupon = NULL) {
191 _uc_coupon_paypal_check();
192
193 if ($action == 'edit') {
194 $value = $coupon;
195 $used = db_result(db_query("SELECT COUNT(*) FROM {uc_coupons_orders} WHERE cid = %d", $value->cid));
196 $form['#uc_coupon_cid'] = $value->cid;
197 $form['#uc_coupon'] = $value;
198 $form['#uc_coupon_used'] = $used;
199 }
200 else {
201 $value->valid_from = time();
202 $value->valid_until = time();
203 $value->minimum_order = 0;
204 $value->max_uses = 0;
205 $used = 0;
206 }
207
208 $value->valid_from = array('year' => format_date($value->valid_from, 'custom', 'Y'), 'month' => format_date($value->valid_from, 'custom', 'n'), 'day' => format_date($value->valid_from, 'custom', 'j'));
209 $value->valid_until = array('year' => format_date($value->valid_until, 'custom', 'Y'), 'month' => format_date($value->valid_until, 'custom', 'n'), 'day' => format_date($value->valid_until, 'custom', 'j'));
210
211 $form['name'] = array(
212 '#type' => 'textfield',
213 '#title' => t('Coupon name'),
214 '#default_value' => $value->name,
215 '#required' => TRUE,
216 );
217
218 $form['code'] = array(
219 '#type' => 'textfield',
220 '#title' => t('Coupon code'),
221 '#description' => t('Coupon codes cannot be changed once they have been used in an order.'),
222 '#default_value' => $value->code,
223 '#size' => 25,
224 '#required' => !$used,
225 '#maxlength' => 14,
226 '#disabled' => $used,
227 );
228
229 $form['bulk'] = array(
230 '#type' => 'fieldset',
231 '#title' => t('Bulk coupon codes'),
232 '#description' => t('The coupon code entered above will be used to prefix each generated code.'),
233 '#collapsible' => TRUE,
234 '#collapsed' => !$value->bulk,
235 );
236
237 if (!$used) {
238 $form['bulk']['bulk_generate'] = array(
239 '#type' => 'checkbox',
240 '#title' => t('Enable bulk generation of coupon codes.'),
241 '#default_value' => $value->bulk,
242 '#disabled' => $used,
243 );
244 }
245 else {
246 $form['bulk']['bulk_generate'] = array(
247 '#type' => 'value',
248 '#default_value' => $value->bulk,
249 );
250 }
251
252 $form['bulk']['bulk_number'] = array(
253 '#type' => 'textfield',
254 '#title' => t('Number of codes to generate'),
255 '#default_value' => $value->data['bulk_number'],
256 '#size' => 10,
257 '#maxlength' => 10,
258 '#disabled' => $used,
259 );
260
261 $form['bulk']['bulk_length'] = array(
262 '#type' => 'select',
263 '#title' => t('Code length'),
264 '#description' => t('The number of characters selected here will be appended to the coupon code entered above..'),
265 '#default_value' => $value->data['bulk_length'],
266 '#options' => drupal_map_assoc(range(8, 30)),
267 '#disabled' => $used,
268 );
269
270 $form['valid_from'] = array(
271 '#type' => 'date',
272 '#title' => t('Start date'),
273 '#default_value' => $value->valid_from,
274 '#required' => TRUE,
275 '#after_build' => array('_uc_coupon_date_range'),
276 );
277
278 $form['valid_until'] = array(
279 '#type' => 'date',
280 '#title' => t('Expiry date'),
281 '#default_value' => $value->valid_until,
282 '#required' => TRUE,
283 '#after_build' => array('_uc_coupon_date_range'),
284 );
285
286 $form['status'] = array(
287 '#type' => 'checkbox',
288 '#title' => t('Active'),
289 '#description' => t('Check to enable the coupon, uncheck to disable the coupon.'),
290 '#default_value' => $value->status,
291 );
292
293 $form['type'] = array(
294 '#type' => 'select',
295 '#title' => t('Discount type'),
296 '#default_value' => $value->type,
297 '#options' => array(
298 'percentage' => 'Percentage',
299 'price' => 'Price'
300 ),
301 );
302
303 $form['value'] = array(
304 '#type' => 'textfield',
305 '#title' => t('Discount value'),
306 '#default_value' => $value->value,
307 '#size' => 10,
308 '#description' => t('Enter values without symbols, for 15%, enter "15" and choose Percentage as the discount type.'),
309 '#required' => TRUE,
310 );
311
312 $form['minimum_order'] = array(
313 '#type' => 'textfield',
314 '#title' => t('Minimum order total'),
315 '#default_value' => $value->minimum_order,
316 '#size' => 10,
317 '#description' => t('A minimum order total that applies to the coupon, or 0 for no minimum order limit.'),
318 '#required' => TRUE,
319 '#field_prefix' => variable_get('uc_sign_after_amount', FALSE) ? '' : variable_get('uc_currency_sign', '$'),
320 '#field_suffix' => variable_get('uc_sign_after_amount', FALSE) ? variable_get('uc_currency_sign', '$') : '',
321 );
322
323 $form['max_uses'] = array(
324 '#type' => 'textfield',
325 '#title' => t('Maximum number of redemptions (per code)'),
326 '#default_value' => $value->max_uses,
327 '#description' => t('Enter the maximum number of times each code for this coupon can be used, or 0 for unlimited.'),
328 '#size' => 5,
329 '#required' => TRUE,
330 );
331
332 $form['max_uses_per_user'] = array(
333 '#type' => 'textfield',
334 '#title' => t('Maximum number of redemptions (per user)'),
335 '#default_value' => isset($value->data['max_uses_per_user']) ? $value->data['max_uses_per_user'] : 0,
336 '#description' => t('Enter the maximum number of times this coupon can be used by a single user, or 0 for unlimited.'),
337 '#size' => 5,
338 '#required' => TRUE,
339 );
340
341 $form['max_applicable_products'] = array(
342 '#type' => 'textfield',
343 '#title' => t('Maximum number of applicable products'),
344 '#description' => t('For coupons that are limited in application by product class, product node, SKU, or taxonomy term, specify the maximum number of applicable products to which the discount should be applied. Enter 0 to apply the discount to every product.'),
345 '#default_value' => isset($value->data['max_applicable_products']) ? $value->data['max_applicable_products'] : 0,
346 '#size' => 5,
347 '#required' => TRUE,
348 );
349
350 $form['max_applicable_products_value'] = array(
351 '#type' => 'radios',
352 '#title' => t('Apply against which products'),
353 '#description' => t('For coupons that have a limited number of applicable products, specify to which products the discount should be applied.'),
354 '#options' => array(
355 'cheapest' => t('The cheapest product(s)'),
356 'expensive' => t('The most expensive product(s)'),
357 ),
358 '#default_value' => isset($value->data['max_applicable_products_value']) ? $value->data['max_applicable_products_value'] : 'cheapest',
359 '#required' => TRUE,
360 );
361
362 $options = array('' => '(none)');
363 foreach (module_invoke_all('product_types') as $type) {
364 $options[$type] = $type;
365 }
366
367 $form['product_types'] = array(
368 '#type' => 'select',
369 '#title' => t('Product classes'),
370 '#description' => t('Selecting one or more product classes will restrict this coupon to matching products only. Discounts will then apply to each matching product.'),
371 '#options' => $options,
372 '#default_value' => $value->data['product_types'],
373 '#multiple' => TRUE,
374 );
375
376 $form['products'] = array(
377 '#type' => 'fieldset',
378 '#title' => t('Applicable products'),
379 '#description' => t('Enter one or more products below to restrict this coupon to a set of products, regardless of any product attributes. Discounts will apply to each matching product.'),
380 '#tree' => TRUE,
381 '#collapsible' => TRUE,
382 '#collapsed' => !isset($value->data['products']),
383 );
384
385 $form['products']['negate_products'] = array(
386 '#type' => 'radios',
387 '#default_value' => isset($value->data['negate_products']) ? 1 : 0,
388 '#options' => array(
389 0 => t('Apply coupon to products listed below.'),
390 1 => t('Apply coupon to all products except those listed below.'),
391 ),
392 '#tree' => FALSE,
393 );
394
395 if (isset($value->data['products'])) {
396 foreach ($value->data['products'] as $nid) {
397 $title = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $nid));
398 $form['products'][] = array(
399 '#type' => 'textfield',
400 '#default_value' => $title .' [nid:'. $nid .']',
401 '#autocomplete_path' => 'uc_coupon/autocomplete/node',
402 );
403 }
404 }
405
406 for ($i = 0; $i < 3; $i++) {
407 $form['products'][] = array(
408 '#type' => 'textfield',
409 '#autocomplete_path' => 'uc_coupon/autocomplete/node',
410 );
411 }
412
413 $form['skus'] = array(
414 '#type' => 'fieldset',
415 '#title' => t('Applicable SKUs'),
416 '#description' => t('Enter one or more SKUs below to restrict this coupon to a set of SKUs, allowing coupons to apply to specific products or attribute options. Discounts will apply to matching SKU.'),
417 '#tree' => TRUE,
418 '#collapsible' => TRUE,
419 '#collapsed' => !isset($value->data['skus']),
420 );
421
422 if (isset($value->data['skus'])) {
423 foreach ($value->data['skus'] as $sku) {
424 $form['skus'][] = array(
425 '#type' => 'textfield',
426 '#default_value' => $sku,
427 );
428 }
429 }
430
431 for ($i = 0; $i < 3; $i++) {
432 $form['skus'][] = array(
433 '#type' => 'textfield',
434 );
435 }
436
437 $form['terms'] = array(
438 '#type' => 'fieldset',
439 '#title' => t('Applicable taxonomy terms'),
440 '#description' => t('Enter one or more taxonomy terms (categories) below to restrict this coupon to a set of products. Discounts will apply to all matching products with these terms.'),
441 '#tree' => TRUE,
442 '#collapsible' => TRUE,
443 '#collapsed' => !isset($value->data['terms']),
444 );
445
446 $form['terms']['negate_terms'] = array(
447 '#type' => 'radios',
448 '#default_value' => isset($value->data['negate_terms']) ? 1 : 0,
449 '#options' => array(
450 0 => t('Apply coupon to products with terms listed below.'),
451 1 => t('Apply coupon to all products except those with terms listed below.'),
452 ),
453 '#tree' => FALSE,
454 );
455
456 if (isset($value->data['terms'])) {
457 foreach ($value->data['terms'] as $tid) {
458 $name = db_result(db_query('SELECT name FROM {term_data} WHERE tid = %d', $tid));
459 $form['terms'][] = array(
460 '#type' => 'textfield',
461 '#default_value' => $name .' [tid:'. $tid .']',
462 '#autocomplete_path' => 'uc_coupon/autocomplete/term',
463 );
464 }
465 }
466
467 for ($i = 0; $i < 3; $i++) {
468 $form['terms'][] = array(
469 '#type' => 'textfield',
470 '#autocomplete_path' => 'uc_coupon/autocomplete/term',
471 );
472 }
473
474 $form['users'] = array(
475 '#type' => 'fieldset',
476 '#title' => t('User restrictions'),
477 '#description' => t('Enter one or more user names and/or "anonymous users" below to make this coupon valid only for those users.'),
478 '#tree' => TRUE,
479 '#collapsible' => TRUE,
480 '#collapsed' => !isset($value->data['users']),
481 );
482
483 if (isset($value->data['users'])) {
484 foreach ($value->data['users'] as $uid) {
485 $username = $uid ? db_result(db_query('SELECT name FROM {users} WHERE uid = %d', $uid)) : t('anonymous users');
486 $form['users'][] = array(
487 '#type' => 'textfield',
488 '#default_value' => $username .' [uid:'. $uid .']',
489 '#autocomplete_path' => 'uc_coupon/autocomplete/user',
490 );
491 }
492 }
493
494 for ($i = 0; $i < 3; $i++) {
495 $form['users'][] = array(
496 '#type' => 'textfield',
497 '#autocomplete_path' => 'uc_coupon/autocomplete/user',
498 );
499 }
500
501 $form['roles'] = array(
502 '#type' => 'fieldset',
503 '#title' => t('Role restrictions'),
504 '#description' => t('Enter one or more role names below to make this coupon valid only for users with those roles.'),
505 '#tree' => TRUE,
506 '#collapsible' => TRUE,
507 '#collapsed' => !isset($value->data['roles']),
508 );
509
510 if (isset($value->data['roles'])) {
511 foreach ($value->data['roles'] as $role) {
512 $form['roles'][] = array(
513 '#type' => 'textfield',
514 '#default_value' => $role,
515 '#autocomplete_path' => 'uc_coupon/autocomplete/role',
516 );
517 }
518 }
519
520 for ($i = 0; $i < 3; $i++) {
521 $form['roles'][] = array(
522 '#type' => 'textfield',
523 '#autocomplete_path' => 'uc_coupon/autocomplete/role',
524 );
525 }
526
527 $form['wholesale'] = array(
528 '#type' => 'radios',
529 '#title' => 'Wholesale permissions',
530 '#description' => t('Select the groups who are able to use this coupon. This option is deprecated, it is recommended that you leave this option as "Both wholesale and retail" use the role selection above instead.'),
531 '#default_value' => isset($value->data['wholesale']) ? $value->data['wholesale'] : 1,
532 '#options' => array(
533 '1' => 'Both wholesale and retail',
534 '2' => 'Wholesale buyers only',
535 '3' => 'Retail buyers only'
536 ),
537 '#required' => TRUE,
538 );
539
540 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
541
542 return $form;
543 }
544
545 function _uc_coupon_date_range($form_element) {
546 $form_element['year']['#options'] = drupal_map_assoc(range(2008, 2020));
547 return $form_element;
548 }
549
550 function uc_coupon_autocomplete_node($string) {
551 $matches = array();
552 $product_types = module_invoke_all('product_types');
553
554 $result = db_query_range("SELECT nid, title FROM {node} WHERE type IN ('". implode("','", $product_types) ."') AND title LIKE '%%%s%'", $string, 0, 10);
555 while ($row = db_fetch_object($result)) {
556 $title = check_plain($row->title);
557 $matches[$title .' [nid:'. $row->nid .']'] = $title;
558 }
559 print drupal_to_js($matches);
560 exit;
561 }
562
563 function uc_coupon_autocomplete_term($string) {
564 $matches = array();
565 $result = db_query_range("SELECT tid, name FROM {term_data} WHERE name LIKE '%%%s%'", $string, 0, 10);
566 while ($row = db_fetch_object($result)) {
567 $matches[$row->name .' [tid:'. $row->tid .']'] = $row->name;
568 }
569 print drupal_to_js($matches);
570 exit;
571 }
572
573 function uc_coupon_autocomplete_user($string) {
574 $matches = array();
575 $anonymous = t('anonymous users');
576 if (strpos($anonymous, $string) !== FALSE) {
577 $matches[$anonymous .' [uid:0]'] = $anonymous;
578 }
579
580 $result = db_query_range("SELECT uid, name FROM {users} WHERE name LIKE '%%%s%'", $string, 0, 10);
581 while ($row = db_fetch_object($result)) {
582 $matches[$row->name .' [uid:'. $row->uid .']'] = $row->name;
583 }
584 print drupal_to_js($matches);
585 exit;
586 }
587
588 function uc_coupon_autocomplete_role($string) {
589 $matches = array();
590 $result = db_query_range("SELECT name FROM {role} WHERE name LIKE '%%%s%'", $string, 0, 10);
591 while ($row = db_fetch_object($result)) {
592 $matches[$row->name] = $row->name;
593 }
594 print drupal_to_js($matches);
595 exit;
596 }
597
598 /**
599 * Coupon form validate handler.
600 */
601 function uc_coupon_add_form_validate($form, &$form_state) {
602 // check to ensure a unique coupon code
603 $name = db_result(db_query("SELECT name FROM {uc_coupons} WHERE code = '%s' AND cid <> %d", strtoupper($form_state['values']['code']), $form['#uc_coupon_cid']));
604 if ($name) {
605 form_set_error('code', t('Coupon code already used by %name.', array('%name' => $name)));
606 }
607
608 foreach ($form_state['values']['products'] as $key => $product) {
609 if ($product && !preg_match('/\[nid:(\d+)\]$/', $product)) {
610 form_set_error('products]['. $key, t('Products must include the node ID.'));
611 }
612 }
613
614 foreach ($form_state['values']['users'] as $key => $user) {
615 if ($user && !preg_match('/\[uid:(\d+)\]$/', $user)) {
616 form_set_error('users]['. $key, t('User names must include the user ID.'));
617 }
618 }
619
620 if (!$form['#uc_coupon_used'] && $form_state['values']['bulk_generate'] && intval($form_state['values']['bulk_number']) <= 0) {
621 form_set_error('bulk_number', t('You must specify the number of codes to generate.'));
622 }
623
624 $valid_from = mktime(0, 0, 0, $form_state['values']['valid_from']['month'], $form_state['values']['valid_from']['day'], $form_state['values']['valid_from']['year']);
625 $valid_until = mktime(0, 0, 0, $form_state['values']['valid_until']['month'], $form_state['values']['valid_until']['day'], $form_state['values']['valid_until']['year']);
626 if ($valid_from > $valid_until) {
627 form_set_error('valid_from', t('The coupon start date must be before the expiration date.'));
628 }
629
630 if ($form_state['values']['max_applicable_products']) {
631 $restrictions = array_filter($form_state['values']['product_types'] + $form_state['values']['products'] + $form_state['values']['skus'] + $form_state['values']['terms']);
632 if (empty($restrictions)) {
633 form_set_error('max_applicable_products', t('To use the maximum number of applicable products feature, you must restrict the coupon by product class, product node, SKU, or taxonomy term.'));
634 }
635 }
636 }
637
638 /**
639 * Coupon form submit handler.
640 */
641 function uc_coupon_add_form_submit($form, &$form_state) {
642 // If the coupon was previously used, reset disabled textfields to their original values.
643 if ($form['#uc_coupon_used']) {
644 $form_state['values']['code'] = $form['#uc_coupon']->code;
645 $form_state['values']['bulk_number'] = $form['#uc_coupon']->data['bulk_number'];
646 }
647
648 $code = strtoupper($form_state['values']['code']);
649 $valid_from = mktime(0, 0, 0, $form_state['values']['valid_from']['month'], $form_state['values']['valid_from']['day'], $form_state['values']['valid_from']['year']);
650 $valid_until = mktime(0, 0, 0, $form_state['values']['valid_until']['month'], $form_state['values']['valid_until']['day'], $form_state['values']['valid_until']['year']);
651 $data = array();
652
653 if ($form_state['values']['bulk_generate']) {
654 $data['bulk_number'] = $form_state['values']['bulk_number'];
655 $data['bulk_length'] = $form_state['values']['bulk_length'];
656 }
657
658 if ($form_state['values']['max_uses_per_user']) {
659 $data['max_uses_per_user'] = $form_state['values']['max_uses_per_user'];
660 }
661
662 if ($form_state['values']['max_applicable_products']) {
663 $data['max_applicable_products'] = $form_state['values']['max_applicable_products'];
664 }
665
666 if ($form_state['values']['max_applicable_products_value']) {
667 $data['max_applicable_products_value'] = $form_state['values']['max_applicable_products_value'];
668 }
669
670 if ($form_state['values']['negate_products']) {
671 $data['negate_products'] = TRUE;
672 }
673
674 if ($form_state['values']['negate_terms']) {
675 $data['negate_terms'] = TRUE;
676 }
677
678 foreach ($form_state['values']['product_types'] as $type) {
679 if ($type) {
680 $data['product_types'][] = $type;
681 }
682 }
683
684 foreach ($form_state['values']['products'] as $key => $product) {
685 if ($product && preg_match('/\[nid:(\d+)\]$/', $product, $matches)) {
686 $data['products'][] = $matches[1];
687 }
688 }
689
690 foreach ($form_state['values']['skus'] as $sku) {
691 if ($sku) {
692 $data['skus'][] = $sku;
693 }
694 }
695
696 foreach ($form_state['values']['terms'] as $key => $term) {
697 if ($term && preg_match('/\[tid:(\d+)\]$/', $term, $matches)) {
698 $data['terms'][] = $matches[1];
699 }
700 }
701
702 foreach ($form_state['values']['users'] as $key => $user) {
703 if ($user && preg_match('/\[uid:(\d+)\]$/', $user, $matches)) {
704 $data['users'][] = $matches[1];
705 }
706 }
707
708 foreach ($form_state['values']['roles'] as $role) {
709 if ($role) {
710 $data['roles'][] = $role;
711 }
712 }
713
714 $data['wholesale'] = $form_state['values']['wholesale'];
715
716 if (!isset($form['#uc_coupon_cid'])) {
717 // Only set bulk coupon seed once.
718 db_query("INSERT INTO {uc_coupons} (name, code, value, type, status, valid_from, valid_until, max_uses, minimum_order, data, bulk, bulk_seed) VALUES ('%s', '%s', %f, '%s', %d, %d, %d, %d, %f, '%s', %d, '%s')", $form_state['values']['name'], $code, $form_state['values']['value'], $form_state['values']['type'], $form_state['values']['status'], $valid_from, $valid_until, $form_state['values']['max_uses'], $form_state['values']['minimum_order'], serialize($data), $form_state['values']['bulk_generate'], md5(uniqid()));
719 drupal_set_message(t('Coupon %name has been created.', array('%name' => $form_state['values']['name'])));
720 }
721 else {
722 db_query("UPDATE {uc_coupons} SET name = '%s', code = '%s', value = %f, type = '%s', status = %d, valid_from = %d, valid_until = %d, max_uses = %d, minimum_order = %f, data = '%s', bulk = %d WHERE cid = %d", $form_state['values']['name'], $code, $form_state['values']['value'], $form_state['values']['type'], $form_state['values']['status'], $valid_from, $valid_until, $form_state['values']['max_uses'], $form_state['values']['minimum_order'], serialize($data), $form_state['values']['bulk_generate'], $form['#uc_coupon_cid']);
723 drupal_set_message(t('Coupon %name has been updated.', array('%name' => $form_state['values']['name'])));
724 }
725
726 $form_state['redirect'] = 'admin/store/customers/coupon'. ($form_state['values']['status'] ? '' : '/inactive');
727 }
728
729
730 /**
731 * Load a coupon into the form for editing
732 *
733 * @param $cid
734 * Unique coupon ID.
735 *
736 * @return $coupon
737 * Returns a coupon object.
738 */
739 function uc_coupon_load($cid) {
740 $coupon = db_fetch_object(db_query("SELECT * FROM {uc_coupons} WHERE cid = %d", $cid));
741 if ($coupon->data) {
742 $coupon->data = unserialize($coupon->data);
743 }
744 return $coupon;
745 }
746
747 /**
748 * Delete coupon confirm form
749 *
750 * @param $cid int
751 * Coupon ID.
752 *
753 * @return $confirm
754 * Return a drupal confirm form.
755 */
756 function uc_coupon_delete_confirm(&$form_state, $coupon) {
757 $form['#uc_coupon_cid'] = $coupon->cid;
758 return confirm_form($form, t('Are you sure you want to delete coupon %name with code %code?', array('%name' => $coupon->name, '%code' => $coupon->code)), 'admin/store/customers/coupon', t('This action cannot be undone. Deleting this coupon will remove all records of past uses as well.'), t('Delete'));
759 }
760
761 function uc_coupon_delete_confirm_submit($form, &$form_state) {
762 $coupon = uc_coupon_load($form['#uc_coupon_cid']);
763
764 db_query("DELETE FROM {uc_coupons} WHERE cid = %d", $form['#uc_coupon_cid']);
765 db_query("DELETE FROM {uc_coupons_orders} WHERE cid = %d", $form['#uc_coupon_cid']);
766
767 drupal_set_message(t('Coupon %name has been deleted.', array('%name' => $coupon->name)));
768 $form_state['redirect'] = 'admin/store/customers/coupon'. ($coupon->status ? '' : '/inactive');
769 }
770
771 /**
772 * Generate a list of bulk coupon codes.
773 */
774 function uc_coupon_codes_csv($coupon = NULL) {
775 if (!$coupon->bulk) {
776 drupal_not_found();
777 return;
778 }
779
780 header('Content-Type: application/octet-stream');
781 header('Content-Disposition: attachment; filename="'. $coupon->code .'.csv";');
782
783 for ($id = 0; $id < $coupon->data['bulk_number']; $id++) {
784 echo uc_coupon_get_bulk_code($coupon, $id) ."\n";
785 }
786 exit;
787 }
788
789 /**
790 * Generate a single bulk coupon code.
791 */
792 function uc_coupon_get_bulk_code($coupon, $id) {
793 $id = str_pad(dechex($id), strlen(dechex($coupon->data['bulk_number'])), '0', STR_PAD_LEFT);
794 $length = strlen($coupon->code) + $coupon->data['bulk_length'];
795 return strtoupper(substr($coupon->code . $id . md5($coupon->bulk_seed . $id), 0, $length));
796 }
797
798 /**
799 * Load a coupon (single or bulk) from the supplied code.
800 */
801 function uc_coupon_find($code) {
802 // Look for matching single coupon first.
803 $coupon = db_fetch_object(db_query("SELECT * FROM {uc_coupons} WHERE code = '%s' AND status = 1 AND bulk = 0 AND valid_from < %d AND valid_until > %d", $code, time(), time()));
804 if ($coupon !== FALSE) {
805 if ($coupon->data) {
806 $coupon->data = unserialize($coupon->data);
807 }
808 return $coupon;
809 }
810
811 // Look through bulk coupons.
812 $result = db_query("SELECT * FROM {uc_coupons} WHERE status = 1 AND bulk = 1 AND valid_from < %d AND valid_until > %d", time(), time());
813 while ($coupon = db_fetch_object($result)) {
814 // Check coupon prefix.
815 $prefix_length = strlen($coupon->code);
816 if (substr($code, 0, $prefix_length) != $coupon->code) {
817 continue;
818 }
819
820 if ($coupon->data) {
821 $coupon->data = unserialize($coupon->data);
822 }
823
824 // Check coupon sequence ID.
825 $id = substr($code, $prefix_length, strlen(dechex($coupon->data['bulk_number'])));
826 if (!preg_match("/^[0-9A-F]+$/", $id)) {
827 continue;
828 }
829 $id = hexdec($id);
830 if ($id < 0 || $id > $coupon->data['bulk_number']) {
831 continue;
832 }
833
834 // Check complete coupon code.
835 if ($code == uc_coupon_get_bulk_code($coupon, $id)) {
836 return $coupon;
837 }
838 }
839
840 return FALSE;
841 }
842
843 /**
844 * Validate a coupon and calculate the coupon amount against the current cart contents.
845 *
846 * @param $code
847 * The coupon code entered at the checkout screen
848 *
849 * @return
850 * Returns a coupon result object with details about the validation
851 */
852 function uc_coupon_validate($code) {
853 global $user;
854
855 $result->valid = FALSE;
856
857 $code = strtoupper($code);
858 $coupon = uc_coupon_find($code);
859
860 if (!$coupon) {
861 $result->message = t('This coupon code is invalid or has expired.');
862 return $result;
863 }
864
865 if (isset($coupon->data['products']) || isset($coupon->data['skus']) || isset($coupon->data['terms']) || isset($coupon->data['product_types'])) {
866 $prices = array();
867
868 // Product coupons apply to the subtotal and quantity of matching products.
869 foreach (uc_cart_get_contents() as $item) {
870 $cart_total += $item->price * $item->qty;
871
872 $terms = array();
873 $query = db_query("SELECT tid FROM {term_node} WHERE vid = %d", $item->vid);
874 while ($tid = db_result($query)) {
875 $terms[] = $tid;
876 }
877
878 if (isset($coupon->data['products']) && (isset($coupon->data['negate_products']) xor in_array($item->nid, $coupon->data['products']))) {
879 $prices = array_pad($prices, count($prices) + $item->qty, $item->price);
880 }
881 else if (isset($coupon->data['skus']) && in_array($item->model, $coupon->data['skus'])) {
882 $prices = array_pad($prices, count($prices) + $item->qty, $item->price);
883 }
884 else if (isset($coupon->data['terms']) && (isset($coupon->data['negate_terms']) xor count(array_intersect($terms, $coupon->data['terms'])))) {
885 $prices = array_pad($prices, count($prices) + $item->qty, $item->price);
886 }
887 else if (isset($coupon->data['product_types'])) {
888 $type = db_result(db_query("SELECT type FROM {node} WHERE nid = %d", $item->nid));
889 if (in_array($type, $coupon->data['product_types'])) {
890 $prices = array_pad($prices, count($prices) + $item->qty, $item->price);
891 }
892 }
893 }
894
895 // If a maximum number of applicable products has been specified...
896 if (isset($coupon->data['max_applicable_products']) && ($num = $coupon->data['max_applicable_products']) > 0) {
897 // Sort the array of applicable product prices.
898 sort($prices);
899
900 // Slice the appropriate number of prices off the array.
901 if ($coupon->data['max_applicable_products_value'] == 'cheapest') {
902 $applicable_prices = array_slice($prices, 0, $num);
903 }
904 else {
905 $applicable_prices = array_slice($prices, -$num);
906 }
907
908 // Set the applicable total and quantity based on our new prices.
909 $applicable_total = 0;
910 foreach ($applicable_prices as $price) {
911 $applicable_total += $price;
912 }
913 $applicable_qty = $num;
914 }
915 else {
916 // Otherwise include all the prices in the applicable totals.
917 $applicable_total = 0;
918 foreach ($prices as $price) {
919 $applicable_total += $price;
920 }
921 $applicable_qty = count($prices);
922 }
923 }
924 else {
925 // Standard coupons apply once to the whole cart.
926 foreach (uc_cart_get_contents() as $item) {
927 $cart_total += $item->price * $item->qty;
928 }
929 $applicable_total = $cart_total;
930 $applicable_qty = 1;
931 }
932
933 if ($applicable_total == 0) {
934 $result->message = t('You do not have applicable products in your cart.');
935 return $result;
936 }
937
938 // CHECK MAX USES
939 if ($coupon->max_uses > 0) {
940 $used = db_result(db_query("SELECT COUNT(*) FROM {uc_coupons_orders} AS uco LEFT JOIN {uc_orders} AS uo ON uco.oid = uo.order_id LEFT JOIN {uc_order_statuses} AS uos ON uo.order_status = uos.order_status_id WHERE uos.weight > 0 AND uco.cid = %d AND uco.code = '%s'", $coupon->cid, $code));
941 if ($used >= $coupon->max_uses) {
942 $result->message = t('This coupon has reached the maximum redemption limit.');
943 return $result;
944 }
945 }
946
947 // CHECK MAX USES PER USER
948 if (isset($coupon->data['max_uses_per_user'])) {
949 $used = db_result(db_query("SELECT COUNT(*) FROM {uc_coupons_orders} AS uco LEFT JOIN {uc_orders} AS uo ON uco.oid = uo.order_id LEFT JOIN {uc_order_statuses} AS uos ON uo.order_status = uos.order_status_id WHERE uos.weight > 0 AND uco.cid = %d AND uo.uid = %d", $coupon->cid, $user->uid));
950 if ($used >= $coupon->data['max_uses_per_user']) {
951 $result->message = t('This coupon has reached the maximum redemption limit.');
952 return $result;
953 }
954 }
955
956 // CHECK MINIMUM PURCHASE VALUE
957 if ($coupon->minimum_order > 0 && $coupon->minimum_order > $cart_total) {
958 $result->message = t('You have not reached the minimum order total for this coupon.');
959 return $result;
960 }
961
962 // CHECK USER ID
963 if (isset($coupon->data['users'])) {
964 if (!in_array("$user->uid", $coupon->data['users'], TRUE)) {
965 $result->message = t('Your user ID is not allowed to use this coupon.');
966 return $result;
967 }
968 }
969
970 // CHECK ROLES
971 if (isset($coupon->data['roles'])) {
972 $role_found = FALSE;
973 foreach ($coupon->data['roles'] as $role) {
974 if (in_array($role, $user->roles)) {
975 $role_found = TRUE;
976 break;
977 }
978 }
979 if (!$role_found) {
980 $result->message = t('You do not have the correct permission to use this coupon.');
981 return $result;
982 }
983 }
984
985 // CHECK USER PERMISSIONS
986 // 1 - both wholesale and retail any user
987 // 2 - wholesale only -> users with 'coupon wholesale pricing'
988 // 3 - retail only -> users without 'coupon wholesale pricing'
989 if ($coupon->data['wholesale'] > 1) {
990 if ($coupon->data['wholesale'] == 2) {
991 if (!user_access('coupon wholesale pricing')) {
992 $result->message = t('You do not have the correct permission to use this coupon.');
993 return $result;
994 }
995 }
996 else if ($coupon->data['wholesale'] == 3) {
997 if (user_access('coupon wholesale pricing')) {
998 $result->message = t('You do not have the correct permission to use this coupon.');
999 return $result;
1000 }
1001 }
1002 }
1003
1004 $result->valid = TRUE;
1005 $result->code = $code;
1006 $result->cid = $coupon->cid;
1007 $result->title = t('Coupon: @code', array('@code' => $code));
1008
1009 if ($coupon->type == 'percentage') {
1010 $result->amount = $applicable_total * $coupon->value / 100;
1011 }
1012 else if ($coupon->type == 'price') {
1013 $result->amount = min($applicable_total, $applicable_qty * $coupon->value);
1014 }
1015
1016 return $result;
1017 }
1018
1019 /**
1020 * Implementation of hook_cart_pane().
1021 */
1022 function uc_coupon_cart_pane($items) {
1023 $panes[] = array(
1024 'id' => 'coupon',
1025 'body' => drupal_get_form('uc_cart_pane_coupon'),
1026 'title' => t('Coupon discount'),
1027 'desc' => t('Allows shoppers to use a coupon during checkout for order discounts.'),
1028 'weight' => 1,
1029 'enabled' => TRUE,
1030 );
1031 return $panes;
1032 }
1033
1034 /**
1035 * Cart pane coupon form.
1036 */
1037 function uc_cart_pane_coupon($form_state) {
1038 $form['code'] = </