TheEdge
TheEdge

Reputation: 9861

How do I skip a page break

I am a total newbie to Drupal having spent most of my time with Joomla! I have a Drupal 7 site that is using the web form component. It has steps defined as:

Steps in the form:

Now the page break component looks like:

Registration Break Settings

If I understand things correctly after filling in GOMEZ in the Coupon text field (I added) the break should not happen and the user should go to the next page. However this is not the case and the user has to proceed via PayPal.

What I want to happen is the user enters a coupon code and the PayPal step is skipped and the user can complete the form. It appears this is possible by using webform_paypal_permission() Any ideas?

I am happy if I have to modify the PHP code but want to avoid it if I can and instead use the functionality that I believe is there.

I also include below the PHP that is being used for the PayPal step in case that is of use in debugging this:

<?php


/**
 * Implementation of hook_init
 *
 * A bit of a hack,Seems that session_id keeps changing for anon users and we need to track that through to the IPN to
 * know if this session has paid or not.
 *
 * So store it in $_SESSION
 */
function webform_paypal_init() {
  global $_SESSION;
  if (empty($_SESSION['webform-session'])) {
    // anonymous users wont have a session stick, so do it this way (session_id changes on each page)
    $_SESSION['webform-session'] = session_id();
  }
}

/**
 * Implements hook_menu().
 */
