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

Contents of /contributions/modules/paypalnode/paypalnode.module

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


Revision 1.1 - (show annotations) (download) (as text)
Thu Aug 2 11:13:13 2007 UTC (2 years, 3 months ago) by budda
Branch: MAIN
CVS Tags: DRUPAL-5--1-0, HEAD
Branch point for: DRUPAL-5
File MIME type: text/x-php
Initial commit of paypalnode module.
1 <?php
2 /*
3 * $Id:$
4 * @name PayPalNode
5 * @description Allows a fee to be charged for publishing a specified content
6 * type based on a combination of taxonomy term prices.
7 * @author Mike Carter <ixis.co.uk/contact>
8 *
9 **/
10
11 define(PAYPALNODE_CHARGE_TYPE, 'story');
12 define(PAYPALNODE_CURRENCY, 'USD');
13 define(PAYPALNODE_EMAIL_ACCOUNT, 'accounts@ixis.co.uk');
14 define(PAYPALNODE_THANKS_PATH, 'paypalnode/thanks');
15
16 /*
17 paypalnode_help
18 */
19 function paypalnode_help($section) {
20 switch($section) {
21 case 'admin/content/fees':
22 return t('Define prices for content published under any of the categories you list.');
23 break;
24
25 case 'admin/settings/paypalnode':
26 return t('This module allows you to charge users for publishing a specific content type. Prices are attached to taxonomy terms, allowing a huge combination of prices to be generated.');
27 break;
28
29 case 'notifyemail/subject':
30 return t('Your content has expired');
31 break;
32
33 case 'notifyemail/body':
34 $body = t("Dear !username,") . "\n\n";
35 $body .= t("The content called '!title' that you created on !created has been un-published due to it being over !expirydays days old.\n");
36 $body .= "\n\n" . t('-- !site-name team');
37 return $body;
38 break;
39 }
40 }
41
42
43 /*
44 * Add a price fields to the taxonomy term form for specified vocabularies
45 */
46 function paypalnode_form_alter($form_id, &$form) {
47 if($form_id == variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE) . '_node_form') {
48 $path = drupal_get_path('module', 'paypalnode');
49
50 // Add A Auto Price calculator widget
51 $form['price'] = array('#type' => 'textfield',
52 '#attributes'=> array('readonly' => 'true'),
53 '#title' => t('Publication cost'),
54 '#default_value' => paypalnode_calculate_fee($form['nid']['#value']));
55
56 $form['price-autocomplete']= array('#type' => 'hidden',
57 '#value' => url('paypalnode/calculatefee', NULL, NULL, TRUE),
58 );
59
60 drupal_add_js($path . '/calculateprice.js', 'module', 'header');
61
62 // Disable Categories if editing a node
63 if(!user_access('administer nodes') &&$form['nid']['#value']) {
64 $attribs['disabled'] = 'disabled';
65 }
66
67
68 // Mark Taxonomy Form Menus Which are related to price calculation
69 $vocabs = variable_get('paypalnode_vocabularies', array());
70 $attribs['class'] = 'paypernode';
71 foreach($vocabs as $vocab_id) {
72 if($form['taxonomy'][$vocab_id]) {
73 $form['taxonomy'][$vocab_id]['#attributes'] = $attribs;
74 }
75 }
76 }
77 }
78
79
80 /*
81 * Returns the price of the specified taxonomy term
82 */
83 function paypalnode_get_price($term_ids) {
84 $p = 0.00;
85
86 if($term_ids) {
87 $p = db_result(db_query("SELECT price FROM {term_price} WHERE tids LIKE '%s%%'", $term_ids));
88 }
89
90 return number_format($p, 2);
91 }
92
93
94 /*
95 * Invoke the Paypal payment process to publish a certain content-type
96 */
97 function paypalnode_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
98 if($node->type == variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE)) {
99
100 switch($op) {
101 case 'insert':
102 // If the content is going to cost to publish, collect the money
103 $amount = paypalnode_calculate_fee($node->nid);
104 if($amount > 0) {
105
106 watchdog('paypalnode', t('about to unpublish <pre>@node</pre>', array(
107 '@node' => print_r($node, TRUE),
108 )), WATCHDOG_NOTICE, l(t('View'), 'node/'.$node->nid));
109
110 // make sure the new node is unpublished until payment is made
111 db_query('UPDATE {node} SET status = 0, changed = %d WHERE nid = %d AND vid = %d', time(), $node->nid, $node->vid);
112
113 $new_node = node_load(array('nid' => $node->nid, 'vid' => $node->vid));
114
115 watchdog('paypalnode', t('completed to unpublish <pre>@node</pre>', array(
116 '@node' => print_r($new_node, TRUE),
117 )), WATCHDOG_NOTICE, l(t('View'), 'node/'.$new_node->nid));
118
119 // Redirect to the paypal form for price calculation
120 $_REQUEST['destination'] = 'paypalnode/'.$node->nid;
121 } else {
122 drupal_set_message('Your free ad was published!');
123 }
124 break;
125 }
126 }
127 }
128
129
130 /*
131 Standard Drupal menu hook
132 */
133 function paypalnode_menu($may_cache) {
134 $items = array();
135
136 if ($may_cache) {
137 $access = user_access('administer site configuration');
138
139 $items[] = array(
140 'path' => 'ipn/paypalnode',
141 'title' => t('IPN post'),
142 'callback' => 'paypalnode_paypal_ipn',
143 'access' => TRUE,
144 'type' => MENU_CALLBACK,
145 );
146 $items[] = array(
147 'path' => 'admin/content/fees',
148 'title' => t('Content Fees'),
149 'callback' => 'drupal_get_form',
150 'callback arguments' => array('paypalnode_fee_settings_form'),
151 'description' => t('Administer costs for publishing content'),
152 'access' => $access,
153 );
154 $items[] = array(
155 'path' => 'admin/content/fees/edit',
156 'title' => t('Edit a content Fee'),
157 'callback' => 'drupal_get_form',
158 'callback arguments' => array('paypalnode_fee_edit_form'),
159 'access' => $access,
160 'type' => MENU_CALLBACK,
161 );
162 $items[] = array(
163 'path' => 'admin/content/fees/delete',
164 'title' => t('Delete a content Fee'),
165 'callback' => 'drupal_get_form',
166 'callback arguments' => array('paypalnode_fee_delete_form'),
167 'description' => t('Delete a cost'),
168 'access' => $access,
169 'type' => MENU_CALLBACK,
170 );
171 $items[] = array(
172 'path' => 'paypalnode',
173 'title' => t('Payment'),
174 'access' => TRUE,
175 'callback' => 'paypalnode_transaction',
176 'type' => MENU_CALLBACK,
177 );
178 $items[] = array(
179 'path' => PAYPALNODE_THANKS_PATH,
180 'title' => t('Thanks'),
181 'access' => TRUE,
182 'callback' => 'paypalnode_thanks',
183 'type' => MENU_CALLBACK,
184 );
185 $items[] = array(
186 'path' => 'paypalnode/calculatefee',
187 'title' => t('Price calculator'),
188 'access' => TRUE,
189 'callback' => 'paypalnode_calculate_fee_ajax_callback',
190 'type' => MENU_CALLBACK,
191 );
192 $items[] = array(
193 'path' => 'admin/settings/paypalnode',
194 'title' => t('PayPal Node'),
195 'callback' => 'drupal_get_form',
196 'callback arguments' => array('paypalnode_settings'),
197 'description' => t('Administer Paypal node'),
198 'access' => user_access('administer site configuration'),
199 );
200 }
201
202 return $items;
203 }
204
205
206 /*
207 Calculates the cost of publishing a specified node
208 */
209 function paypalnode_calculate_fee($node_id) {
210 $node = node_load(array('nid' => $node_id, 'type' => variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE)));
211 $vocabs = variable_get('paypalnode_vocabularies', PAYPALNODE_CHARGE_VOCABULARIES);
212 $price = 0;
213 $tids = array();
214
215 // Build up an array of taxonomay term ids
216 if(is_array($node->taxonomy)) {
217 foreach($node->taxonomy as $term) {
218 // Make sure the term belongs in a vocabulary we are calculating a cost on
219 if(in_array($term->vid, $vocabs)) {
220 $tids[] = $term->tid;
221 }
222 }
223
224 // Reverse the taxonomy order to match Drupal's dipslay order
225 $tids = array_reverse($tids);
226 }
227
228 // Calculate price based on term ids
229 $price = paypalnode_get_price(implode(',', $tids));
230
231 // Format price if available
232 if($price > 0) {
233 $price = number_format($price, 2);
234 }
235
236
237 return $price;
238 }
239
240
241 /*
242 * Accepts a list of terms ids and calculates the total cost.
243 * Used in the ajax callback for the content-type submission form.
244 */
245 function paypalnode_calculate_fee_ajax_callback() {
246 $price = 0;
247 $terms = explode(',', $_GET['terms']);
248
249 $tids = paypalnode_terms_to_tids($terms);
250 $price = paypalnode_get_price(implode(',', $tids));
251
252 if($price > 0) {
253 print number_format($price,2);
254 } else {
255 print "FREE";
256 }
257 }
258
259
260 function paypalnode_transaction() {
261 $output = theme('paypalnode_interstitial', drupal_get_form('paypalnode_transaction_form'));
262
263 print $output;
264 }
265
266
267 /*
268 paypalnode_paypalnode_form_build
269 */
270 function paypalnode_transaction_form() {
271
272 $nid = arg(1);
273 $url = module_invoke('simple_paypal', 'get_url');
274
275 if($url && is_numeric($nid)) {
276 $amount = paypalnode_calculate_fee($nid);
277 $currency = variable_get('paypalnode_currency', PAYPALNODE_CURRENCY);
278 $node = node_load(array('nid' => $nid, 'type' => variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE)));
279
280 foreach($node->taxonomy as $term) {
281 $terms[] = $term->name;
282 }
283
284 $form['#action'] = $url;
285 $form['business'] = array(
286 '#type' => 'hidden',
287 '#name' => 'business',
288 '#value' => variable_get('paypalnode_email_account', PAYPALNODE_EMAIL_ACCOUNT),
289 );
290 $form['currency_code'] = array(
291 '#type' => 'hidden',
292 '#value' => $currency,
293 '#name' => 'currency_code',
294 );
295 $form['amount'] = array(
296 '#type' => 'hidden',
297 '#value' => $amount,
298 '#name' => 'amount',
299 );
300 $form['cmd'] = array(
301 '#type' => 'hidden',
302 '#value' => '_xclick',
303 '#name' => 'cmd',
304 );
305 $form['item_number'] = array(
306 '#type' => 'hidden',
307 '#value' => $node->nid,
308 '#name' => 'item_number',
309 );
310 $form['item_name'] = array(
311 '#type' => 'hidden',
312 '#value' => t('@terms / @title', array('@title' => $node->title, '@terms' => implode(' / ', $terms))),
313 '#name' => 'item_name',
314 );
315 $form['no_shipping'] = array(
316 '#type' => 'hidden',
317 '#value' => 1,
318 '#name' => 'no_shipping',
319 );
320 $form['return'] = array(
321 '#type' => 'hidden',
322 '#value' => url(variable_get('paypalnode_thanks_path', PAYPALNODE_THANKS_PATH), NULL, NULL, TRUE),
323 '#name' => 'return',
324 );
325 $form['custom'] = array(
326 '#type' => 'hidden',
327 '#value' => $user->uid,
328 '#name' => 'custom',
329 );
330 $form['notify_url'] = array(
331 '#type' => 'hidden',
332 '#value' => url('ipn/paypalnode', NULL, NULL, TRUE),
333 '#name' => 'notify_url',
334 );
335
336 $interstitial = variable_get('paypalnode_interstitial', '');
337 if($interstitial) {
338 $html = $interstitial;
339 $form['html'] = array('#value' => $html);
340 }
341
342 $form['submit'] = array(
343 '#type' => 'submit',
344 '#value' => t('Proceed To Payment Page'),
345 '#name' => 'paysubmit',
346 );
347
348 $script = "function paypalnode_overlay() { $('body').append('<iframe id=\"TB_HideSelect\"></iframe><div id=\"TB_overlay\"></div>'); return true; }";
349
350 // Add some Javascript to automatically submit the form if no interstitial has been provided
351 if(!$interstitial) {
352 $script .= "paypalnode_overlay();";
353 $script .= "$('#paypalnode-transaction-form')[0].submit();";
354 } else {
355 $script .= "$('#paypalnode-transaction-form #edit-submit').bind('click', {}, paypalnode_overlay);";
356 }
357
358 $path = drupal_get_path('module', 'paypalnode');
359 drupal_add_js($script, 'inline', 'footer', TRUE, TRUE);
360 drupal_add_css($path.'/paypalnode.css');
361 } else {
362 watchdog('paypalnode', t('No PayPal URL is defined. Payment processing failed.'), WATCHDOG_ERROR, l(t('View :ct', array(':ct' => variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE))), 'node/'.$nid));
363 drupal_set_message(t('Unable to process the payment for your content.'), 'error');
364 drupal_goto();
365 }
366
367 return $form;
368 }
369
370
371 /*
372 * Handles the callback from Paypal confirming the payment is cleared
373 */
374 function paypalnode_paypal_ipn() {
375 // Verify that the request came from Paypal, and not from some intrusion
376 if (!simple_paypal_ipn_verify($_POST)) {
377 // Verification failed
378 return;
379 }
380
381 if ($_POST['business'] != variable_get('paypalnode_email_account', PAYPALNODE_EMAIL_ACCOUNT) ||
382 $_POST['receiver_email'] != variable_get('paypalnode_email_account', PAYPALNODE_EMAIL_ACCOUNT)) {
383 // Payment is not for the email address configured
384 watchdog('paypalnode', t("A payment notification was received which didn't match your configured business account @email", array('@email' => $_POST['business'])), WATCHDOG_ERROR);
385 return;
386 }
387
388 $uid = (int)($_POST['custom']);
389 $time_paid = check_plain(strtotime($_POST['payment_date']));
390 $name = check_plain($_POST['first_name'] .' '. $_POST['last_name'] . ($_POST['payer_business_name'] ? ' ('. $_POST['payer_business_name'] .')' : ''));
391 $gross_amount = check_plain((float)$_POST['mc_gross']);
392 $net_amount = check_plain((float)$_POST['mc_gross'] - (float)$_POST['mc_fee']);
393 $mail = check_plain($_POST['payer_email']);
394 $currency = check_plain($_POST['mc_currency']);
395 $node_id = check_plain($_POST['item_number']);
396 $status = check_plain($_POST['payment_status']);
397
398 switch($status) {
399 // Transaction has been accepted by the merchant
400 case 'Completed':
401 watchdog('paypalnode', t('Node payment from @name (@mail) amount of @amount @currency has cleared. A fee of @fee @currency was charged by PayPal.', array(
402 '@name' => $name,
403 '@mail' => $mail,
404 '@amount' => $gross_amount,
405 '@fee' => check_plain($_POST['mc_fee']),
406 '@currency' => $currency,
407 )), WATCHDOG_NOTICE, l(t('View'), 'node/'.$node_id));
408
409 // Re-Calculate The Cost For The Advert
410 $cost = paypalnode_calculate_fee($node_id);
411
412 // Publish the corresponding node if the price is right!
413 if($cost == $gross_amount) {
414 $node = node_load(array('nid' => $node_id, 'type' => variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE) ));
415
416 if($node) {
417 db_query('UPDATE {node} SET status = 1 WHERE nid = %d AND vid = %d', $node->nid, $node->vid);
418
419 watchdog('paypalnode', t('%title has now been published. <pre>@node</pre>', array(
420 '%title' => $node->title,
421 '@node' => print_r($node, TRUE)
422 )), WATCHDOG_NOTICE, l(t('View'), 'node/'.$node->nid));
423 }
424 }
425 break;
426
427 // A payment has been made but PayPal have not cleared the payment yet
428 case 'Pending';
429 watchdog('paypalnode', t('Node payment from @name (@mail) amount of @amount @currency is pending due to %reason.', array(
430 '@name' => $name,
431 '@mail' => $mail,
432 '@amount' => $net_amount,
433 '@currency' => $currency,
434 '%reason' => check_plain($_POST['pending_reason']),
435 )), WATCHDOG_NOTICE, l(t('View'), 'node/'.$node_id));
436 break;
437 }
438 }
439
440
441 /*
442 Displays a thankyou page after the payment process done.
443 */
444 function paypalnode_thanks() {
445 $node_id = (int)check_plain($_REQUEST['item_number']);
446
447 // If a node id was specified
448 if($node_id) {
449
450 // Check if the payment completed straight away, if so redirect to advert
451 if(check_plain($_REQUEST['payment_status']) == 'Completed') {
452 drupal_goto('node/'.$node_id);
453 }
454
455 $node = node_load(array('nid' => $node_id, 'type' => variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE) ));
456
457 // Display thankyou message
458 $output = '<p>' . t('Thanks for your payment to publish !title.', array('!title' => l($node->title, 'node/'.$node->nid))) . '</p>';
459 $output .= '<p>' . t('Your payment is currently being processed, but your content will be visible soon.') . '</p>';
460
461 return $output;
462 }
463
464 // Must be some idiot trying to access the thankyou page
465 drupal_not_found();
466 }
467
468
469 /**
470 * Format currency.
471 */
472 function _paypalnode_format_amount($amount, $currency) {
473 $amount = number_format($amount, 2);
474 switch($currency) {
475 case 'EUR':
476 return $amount";
477 case 'USD':
478 return "$ $amount";
479 case 'CAD':
480 return "C$ $amount";
481 default:
482 return check_plain($currency). " $amount";
483 }
484 }
485
486
487 /*
488 * Converts an array of term names in to an array of term ids
489 */
490 function paypalnode_terms_to_tids($terms) {
491 $tids = array();
492 foreach($terms as $vid => $term_name) {
493
494 if((substr($term_name,0,1) == '"') && (substr($term_name,-1,1) == '"')) {
495
496 // Trim the enclosing double quotes from the term name
497 $term_name = substr($term_name, 1, strlen($term_name)-2);
498
499 // Convert term name to a term id
500 $term = taxonomy_get_term_by_name($term_name);
501
502 if($term) {
503 $tids[] = $term[0]->tid;
504 }
505 } else {
506 $tids[] = $term_name;
507 }
508 }
509
510 return $tids;
511 }
512
513
514 /*
515 paypalnode_cron
516 */
517 function paypalnode_cron() {
518 // Only check for expired nodes every 4 hours (14400)
519 if(variable_get('paypalnode_cron_last', 0) < (time()-3600)) {
520
521 watchdog('paypalnode', t('Checking for expired @content content.', array('@content' => variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE))), WATCHDOG_NOTICE);
522
523 $expiry_days = variable_get('paypalnode_expiry_days', '30');
524 $expiry_time = paypalnode_days_to_seconds($expiry_days);
525
526 // Unpublish Old Nodes
527 $unpublished_nodes = array();
528 $old_nodes = db_query("SELECT u.name, u.mail, n.nid, n.vid, n.title, n.created FROM {node} n LEFT JOIN {users} u ON n.uid = u.uid WHERE n.type = '%s' AND n.status = 1 AND n.nid > %d AND n.created < %d ORDER BY n.nid", variable_get('paypalnode_content_type', PAYPALNODE_CHARGE_TYPE), variable_get('paypalnode_cron_last_nid', 0), time() - $expiry_time);
529 if($old_nodes) {
530 while($node = db_fetch_array($old_nodes)) {
531 // Get the advertising price for the node
532 $price = paypalnode_calculate_fee($node['nid']);
533
534 // Only unpublish adverts which have a price
535 if($price > 0) {
536 db_query('UPDATE {node} SET status = 0, changed = %d WHERE nid = %d AND vid = %d', time(), $node['nid'], $node['vid']);
537 $unpublished_nodes[$node['nid']] = l(t('@title (@cost @currency)', array('@title' => $node['title'], '@cost' => $price, '@currency' => variable_get('paypalnode_currency', PAYPALNODE_CURRENCY))), 'node/'.$node['nid']);
538 paypalnode_cron_notify_author($node);
539 }
540 }
541
542 // Record last node id checked, so it doesn't get checked again (because it was a free advert)
543 variable_get('paypalnode_cron_last_nid', $node['nid']);
544 }
545
546 // Record the time we last checked for expired nodes
547 variable_set('paypalnode_cron_last', time());
548
549 // Log any expired nodes
550 if(count($unpublished_nodes) > 0) {
551 watchdog('paypalnode', t('Unpublished @counter nodes due to being over @expiry days old. !nodelist', array(
552 '@counter' => count($unpublished_nodes),
553 '@expiry' => $expiry_days,
554 '!nodelist' => theme('item_list', $unpublished_nodes)
555 )), WATCHDOG_NOTICE);
556 }
557 }
558 }
559
560
561 /**
562 * convert days to # seconds
563 */
564 function paypalnode_days_to_seconds($days) {
565 return $days * 86400; // 60 * 60 * 24 = 86400
566 }
567
568
569 function paypalnode_cron_notify_author($node) {
570 if(variable_get('paypalnode_expiry_notify', '0')) {
571 $from = variable_get('site_mail', '');
572 $subject = token_replace(variable_get('paypalnode_expiry_email_subject', paypalnode_help('notifyemail/subject')), 'PayPal Node', $node, '!', '');
573 $body = token_replace(variable_get('paypalnode_expiry_email_body', paypalnode_help('notifyemail/body')), 'PayPal Node', $node, '!', '');
574 drupal_mail('paypalnode-expiry', $node['mail'], $subject, $body, $from);
575 }
576 }
577
578
579 function theme_paypalnode_interstitial($content) {
580 $output = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
581 $output .= '<html xmlns="http://www.w3.org/1999/xhtml">';
582 $output .= '<head>';
583 $output .= '<title>'. (drupal_get_title() ? strip_tags(drupal_get_title()) : variable_get('site_name', 'Drupal')) .'</title>';
584 $output .= drupal_get_html_head();
585 $output .= drupal_get_css();
586 $output .= drupal_get_js();
587 $output .= '</head>';
588 $output .= '<body>';
589 $output .= "\n<!-- begin content -->\n";
590 $output .= $content;
591 $output .= "\n<!-- end content -->\n";
592 $output .= '<div class="cache">&nbsp;</div>';
593 $output .= theme('closure');
594 $output .= '</body></html>';
595
596 return $output;
597 }
598
599 include 'paypalnode-admin.inc';

  ViewVC Help
Powered by ViewVC 1.1.2