You are here

uc_product_minmax.module in Ubercart Product Minimum & Maximum 6

Same filename and directory in other branches
  1. 7 uc_product_minmax.module

This module adds a textfield to product forms for you to enter a minimum and maximum quantity of the product that must be in the cart for it to be checked out.

The product min/max checkout pane must be the lowest weighted cart pane in order for the logic to properly intercept any invalid carts.

Product Min & Max modules originally coded by Ryan of Ubercart.org. Combined and converted to product feature by fred0.

Original development sponsored by Plum Drama - http://www.plumdrama.com

File

uc_product_minmax.module
View source
<?php

/**
 * @file
 * This module adds a textfield to product forms for you to enter a minimum
 * and maximum quantity of the product that must be in the cart for it to be checked out.
 *
 * The product min/max checkout pane must be the lowest weighted cart pane in
 * order for the logic to properly intercept any invalid carts.
 *
 * Product Min & Max modules originally coded by Ryan of Ubercart.org.
 * Combined and converted to product feature by fred0.
 *
 * Original development sponsored by Plum Drama - http://www.plumdrama.com
 */

/*******************************************************************************
 * Product Feature API (Ubercart)
 ******************************************************************************/

/**
 * Implementation of hook_product_feature().
 */
function uc_product_minmax_product_feature() {
  $features[] = array(
    'id' => 'minmax',
    'title' => t('Product Minimum & Maximum'),
    'callback' => 'uc_product_minmax_feature_form',
    'delete' => 'uc_product_minmax_feature_delete',
    'settings' => 'uc_product_minmax_feature_settings',
  );
  return $features;
}

/**
 * This function gets called when an administrator browses to the product
 * features settings form.  It should return an array of form elements to add to
 * this feature's fieldset in the form if necessary.
 */
function uc_product_minmax_feature_settings() {
  $form['uc_product_minmax_position'] = array(
    '#type' => 'radios',
    '#title' => t('Display Position'),
    '#description' => t('Set the default position of the minimum and maximum on the product page.<br />This can be overridden on individual products..'),
    '#default_value' => variable_get('uc_product_minmax_position', 0),
    '#options' => array(
      t('As node item'),
      t('As description below Quantity field'),
    ),
  );
  $form['uc_product_minmax_weight'] = array(
    '#type' => 'weight',
    '#title' => t('Node item page weight'),
    '#description' => t('Only has an effect if the above setting is <b>As node item</b>.<br />Set the default position of the minimum and maximum on the product page.<br />This can be overridden on individual products.<br />Refer to the <a href="/admin/store/settings/products/edit/fields">Product Field settings</a> for other element weights.'),
    '#default_value' => variable_get('uc_product_minmax_weight', 9),
    '#delta' => 100,
  );
  return $form;
}

/**
 * This form gets displayed when a product feature is added or edited for this
 * product feature type.
 */
