/** * Iterate over the product variants or provide markup for a product variants chooser widget * * @api `shopp('product.variants')` * @since 1.1 * * @param string $result The output * @param array $options The options * - **mode**: `loop` (loop, single, multiple) Iterate over the variants with `loop` or provide an variant chooser widget using a `single` drop-down menu for all variants or `multiple` menus * - **defaults**: Specify a default option that is displayed as the initial selection for the `menu` * - **before_menu**: Markup to add before the widget * - **after_menu**: Markup to add after the widget * - **label**: `on` (on,off) Show or hide the menu name labels from the `menu` widget * - **format**: `%l (+%p)` The variant option label format * - **%p**: shows the current variant price including available discounts. * - **%l**: show the option label. * - **%s**: show the stock amount of a product in inventory * - **%d**: show the discount amount of an on sale variant. * - **%r**: show the original price (the non-sale price) of the product variant. * - **%u**: show the SKU for the product variant. * - **required**: `You must select the options for this item before you can add it to your shopping cart.` The error message to show when adding to the cart without selecting an variant when the **required** option is `on` * - **taxes**: Include or exclude taxes from prices * - **class**: The class attribute specifies one or more class-names for the menu elements * @param ShoppProduct $O The working object * @return bool|string True if the next variant exists, or false otherwise, or the variant chooser markup **/ public static function variants($result, $options, $O) { $string = ''; if (!isset($options['mode'])) { if (!isset($O->_prices_loop)) { reset($O->prices); $O->_prices_loop = true; } else { next($O->prices); } $price = current($O->prices); while (false !== $price && ('N/A' == $price->type || 'variation' != $price->context)) { $price = next($O->prices); } if (false !== current($O->prices)) { return true; } else { $O->_prices_loop = false; return false; } } if (shopp_setting_enabled('inventory') && $O->outofstock) { return false; } // Completely out of stock, hide menus if (!isset($options['taxes'])) { $options['taxes'] = null; } $defaults = array('defaults' => '', 'disabled' => 'show', 'pricetags' => 'show', 'before_menu' => '', 'after_menu' => '', 'format' => '%l (%p)', 'label' => 'on', 'mode' => 'multiple', 'taxes' => null, 'required' => __('You must select the options for this item before you can add it to your shopping cart.', 'Shopp')); $options = array_merge($defaults, $options); extract($options); $taxes = isset($taxes) ? Shopp::str_true($taxes) : null; $taxrates = self::_taxes($O, 'price'); $collection_class = ShoppCollection() && isset(ShoppCollection()->slug) ? 'category-' . ShoppCollection()->slug : ''; if ('single' == $mode) { if (!empty($before_menu)) { $string .= $before_menu . "\n"; } if (Shopp::str_true($label)) { $string .= '<label for="product-options' . (int) $O->id . '">' . Shopp::esc_html__('Options') . ': </label> ' . "\n"; } $string .= '<select name="products[' . (int) $O->id . '][price]" id="product-options' . (int) $O->id . '" class="' . esc_attr($collection_class) . ' product' . (int) $O->id . ' options">'; if (!empty($defaults)) { $string .= '<option value="">' . esc_html($options['defaults']) . '</option>' . "\n"; } foreach ($O->prices as $pricing) { if ('variation' != $pricing->context) { continue; } $currently = Shopp::str_true($pricing->sale) ? $pricing->promoprice : $pricing->price; $disable = $pricing->type == 'N/A' || Shopp::str_true($pricing->inventory) && $pricing->stock == 0; $currently = self::_taxed((double) $currently, $O, $pricing->tax, $taxes); $discount = 0 == $pricing->price ? 0 : 100 - round($pricing->promoprice * 100 / $pricing->price); $_ = new StdClass(); $_->p = 'Donation' != $pricing->type && !$disable ? money($currently) : false; $_->l = $pricing->label; $_->i = Shopp::str_true($pricing->inventory); $_->s = $_->i ? (int) $pricing->stock : false; $_->u = $pricing->sku; $_->tax = Shopp::str_true($pricing->tax); $_->t = $pricing->type; $_->r = $pricing->promoprice != $pricing->price ? money($pricing->price) : false; $_->d = $discount > 0 ? $discount : false; if (!$disable || 'show' == $disabled) { $string .= '<option value="' . $pricing->id . '"' . ($disable ? ' disabled="disabled"' : '') . '>' . esc_html(self::_variant_formatlabel($format, $_)) . '</option>' . "\n"; } } $string .= '</select>'; if (!empty($options['after_menu'])) { $string .= $options['after_menu'] . "\n"; } } else { if (!isset($O->options)) { return; } $menuoptions = $O->options; if (!empty($O->options['v'])) { $menuoptions = $O->options['v']; } $precision = ShoppBaseCurrency()->precision(); $pricekeys = array(); foreach ($O->pricekey as $key => $pricing) { $currently = Shopp::str_true($pricing->sale) ? $pricing->promoprice : $pricing->price; $disable = $pricing->type == 'N/A' || Shopp::str_true($pricing->inventory) && $pricing->stock == 0; $currently = self::_taxed((double) $currently, $O, $pricing->tax, $taxes); $discount = 100 - round($pricing->promoprice * 100 / $pricing->price); $_ = new StdClass(); $_->p = 'Donation' != $pricing->type && !$disable ? (double) apply_filters('shopp_product_variant_price', Shopp::str_true($pricing->sale) ? $pricing->promoprice : $currently) : false; $_->i = Shopp::str_true($pricing->inventory); $_->s = $_->i ? (int) $pricing->stock : false; $_->u = $pricing->sku; $_->tax = Shopp::str_true($pricing->tax); $_->t = $pricing->type; $_->r = $pricing->promoprice != $pricing->price ? money($pricing->price) : false; $_->d = $discount > 0 ? $discount : false; $pricekeys[$key] = $_; } // Output a JSON object for JS manipulation if ('json' == $options['mode']) { return json_encode($pricekeys); } $jsoptions = array('prices' => $pricekeys, 'format' => $format); if ('hide' == $options['disabled']) { $jsoptions['disabled'] = false; } if ('hide' == $options['pricetags']) { $jsoptions['pricetags'] = false; } if (!empty($taxrate)) { $jsoptions['taxrate'] = Shopp::taxrate($O); } $select_collection = !empty($collection_class) ? '.' . $collection_class : ''; ob_start(); if (!empty($options['defaults'])) { ?> $s.opdef = true; <?php } if (!empty($options['required'])) { ?> $s.opreq = "<?php echo $options['required']; ?> "; <?php } ?> new ProductOptionsMenus(<?php printf("'select%s.product%d.options'", $select_collection, $O->id); ?> ,<?php echo json_encode($jsoptions); ?> ); <?php $script = ob_get_clean(); add_storefrontjs($script); foreach ($menuoptions as $id => $menu) { if (!empty($before_menu)) { $string .= $before_menu . "\n"; } if (Shopp::str_true($label)) { $string .= '<label for="options-' . esc_attr($menu['id']) . '">' . esc_html($menu['name']) . '</label> ' . "\n"; } $string .= '<select name="products[' . (int) $O->id . '][options][]" class="' . esc_attr($collection_class) . ' product' . (int) $O->id . ' options" id="options-' . esc_attr($menu['id']) . '">'; if (!empty($defaults)) { $string .= '<option value="">' . esc_html($options['defaults']) . '</option>' . "\n"; } foreach ($menu['options'] as $key => $option) { $string .= '<option value="' . esc_attr($option['id']) . '">' . esc_html($option['name']) . '</option>' . "\n"; } $string .= '</select>'; if (!empty($after_menu)) { $string .= $after_menu . "\n"; } } } return $string; }
/** * Setup the module for runtime * * Auto-loads settings for the module and setups defaults * * @author Jonathan Davis * @since 1.1 * * @return void **/ public function __construct() { $this->module = get_class($this); $this->session = ShoppShopping()->session; $this->Order = ShoppOrder(); if ('ShoppFreeOrder' != $this->module) { // There are no settings for ShoppFreeOrder $this->settings = shopp_setting($this->module); // @todo Remove legacy gateway settings migrations // Attempt to copy old settings if this is a new prefixed gateway class if (empty($this->settings) && false !== strpos($this->module, 'Shopp')) { $legacy = substr($this->module, 5); $this->settings = shopp_setting($legacy); if (!empty($this->settings)) { shopp_set_setting($this->module, $this->settings); } } } if (!isset($this->settings['label']) && $this->cards) { $this->settings['label'] = __('Credit Card', 'Shopp'); } $this->baseop = ShoppBaseLocale()->settings(); $this->currency = ShoppBaseCurrency()->code(); $this->precision = ShoppBaseCurrency()->precision(); $this->_loadcards(); $gateway = GatewayModules::hookname($this->module); add_action('shopp_init', array($this, 'myactions'), 30); add_action('shopp_' . $gateway . '_refunded', array($this, 'cancelorder')); if ($this->authonly) { add_filter('shopp_purchase_order_' . $gateway . '_processing', create_function('', 'return "auth";')); } elseif ($this->saleonly) { add_filter('shopp_purchase_order_' . $gateway . '_processing', create_function('', 'return "sale";')); } }
/** * Formats tax rates to a precision beyond the currency format * * @author Jonathan Davis * @since 1.3 * * @param scalar $amount An amount to convert to a float * @return float The float amount **/ private static function float($amount) { $format = ShoppBaseCurrency()->settings(); $format['precision'] = 6; // Override the precision to 6 digits return Shopp::floatval($amount, true, $format); }