I've got a problem when adding a grouped product to cart.
I need to set a custom option for all products that are added to cart while adding a grouped product to cart.
What I have tried last (with a little bit of success):
<checkout_cart_product_add_after>
<observers>
<customoptions>
<type>singleton</type>
<class>Company_CustomOptions_Model_Observer</class>
<method>addCustomOptionGroupSku</method>
</customoptions>
</observers>
</checkout_cart_product_add_after>
and
public function addCustomOptionGroupSku(Varien_Event_Observer $observer) {
$product = $observer->getProduct ();
if ($product->isGrouped ()) {
$quoteItem = $observer->getQuoteItem ();
$additionalOptions = array (
'options' => array (
'label' => 'GROUPSKU',
'value' => $product->getSku ()
)
);
$quoteItem->addOption ( new Varien_Object ( array (
'product' => $quoteItem->getProduct (),
'code' => 'additional_options',
'value' => serialize ( $additionalOptions )
) ) );
}
}
I have created one grouped product, containing two products.
But that code only adds the custom option "GROUPSKU" to one of the items in the cart. The other one is untouched.
How do I get all the QuoteItems that are about to be added to the cart?
PS: I have also added this question to the Magento part of StackExchange: https://magento.stackexchange.com/questions/51883/add-custom-options-while-adding-grouped-product-to-cart
I found a solution which does not need an observer.
I had to do a rewrite to Mage_Sales_Model_Quote. More specific the method addProductAdvanced()
Bad news: You can not simply use parent::addProductAdvanced() and then do your own stuff. You have to copy the original code and fill it up with your code.
Here is what I did (look out for the comment starting with /********):
public function addProductAdvanced(Mage_Catalog_Model_Product $product, $request = null, $processMode = null) {
if ($request === null) {
$request = 1;
}
if (is_numeric ( $request )) {
$request = new Varien_Object ( array (
'qty' => $request
) );
}
if (! ($request instanceof Varien_Object)) {
Mage::throwException ( Mage::helper ( 'sales' )->__ ( 'Invalid request for adding product to quote.' ) );
}
$cartCandidates = $product->getTypeInstance ( true )->prepareForCartAdvanced ( $request, $product, $processMode );
/**
* Error message
*/
if (is_string ( $cartCandidates )) {
return $cartCandidates;
}
/**
* If prepare process return one object
*/
if (! is_array ( $cartCandidates )) {
$cartCandidates = array (
$cartCandidates
);
}
$parentItem = null;
$errors = array ();
$items = array ();
foreach ( $cartCandidates as $candidate ) {
// Child items can be sticked together only within their parent
$stickWithinParent = $candidate->getParentProductId () ? $parentItem : null;
$candidate->setStickWithinParent ( $stickWithinParent );
$item = $this->_addCatalogProduct ( $candidate, $candidate->getCartQty () );
/******** own modification for custom option GROUPSKU*/
if($product->isGrouped()){
$additionalOptions = array (
'options' => array (
'label' => 'GROUPSKU',
'value' => $product->getSku ()
)
);
$item->addOption ( new Varien_Object ( array (
'product' => $item->getProduct (),
'code' => 'additional_options',
'value' => serialize ( $additionalOptions )
) ) );
}
/******** own modification end*/
if ($request->getResetCount () && ! $stickWithinParent && $item->getId () === $request->getId ()) {
$item->setData ( 'qty', 0 );
}
$items [] = $item;
/**
* As parent item we should always use the item of first added product
*/
if (! $parentItem) {
$parentItem = $item;
}
if ($parentItem && $candidate->getParentProductId ()) {
$item->setParentItem ( $parentItem );
}
/**
* We specify qty after we know about parent (for stock)
*/
$item->addQty ( $candidate->getCartQty () );
// collect errors instead of throwing first one
if ($item->getHasError ()) {
$message = $item->getMessage ();
if (! in_array ( $message, $errors )) { // filter duplicate messages
$errors [] = $message;
}
}
}
if (! empty ( $errors )) {
Mage::throwException ( implode ( "\n", $errors ) );
}
Mage::dispatchEvent ( 'sales_quote_product_add_after', array (
'items' => $items
) );
return $item;
}
This way all the products that are added to the cart using a grouped product now have the custom option.
Related
For checkout I use one script to show and hide billing fields depending on the shipping way.
Earlier it worked fine, but that time - I think because of using select2 in the city field instead of the text input - the address text field is making issues.
When user writes their address not so fast, the form is starting to update too quick, can delete the new written after that short pause characters or not delete, randomly, and when I want to delete something - the select 2 can appear on the top of the page and not closing.
add_filter( 'woocommerce_update_order_review_fragments', 'awoohc_add_update_form_billing', 99 );
function awoohc_add_update_form_billing( $fragments ) {
$checkout = WC()->checkout();
ob_start();
?>
<div class="woocommerce-billing-fields__field-wrapper">
<?php
$fields = $checkout->get_checkout_fields( 'billing' );
foreach ( $fields as $key => $field ) {
if ( isset( $field['country_field'], $fields[ $field['country_field'] ] ) ) {
$field['country'] = $checkout->get_value( $field['country_field'] );
}
woocommerce_form_field( $key, $field, $checkout->get_value( $key ) );
}
?>
</div>
<?php
$art_add_update_form_billing = ob_get_clean();
$fragments['.woocommerce-billing-fields'] = $art_add_update_form_billing;
return $fragments;
}
add_filter( 'woocommerce_checkout_fields' , 'override_billing_checkout_fields', 20, 1 );
function override_billing_checkout_fields( $fields ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
if ( 'local_pickup:1' === $chosen_methods[0] ) {
unset( $fields['billing']['billing_address_1'] );
unset( $fields['billing']['billing_city'] );
unset( $fields['billing']['billing_state'] );
}
return $fields;
}
add_filter( 'woocommerce_checkout_fields' , 'b_override_billing_checkout_fields', 20, 1 );
function b_override_billing_checkout_fields( $fields ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
if ( 'boxberry_self:4' === $chosen_methods[0] || 'boxberry_self_after:6' === $chosen_methods[0] ) {
unset( $fields['billing']['billing_address_1'] );
}
return $fields;
}
// Just hide woocommerce billing country
add_action( 'woocommerce_before_checkout_form', 'hide_checkout_billing_country', 5 );
function hide_checkout_billing_country() {
echo '<style>#billing_country_field{display:none;}</style>';
}
add_filter('woocommerce_billing_fields', 'customize_checkout_fields', 100 );
function customize_checkout_fields( $fields ) {
if ( is_checkout() ) {
// HERE set the required key fields below
$chosen_fields = array( 'postcode', 'country', 'company','last_name', 'address_2');
foreach( $chosen_fields as $key ) {
if( isset($fields['billing_'.$key]) && $key !== 'country') {
unset($fields['billing_'.$key]); // Remove all define fields except country
}
}
}
return $fields;
}
/*
* Updating the form
*/
add_action( 'wp_footer', 'awoohc_add_script_update_shipping_method' );
function awoohc_add_script_update_shipping_method() {
if ( is_checkout() ) {
?>
<script>
jQuery(document).ready(function ($) {
$(document.body).on('updated_checkout updated_shipping_method', function (event, xhr, data) {
$('input[name^="shipping_method"]').on('change', function () {
$('.woocommerce-billing-fields__field-wrapper').block({
message: null,
overlayCSS: {
background: '#fff',
'z-index': 1000000,
opacity: 0.3
}
});
$('select#billing_city').select2();
});
var first_name = $('#billing_first_name').val(),
phone = $('#billing_phone').val(),
email = $('#billing_email').val(),
city = $('#billing_city').val(),
address_1 = $('#billing_address_1').val(),
$(".woocommerce-billing-fields__field-wrapper").html(xhr.fragments[".woocommerce-billing-fields"]);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_first_name"]').val(first_name);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_phone"]').val(phone);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_email"]').val(email);
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_city"]').val(city);
$('select#billing_city').select2();
$(".woocommerce-billing-fields__field-wrapper").find('input[name="billing_address_1"]').val(address_1);
$('.woocommerce-billing-fields__field-wrapper').unblock();
});
});
</script>
<?php
}
}
Yes, I needed to use select2 twice, in other ways it didn't work as needed. But when even I use it once, it didn't help me with that issue. I can't disable select2, the client needs it, and needs to fix changing the address.
How I replaced the city input to select
add_filter( 'woocommerce_checkout_fields' , 'override_checkout_city_fields' );
function override_checkout_city_fields( $fields ) {
// Define here in the array your desired cities (Here an example of cities)
$option_cities = array(
'city1' => 'city1',
'city2' => 'city2',
);
$fields['billing']['billing_city']['type'] = 'select';
$fields['billing']['billing_city']['options'] = $option_cities;
$fields['shipping']['shipping_city']['type'] = 'select';
$fields['shipping']['shipping_city']['options'] = $option_cities;
return $fields;
}
Or maybe it's a delivery service plugin. Maybe you see what I don't see.
I know that exist many plugins, but I need to find what's wrong manually.
I tried to /comment/ different parts of the script, so I think it's something with select, or with delicery service plugin if not it.
I have a fully working AJAX load more button on my Wordpress posts. On click, it loads the next page of posts and appends them directly to the end of the first set.
I now need to create category filters. So when a user clicks a category name, it updates the results below via AJAX to only show posts in that category - BUT I need the load more ajax to continue to work correctly. I've tried hashing together two different AJAX calls, and also tried combining both AJAX into one, but I can't seem to get them talking...
Here's the code:
functions.php
function we_title_filters() {
if(is_home()): ?>
<div class="title-filters">
<h3 class="widget-title" style="display:inline-block;">Latest News</h3>
<div class="filters">
<?php
$include = array(3651, 7, 2828, 2829, 2172);
$categories = get_categories( array(
'orderby' => 'name',
'parent' => 0,
'include' => $include
) );
foreach ( $categories as $category ) {
printf( '%2$s',
esc_url( get_category_link( $category->term_id ) ),
esc_html( $category->name )
);
}
?>
<div class="dropdown" style="float:right;">
<span class="dropbtn">More</span>
<ul class="more-list">
<?php
$include = array(3651, 7, 2828, 2829, 2172);
$categories = get_categories( array(
'orderby' => 'name',
'parent' => 0,
'include' => $include
) );
foreach ( $categories as $category ) {
printf( '<li>%2$s</li>',
esc_url( get_category_link( $category->term_id ) ),
esc_html( $category->name )
);
}
?>
</ul>
</div>
</div>
</div>
<?php endif;
}
This outputs the filters in a list. On click of one of them, it needs to AJAX change the results, but also keep the load more button working, within that category
/* AJAX load more stuff */
/**
*
* Infinite Scroll
*
* #since 1.0.0
*
* #author Lauren Gray
* #author Bill Erickson
* #link http://www.billerickson.net/infinite-scroll-in-wordpress
*
* Enqueue necessary JS file and localize.
*
*/
add_action( 'wp_enqueue_scripts', 'sn_infinite_scroll_enqueue' );
function sn_infinite_scroll_enqueue() {
if ( ! is_singular() ) {
global $wp_query;
$args = array(
'nonce' => wp_create_nonce( 'be-load-more-nonce' ),
'url' => admin_url( 'admin-ajax.php' ),
'query' => $wp_query->query,
'maxpage' => $wp_query->max_num_pages,
);
wp_enqueue_script( 'sn-load-more', get_stylesheet_directory_uri() . '/js/load-more.js', array( 'jquery' ), '1.0', true );
wp_localize_script( 'sn-load-more', 'beloadmore', $args );
}
}
/**
*
* Infinite Scroll
*
* #since 1.0.0
*
* #author Lauren Gray
* #author Bill Erickson
* #link http://www.billerickson.net/infinite-scroll-in-wordpress
*
* Parse information
*
*/
add_action( 'wp_ajax_sn_infinite_scroll_ajax', 'sn_infinite_scroll_ajax' );
add_action( 'wp_ajax_nopriv_sn_infinite_scroll_ajax', 'sn_infinite_scroll_ajax' );
function sn_infinite_scroll_ajax() {
if ( ! is_singular() ) {
check_ajax_referer( 'be-load-more-nonce', 'nonce' );
$excluded_ids = get_field('top_stories', option);
$args = isset( $_POST['query'] ) ? array_map( 'esc_attr', $_POST['query'] ) : array();
$args['post_type'] = isset( $args['post_type'] ) ? esc_attr( $args['post_type'] ) : 'post';
$args['paged'] = esc_attr( $_POST['page'] );
$args['post_status'] = 'publish';
$args['post__not_in'] = $excluded_ids;
$pageType = esc_attr( $_POST['pageType'] );
if ( $pageType == "is-home" ) {
$initial = 30;
$ppp = 30;
$columns = 3;
}
else {
$initial = 30;
$ppp = 30;
$columns = 3;
}
$args['posts_per_page'] = $ppp;
$args['offset'] = $initial + ( $ppp * ( $_POST['page'] ) );
ob_start();
$loop = new WP_Query( $args );
if( $loop->have_posts() ): while( $loop->have_posts() ): $loop->the_post();
sn_post_summary( $loop->current_post, $columns );
endwhile;
endif;
wp_reset_postdata();
$page = esc_attr( $_POST['page'] );
}
$data = ob_get_clean();
wp_send_json_success( $data );
wp_die();
}
}
/**
*
* Infinite Scroll
*
* #since 1.0.0
*
* #author Lauren Gray
* #author Bill Erickson
* #link http://www.billerickson.net/infinite-scroll-in-wordpress
*
* Output articles
*
*/
function sn_post_summary( $count, $columns ) {
// Be able to convert the number of columns to the class name in Genesis
$fractions = array( '', 'half', 'third', 'fourth', 'fifth', 'sixth' );
// Make a note of which column we're in
$column_number = ( $count % $columns ) + 1;
// Add one-* class to make it correct width
$countClasses = sprintf( 'one-' . $fractions[$columns - 1] . ' ', $columns );
// Add a class to the first column, so we're sure of starting a new row with no padding-left
if ( 1 == $column_number )
$countClasses .= 'first ';
remove_action( 'genesis_entry_content', 'genesis_do_post_content' );
remove_action( 'genesis_entry_header', 'genesis_post_info', 12 );
remove_action( 'genesis_entry_content', 'genesis_do_post_image', 8 );
add_action( 'genesis_entry_header', 'genesis_do_post_image', 8 );
echo '<article class="' . $countClasses . implode( ' ', get_post_class() ) . '">'; // add column class
do_action( 'genesis_entry_header' );
echo '</article>';
}
This is a modified version of the infinite scroll loader found here: https://gist.github.com/graylaurenm/86daa4f23aa8749c0933f72133ac7106
I removed the infinite scroll options so it only loads on click of the button.
Hi I have added a new column in a shipment grid as below
$this->addColumn('telephone', array(
'header' => Mage::helper('sales')->__('Billing Phone'),
'index' => 'telephone',
'renderer'=> new OSP_Adminhtml_Block_Customer_Renderer_BillingPhone()
));
in this i am using a renderer to show custom values as below
public function render(Varien_Object $row) {
$customer_id = $row->getData('customer_id');
if( $customer_id > 0 ) {
// get member_id (club canon)
$customer = Mage::getModel('customer/customer')->load($customer_id);
if( is_object( $customer ) ) {
$value = $customer->getData('mobile');
}
}else{
$id = $row->getData('order_increment_id');
$order = Mage::getModel('sales/order')->loadByIncrementId($id);
$value = $order->getBillingAddress()->getTelephone();
}
return $value;
}
which is working fine and it shows data properly on the basis of
condition in renderer.
But the problem is now I need to filter the data also which is not
working as it looks for data in only one column as telephone or mobile
I have read about filter_condition_callback but unable to make the work . Can you please suggest me how can I make this work.
Thanks in advance
1.Add line filter_condition_callback to column, like that:
$this->addColumn('telephone', array(
'header' => Mage::helper('sales')->__('Billing Phone'),
'index' => 'telephone',
'filter_condition_callback' => array($this, '_someCallBackFunction'),
'renderer'=> new OSP_Adminhtml_Block_Customer_Renderer_BillingPhone()
));
After you added this line in your column, Magento will call this callback function, look app/code/core/Mage/Adminhtml/Block/Widget/Grid.php in 468 line.
2.In your block grid file create _someCallBackFunction function, which will take two parameters, $collection - collection object not loaded, and $column Mage_Adminhtml_Block_Widget_Grid_Column
protected function _someCallBackFunction($collection, $column)
{
$value = $column->getFilter()->getValue();
if ($value === null) { //here check if filter is not null
return $this;
}
/**
* Here you can add filter to collection
* or do other manipulations with collection.
* As example you can check filter value and filter collection.
*/
if ($value != 0) {
$collection->addFieldToFilter('telephone', array('neq' => 0));
}
return $this;
}
Using WooCommerce 2.6.1
I cannot delete a product variation for a variable product: the record is still in the database after the ajax call.
It seems the ajax call doesn't go through: putting error_log(print_r('remove_variation', true)); doesn't output anything (line 387 in class-wc-ajax.php).
The action is added in the constructor of the class. The function public function remove_variation() is just not called.
Has anyone had the same issue, and found a way to make it work?
/**
* Trash a variation, don't delete it permanently.
*
* This is hooked to
* Hijack WooCommerce's WC_AJAX::remove_variation() "Delete Variation" Trash a variation if it is a subscription variation via ajax function
*/
public static function remove_variations() {
if ( isset( $_POST['variation_id'] ) ) { // removing single variation
error_log("here3");
check_ajax_referer( 'delete-variation', 'security' );
$variation_ids = array( $_POST['variation_id'] );
error_log($_POST['variation_id']);
} else { // removing multiple variations
error_log("here4");
check_ajax_referer( 'delete-variations', 'security' );
$variation_ids = (array) $_POST['variation_ids'];
}
foreach ( $variation_ids as $variation_id ) {
$variation_post = get_post( $variation_id );
error_log(print_r($variation_post, ));
if ( $variation_post && $variation_post->post_type == 'product_variation' ) {
$variation_product = get_product( $variation_id );
if ( $variation_product && $variation_product->is_type( 'subscription_variation' ) ) {
wp_trash_post( $variation_id );
}
}
}
die();
}
remove && $variation_product->is_type( 'subscription_variation' ) to solve the problem of un-deletable variations. http://support.woothemes.com/requests/162693 should provide a patch, issue has been reported.
Deleting the variation completely
This is for WooCommerece version 3+
(this code to be put in functions or a custom plugin: not Rest api)
Trashing the variation does not remove the swatch,
it just makes the swatch disabled.(which may be a litle bit embarasing)
If you don't want the variation in your product portfolio anymore, you should delete it.
If you have created your variation using recognizable string in the slug (SKU), you can use below code.
function delete_variation($product_cat="all",$Sku__search_string="",$force_delete=false,$document_it=false){
// 'posts_per_page' => -1: goes through all posts
if($product_cat=="all") {
$args = array(
'post_type' => 'product',
'posts_per_page' => -1,
);
} else {
$args = array(
'post_type' => 'product',
'posts_per_page' => -1,
'product_cat' => $product_cat
);
}
$query = new WP_Query($args);
while ( $query->have_posts() ) {
$query->the_post();
$post_id = get_the_ID();
$product = wc_get_product($post_id);
$product_id = $product->get_id(); //same as post_id
//if you want to see what you are doing put $document_it to true;
if($document_it) echo "<br>*********** $product_id ****************<br>";
if($Sku__search_string!="") {
$variations = $product->get_available_variations();
foreach($variations as $variation){
//get the SKU slug of the variation
$sku=$variation["sku"];
//get the variation id
$variation_id=$variation['variation_id'];
if($document_it) echo "varid $variation_id <br>";
//and then get the actual variation product
$var_product=wc_get_product($variation_id);
//Check if the search_string is in the slug
//You can modify this, or modify the $args above,
//if you have other criteria
if(stripos($sku,$Sku__search_string)>0){
$is_deleted=false;
//here the variation is deleted
$is_deleted=$var_product->delete($force_delete);
if($is_deleted){
if($document_it) echo "<br>Deleted: $sku";
} else {
if($document_it) echo "<br>Not deleted: $sku";
}
}
}
}
}
}
If you want to delete all variations you may use this
The following code is straight from the docs and should insert a row into the "test" table.
$row = &JTable::getInstance('test', 'Table');
if (!$row->bind( array('user_id'=>123, 'customer_id'=>1234) )) {
return JError::raiseWarning( 500, $row->getError() );
}
if (!$row->store()) {
JError::raiseError(500, $row->getError() );
}
My table class looks like this:
class TableTest extends JTable
{
var $user_id = null;
var $customer_id = null;
function __construct(&$db)
{
parent::__construct( '#__ipwatch', 'user_id', $db );
}
}
SELECT queries work fine, but not INSERT ones. Through debugging I find that the query being executed is UPDATE jos_test SET customer_id='1234' WHERE user_id='123', which fails because the row doesn't exist yet in the database (should INSERT instead of UPDATE).
While digging through the Joomla code I find this:
function __construct( $table, $key, &$db )
{
$this->_tbl = $table;
$this->_tbl_key = $key;
$this->_db =& $db;
}
.....
.....
function store( $updateNulls=false )
{
$k = $this->_tbl_key;
if( $this->$k)
{
$ret = $this->_db->updateObject( $this->_tbl, $this, $this->_tbl_key, $updateNulls );
}
else
{
$ret = $this->_db->insertObject( $this->_tbl, $this, $this->_tbl_key );
}
if( !$ret )
{
$this->setError(get_class( $this ).'::store failed - '.$this->_db->getErrorMsg());
return false;
}
else
{
return true;
}
}
_tbl_key here is "user_id" that I passed in my TableTest class, so it would appear that it will always call updateObject() when this key is set. Which is baffling.
Anyone have any insight?
function store( $updateNulls=false )
{
// You set user_id so this branch is executed
if( $this->$k)
{
$ret = $this->_db->updateObject( $this->_tbl, $this, $this->_tbl_key, $updateNulls );
}
// ...
Looking at the code it seems to me that store() check if the primary key field is present and if it use updateObject(). This means if you want to store a new user you can't specify the user_id.