ID
public $ID;
public $exists; // : jigoshop_cart.class.php on line 66
public $product_type; // : jigoshop_template_functions.php on line 271
public $sku; // : jigoshop_template_functions.php on line 246
public $data; // jigoshop_tax.class.php on line 186
public $post; // for get_title()
public $meta; // for get_child()
protected $regular_price;
protected $sale_price;
private $sale_price_dates_from;
private $sale_price_dates_to;
private $weight;
private $length;
private $width;
private $height;
private $tax_status = 'taxable';
private $tax_class;
public $visibility = 'visible'; // : admin/jigoshop-admin-post-types.php on line 168
private $featured = false;
private $manage_stock = false;
private $stock_status = 'instock';
private $backorders;
public $stock; // : admin/jigoshop-admin-post-types.php on line 180
private $stock_sold;
private $attributes = array();
public $children = array(); // : jigoshop_template_functions.php on line 328
/**
* Loads all product data from custom fields
*
* @param int ID of the product to load
* @return jigoshop_product
*/
public function __construct( $ID ) {
// Grab the product ID & get the product meta data
// TODO: Change to uppercase for consistency sake
$this->ID = (int) $ID;
$this->id = $this->ID;
if ( ! $this->meta ) {
$this->meta = get_post_custom( $this->ID );
}
$meta = $this->meta;
// Check if the product has meta data attached
// If not then it might not be a product
$this->exists = (bool) $meta;
// Get the product type, from the cache if we can
$terms = current( (array) get_the_terms( $this->ID, 'product_type' ) );
// Use slug as it is already santizied.
$this->product_type = ( ! empty( $terms ) ) ? $terms->slug : 'simple';
// Define data
$this->regular_price = isset($meta['regular_price'][0]) ? $meta['regular_price'][0] : null;
$this->sale_price = isset($meta['sale_price'][0]) ? $meta['sale_price'][0] : null;
$this->sale_price_dates_from = isset($meta['sale_price_dates_from'][0]) ? $meta['sale_price_dates_from'][0] : null;
$this->sale_price_dates_to = isset($meta['sale_price_dates_to'][0]) ? $meta['sale_price_dates_to'][0] : null;
$this->weight = isset($meta['weight'][0]) ? $meta['weight'][0] : null;
$this->length = isset($meta['length'][0]) ? $meta['length'][0] : null;
$this->width = isset($meta['width'][0]) ? $meta['width'][0] : null;
$this->height = isset($meta['height'][0]) ? $meta['height'][0] : null;
$this->tax_status = isset($meta['tax_status'][0]) ? $meta['tax_status'][0] : null;
$this->tax_class = isset($meta['tax_class'][0]) ? $meta['tax_class'][0] : null;
$this->sku = isset($meta['sku'][0]) ? $meta['sku'][0] : $this->ID;
$this->visibility = isset($meta['visibility'][0]) ? $meta['visibility'][0] : null;
$this->featured = isset($meta['featured'][0]) ? $meta['featured'][0] : null;
$this->manage_stock = isset($meta['manage_stock'][0]) ? $meta['manage_stock'][0] : null;
$this->stock_status = isset($meta['stock_status'][0]) ? $meta['stock_status'][0] : null;
$this->backorders = isset($meta['backorders'][0]) ? $meta['backorders'][0] : null;
$this->stock = isset($meta['stock'][0]) ? $meta['stock'][0] : null;
$this->stock_sold = isset($meta['stock_sold'][0]) ? $meta['stock_sold'][0] : null;
return $this;
}
/**
* Get the main product image or parents image
*
* @return html
**/
public function get_image( $size = 'shop_thumbnail' ) {
// Get the image size
$size = jigoshop_get_image_size( $size );
// If product has an image
if( has_post_thumbnail( $this->ID ) )
return get_the_post_thumbnail( $this->ID, $size );
// If product has a parent and that has an image display that
if( ($parent_ID = wp_get_post_parent_id( $this->ID )) && has_post_thumbnail( $parent_ID ) )
return get_the_post_thumbnail( $this->ID, $size );
// Otherwise just return a placeholder
return '';
}
/**
* Get SKU (Stock-keeping unit) - product uniqe ID
*
* @return mixed
*/
public function get_sku() {
return $this->sku;
}
/**
* Returns the product's children
*
* @return array Child IDs
*/
public function get_children() {
// Check if the product type can hold child products
if ( ! $this->is_type( array('variable', 'grouped') ) )
return false;
// Stop here if we already have the children
if ( ! empty($this->children) )
return $this->children;
// Get the child IDs
$this->children = get_posts(array(
'post_parent' => $this->ID,
'post_type' => ($this->is_type('variable')) ? 'product_variation' : 'product',
'orderby' => 'menu_order',
'order' => 'ASC',
'fields' => 'ids',
'post_status' => 'any',
'numberposts' => -1
));
return $this->children;
}
/**
* Return an instance of a child
*
* @param int Child Product ID
* @return jigoshop_product
*/
public function get_child( $child_ID ) {
if ( $this->is_type('variable') )
return new jigoshop_product_variation( $child_ID );
return new jigoshop_product( $child_ID );
}
/**
* Reduce stock level of the product
* Acts as an alias for modify_stock()
*
* @param int Amount to reduce by
* @return int
*/
public function reduce_stock( $by = -1 ) {
return $this->modify_stock( -$by );
}
/**
* Increase stock level of the product
* Acts as an alias for modify_stock()
*
* @param int Amount to increase by
* @return int
*/
public function increase_stock( $by = 1 ) {
return $this->modify_stock( $by );
}
/**
* Modifies the stock levels
*
* @param int Amount to modify
* @return int
*/
public function modify_stock( $by ) {
// Only do this if we're updating
if ( ! $this->managing_stock() )
return false;
// +- = minus
$this->stock = $this->stock + $by;
$amount_sold = $this->stock_sold + $by;
// Update & return the new value
update_post_meta( $this->ID, 'stock', $this->stock );
update_post_meta( $this->ID, 'stock_sold', $amount_sold );
return $this->stock;
}
/**
* Checks if a product requires shipping
*
* @return bool
*/
public function requires_shipping() {
// If it's virtual or downloadable dont require shipping
if ( $this->is_type( array('downloadable', 'virtual') ) )
return false;
return true;
}
/**
* Checks the product type
*
* @param string Type to check against
* @return bool
*/
public function is_type( $type ) {
if ( is_array($type) && in_array($this->product_type, $type) )
return true;
if ($this->product_type == $type)
return true;
return false;
}
/**
* Returns whether or not the product has any child product
*
* @return bool
*/
public function has_child() {
return (bool) $this->get_children();
}
/**
* Checks to see if a product exists
*
* @return bool
*/
public function exists() {
return (bool) $this->exists;
}
/**
* Returns whether or not the product is taxable
*
* @return bool
*/
public function is_taxable() {
return ( $this->tax_status == 'taxable' );
}
/**
* Returns whether or not the product shipping is taxable
*
* @return bool
*/
public function is_shipping_taxable() {
return ( $this->is_taxable() || $this->tax_status == 'shipping' );
}
/**
* Get the product's post data
* @deprecated Should be using WP native the_title() right? -Rob
* NOTE: Only used for get_title()
*
* @return object
*/
public function get_post_data() {
if (empty($this->post)) {
$this->post = get_post( $this->ID );
}
return $this->post;
}
/**
* Get the product's post data
* @deprecated Should be using WP native the_title() right? -Rob
* NOTE: Only used for get_title()
*
* @return string
*/
public function get_title() {
$this->get_post_data();
return apply_filters('jigoshop_product_title', get_the_title($this->post->ID), $this);
}
/**
* Get the add to url
*
* @return mixed
*/
public function add_to_cart_url() {
if ( $this->has_child() ) {
$url = add_query_arg('add-to-cart', 'group');
$url = add_query_arg('product', $this->ID, $url);
if ($this->is_type('variable')) {
$url = add_query_arg('add-to-cart', 'variation');
}
}
else {
$url = add_query_arg('add-to-cart', $this->ID);
}
$url = jigoshop::nonce_url( 'add_to_cart', $url );
return $url;
}
/**
* Check if we are managing stock
*
* @return bool
*/
public function managing_stock() {
// If we're not managing stock at all
if (get_option('jigoshop_manage_stock') != 'yes')
return false;
return (bool) $this->manage_stock;
}
/**
* Returns whether or not the product is in stock
*
* @return bool
*/
public function is_in_stock() {
// Always return in stock if product is in stock
if (get_option('jigoshop_manage_stock') != 'yes')
return true;
if ( $this->is_type( array('grouped', 'variable') ) ) {
foreach( $this->get_children() as $child_ID ) {
// Get the children
$child = $this->get_child( $child_ID );
// If one of our children is in stock then return true
if ( $child->is_in_stock() )
return true;
}
}
// If we arent managing stock then it should always be in stock
if( ! $this->managing_stock() && $this->stock_status == 'instock' )
return true;
// Check if we allow backorders
if( $this->managing_stock() && $this->backorders_allowed() )
return true;
// Check if we have stock
if( $this->managing_stock() && $this->stock )
return true;
return false;
}
/**
* Returns whether or not the product can be backordered
*
* @return bool
*/
public function backorders_allowed() {
if ( $this->backorders == 'yes' || $this->backorders_require_notification() )
return true;
return false;
}
/**
* Returns whether or not the product needs to notify the customer on backorder
*
* TODO: Consider a shorter method name?
*
* @return bool
*/
public function backorders_require_notification() {
return ($this->backorders == 'notify');
}
/**
* Returns whether or not the product has enough stock for the order
*
* TODO: Consider a shorter method name?
*
* @return bool
*/
public function has_enough_stock( $quantity ) {
return ($this->backorders_allowed() || $this->stock >= $quantity);
}
/**
* Returns number of items available for sale.
*
* @return int
*/
public function get_stock() {
return (int) $this->stock;
}
/**
* Returns a string representing the availability of the product
*
* @return string
*/
public function get_availability() {
// Do not display availability if we aren't managing stock
if ( get_option('jigoshop_manage_stock') != 'yes' )
return false;
// Start as in stock
$notice = array(
'availability' => __( 'In Stock', 'jigoshop' ),
'class' => null,
);
// If stock is being managed & has stock
if ( $this->managing_stock() && $this->stock ) {
$notice['availability'] .= (get_option('jigoshop_show_stock') == 'yes') ? " – {$this->stock} ".__(' available', 'jigoshop' ) : '';
// If customers require backorder notification
if ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
$notice['availability'] = $notice['availability'] .' ('.__('backorders allowed','jigoshop').')';
}
}
else if ( $this->backorders_allowed() && $this->backorders_require_notification() ) {
$notice['availability'] = __( 'Available on Backorder', 'jigoshop' );
}
// Declare out of stock if we don't have any stock
if ( ! $this->is_in_stock() ) {
$notice['availability'] = __( 'Out of Stock', 'jigoshop' );
$notice['class'] = 'out-of-stock';
}
return $notice;
}
/**
* Returns whether or not the product is featured
*
* @return bool
*/
public function is_featured() {
return (bool) $this->featured;
}
/**
* Checks if the product is visibile
*
* @return bool
*/
public function is_visible( ) {
// Disabled due to incorrect stock handling -Rob
//if( (bool) $this->stock )
// return false;
switch($this->visibility) {
case 'hidden':
return false;
break;
case 'search':
return is_search();
break;
case 'catalog':
return ! is_search(); // don't display in search results
break;
default:
return true; // By default always display a product
}
}
/**
* Returns whether or not the product is on sale.
* If one of the child products is on sale, product is considered to be on sale
*
* @return bool
*/
public function is_on_sale() {
// Check child products for items on sale
if ( $this->is_type( array('grouped', 'variable') ) ) {
foreach( $this->get_children() as $child_ID ) {
$child = $this->get_child( $child_ID );
if( $child->is_on_sale() )
return true;
}
}
$time = current_time('timestamp');
// Check if the sale is still in range (if we have a range)
if ( $this->sale_price_dates_from <= $time &&
$this->sale_price_dates_to >= $time &&
$this->sale_price)
return true;
// Otherwise if we have a sale price
if ( ! $this->sale_price_dates_to && $this->sale_price )
return true;
// Just incase return false
return false;
}
/**
* Returns the product's weight
* @deprecated not required since we can just call $this->weight if the var is public
*
* @return mixed weight
*/
public function get_weight() {
return $this->weight;
}
/** Returns the price (excluding tax) */
function get_price_excluding_tax() {
// to avoid rounding errors multiply by 100. Since we loop through the cart, rather than provide
// a full subtotal, this is necessary.
$price = $this->get_price() * 100;
if (get_option('jigoshop_prices_include_tax') == 'yes') :
$rates = (array) $this->get_tax_base_rate();
if (count($rates > 0)) :
// rates array sorted so that taxes applied to retail value come first. To reverse taxes
// need to reverse this array
$new_rates = array_reverse($rates, true);
$tax_applied_after_retail = 0;
$tax_totals = 0;
$_tax = new jigoshop_tax(100);
foreach ( $new_rates as $key=>$value ) :
if ($value['is_not_compound_tax']) :
$tax_totals += $_tax->calc_tax($price - $tax_applied_after_retail, $value['rate'], true);
else :
$tax_amount[$key] = $_tax->calc_tax($price, $value['rate'], true);
$tax_applied_after_retail += $tax_amount[$key];
$tax_totals += $tax_amount[$key];
endif;
endforeach;
$price = $price - $tax_totals;
endif;
endif;
return $price / 100;
}
/**
* Returns the base tax rate
* TODO: why is this here? shouldn't it be in the tax class?
*
* @return ???|false
*/
public function get_tax_base_rate() {
$rate = array();
if ($this->is_taxable() && get_option('jigoshop_calc_taxes') == 'yes') :
$_tax = new jigoshop_tax();
if ($_tax->get_tax_classes_for_base()) foreach ( $_tax->get_tax_classes_for_base() as $tax_class ) :
if ( !in_array($tax_class, $this->get_tax_classes())) continue;
$my_rate = $_tax->get_shop_base_rate($tax_class);
if ($my_rate > 0) :
$rate[$tax_class] = array('rate'=>$my_rate, 'is_not_compound_tax'=>!$_tax->is_compound_tax());
endif;
endforeach;
endif;
return $rate;
}
/**
* This function returns the tax rate for a particular tax_class applied to the product
*
* @param string tax_class the class of tax to find
* @param array product_tax_rates the tax rates applied to the product
* @return double the tax rate percentage
*/
public static function get_product_tax_rate($tax_class, $product_tax_rates) {
if ($tax_class && $product_tax_rates && is_array($product_tax_rates)) :
return $product_tax_rates[$tax_class]['rate'];
endif;
return (double) 0;
}
/**
* Returns true if the tax is not compounded.
* @param string tax_class the tax class return value on
* @param array product_tax_rates the array of tax rates on the product
* @return bool true if tax class is not compounded. False otherwise. Default true.
*/
public static function get_non_compounded_tax($tax_class, $product_tax_rates) {
if ($tax_class && $product_tax_rates && is_array($product_tax_rates)) :
return $product_tax_rates[$tax_class]['is_not_compound_tax'];
endif;
return true; // default to true for non compound tax
}
/**
* Returns the percentage saved on sale products
* @note was called get_percentage()
*
* @return string
*/
public function get_percentage_sale() {
if ( $this->is_on_sale() ) {
// 100% - sale price percentage over regular price
$percentage = 100 - ( ($this->sale_price / $this->regular_price) * 100);
// Round & return
return round($percentage).'%';
}
}
/**
* Returns the products current price
*
* @return int
*/
public function get_price() {
if ( strstr($this->sale_price,'%') )
return round($this->regular_price * ( (100 - str_replace('%','',$this->sale_price) ) / 100 ), 2);
else if ( $this->sale_price )
return $this->sale_price;
else return $this->regular_price;
}
/**
* Returns the products sale value, either with or without a percentage
*
* @return html
*/
public function calculate_sale_price() {
if ( $this->is_on_sale() ) :
if ( strstr($this->sale_price,'%') )
return '
' . jigoshop_price( $this->regular_price ) . '' . jigoshop_price( $this->get_price() ) . '
' . $this->sale_price . ' off!';
else
return '
' . jigoshop_price( $this->regular_price ) . '
' . jigoshop_price( $this->sale_price ) . '';
endif;
}
/**
* Adjust the products price during runtime
*
* @param mixed
* @return void
*/
public function adjust_price( $new_price ) {
// Only adjust sale price if we are on sale
if($this->sale_price)
$this->sale_price += $new_price;
$this->regular_price += $new_price;
}
/**
* Returns the price in html format
*
* @return html
*/
public function get_price_html() {
$html = null;
// First check if the product is grouped
if ( $this->is_type( array('grouped', 'variable') ) ) {
if ( ! ($children = $this->get_children()) )
return __( 'Unavailable', 'jigoshop' );
$array = array();
foreach ( $children as $child_ID ) {
$child = $this->get_child($child_ID);
// Only get prices that are in stock
if ( $child->is_in_stock() ) {
$array[] = $child->get_price();
}
}
sort($array);
if ($this->is_type('variable')) :
// for variable products, only display From if prices differ among them
if (count($array) >= 2 && $array[count($array) - 1] != $array[0]) :
$html = '' . _x('From:', 'jigoshop') . ' ';
endif;
else :
$html = '' . _x('From:', 'jigoshop') . ' ';
endif;
return $html . jigoshop_price( $array[0] );
}
// For standard products
if ( $this->is_on_sale() )
$html = $this->calculate_sale_price();
else
$html = jigoshop_price( $this->regular_price );
if ( $this->get_price() == 0 )
$html = __( 'Free', 'jigoshop' );
if ( $this->regular_price == '' )
$html = __( 'Price Not Announced', 'jigoshop' );
return $html;
}
/**
* Returns the upsell product ids
*
* @return mixed
*/
public function get_upsells() {
$ids = get_post_meta( $this->id, 'upsell_ids' );
if ( ! empty( $ids )) return $ids[0];
else return array();
}
/**
* Returns the cross_sells product ids
*
* @return mixed
*/
public function get_cross_sells() {
$ids = get_post_meta( $this->id, 'crosssell_ids' );
if ( ! empty( $ids )) return $ids[0];
else return array();
}
/**
* Returns the product's length
* @deprecated not required since we can just call $this->weight if the var is public
*
* @return mixed length
*/
public function get_length() {
return $this->length;
}
/**
* Returns the product's width
* @deprecated not required since we can just call $this->weight if the var is public
*
* @return mixed width
*/
public function get_width() {
return $this->width;
}
/**
* Returns the product's height
* @deprecated not required since we can just call $this->weight if the var is public
*
* @return mixed height
*/
public function get_height() {
return $this->height;
}
/**
* Returns the tax classes
* @return array the tax classes on the product
*/
public function get_tax_classes() {
return (array) get_post_meta($this->ID, 'tax_classes', true);
}
/**
* Returns the product categories
*
* @return HTML
*/
public function get_categories( $sep = ', ', $before = '', $after = '' ) {
return get_the_term_list($this->ID, 'product_cat', $before, $sep, $after);
}
/**
* Returns the product tags
*
* @return HTML
*/
public function get_tags( $sep = ', ', $before = '', $after = '' ) {
return get_the_term_list($this->ID, 'product_tag', $before, $sep, $after);
}
// Returns the product rating in html format
// TODO: optimize this code
public function get_rating_html( $location = '' ) {
if( $location )
$location = '_'.$location;
$star_size = apply_filters('jigoshop_star_rating_size'.$location, 16);
global $wpdb;
// Do we really need this? -Rob
$count = $wpdb->get_var("
SELECT COUNT(meta_value) FROM $wpdb->commentmeta
LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID
WHERE meta_key = 'rating'
AND comment_post_ID = $this->id
AND comment_approved = '1'
AND meta_value > 0
");
$ratings = $wpdb->get_var("
SELECT SUM(meta_value) FROM $wpdb->commentmeta
LEFT JOIN $wpdb->comments ON $wpdb->commentmeta.comment_id = $wpdb->comments.comment_ID
WHERE meta_key = 'rating'
AND comment_post_ID = $this->id
AND comment_approved = '1'
");
// If we don't have any posts
if ( ! (bool)$count )
return false;
// Figure out the average rating
$average_rating = number_format($ratings / $count, 2);
// If we don't have an average rating
if( ! (bool)$average_rating )
return false;
// If all goes well echo out the html
return '
'.__('Weight', 'jigoshop').' | '. $this->get_weight() . get_option('jigoshop_weight_unit') .' |
---|---|
'.__('Length', 'jigoshop').' | '. $this->get_length() . get_option('jigoshop_dimension_unit') .' |
'.__('Width', 'jigoshop').' | '. $this->get_width() . get_option('jigoshop_dimension_unit') .' |
'.__('Height', 'jigoshop').' | '. $this->get_height() . get_option('jigoshop_dimension_unit') .' |
$name | $value |