function uc_product_minmax_feature_form($form_state, $node, $feature) {
  if (!empty($feature)) {
    $minmax = uc_product_minmax_values_load($feature['pfid']);
  }
  $form['nid'] = array(
    '#type' => 'hidden',
    '#value' => $node->nid,
  );
  $form['min_fieldset'] = array(
    '#type' => 'fieldset',
    '#title' => t('Minimum'),
  );
  $form['min_fieldset']['product_min'] = array(
    '#type' => 'textfield',
    '#title' => t('Minimum quantity to checkout'),
    '#description' => t('Enter the minimum quantity of this product needed to checkout.'),
    '#default_value' => $minmax->product_min,
    '#size' => 10,
  );
  $form['min_fieldset']['display_min'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display the Minimum text on product page'),
    '#default_value' => !is_null($minmax->display_min) ? $minmax->display_min : 1,
    '#description' => t('Only has effect when Minimum is greater than 1 or multiples is checked.'),
  );
  $form['min_fieldset']['pmin_multiple'] = array(
    '#type' => 'textfield',
    '#title' => t('Products must be purchased in multiples of'),
    '#default_value' => !empty($minmax->pmin_multiple) ? $minmax->pmin_multiple : 1,
    '#size' => 10,
  );
  $form['min_fieldset']['display_multiple'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display the Multiple text on product page'),
    '#default_value' => !is_null($minmax->display_multiple) ? $minmax->display_multiple : 1,
    '#description' => t('Only has effect when Multiple is greater than 1.'),
  );
  $form['max_fieldset'] = array(
    '#type' => 'fieldset',
    '#title' => t('Maxmimum'),
  );
  $form['max_fieldset']['product_max'] = array(
    '#type' => 'textfield',
    '#title' => t('Maximum quantity allowed to checkout'),
    '#description' => t('Enter the maximum quantity of this product allowed to checkout. Set 0 for no limit.'),
    '#default_value' => !empty($minmax->product_max) ? $minmax->product_max : 0,
    '#size' => 10,
  );
  $form['max_fieldset']['display_max'] = array(
    '#type' => 'checkbox',
    '#title' => t('Display the Maximum text on product page'),
    '#default_value' => !is_null($minmax->display_max) ? $minmax->display_max : 1,
    '#description' => t('Only has effect when Maximum is greater than 0.'),
  );
  $form['display_weight'] = array(
    '#type' => 'weight',
    '#title' => t('Product Page weight'),
    '#description' => t('Set the position of the minimum and maximum on the product page.<br ?> This overrides the default setting.<br />Refer to the <a href="/admin/store/settings/products/edit/fields">Product Field settings</a> for other element weights.'),
    '#default_value' => !empty($minmax->display_weight) ? $minmax->display_weight : variable_get('uc_product_minmax_weight', 9),
    '#delta' => 100,
  );
  return uc_product_feature_form($form);
}
function uc_product_minmax_feature_form_validate($form, &$form_state) {
  $minmax = uc_product_minmax_feature_load($form_state['values']['nid']);
  if ($minmax && $minmax->pfid != $form_state['values']['pfid']) {
    form_set_error('minmax_exists', t('Product Minimum & Maximum feature already exists for this product.'));
  }
  if (!ctype_digit($form_state['values']['product_min'])) {
    form_set_error('product_min', t('The value for Minimum must be zero or a positive integer.'));
  }
  if (empty($form_state['values']['pmin_multiple']) || !ctype_digit($form_state['values']['pmin_multiple'])) {
    form_set_error('pmin_multiple', t('The value for Multiple must be an integer greater than zero'));
  }
  if (!ctype_digit($form_state['values']['product_max'])) {
    form_set_error('product_max', t('The value for Maximum must be zero or a positive integer.'));
  }
}
function uc_product_minmax_feature_form_submit($form, &$form_state) {

  // Use the form specified pfid if available.
  if (!empty($form_state['values']['pfid'])) {
    $pfid = $form_state['values']['pfid'];
  }
  $minmax->pfid = $pfid;
  $minmax->nid = $form_state['values']['nid'];
  $minmax->product_min = intval($form_state['values']['product_min']);
  $minmax->pmin_multiple = intval($form_state['values']['pmin_multiple']);
  $minmax->product_max = intval($form_state['values']['product_max']);
  $minmax->display_min = $form_state['values']['display_min'];
  $minmax->display_multiple = $form_state['values']['display_multiple'];
  $minmax->display_max = $form_state['values']['display_max'];
  $minmax->display_weight = $form_state['values']['display_weight'];
  $args = array(
    '@min' => $minmax->pmin_multiple > 1 ? t('Requires item to be purchased in multiples of @multiple.', array(
      '@multiple' => $minmax->pmin_multiple,
    )) : t('Requires a minimum of @minimum to checkout.', array(
      '@minimum' => $minmax->product_min,
    )),
    '@max' => $minmax->product_max >= 1 ? t('Limits checkout to a maximum of @maximum.', array(
      '@maximum' => $minmax->product_max,
    )) : '',
  );
  $data = array(
    'pfid' => $form_state['values']['pfid'],
    'nid' => $form_state['values']['nid'],
    'fid' => 'minmax',
    'description' => t('@min @max', $args),
  );

  // Save the product feature and store the returned URL as our redirect.
  $form_state['redirect'] = uc_product_feature_save($data);
  if (empty($pfid)) {
    $minmax->pfid = db_last_insert_id('uc_product_features', 'pfid');
  }
  uc_product_minmax_values_save($minmax);
}

