View source
<?php
define('UC_OUT_OF_STOCK_DEFAULT_HTML', '<span style="color: red;">Out of stock</span>');
function uc_out_of_stock_boot() {
global $conf;
$conf['i18n_variables'][] = 'uc_out_of_stock_text';
}
function uc_out_of_stock_form_alter(&$form, $form_state, $form_id) {
static $settings_js = array();
$forms = array(
'uc_product_add_to_cart_form',
'uc_catalog_buy_it_now_form',
);
foreach ($forms as $id) {
if (substr($form_id, 0, strlen($id)) == $id) {
if (!variable_get('uc_out_of_stock_disable_js', FALSE)) {
drupal_add_js(drupal_get_path('module', 'uc_out_of_stock') . '/uc_out_of_stock.js');
drupal_add_css(drupal_get_path('module', 'uc_out_of_stock') . '/uc_out_of_stock.css');
if (empty($settings_js)) {
$settings_js['uc_out_of_stock']['uc_aac'] = false;
if (module_exists('uc_aac')) {
$path = drupal_get_path('module', 'uc_aac') . '/uc_aac.info';
$info = drupal_parse_info_file($path);
$matches = array();
preg_match('/6.x-([0-9])/', $info['version'], $matches);
$settings_js['uc_out_of_stock']['uc_aac'] = $matches[1];
}
$settings_js['uc_out_of_stock']['path'] = url('uc_out_of_stock/query');
$settings_js['uc_out_of_stock']['throbber'] = variable_get('uc_out_of_stock_throbber', TRUE);
$settings_js['uc_out_of_stock']['instock'] = variable_get('uc_out_of_stock_instock', TRUE);
$settings_js['uc_out_of_stock']['msg'] = check_markup(variable_get('uc_out_of_stock_text', UC_OUT_OF_STOCK_DEFAULT_HTML), variable_get('uc_out_of_stock_format', FILTER_FORMAT_DEFAULT), FALSE);
drupal_add_js($settings_js, 'setting');
}
}
$instock = $html = $qty_hide = '';
if ($settings_js['uc_out_of_stock']['uc_aac'] == '2') {
$attrs = array();
if (isset($form['attributes'])) {
foreach ($form['attributes'] as $aid => $attribute) {
if (is_numeric($aid)) {
$attrs[$aid] = isset($form_state['post']['attributes'][$aid]) ? $form_state['post']['attributes'][$aid] : $attribute['#default_value'];
}
}
foreach ((array) $attrs as $aid => $oids) {
$attribute = uc_attribute_load($aid);
if ($attribute->display != 1 && $attribute->display != 2) {
unset($attrs[$aid]);
}
}
}
$nid = $form["nid"]['#value'];
$stockinfo = uc_out_of_stock_getstockinfo($nid, $attrs);
if ($stockinfo && $stockinfo['stock'] == 0) {
$html = $settings_js['uc_out_of_stock']['msg'];
$form['submit']['#attributes']['style'] = 'display: none;';
$qty_hide = 'style="display: none;"';
}
else {
$instock = theme('uc_out_of_stock_instock', $stockinfo);
}
}
$form['qty']['#prefix'] = '<div class="uc-out-of-stock-qty" ' . $qty_hide . '>';
$form['qty']['#suffix'] = '</div>';
if ($settings_js['uc_out_of_stock']['throbber']) {
$offset = array_search('qty', array_keys($form), TRUE);
$form = array_slice($form, 0, $offset + 1, true) + array(
'uc_out_of_stock_throbber' => array(
'#type' => 'markup',
'#value' => '<div class="uc_out_of_stock_throbbing"> </div>',
),
) + array_slice($form, $offset + 1, NULL, true);
}
if ($form['qty']['#type'] != 'hidden' && $form['qty']['#type'] != 'value') {
$offset = array_search('qty', array_keys($form), TRUE);
}
else {
$offset = array_search('uc_out_of_stock_throbber', array_keys($form), TRUE);
}
if ($settings_js['uc_out_of_stock']['instock']) {
$form = array_slice($form, 0, $offset + 1, true) + array(
'uc_out_of_stock_instock' => array(
'#type' => 'markup',
'#value' => '<div class="uc-out-of-stock-instock">' . $instock . '</div>',
),
) + array_slice($form, $offset + 1, NULL, true);
}
$offset = array_search('submit', array_keys($form), TRUE);
$form = array_slice($form, 0, $offset + 1, true) + array(
'uc_out_of_stock_html' => array(
'#type' => 'markup',
'#value' => '<div class="uc_out_of_stock_html">' . $html . '</div>',
),
) + array_slice($form, $offset + 1, NULL, true);
$form['#validate'][] = 'uc_out_of_stock_validate_form_addtocart';
}
}
if ($form_id == 'uc_cart_view_form') {
$form['#validate'][] = 'uc_out_of_stock_validate_form_cart';
}
if ($form_id == 'uc_cart_checkout_form' || $form_id == 'uc_cart_checkout_review_form') {
$form['#validate'][] = 'uc_out_of_stock_validate_form_checkout';
}
}
function uc_out_of_stock_menu() {
$items = array();
$items['admin/store/settings/uc_out_of_stock'] = array(
'title' => 'Out of Stock Settings',
'access arguments' => array(
'administer store',
),
'description' => 'Configure out of stock settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'uc_out_of_stock_settings',
),
'type' => MENU_NORMAL_ITEM,
);
$items['uc_out_of_stock/query'] = array(
'title' => 'stock query',
'page callback' => 'uc_out_of_stock_query',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
return $items;
}
function uc_out_of_stock_getstockinfo_from_model($model) {
$stock = db_result(db_query("SELECT ups.stock FROM {uc_product_stock} ups WHERE ups.active = 1 AND ups.sku = '%s'", $model));
if (is_numeric($stock)) {
$stockinfo['stock'] = $stock;
$stockinfo['model'] = $model;
}
return $stockinfo;
}
function uc_out_of_stock_getstockinfo($nid, $attrs) {
$stockinfo = array();
$query_main_sku = TRUE;
if (module_exists('uc_attribute')) {
$post_attrs = count($attrs);
$sql = "SELECT %s FROM {uc_product_adjustments} upa LEFT JOIN {uc_product_stock} ups ON ups.sku = upa.model WHERE upa.nid = %d";
$db_attrs = db_result(db_query($sql, 'COUNT(*)', $nid));
if ($post_attrs && $db_attrs > 0) {
$result = db_query($sql, '*', $nid);
while ($row = db_fetch_object($result)) {
$combination = unserialize($row->combination);
if (isset($row->sku) && $combination == $attrs) {
$query_main_sku = FALSE;
if ($row->active) {
$stockinfo['stock'] = $row->stock;
$stockinfo['model'] = $row->model;
}
}
}
}
else {
if ($post_attrs == 0 && $db_attrs > 0) {
$query_main_sku = FALSE;
}
}
}
if ($query_main_sku) {
$result = db_query("SELECT * FROM {uc_products} up LEFT JOIN {uc_product_stock} ups ON ups.sku = up.model WHERE up.nid = %d AND ups.active = 1", $nid);
while ($row = db_fetch_object($result)) {
$stockinfo['stock'] = $row->stock;
$stockinfo['model'] = $row->model;
}
}
return $stockinfo;
}
function uc_out_of_stock_query() {
if (count($_POST['form_ids']) != count($_POST['node_ids'])) {
print 'Different amount of form_ids than node_ids.';
exit;
}
if (count($_POST['node_ids']) == 0) {
print 'No node_ids sent.';
exit;
}
$return = array_combine($_POST['form_ids'], array_fill(0, count($_POST['form_ids']), NULL));
if (empty($_POST['attr_ids'])) {
$result = db_query('SELECT ups.stock, up.nid
FROM {uc_products} up
LEFT JOIN {uc_product_stock} ups ON ups.sku = up.model
WHERE up.nid IN(' . db_placeholders($_POST['node_ids'], 'int') . ')
AND ups.active = 1', $_POST['node_ids']);
while ($product = db_fetch_object($result)) {
$key = array_search($product->nid, $_POST['node_ids']);
$return[$_POST['form_ids'][$key]] = $product->stock;
}
}
else {
$attribs = array();
foreach ($_POST['attr_ids'] as $value) {
list($nid, $attr_id, $attr_val) = explode(':', $value);
$attribs[$nid][$attr_id] = $attr_val;
}
foreach ($_POST['node_ids'] as $key => $nid) {
$stockinfo = uc_out_of_stock_getstockinfo($nid, isset($attribs[$nid]) ? $attribs[$nid] : array());
if ($stockinfo) {
$return[$_POST['form_ids'][$key]] = $stockinfo['stock'];
}
}
}
print drupal_json($return);
exit;
}
function uc_out_of_stock_settings() {
$form['uc_out_of_stock_throbber'] = array(
'#type' => 'checkbox',
'#title' => t('Display throbber'),
'#default_value' => variable_get('uc_out_of_stock_throbber', TRUE),
'#description' => t('Display a throbber (animated wheel) next to add to cart button. This can be themed/styled and removed via CSS as well, but you can just disable with this setting.'),
);
$form['uc_out_of_stock_instock'] = array(
'#type' => 'checkbox',
'#title' => t('Display in stock information'),
'#default_value' => variable_get('uc_out_of_stock_instock', TRUE),
'#description' => t('Display how many items are in stock above the add to cart button or below the qty field if it is present. It can be themed and translated as normal. Please see the uc_out_of_stock.js file for hints. If product has attributes, it may be impossible to know the stock until the attribute is selected, so it will simply display a default "In stock" message.'),
);
$text = check_markup(variable_get('uc_out_of_stock_text', UC_OUT_OF_STOCK_DEFAULT_HTML), variable_get('uc_out_of_stock_format', FILTER_FORMAT_DEFAULT), FALSE);
$description = t('<div class="description">This is the value below rendered as you would expect to see it</div>');
$text = '<div style="border: 1px solid lightgrey; padding: 10px;">' . $text . '</div>' . $description;
$form['uc_out_of_stock_demo'] = array(
'#type' => 'markup',
'#value' => $text,
);
$form['uc_out_of_stock_text'] = array(
'#type' => 'textarea',
'#title' => t('Out of stock replacement HTML'),
'#default_value' => variable_get('uc_out_of_stock_text', UC_OUT_OF_STOCK_DEFAULT_HTML),
'#description' => t('The HTML that will replace the Add To Cart button if no stock is available.'),
);
$form['uc_out_of_stock_format'] = filter_form(variable_get('uc_out_of_stock_format', FILTER_FORMAT_DEFAULT), NULL, array(
'uc_out_of_stock_format',
));
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['advanced']['uc_out_of_stock_disable_js'] = array(
'#type' => 'checkbox',
'#title' => t('Disable javascript'),
'#default_value' => variable_get('uc_out_of_stock_disable_js', FALSE),
'#description' => t('If you disable javascript you will lose real-time checking of stock, support for checking for stock on attribute change and all of the HTML replacement confgured above. Use it with caution. This can be useful if you think this module is in conflict with some other third-party Ubercart module and you want to keep the server side validation of this module.'),
);
return system_settings_form($form);
}
function uc_out_of_stock_validate_addtocart($nid, $attrs, $qty_add) {
$error = FALSE;
$stockinfo = uc_out_of_stock_getstockinfo($nid, $attrs);
if ($stockinfo) {
if ($stockinfo['stock'] <= 0) {
$error = _uc_out_of_stock_get_error('out_of_stock', $nid, $attrs, $stockinfo['stock']);
}
else {
$qty = 0;
$items = uc_cart_get_contents();
foreach ($items as $item) {
if ($item->nid == $nid && $stockinfo['model'] == $item->model) {
$qty += $item->qty;
}
}
if ($stockinfo['stock'] - ($qty + $qty_add) < 0) {
$error = _uc_out_of_stock_get_error('not_enough', $nid, $attrs, $stockinfo['stock'], $qty);
}
}
}
return $error;
}
function uc_out_of_stock_validate_form_addtocart($form, &$form_state) {
if (!is_array($form_state['clicked_button']['#attributes']['class'])) {
$class = explode(' ', $form_state['clicked_button']['#attributes']['class']);
}
if (in_array('node-add-to-cart', $class) || in_array('list-add-to-cart', $class)) {
$attrs = $form_state['values']['attributes'];
foreach ((array) $attrs as $aid => $oids) {
$attribute = uc_attribute_load($aid);
if ($attribute->display != 1 && $attribute->display != 2) {
unset($attrs[$aid]);
}
}
$nid = $form_state['values']['nid'];
$qty = $form_state['values']['qty'] ? $form_state['values']['qty'] : 1;
$error = uc_out_of_stock_validate_addtocart($nid, $attrs, $qty);
if ($error !== FALSE) {
form_set_error('uc_out_of_stock', $error['msg']);
}
}
}
function uc_out_of_stock_validate_cart_items($items, $page = 'cart') {
if (!is_array($items)) {
return;
}
$cart_items = array();
$stored_cart_items = uc_cart_get_contents();
foreach ($items as $k => $item) {
$item = (object) $item;
if (!is_numeric($item->remove) || !$item->remove) {
if (is_string($item->data)) {
$item->data = unserialize($item->data);
}
if (!isset($item->model)) {
$stored_item = $stored_cart_items[$k];
if ($item->nid == $stored_item->nid && $item->data == $stored_item->data) {
$model = $stored_item->model;
}
else {
foreach ($stored_cart_items as $stored_item) {
if ($item->nid == $stored_item->nid && $item->data == $stored_item->data) {
$model = $stored_item->model;
}
}
}
$item->model = $model;
}
$cart_items[$item->model]['item'] = $item;
$cart_items[$item->model]['qty'] += $item->qty;
$cart_items[$item->model]['key'] = $k;
}
}
foreach ($cart_items as $model => $cart_item) {
$item = $cart_item['item'];
if ($cart_item['qty'] > 0) {
$stockinfo = uc_out_of_stock_getstockinfo_from_model($model);
if ($stockinfo) {
if ($stockinfo['stock'] - $cart_item['qty'] < 0) {
$qty = 0;
if ($page == 'checkout') {
$qty = $cart_item['qty'];
}
if ($stockinfo['stock'] <= 0) {
$error = _uc_out_of_stock_get_error('out_of_stock', $item->nid, $item->data['attributes'], $stockinfo['stock']);
}
else {
$error = _uc_out_of_stock_get_error('not_enough', $item->nid, $item->data['attributes'], $stockinfo['stock'], $qty);
}
form_set_error("items][{$cart_item['key']}][qty", $error['msg']);
}
}
}
}
}
function uc_out_of_stock_validate_form_checkout($form, &$form_state) {
$items = uc_cart_get_contents();
uc_out_of_stock_validate_cart_items($items, 'checkout');
}
function uc_out_of_stock_validate_form_cart($form, &$form_state) {
$items = $form_state['values']['items'];
if (substr($form_state['clicked_button']['#name'], 0, 7) != 'remove-') {
uc_out_of_stock_validate_cart_items($items, 'cart');
}
}
function _uc_out_of_stock_get_error($type, $nid, $attrs, $stock, $qty = 0) {
$product = node_load($nid);
if (count($attrs)) {
foreach ($attrs as $attr_id => $option_id) {
$attr = uc_attribute_load($attr_id);
$option = uc_attribute_option_load($option_id);
$attr_names[] = $attr->name;
$option_names[] = $option->name;
}
}
$error['stock'] = $stock;
$error['qty_in_cart'] = $qty;
$error['type'] = $type;
$error['product'] = $product->title;
if (count($attrs)) {
$error['attributes'] = implode('/', $attr_names);
$error['options'] = implode('/', $option_names);
}
if ($type == 'out_of_stock') {
if (count($attrs)) {
$error['msg'] = t("We're sorry. The product @product (@options) is out of stock. Please consider trying this product with a different @attributes.", array(
'@product' => $error['product'],
'@attributes' => $error['attributes'],
'@options' => $error['options'],
));
}
else {
$error['msg'] = t("We're sorry. The product @product is out of stock.", array(
'@product' => $error['product'],
));
}
}
if ($type == 'not_enough') {
if (count($attrs)) {
$error['msg'] = t("We're sorry. We have now only @qty of the product @product (@options) left in stock.", array(
'@qty' => format_plural($error['stock'], '1 unit', '@count units'),
'@product' => $error['product'],
'@options' => $error['options'],
));
}
else {
$error['msg'] = t("We're sorry. We have now only @qty of the product @product left in stock.", array(
'@qty' => format_plural($error['stock'], '1 unit', '@count units'),
'@product' => $error['product'],
));
}
if ($error['qty_in_cart']) {
$error['msg'] .= ' ' . t("You have currently @qty in your <a href=\"@cart-url\">shopping cart</a>.", array(
'@qty' => format_plural($error['qty_in_cart'], '1 unit', '@count units'),
'@cart-url' => url('cart'),
));
}
}
drupal_alter('uc_out_of_stock_error', $error, $product);
return $error;
}
function uc_out_of_stock_theme() {
return array(
'uc_out_of_stock_instock' => array(
'arguments' => array(
'stockinfo' => NULL,
),
),
);
}
function theme_uc_out_of_stock_instock($stockinfo = NULL) {
if ($stockinfo) {
return t('@stock in stock', array(
'@stock' => $stockinfo['stock'],
));
}
else {
return t('In stock');
}
}