Display In stock available variations in WooCommerce single product - variables

I have variable products with many variations where only a few items are actually In Stock while the majority of other variations are ''available on backorder''
I would like to be able to display a quick list of ONLY the items that are IN STOCK in the short product description of each product page so the customer doesn't have to try all variations one-by-one to finally find out which ones are in stock.
add_filter( 'woocommerce_short_description', 'display_in_stock_variations_to_short_description' );
function display_in_stock_variations_to_short_description( $excerpt ){
global $product;
if ( ! is_product() || empty($product) || ! is_a( $product, 'WC_Product' ) )
return $excerpt;
if( $product->is_type('variable') ) {
// Loop through visible children
foreach( $product->get_children() as $variation_id ) {
$variation = wc_get_product( $variation_id );
// Hide out of stock variations if 'Hide out of stock items from the catalog' is checked.
if ( ! $variation || ! $variation->exists() || ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && ! $variation->is_in_stock() ) ) {
continue;
}
// Filter 'woocommerce_hide_invisible_variations' to optionally hide invisible variations (disabled variations and variations with empty price).
if ( apply_filters( 'woocommerce_hide_invisible_variations', true, $product->get_id(), $variation ) && ! $variation->variation_is_visible() ) {
continue;
}
$max_qty = 0 < $variation->get_max_purchase_quantity() ? $variation->get_max_purchase_quantity() : $variation->get_stock_quantity();
$term_names = []; // Initializing
// Loop through variation attributes for current varation
foreach ( $variation->get_variation_attributes() as $attribute => $term_slug ) {
// Set the term name in an array
$term_names[] = ucfirst( str_replace( ['-', '_'],[' ', ' '], $term_slug ) );
}
if ( $max_qty > 0 ) {
$excerpt .= sprintf( '<br/>%s: %s %s',
implode(', ', $term_names),
$max_qty,
__('in stock', 'woocommerce')
);
}
}
}
return $excerpt;
}
// Avoid additional content from product short description to be displayed in variation description
add_filter( 'woocommerce_available_variation', 'filter_wc_available_variation_desscription', 10, 3);
function filter_wc_available_variation_desscription( $data, $product, $variation ) {
$max_qty = 0 < $variation->get_max_purchase_quantity() ? $variation->get_max_purchase_quantity() : $variation->get_stock_quantity();
if( $max_qty > 0 )
$data['variation_description'] = get_post_meta( $variation->get_id(), '_variation_description', true );
return $data;
}
This code displays an inventory, but my website language is Persian and the names of the variables are defined in Persian, and this line does not display the variable name code correctly.
How can I fix his language?
And I want to translate the word instock into موجود
And the next point is that I want a button to be displayed on the page first with the title Show product inventory and when the method clicks, the customer will open a pop-up and display the inventory.

Here is the soultion with Persian language:
change
// Loop through variation attributes for current varation
foreach ( $variation->get_variation_attributes() as $attribute => $term_slug ) {
// Set the term name in an array
$term_names[] = ucfirst( str_replace( ['-', '_'],[' ', ' '], $term_slug ) );
}
if ( $max_qty > 0 ) {
$excerpt .= sprintf( '<br/>%s: %s %s',
implode(', ', $term_names),
$max_qty,
__('in stock', 'woocommerce')
);
}
to
// Loop through variation attributes for current varation
foreach ( $variation->get_variation_attributes() as $attribute => $term_name ) {
// Set the term name in an array
$taxonomy = str_replace('attribute_', '', $attribute);
$attribute_name = wc_attribute_label($taxonomy);
$term_name = get_term_by( 'slug', $term_name, $taxonomy)->name;
$term_names[] = $term_name;
}
if ( $max_qty > 0 ) {
$excerpt .= sprintf( '<br/>%s: %s %s',
implode(', ', $term_names),
$max_qty,
__('موجود')
);
}

Related

Get product total sales based on order status in WooCommerce