/**
 * This function gets called when a feature of this type is deleted.
 */
function uc_product_minmax_feature_delete($pfid) {
  db_query("DELETE FROM {uc_product_minmax} WHERE pfid = %d", $pfid);
}

/*******************************************************************************
 * Hook Functions (Ubercart)
 ******************************************************************************/

/**
 * Implementation of hook_add_to_cart().
 */
function uc_product_minmax_add_to_cart($nid, $qty, $data) {
  $minmax = uc_product_minmax_feature_load($nid);
  $cart_total = 0;
  $items = uc_cart_get_contents();
  foreach ($items as $item) {
    if ($item->nid === $nid) {
      if (module_exists('uc_product_kit') && in_array('uc_product_kit', $item->data)) {
        continue;
      }

      // If item is part of a product kit, do not count cart items
      $cart_total += $item->qty;
    }
  }
  $node = node_load($nid);
  $message = array();
  if ($minmax->product_min && $qty < $minmax->product_min) {
    $message[] = t('The minimum order quantity for <em>!item</em> is @qty.', array(
      '@qty' => $minmax->product_min,
      '!item' => $node->title,
    ));
  }
  if ($minmax->product_max && $qty + $cart_total > $minmax->product_max) {
    $message[] = t('The maximum order quantity for <em>!item</em> is @qty and you already have @qty in your cart.', array(
      '@qty' => $minmax->product_max,
      '!item' => $node->title,
    ));
  }
  else {
    if ($minmax->product_max && $qty > $minmax->product_max) {
      $message[] = t('The maximum order quantity for <em>!item</em> is @qty.', array(
        '@qty' => $minmax->product_max,
        '!item' => $node->title,
      ));
    }
  }
  if ($minmax->pmin_multiple && $qty % $minmax->pmin_multiple) {
    $message[] = t('Orders for <em>!item</em> must be in multiples of @qty.', array(
      '@qty' => $minmax->pmin_multiple,
      '!item' => $node->title,
    ));
  }
  if (count($message)) {
    $result[] = array(
      'success' => FALSE,
      'message' => theme_item_list($message, t('Please check your quantity and try again.'), 'ul'),
      'silent' => FALSE,
    );
  }
  if (module_exists('uc_product_kit') && isset($data['kit_id'])) {
    return;

    // If item is part of a product kit, do not apply minmax rules
  }
  else {
    return $result;
  }
}

/**
 * Prevent illegal modifications of quantities in cart
 */
function uc_product_minmax_form_uc_cart_view_form_alter(&$form, &$form_state) {
  foreach (element_children($form['items']) as $i) {
    if (!$form['items'][$i]['nid']) {
      continue;
    }

    // if removing all items or items with errors, skip validation
    if (array_key_exists('post', $form_state)) {
      if (array_key_exists('items', $form_state['post'])) {
        if (array_key_exists($i, $form_state['post']['items'])) {
          if (array_key_exists('remove', $form_state['post']['items'][$i])) {
            continue;
          }
        }
      }
    }

    // If item in cart is part of a product kit, do not apply minmax rules
    if (module_exists('uc_product_kit')) {
      if (in_array('uc_product_kit', (array) $form['items'][$i]['module'])) {
        continue;
      }
    }
    $nid = $form['items'][$i]['nid']['#value'];
    $minmax = uc_product_minmax_feature_load($nid);
    if (!$minmax) {
      continue;
    }
    $form['items'][$i]['#element_validate'][] = 'uc_product_minmax_cart_validate';
  }
}
function uc_product_minmax_cart_validate($element, &$form_state) {
  $nid = $element['nid']['#value'];
  $qty = $element['qty']['#value'];
  $minmax = uc_product_minmax_feature_load($nid);
  $message = array();
  if ($minmax->pmin_multiple && $qty % $minmax->pmin_multiple) {
    $message[] = t('!item must be ordered in multiples of !qty.', array(
      '!item' => $element['title']['#value'],
      '!qty' => $minmax->pmin_multiple,
    ));
  }
  if ($minmax->product_min && $qty < $minmax->product_min) {
    $message[] = t('You must order !min or more of !item.', array(
      '!item' => $element['title']['#value'],
      '!min' => $minmax->product_min,
    ));
  }
  if ($minmax->product_max && $qty > $minmax->product_max) {
    $message[] = t('You cannot order more than !max of !item.', array(
      '!item' => $element['title']['#value'],
      '!max' => $minmax->product_max,
    ));
  }
  if (count($message)) {
    form_error($element['qty'], theme_item_list($message, t('Please check your quantity and try again.'), 'ul'));
  }
}