function webform_paypal_menu() {
  $items = array();

  $items['webform_paypal_ipn/%/%/%'] = array(
    'page callback' => 'webform_paypal_ipn_callback',
    'page arguments' => array(1, 2, 3),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

  $items['webform_paypal_lander/%/%'] = array(
    'page callback' => 'webform_paypal_lander',
    'page arguments' => array(1, 2),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_permission().
 */
function webform_paypal_permission() {
  return array(
    'bypass payment' => array(
      'title' => t('Bypass payment'),
      'description' => t('Complete the webform without payment.'),
    ),
  );
}

/**
 * This is the 'landing' or 'return' page that return= will return a user to from PayPal
 * It's responsible for waiting for a paypal IPN notification then pushing the user onto the next
 * page as its stored in $_SESSION, it does so by faking a HTTP POST, yuck!
 *
 * @param $nid - the NodeID to resubmit to (will be converted to full URL)
 * @param $cid - the ComponentID that we need to wait to be paid by IPN callback
 *
 */
function webform_paypal_lander($nid, $cid) {
  $output = array();

  // Just do some idle stuff until the IPN notification lands

  $waits = 0;
  while (!webform_paypal_ipn_submission_exists($cid, $nid)) {
    sleep(1);

    $waits++;
    if ($waits > 15) {
      watchdog('webform_paypal', "No IPN update was found from PayPal, cant allow user to continue.", array(), WATCHDOG_ERROR);
      return t('No reply found from PayPal, unable to process your payment at this time.');
    }
  }

  // Once it's here, resubmit the form with their stored previous form submission
  // The IPN should then be registred in the DB by our callback
  // And the #submit processor wont care about them anymore and they will continue on.


  foreach ($_SESSION['page_store'] as $id => $row) {
    if (is_array($row)) {
      foreach ($row as $sub_id => $sub_row) {
        $output[] = $id . "[$sub_id]=$sub_row";
      }
    }
    else {
      $output[] = "$id=$row";
    }
  }

  $cookies = array();
  foreach ($_COOKIE as $name => $value) {
    $cookies[] = "$name=$value";
  }

  $options = array(
    'method' => 'POST',
    'data' => implode('&', $output),
    'timeout' => 15,
    'headers' => array('Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => implode('; ', $cookies)),
  );

  $url = url('node/' . $nid, array('absolute' => TRUE));
  $result = drupal_http_request($url, $options);

  // Everything should generally work out just fine here
  if ($result->code == 200) {
    print $result->data;
    drupal_exit();
  }
}


/**
 * #submit processor
 *
 * Send the user to the PayPal payment page if neccessary
 *
 * @param $form
 * @param $form_state
 */
function webform_paypal_pause_or_redirect($form, &$form_state) {

  // Know which CID (Component ID) was clicked as part of the 'page_break'
  // page_num would represent number of page_breaks to skip to know
  $page_num = $form_state['webform']['page_num'];

  $current_page = 1;

  foreach ($form_state['webform']['component_tree']['children'] as $cid => $component) {
    if ($component['type'] == 'pagebreak') {
      if ($page_num == $current_page) {
        break;
      }
      $current_page++;
    }

  }

  // See if this component is in any mode to be redirected
  $mode = variable_get('webform_paypal_button_mode_' . $cid, '');

  if ($mode == 'live' || $mode == 'sandbox') {
    // If so, redirect to PayPal if theres no IPN submission from PayPal

    // This should only happen the first time because users returning from PayPal enter a sleep loop to wait
    // for the IPN update.
    // Bypass for users with the 'bypass payment' permission

    if (!webform_paypal_ipn_submission_exists($cid, $form['#node']->nid) && !user_access('bypass payment')) {
      $store = $_POST;
      // Store the form in a session var, we will resubmit the form when we return from PayPal
      // Which should make the user goto the next page if the payment has been received OK
      $_SESSION['page_store'] = $store;
      drupal_goto(webform_paypal_submit_url($form['#node']->nid, $cid));
    }
  }

}

function webform_paypal_ipn_submission_exists($cid, $nid) {

  $result = db_select('webform_paypal_ipn', 'wp_ipn')
    ->fields('wp_ipn', array('extra'))
    ->condition('cid', $cid)
    ->condition('sessionID', $_SESSION['webform-session'])
    ->condition('nid', $nid)
    ->execute();

  return $result->rowCount();
}

/**
 * Implements of hook_form_alter().
 *
 * @todo there must be a better way to theme in the button than to use a form_alter
 */
function webform_paypal_form_alter(&$form, &$form_state, $form_id) {

  // Handle a redirect to PayPal if neccessary
  if (strpos($form_id, 'webform_client_form_') !== FALSE) {
    $form['#submit'][] = 'webform_paypal_pause_or_redirect';
  }


  // Add the config to the pagebreak component
  if ($form_id == 'webform_component_edit_form') {
    $cid = arg(4);
    if ($form['type']['#value'] == 'pagebreak') {
      $form['webform_paypal'] = array(
        '#type' => 'fieldset',
        '#title' => t('Paypal Button integration'),
      );
      $form['webform_paypal']['webform_paypal_button_id'] = array(
        '#type' => 'textfield',
        '#title' => t('PayPal Button ID'),
        '#default_value' => variable_get('webform_paypal_button_id_' . $cid, ''),
        '#description' => t('If this is requires a PayPal Payment to continue, enter the Button ID here'),
        '#weight' => -8
      );
      $form['webform_paypal']['webform_button_mode'] = array(
        '#type' => 'select',
        '#title' => t('PayPal Button Mode'),
        '#default_value' => variable_get('webform_paypal_button_mode_' . $cid, ''),
        '#options' => array('disabled' => t('DISABLED'), 'sandbox' => t('SANDBOX'), 'live' => t('LIVE')),
        '#weight' => -9
      );
      $form['#submit'][] = 'webform_paypal_component_edit_submit';
    }
  }
}

function webform_paypal_component_edit_submit($form, &$form_state) {

  $cid = $form_state['values']['cid'];

  $id = $form_state['values']['webform_paypal']['webform_paypal_button_id'];
  variable_set('webform_paypal_button_id_' . $cid, $id);

  $id = $form_state['values']['webform_paypal']['webform_button_mode'];
  variable_set('webform_paypal_button_mode_' . $cid, $id);


}

/**
 * Get the URL for the user to connect to PayPal
 *
 * This special URL includes things like return URL's, IPN etc
 */
function webform_paypal_submit_url($nid, $cid, $tracking_url = "") {
  global $base_url;
  global $_SESSION;


  $mode = variable_get('webform_paypal_button_mode_' . $cid, '');

  if($mode == 'live') {
    $url = 'https://www.paypal.com/cgi-bin/webscr';
  }
  else {
    $url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
  }

  $post_data = array();
  $post_data['cmd'] = '_s-xclick';

  $post_data['return'] = $base_url . '/webform_paypal_lander/' . $nid . '/' . $cid;

  $post_data['hosted_button_id'] = variable_get('webform_paypal_button_id_' . $cid, '');

  //send return path to submission for better tracking
  $post_data['custom'] = $tracking_url;

  // setup IPN callback
  $post_data['notify_url'] = $base_url . '/webform_paypal_ipn/' . $nid . '/' . $cid . '/' . $_SESSION['webform-session'];

  // return the URL
  $url = url($url, array('query' => $post_data, 'external' => TRUE));

  return $url;

}

/**
 * Menu callback for receving the PayPal IPN calls
 * Arguments were handed to PayPal in the IPN link
 *
 * @param $nid Node ID
 * @param $cid Component ID
 * @param $sessionID Session ID from session_id() of the submitter
 */
function webform_paypal_ipn_callback($nid, $cid, $sessionID) {

  // get the URL depending on the components configuration
  $mode = variable_get('webform_paypal_button_mode_' . $cid, '');

  if ($mode == 'live') {
    $url = 'https://www.paypal.com/cgi-bin/webscr';
  }
  else {
    $url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
  }

  if (webform_paypal_ipn_verify_message($url)) {
    $key = array(
      'nid' => $nid,
      'cid' => $cid,
      'sessionID' => $sessionID
    );

    db_merge('webform_paypal_ipn')
      ->key($key)
      ->fields(array_merge($key, array(
        'extra' => serialize($_POST)
      )))
      ->execute();
  }

  return 'OK';
}

/**
 * Input IPN processor
 * from https://developer.paypal.com/webapps/developer/docs/classic/ipn/ht_ipn/
 *
 * @return bool
 */
function webform_paypal_ipn_verify_message($url) {
  // STEP 1: read POST data
// Reading POSTed data directly from $_POST causes serialization issues with array data in the POST.
// Instead, read raw POST data from the input stream.
  $raw_post_data = file_get_contents('php://input');
  $raw_post_array = explode('&', $raw_post_data);
  $myPost = array();
  foreach ($raw_post_array as $keyval) {
    $keyval = explode('=', $keyval);
    if (count($keyval) == 2) {
      $myPost[$keyval[0]] = urldecode($keyval[1]);
    }
  }
// read the IPN message sent from PayPal and prepend 'cmd=_notify-validate'
  $req = 'cmd=_notify-validate';
  if (function_exists('get_magic_quotes_gpc')) {
    $get_magic_quotes_exists = TRUE;
  }
  foreach ($myPost as $key => $value) {
    if ($get_magic_quotes_exists == TRUE && get_magic_quotes_gpc() == 1) {
      $value = urlencode(stripslashes($value));
    }
    else {
      $value = urlencode($value);
    }
    $req .= "&$key=$value";
  }


// Step 2: POST IPN data back to PayPal to validate

  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, variable_get('webform_paypal_verifypeer', TRUE));
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));

// In wamp-like environments that do not come bundled with root authority certificates,
// please download 'cacert.pem' from "http://curl.haxx.se/docs/caextract.html" and set
// the directory path of the certificate as shown below:
  if ($cacert = variable_get('webform_paypal_cacert', FALSE)) {
    if (!file_exists($cacert)) {
      watchdog('webform_paypal', "unable to open cert file", array(), WATCHDOG_ERROR);
    }
    curl_setopt($ch, CURLOPT_CAINFO, drupal_realpath($cacert));
  }

  if (!($res = curl_exec($ch))) {
    watchdog('webform_paypal', curl_error($ch), array(), WATCHDOG_ERROR);
    curl_close($ch);
    exit;
  }
  curl_close($ch);

// inspect IPN validation result and act accordingly

  if (strcmp($res, "VERIFIED") == 0) {

    // The IPN is verified, process it:
    // check whether the payment_status is Completed
    // check that txn_id has not been previously processed
    // check that receiver_email is your Primary PayPal email
    // check that payment_amount/payment_currency are correct
    // process the notification

    // assign posted variables to local variables
    $item_name = $_POST['item_name'];
    $item_number = $_POST['item_number'];
    $payment_status = $_POST['payment_status'];
    $payment_amount = $_POST['mc_gross'];
    $payment_currency = $_POST['mc_currency'];
    $txn_id = $_POST['txn_id'];
    $receiver_email = $_POST['receiver_email'];
    $payer_email = $_POST['payer_email'];
    if ($payment_status == 'Completed') {
      watchdog('webform_paypal', "PayPal IPN success verification %id", array("%id" => $txn_id));
      return TRUE;
    }
    else {
      watchdog('webform_paypal', "PayPal IPN FAIL verification %id (not Completed)", array("%id" => $txn_id));
    }
  }

  watchdog('webform_paypal', "PayPal IPN success FAIL (Not verified) - %msg", array("%msg" => $res));
  return FALSE;
}

Upvotes: 2

Views: 1088

Answers (1)

grim
grim

Reputation: 6769

First of all, it seems you are using Webform 3 with webform_conditional module. I advice you to upgrade to Webform 4 to which webform_conditional is incorporated and works on components on the same page. Webform 4 is fairly stable and personally I think it's more stable than webform 3 + webform_conditional.

Second it could be better to create a new webform_paypal component instead of altering the page break component as it could break something else. There is for example the webform_paypal module which is a good start for you to add/edit your code.

Unfortunately, the webform_paypal module doesn't work out of the box with neither the page breaks nor with with conditions with both Webform 3 and 4: https://drupal.org/node/1413182. I've even tried to put the webform_paypal component in a fieldset and set the condition on the fieldset but failed miserably.

I'm sorry I can't help more than that. I hope the little information I have provided will put you in the right direction.

Upvotes: 2

Related Questions