I use product meta total_sales to display the total sales of a product, on the single product page in WooCommerce:
add_action( 'woocommerce_single_product_summary', 'wc_product_sold_count', 11 );
function wc_product_sold_count() {
global $product;
$units_sold = get_post_meta( $product->get_id(), 'total_sales', true );
echo '<p>' . sprintf( __( 'Units Sold: %s', 'woocommerce' ), $units_sold ) . '</p>';
}
But when some orders are canceled, the total sales are not automatically decreased. So, how can I get the total sales based on order status in WooCommerce?
To obtain the total sales, based on order statuses, you will have to use a custom function:
function get_product_total_sales_by_order_status( $product_id, $order_statuses ) {
global $wpdb;
return $wpdb->get_var( $wpdb->prepare("
SELECT SUM( opl.product_qty )
FROM {$wpdb->prefix}wc_order_product_lookup as opl
INNER JOIN {$wpdb->prefix}posts as p
ON opl.order_id = p.ID
AND p.post_status IN ( 'wc-" . implode( "', 'wc-", $order_statuses ) . "' )
AND opl.product_id = %d
", $product_id ) );
}
Which can then be used as:
// Set the product ID
$p_id = 30;
// Set the orders statuses (without wc-)
$statuses = array( 'on-hold', 'processing', 'completed' );
// Call function, display result
echo get_product_total_sales_by_order_status( $p_id, $statuses );
Or in your specific case:
function action_woocommerce_single_product_summary() {
global $product;
// Is a WC product
if ( is_a( $product, 'WC_Product' ) ) {
// Set the orders statuses (without wc-)
$statuses = array( 'completed' );
// Call function
$total_sales = get_product_total_sales_by_order_status( $product->get_id(), $statuses );
// Display result
if ( $total_sales >= 1 ) {
echo sprintf( __( 'Total sales: %d', 'woocommerce' ), $total_sales );
} else {
echo __( 'N/A', 'woocommerce' );
}
}
}
add_action( 'woocommerce_single_product_summary', 'action_woocommerce_single_product_summary', 11 );

What I did wrong with Db::getInstance()->insert('product', $dimension)); not inserting data to database?

I am developing a module that would save a custom field from product admin.. but my code is not inserting data into database.. below is my code.
public function hookActionProductUpdate($params)
{
if(Tools::isSubmit('submitDimension'))
{
$name = Tools::getValue('name');
$length = Tools::getValue('custom_length');
$width = Tools::getValue('custom_width');
if(empty($name) || !Validate::isGenericName($name))
$this->errors[] = $this->module->l('Invalid name');
if(empty($length) || !Validate::isGenericName($length))
$this->errors[] = $this->module->l('Invalid length');
if(empty($width) || !Validate::isGenericName($width))
$this->errors[] = $this->module->l('Invalid width');
if(!$this->errors)
{
$dimension = array(
'name' => $name,
'custom_length' => $length,
'custom_width' => $width
);
if(!Db::getInstance()->insert('product', $dimension));
$this->errors[] = Tools::displayError('Error while updating database');
}
}
}
Anyone can help me please..
Here is my install function
function install()
{
if (!parent::install() ||
!$this->alterProductTable() ||
!$this->registerHook('extraright') ||
!$this->registerHook('displayAdminProductsExtra') ||
!$this->registerHook('actionProductSave') ||
!$this->registerHook('actionProductUpdate') ||
!$this->registerHook('header')
)
return false;
return true;
}
Here is my alter table function
private function alterProductTable($method = 'add')
{
if($method = 'add')
$sql = '
ALTER TABLE '._DB_PREFIX_.'product
ADD COLUMN `custom_length` decimal(20,6) NOT NULL,
ADD COLUMN `custom_width` decimal(20,6) NOT NULL,
ADD COLUMN `name` VARCHAR(64) NOT NULL';
if(!Db::getInstance()->Execute($sql))
return false;
return true;
}
.. the columns are there..
And here is my display admin hook
public function hookDisplayAdminProductsExtra($params)
{
$name = Db::getInstance()->getValue('SELECT `name` FROM '._DB_PREFIX_.'product WHERE id_product = '.Tools::getValue('id_product'));
$length = Db::getInstance()->getValue('SELECT custom_length FROM '._DB_PREFIX_.'product WHERE id_product = '.(int)Tools::getValue('id_product'));
$width = Db::getInstance()->getValue('SELECT custom_width FROM '._DB_PREFIX_.'product WHERE id_product = '.(int)Tools::getValue('id_product'));
$this->context->smarty->assign(array(
'name' => $name,
'length' => $length,
'width' => $width
));
return $this->display(__FILE__, 'views/templates/hook/adminProductsExtra.tpl');
}
I have been looking at this for 2 days.. and I cant seem to find what I did wrong.. I have gone to prestashop forum but no help so far.. I hope I can get something from good people here. thanks in advance!
Do you check the Prestashop db best practice guide if not than please check first.
This is not the correct way to insert data you also need to mention fields in which you want to insert data particular table.
Best Practices of the Db Class - Prestashop
All is wrong, you are trying to insert data in a table which have many other fields required, and you are trying also to insert data in columns that doesn't exist in that table. The best way to create new products by code is using their object, like this...
$product = new Product()
$product->id_tax_rules_group = 1;
$product->redirect_type = '404';
$product->name = array(
$id_lang => 'Product name in this lang',
$id_lang => 'Product name in this lang',
);
/*
* And so on all the mandatory fields
*/
$product->save();

Disable WooCommerce email notification for specific product

I can refer to this function to disable email notification:
https://docs.woocommerce.com/document/unhookremove-woocommerce-emails/
But I would like to disable it only for a specific product or, if it can be more simple, for a specific product category.
Thanks for your help
Thanks to #vidish-purohit for the help!
Here is my code to use if you need to disable admin email notification for a specific product:
function change_email_recipient_depending_of_product_id( $recipient, $order ) {
global $woocommerce;
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item['product_id'];
if ( $product_id == xxx ) {
$recipient = '';
}
return $recipient;
}
}
add_filter( 'woocommerce_email_recipient_new_order', 'change_email_recipient_depending_of_product_id', 10, 2 );
And if you need to disable customer email notification for a specific product:
function change_email_recipient_depending_of_product_id( $recipient, $order ) {
global $woocommerce;
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item['product_id'];
if ( $product_id == xxx ) {
$recipient = '';
}
return $recipient;
}
}
add_filter( 'woocommerce_email_recipient_customer_processing_order', 'change_email_recipient_depending_of_product_id', 10, 2 );
I think when you try to hook email notification from template, where you can find order, at that time emails are already sent.
You can try one thing - using recipient's hook you can remove recipient email and return empty string. Or if empty string triggers error, then you can give some dummy email.
Use this code for this:
// Change new order email recipient for registered customers
function wc_change_admin_new_order_email_recipient( $recipient, $order ) {
global $woocommerce;
// check if product in order
if ( true ) ) {
$recipient = "";
} else {
$recipient = "newbusiness#yourdomain.com";
}
return $recipient;
}
add_filter('woocommerce_email_recipient_new_order', 'wc_change_admin_new_order_email_recipient', 1, 2);
// Change new order email recipient for registered customers
function wc_change_admin_new_order_email_recipient( $recipient, $order ) {
$flagHasProduct = false;
// Get items in order
$items = $order->get_items();
// Loop for all items
foreach ( $items as $item ) {
$product_id = $item['product_id'];
// check if specific product is in order
if ( $product_id == 102 ) {
$flagHasProduct = true;
}
}
// if product is found then remove recipient
if ($flagHasProduct) {
$recipient = "";
} else {
$recipient = "newbusiness#yourdomain.com";
}
return $recipient;
}
add_filter('woocommerce_email_recipient_new_order', 'wc_change_admin_new_order_email_recipient', 1, 2);
The above code will disable the email option in the Woocommerce email setting option page.
/**
* Disable Admin email Notification for Specific Product
*/
function cstm_change_email_recipient_for_giftcard_product($recipient, $order)
{
// Bail on WC settings pages since the order object isn't yet set yet
// Not sure why this is even a thing, but shikata ga nai
$page = $_GET['page'] = isset($_GET['page']) ? $_GET['page'] : '';
if ('wc-settings' === $page) {
return $recipient;
}
// just in case
if (!$order instanceof WC_Order) {
return $recipient;
}
$items = $order->get_items();
foreach ($items as $item) {
$product_id = $item['product_id'];
if ($product_id == xxxx) {
$recipient = '';
}
return $recipient;
}
}
add_filter('woocommerce_email_recipient_new_order', 'cstm_change_email_recipient_for_giftcard_product', 10, 2);
this code works fine in latest version of Woocommerce.