/*******************************************************************************
 * Theme and Node functions to display minmax restrictions as node item
 * if selected in product feature general settings.
 ******************************************************************************/

/**
 * Implementation of hook_theme()
 * Themes the product minimum notice line on product view pages.
 */
function uc_product_minmax_theme() {
  return array(
    'uc_product_minmax' => array(
      'arguments' => array(
        'min' => NULL,
        'multiple' => NULL,
        'max' => NULL,
        'display_min' => NULL,
        'display_multiple' => NULL,
        'display_max' => NULL,
        'display_weight' => NULL,
      ),
    ),
  );
}
function theme_uc_product_minmax($min, $multiple, $max, $display_min, $display_multiple, $display_max) {
  if ($multiple > 1 && $display_multiple) {
    $output = '<div class="uc_product_multiple">' . t('This product must be ordered in sets of !qty.', array(
      '!qty' => $multiple,
    )) . '</div>';
  }
  if ($min > 1 && $display_min) {
    $output .= '<div class="uc_product_min">' . t('A minimum order of at least !qty is required.', array(
      '!qty' => $min,
    )) . '</div>';
  }
  if ($max >= 1 && $display_max) {
    $output .= '<div class="uc_product_max">' . t('A maximum order of !qty is allowed.', array(
      '!qty' => $max,
    )) . '</div>';
  }
  return '<div class="product-minmax">' . $output . '</div>';
}

/**
 * Implementation of hook_nodeapi().
 */
function uc_product_minmax_nodeapi(&$node, $op, $a3 = null, $a4 = null) {
  switch ($op) {
    case 'load':
      if (variable_get('uc_product_minmax_position', 0) == 0) {
        return (array) uc_product_minmax_feature_load($node->nid);
      }
    case 'view':
      if (variable_get('uc_product_minmax_position', 0) == 0) {
        if ($node->display_min || $node->display_max || $node->display_multiple) {
          if ($node->product_min > 1 || $node->display_multiple > 1 || $node->product_max >= 1) {
            drupal_add_css(drupal_get_path('module', 'uc_product_minmax') . '/uc_product_minmax.css', 'module', 'all', TRUE);
            $node->content['product_minmax'] = array(
              '#value' => theme('uc_product_minmax', $node->product_min, $node->pmin_multiple, $node->product_max, $node->display_min, $node->display_multiple, $node->display_max, $minmax->display_weight),
              '#weight' => !empty($node->display_weight) ? $node->display_weight : variable_get('uc_product_minmax_weight', -9),
            );
          }
        }
      }
      break;
    case 'delete':

      // Delete corresponding data from {uc_product_features} and {uc_product_minmax} tables
      db_query("DELETE FROM {uc_product_features} WHERE nid = %d AND fid='minmax'", $node->nid);
      db_query("DELETE FROM {uc_product_minmax} WHERE nid = %d", $node->nid);
      break;
  }
}

/*******************************************************************************
 * Display minmax restrictions in add to cart form if selected in product
 * feature general settings.
 ******************************************************************************/

/**
 * Implementation of hook_form_alter().
 */