replacing "view cart" button with "proceed to checkout" button upon adding a product to cart

After product page reloads upon adding the product to cart, a notification appears on top saying "product was added to cart" followed by a "view cart" button.
However after product gets added to cart I want to direct customers to checkout page (where I'll add cart page functionality), not cart page. How can I replace the notification's link to cart with a link to checkout page?
wc_add_to_cart_message() function (in includes/wc-cart-functions.php) seems to generate this particular notification, should I override this function? If so, how?
try this...
function rei_wc_add_to_cart_message( $message, $product_id ) {
$titles = array();
if ( is_array( $product_id ) ) {
foreach ( $product_id as $id ) {
$titles[] = get_the_title( $id );
}
} else {
$titles[] = get_the_title( $product_id );
}
$titles = array_filter( $titles );
$added_text = sprintf( _n( '%s has been added to your cart.', '%s have been added to your cart.', sizeof( $titles ), 'woocommerce' ), wc_format_list_of_items( $titles ) );
// Output success messages
if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
$return_to = apply_filters( 'woocommerce_continue_shopping_redirect', wp_get_referer() ? wp_get_referer() : home_url() );
$message = sprintf( '%s %s', esc_url( $return_to ), esc_html__( 'Continue Shopping', 'woocommerce' ), esc_html( $added_text ) );
} else {
$message = sprintf( '%s %s', esc_url( wc_get_page_permalink( 'checkout' ) ), esc_html__( 'Proceed to Checkout', 'woocommerce' ), esc_html( $added_text ) );
}
return $message;
}
add_filter('wc_add_to_cart_message','rei_wc_add_to_cart_message',10,2);