function uc_product_minmax_form_alter(&$form, &$form_state, $form_id) {

  // Add text for "Add to cart" and "Buy it now" forms
  if (variable_get('uc_product_minmax_position', 0) == 1) {
    if (strpos($form_id, 'add_to_cart_form') > 0 || strpos($form_id, 'uc_catalog_buy_it_now_form_') === 0) {
      $minmax = uc_product_minmax_feature_load($form['nid']['#value']);
      if ($minmax->pmin_multiple > 1 && $minmax->display_multiple) {
        $output = '<div class="uc_product_multiple">' . t('This product must be ordered in sets of !qty.', array(
          '!qty' => $minmax->pmin_multiple,
        )) . '</div>';
      }
      if ($minmax->product_min > 1 && $minmax->display_min) {
        $output .= '<div class="uc_product_min">' . t('A minimum order of at least !qty is required.', array(
          '!qty' => $minmax->product_min,
        )) . '</div>';
      }
      if ($minmax->product_max >= 1 && $minmax->display_max) {
        $output .= '<div class="uc_product_max">' . t('A maximum order of !qty is allowed.', array(
          '!qty' => $minmax->product_max,
        )) . '</div>';
      }
      $form['qty']['#description'] = $output;
    }
  }
}

/*******************************************************************************
 * Misc and callbacks
 ******************************************************************************/

/**
 * Restrict multiple minmax features from being added to a single product.
 * Disable feature on product kits since it doesn't have any effect on them.
 */
function uc_product_minmax_form_uc_product_feature_add_form_alter(&$form, &$form_state) {
  if (uc_product_minmax_feature_load(arg(1)) || uc_product_minmax_product_kit_load(arg(1))) {
    unset($form['feature']['#options']['minmax']);
  }
}

/**
 * Load minmax product feature for the given node
 */
function uc_product_minmax_feature_load($nid) {
  return db_fetch_object(db_query("SELECT * FROM {uc_product_minmax} WHERE nid = %d", $nid));
}

/**
 * Load product kit matches for the given node
 */
function uc_product_minmax_product_kit_load($nid) {
  if (module_exists('uc_product_kit')) {
    return db_fetch_object(db_query("SELECT * FROM {uc_product_kits} WHERE nid = %d", $nid));
  }
}

/**
 * Saves the min & max values.
 *
 * @param $minmax
 *   A min and max value object.
 */
function uc_product_minmax_values_save($minmax) {

  // Allow other modules to change the saved data.
  drupal_alter('product_minmax_values_save', $minmax);
  db_query("DELETE FROM {uc_product_minmax} WHERE pfid = %d", $minmax->pfid);
  drupal_write_record('uc_product_minmax', $minmax);
}

/**
 * Loads min and max value based on pfid.
 *
 * @param $pfid
 *   The product feature ID to load.
 * @return
 *   The min and max object.
 */
function uc_product_minmax_values_load($pfid) {
  return db_fetch_object(db_query("SELECT * FROM {uc_product_minmax} WHERE pfid = %d", $pfid));
}

Functions

Namesort descending Description
theme_uc_product_minmax
uc_product_minmax_add_to_cart Implementation of hook_add_to_cart().
uc_product_minmax_cart_validate
uc_product_minmax_feature_delete This function gets called when a feature of this type is deleted.
uc_product_minmax_feature_form This form gets displayed when a product feature is added or edited for this product feature type.
uc_product_minmax_feature_form_submit
uc_product_minmax_feature_form_validate
uc_product_minmax_feature_load Load minmax product feature for the given node
uc_product_minmax_feature_settings This function gets called when an administrator browses to the product features settings form. It should return an array of form elements to add to this feature's fieldset in the form if necessary.
uc_product_minmax_form_alter Implementation of hook_form_alter().
uc_product_minmax_form_uc_cart_view_form_alter Prevent illegal modifications of quantities in cart
uc_product_minmax_form_uc_product_feature_add_form_alter Restrict multiple minmax features from being added to a single product. Disable feature on product kits since it doesn't have any effect on them.
uc_product_minmax_nodeapi Implementation of hook_nodeapi().
uc_product_minmax_product_feature Implementation of hook_product_feature().
uc_product_minmax_product_kit_load Load product kit matches for the given node
uc_product_minmax_theme Implementation of hook_theme() Themes the product minimum notice line on product view pages.
uc_product_minmax_values_load Loads min and max value based on pfid.
uc_product_minmax_values_save Saves the min & max values.