jquery DataTables erroring on search box

Trying to get DataTables to work with PDO. I found this script online and it works fine, BUT, when I set ATTR_EMULATE_PREPARES to false the search capability does not work and reports back this error.
I cannot view the json response as there is none to view when this error happens, however, in all other cases other than using the search the json is returned properly and it works perfectly fine. Since the error only happens when emulation is set to false I am thinking this has something to do with binding? I cannot figure this one out as I don't see anything wrong that is sticking out at me.
Also, I am not looking to turn on emulation as a solution either. Help would be very appreciated.
the get in firebug:
http://www.example.com/assets/data-tables/test-pdo.php?sEcho=3&iColumns=4&sColumns=&iDisplayStart=0&iDisplayLength=10&mDataProp_0=0&mDataProp_1=1&mDataProp_2=2&mDataProp_3=3&sSearch=d&bRegex=false&sSearch_0=&bRegex_0=false&bSearchable_0=true&sSearch_1=&bRegex_1=false&bSearchable_1=true&sSearch_2=&bRegex_2=false&bSearchable_2=true&sSearch_3=&bRegex_3=false&bSearchable_3=true&iSortCol_0=2&sSortDir_0=asc&iSortingCols=1&bSortable_0=false&bSortable_1=true&bSortable_2=true&bSortable_3=true&_=1388479579319
Error in firebug:
<br />
<b>Fatal error</b>: Uncaught exception 'PDOException' with message 'SQLSTATE[HY093]: Invalid parameter number' in /home/test/public_html/assets/data-tables/test-pdo.php:107
Stack trace:
#0 /home/test/public_html/assets/data-tables/test-pdo.php(107): PDOStatement->execute()
#1 /home/test/public_html/assets/data-tables/test-pdo.php(155): TableData->get('accounts', 'account_id', Array)
#2 {main}
thrown in <b>/home/test/public_html/assets/data-tables/test-pdo.php</b> on line <b>107</b><br />
db connection:
$db = new PDO("mysql:host=$db_host;dbname=$db_database;charset=utf8", $db_user, $db_pass, array(PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_PERSISTENT => true));
processing:
<?php
/*
* Script: DataTables server-side script for PHP and MySQL
* Copyright: 2012 - John Becker, Beckersoft, Inc.
* Copyright: 2010 - Allan Jardine
* License: GPL v2 or BSD (3-point)
*/
define('INCLUDE_CHECK',true);
// These files can be included only if INCLUDE_CHECK is defined
require '/home/test/public_html/assets/functions/connect.php';
//inject db connection into class
class TableData {
/** #var \PDO */
protected $_db;
public function __construct(\PDO $_db) {
$this->_db = $_db;
}
public function get($table, $index_column, $columns) {
// Paging
$sLimit = "";
if ( isset( $_GET['iDisplayStart'] ) && $_GET['iDisplayLength'] != '-1' ) {
$sLimit = "LIMIT ".intval( $_GET['iDisplayStart'] ).", ".intval( $_GET['iDisplayLength'] );
}
// Ordering
$sOrder = "";
if ( isset( $_GET['iSortCol_0'] ) ) {
$sOrder = "ORDER BY ";
for ( $i=0 ; $i<intval( $_GET['iSortingCols'] ) ; $i++ ) {
if ( $_GET[ 'bSortable_'.intval($_GET['iSortCol_'.$i]) ] == "true" ) {
$sortDir = (strcasecmp($_GET['sSortDir_'.$i], 'ASC') == 0) ? 'ASC' : 'DESC';
$sOrder .= "`".$columns[ intval( $_GET['iSortCol_'.$i] ) ]."` ". $sortDir .", ";
}
}
$sOrder = substr_replace( $sOrder, "", -2 );
if ( $sOrder == "ORDER BY" ) {
$sOrder = "";
}
}
/*
* Filtering
* NOTE this does not match the built-in DataTables filtering which does it
* word by word on any field. It's possible to do here, but concerned about efficiency
* on very large tables, and MySQL's regex functionality is very limited
*/
//need this change to only show correct responses from db
//$test = 100;
//$sWhere = ""; OR $sWhere = "WHERE account_id < ".$test;
$sWhere = "";
if ( isset($_GET['sSearch']) && $_GET['sSearch'] != "" ) {
// changes for correct display from db plus searching
if ($sWhere == ""){
$sWhere = "WHERE (";
}
else {
$sWhere .= " AND (";
}
//$sWhere = "WHERE (";
for ( $i=0 ; $i<count($columns) ; $i++ ) {
if ( isset($_GET['bSearchable_'.$i]) && $_GET['bSearchable_'.$i] == "true" ) {
$sWhere .= "`".$columns[$i]."` LIKE :search OR ";
}
}
$sWhere = substr_replace( $sWhere, "", -3 );
$sWhere .= ')';
}
// Individual column filtering
for ( $i=0 ; $i<count($columns) ; $i++ ) {
if ( isset($_GET['bSearchable_'.$i]) && $_GET['bSearchable_'.$i] == "true" && $_GET['sSearch_'.$i] != '' ) {
if ( $sWhere == "" ) {
$sWhere = "WHERE ";
}
else {
$sWhere .= " AND ";
}
$sWhere .= "`".$columns[$i]."` LIKE :search".$i." ";
}
}
// SQL queries get data to display
$sQuery = "SELECT SQL_CALC_FOUND_ROWS `".str_replace(" , ", " ", implode("`, `", $columns))."` FROM `".$table."` ".$sWhere." ".$sOrder." ".$sLimit;
$statement = $this->_db->prepare($sQuery);
// Bind parameters
if ( isset($_GET['sSearch']) && $_GET['sSearch'] != "" ) {
$statement->bindValue(':search', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
}
for ( $i=0 ; $i<count($columns) ; $i++ ) {
if ( isset($_GET['bSearchable_'.$i]) && $_GET['bSearchable_'.$i] == "true" && $_GET['sSearch_'.$i] != '' ) {
$statement->bindValue(':search'.$i, '%'.$_GET['sSearch_'.$i].'%', PDO::PARAM_STR);
}
}
$statement->execute();
$rResult = $statement->fetchAll();
$iFilteredTotal = current($this->_db->query('SELECT FOUND_ROWS()')->fetch());
// Get total number of rows in table
$sQuery = "SELECT COUNT(`".$index_column."`) FROM `".$table."`";
//$sQuery = "SELECT COUNT(`".$index_column."`) FROM `".$table."` WHERE account_id < 100";
$iTotal = current($this->_db->query($sQuery)->fetch());
// Output
$output = array(
"sEcho" => intval($_GET['sEcho']),
"iTotalRecords" => $iTotal,
"iTotalDisplayRecords" => $iFilteredTotal,
"aaData" => array()
);
// Return array of values
foreach($rResult as $aRow) {
$row = array();
for ( $i = 0; $i < count($columns); $i++ ) {
//else if ( $aColumns[$i] != ' ' )
if ( $columns[$i] != ' ' )
{
/* General output */
//if column is empty give it n/a
$row[] = ($aRow[ $columns[$i] ]=="") ? 'n/a' : $aRow[ $columns[$i] ];
}
}
$output['aaData'][] = $row;
}
echo json_encode( $output );
}
}
header('Pragma: no-cache');
header('Cache-Control: no-store, no-cache, must-revalidate');
// Create instance of TableData class
$table_data = new TableData($db);
// Get the data
//$table_data->get('table_name', 'index_column', array('column1', 'column2', 'columnN'));
$table_data->get('accounts', 'account_id', array('account_id', 'account_username', 'account_password', 'account_email'));
?>
I doubt that anyone will be interested, but I finally figured this out. The script was trying to use the same binding, :search, multiple times in the statement.
Even though it will always be the same actual value the error was being thrown as it was the same binding. How I did not see this earlier I do not know, but it is obvious to me now.
//$sWhere .= "`".$columns[$i]."` LIKE :search OR ";
$sWhere .= "`".$columns[$i]."` LIKE :searchm".$i." OR ";
// Bind parameters
//if ( isset($_GET['sSearch']) && $_GET['sSearch'] != "" ) {
// $statement->bindValue(':search', '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
//}
if ( isset($_GET['sSearch']) && $_GET['sSearch'] != "" ) {
for ( $i=0 ; $i<count($columns) ; $i++ ) {
$statement->bindValue(':searchm'.$i, '%'.$_GET['sSearch'].'%', PDO::PARAM_STR);
}
}