I have to add to my cart some line items with a custom amount.
The commerce product is saved with price = 0, and my module compute the price and add the line item to the cart/order, but i dont understand how to set programmatically the price.
I've read about using Rules, but I need my module to be able to set/alter the price, without invoking rules.
I've tryed with an entity wrapper, i tryed to alter the line item created with commerce_product_line_item_new(), but nothing, when the line item gets into the cart always has the original product price (in my case, 0).
How to alter a line item price programmatically?
My code so far looks like:
// For debugging, this function is called by hook_menu()
function mymodule_test($product_id)
{
global $user;
$user = user_load($user->uid);
$order = commerce_cart_order_load($user->uid);
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$product = commerce_product_load($product_id);
$line_item = commerce_product_line_item_new(
$product,
1,
0,
array(
),
'cover'
);
$line_item_wrapper = entity_metadata_wrapper("commerce_line_item", $line_item);
$line_item_wrapper->commerce_unit_price->data = commerce_price_component_add(
$line_item_wrapper->commerce_unit_price->value(),
'base_price',
array(
'amount' => 1234,
'currency_code' => 'EUR',
'data' => array(),
),
TRUE
);
$insert_line_item = commerce_cart_product_add($user->uid, $line_item_wrapper->value(), FALSE);
return 'done';
}
The strange thing, is that I tryed to adapt the code of commerce_line_item_unit_price_amount() found in commerce/modules/line_item/commerce_line_item.rules.inc, but this test:
<?php
global $user;
$product = commerce_product_load(4); // my commerce product for test
$line_item = commerce_product_line_item_new(
$product,
1,
0,
array(
),
'cover' // I do have this line_items type
);
// manually set amount and component name
$amount = 1234;
$component_name = 'base_price'; // tryed with discount, nothing change
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$unit_price = commerce_price_wrapper_value($wrapper, 'commerce_unit_price', TRUE);
// Calculate the updated amount and create a price array representing the
// difference between it and the current amount.
$current_amount = $unit_price['amount'];
$updated_amount = commerce_round(COMMERCE_ROUND_HALF_UP, $amount);
$difference = array(
'amount' => $updated_amount - $current_amount,
'currency_code' => $unit_price['currency_code'],
'data' => array(),
);
// Set the amount of the unit price and add the difference as a component.
$wrapper->commerce_unit_price->amount = $updated_amount;
$wrapper->commerce_unit_price->data = commerce_price_component_add(
$wrapper->commerce_unit_price->value(),
$component_name,
$difference,
TRUE
);
$insert_line_item = commerce_cart_product_add($user->uid, $line_item, FALSE);
?>
still fail, the line_item get into the cart but with the original price of the referenced product.
Any idea?
For those people who don't want to use rules and hope to alter the price directly. Here is my solution:
// Alter the price in list and single product page.
function my_module_commerce_product_calculate_sell_price_line_item_alter($line_item){
$price = 100; //1 dollar
$line_item->commerce_unit_price[LANGUAGE_NONE]['0']['amount'] = $price;
}
// Alter the price in cart & order.
function my_module_commerce_cart_line_item_refresh($line_item, $order_wrapper){
$price = 100; //1 dollar
$line_item->commerce_unit_price[LANGUAGE_NONE]['0']['amount'] = $price;
// Alter the base_price component.
$line_item->commerce_unit_price[LANGUAGE_NONE]['0']['data']['components']['0']['price']['amount'] = $price;
}
If you're looking to ignore whatever previous values have been saved to a line item and recalculate the total from your new amount the function you're looking for is commerce_line_item_rebase_unit_price.
Set the new amount value and then run your line item through there, save the line item and the order:
$line_item_wrapper->commerce_unit_price->amount = 13;
commerce_line_item_rebase_unit_price($line_item_wrapper->value());
commerce_line_item_save($line_item_wrapper->value());
I struggled through this issue all day today and final figured out the correct path to altering line items prices. The problem is that, even if you successfully change the line item price to a custom value, on the next page refresh the cart will reset the line items to match the original product price. Take a look at the commerce_cart_order_refresh() function for details. This function is executed every time an order/cart is loaded on the page and there is no way around it.
It turns out that the proper way to alter a line item price is to either use Rules or to implement the hook_commerce_cart_line_item_refresh() function. Either way, Drupal Commerce need to be able to apply the alteration logic each time the cart/order is loaded.
I ended up creating a custom field in the Line Item where I stored the custom price value I wanted. I then used a Pricing Rule to copy the custom price value to the product price value whenever the cart is refreshed.
The following blog post was very helpful in figuring this out. It shows you how to add a custom field to a line item type and how to setup a pricing rule to copy the custom amount to the unit price.
http://commerceguys.com/blog/using-custom-line-items-provide-donation-feature-drupal-commerce
Recently I had to implement a donation form in Commerce but the Commerce Express Checkout module doesn't handle custom line items. Since it was a donation and all (who is trying to screw the house?), I felt it appropriate to pass the donation amount as a 3rd parameter in URL the Express Checkout module provides. Here is how I went about hacking the module:
I added a new entry to the router:
$items['commerce-express-checkout/%/%/%'] = array(
'title' => 'Express Checkout w/ extra argument',
// 'page callback' => 'commerce_express_checkout_create_order',
'page callback' => 'commerce_express_checkout_create_order_extra',
'page arguments' => array(1, 2, 3),
'access arguments' => array('access checkout'),
'type' => MENU_CALLBACK,
);
I duplicated and tweaked the default callback and tacked '_extra' to it. Note that the "data" property seems to be a static variable store for occasions just such as this and persists the life of the line item.
function commerce_express_checkout_create_order_extra($product_id, $token, $amount) {
if (drupal_hmac_base64($product_id, drupal_get_private_key().drupal_get_hash_salt()) == $token && is_numeric($amount)) {
global $user;
$product = commerce_product_load($product_id);
$product->commerce_price['und'][0]['amount'] = (int)$amount;
$order = ($user->uid) ? commerce_order_new($user->uid, 'checkout_checkout') : commerce_cart_order_new();
commerce_order_save($order);
$price = array('amount' => commerce_round(COMMERCE_ROUND_HALF_UP, $amount), 'currency_code' => commerce_default_currency());
$line_item = commerce_product_line_item_new($product, 1, $order->order_id);
$line_item->data = array('und' => array('0' => $price));
commerce_line_item_save($line_item);
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
$order_wrapper->commerce_line_items[] = $line_item;
$order->data['type'] = 'commerce_express_checkout_order';
commerce_order_save($order);
drupal_goto('checkout/' . $order->order_id);
return "";
}
return "";
}
Here is the part that ended up being most tricky simply due to the learning curve and not knowing what the heck function to use:
/**
* Implements hook_commerce_cart_line_item_refresh().
*/
function commerce_express_checkout_commerce_cart_line_item_refresh($line_item, $order_wrapper) {
if ($line_item->commerce_product['und'][0]['line_item_label'] == 'DONATE' || $line_item->commerce_product['und'][0]['product_id'] == '11') {
$price = array('amount' => commerce_round(COMMERCE_ROUND_HALF_UP, $line_item->data['und'][0]['amount']), 'currency_code' => commerce_default_currency());
$line_item->commerce_unit_price = array('und' => array('0' => $price));
$line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$line_item_wrapper->commerce_unit_price->data = commerce_price_component_add(
$line_item_wrapper->commerce_unit_price->value(), 'base_price', $price, TRUE
);
}
}
Every single time the cart is modified, it refreshes and attempts to set the products in the cart to their in-code prototype. It seems pretty inefficient to me too, but I could be missing something.
This post pointed me in the right direction for programmatically altering a drupal commerce line item by using hook_commerce_cart_line_item_refersh(). However, some of the answers here are either outright wrong, or very inefficient and sloppy.
This would be a correct working solution for altering the line item type in Drupal Commerce:
/*
* implements hook_commerce_cart_line_item_refresh()
*
*/
function MYMODULE_commerce_cart_line_item_refresh($line_item, $order_wrapper){
$line_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
$new_price = 100; //I use a function to calculate the value of $new_price
if(!empty($new_price)){
$line_wrapper->commerce_unit_price->amount->set($new_price);
$line_wrapper->save();
}
}
Related
I'm working on a property feed importer for a website I'm having some trouble getting the Taxonomy to synchronize correctly.
This is the code to add the translations
foreach($languages as $lang => $data){
$translation = 0;
$trans_arr = array(
'post_author'=>$author,
'post_content'=>$property->get_description($lang),
'post_title'=>$property->get_title($lang),
'post_status'=>'importing', //Don't show the property while its still importing
'post_type'=>'property',
'comment_status'=>'closed',
'ping_status'=>'closed',
'post_date'=>$property->date, //Use the property date to keep track of changes
'meta_input'=>array("kyero_data"=>json_encode(array('feed' => $feed_token,"kyeor_id" => $property->ref, "lang"=>$lang ))),
);
// Check if the translation already exists so we update it instead
$_translation = icl_object_id( $post, 'property', false, $lang );
if($_translation){
$trans_arr['ID'] = $_translation;
}
// Create/update the translation
$translation = wp_insert_post($trans_arr);
// If successfull set the correct language information of the post.
if(is_int($translation) && $translation !== 0){
wp_set_post_terms( $translation, $tax['property_feature'], 'property_feature', false );
wp_set_post_terms( $translation, $tax['property_status'], 'property_status', false );
$sitepress->set_element_language_details($translation, 'post_property', $def_trid, $lang);
set_post_thumbnail($translation, $feat);
wp_publish_post($translation);
}
}
$languages is an array with all the languages (keys are the language codes) except for the default language.
$tax['property_feature'] and $tax['property_status'] contain a list of IDs are filled by the feed and are only available in the default language inside the feed (ID are fetched by get_term_by using the name) however they are translated in the website.
Please can some one help me out, is there a way to synchronize the the taxonomy of a single WP_Post object with its parent programmatically
I found a solution and taught I mention it here in case some one else may need it at some point.
Instead of using the wpml class oject to set the language $sitepress->set_element_language_details using the hook wpml_set_element_language_details
foreach($languages as $lang => $data){
$translation = 0;
$trans_arr = array(
'post_author'=>$author,
'post_content'=>$property->get_description($lang),
'post_title'=>$property->get_title($lang),
'post_status'=>'importing', //Don't show the property while its still importing
'post_type'=>'property',
'comment_status'=>'closed',
'ping_status'=>'closed',
'post_date'=>$property->date, //Use the property date to keep track of changes
'meta_input'=>array("kyero_data"=>json_encode(array('feed' => $feed_token,"kyeor_id" => $property->ref, "lang"=>$lang ))),
);
// Check if the translation already exists so we update it instead
$_translation = icl_object_id( $post, 'property', false, $lang );
if($_translation){
$trans_arr['ID'] = $_translation;
}
// Create/update the translation
$translation = wp_insert_post($trans_arr);
// If successfull set the correct language information of the post.
if(is_int($translation) && $translation !== 0){
do_action('wpml_set_element_language_details',array('element_id'=>$translation, 'element_type'=>'post_property', 'trid'=>$def_trid, 'language_code'=>$lang, 'source_language_code'=>$default_lang));
set_post_thumbnail($translation, $feat);
wp_publish_post($translation);
}
}
i want to change or modify all taxonomy pages i write this piece of code in template.php in my bartik theme but some weird things happens.
the $term argument in second function "bartik_term_page" is null.
what is missing?
and it sometimes an error like this
it gives an error.
Warning: Parameter 1 to bartik_term_page() expected to be a reference, value given in menu_execute_active_handler() (line path\includes\menu.inc).
function bartik_menu_alter(&$menu) {
$menu['taxonomy/term/%']['page callback'] = 'bartik_term_page';
$menu['taxonomy/term/%']['access arguments'] = array('access content');
}
function bartik_term_page(&$term){
$voc = taxonomy_vocabulary_load($term->vid);
var_dump( $term ); die();
// here you generate the actual content of the page
// could be done e.g. with an entityfieldquery as follows
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
->fieldCondition('field_category', 'tid', $term->tid);
$result = $query->execute();
if (!empty($result['node'])) {
$build['content']['nodes'] = node_view_multiple(node_load_multiple(array_keys($result['node'])), 'teaser'); // output the node teasers. you can control the markup of the 'teaser' view mode with a template in the theme folder
} else {
$build['content']['status']['#markup'] = t('No results found for term ID !tid.', array('!tid' => $term->tid));
}
return $build;
}
You're actually introducing a new router item there instead of overriding the existing one.
The path for the taxonomy page is taxonomy/term/%taxonomy_term, so...
function bartik_menu_alter(&$menu) {
$menu['taxonomy/term/%taxonomy_term']['page callback'] = 'bartik_term_page';
$menu['taxonomy/term/%taxonomy_term']['access arguments'] = array('access content');
}
Clear caches and you should be good to go.
Add the following line after the 'page callback' line in your bartik_menu_alter() function:
$menu['taxonomy/term/%']['page arguments'] = array(3);
This tells Drupal where to find the parameter in your URL (it's the third component).
I've been stuck trying to apply a Drupal commerce coupon for about 2 days now. I have taken care of validating the coupon and currently stumped when I'm trying to redeem it.
So inside my callback function I'm calling:
my_module_coupons_coupon_redeem($coupon);
And inside the redeem function I have:
function my_module_coupons_coupon_redeem($coupon) {
global $user;
$uid = $user->uid;
$order = commerce_cart_order_load($uid);
// Wrap the order for easy access to field data.
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
// Create the new line item.
$line_item = commerce_coupon_line_item_new($coupon, $order->order_id);
$line_item->commerce_unit_price = array('und' => array(
'0' => array('amount' => 500, 'currency_code' => commerce_default_currency())
));
$line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
if (!is_null($line_item_wrapper->commerce_unit_price->value())) {
// Add the base price to the components array.
if (!commerce_price_component_load($line_item_wrapper->commerce_unit_price->value(), 'base_price')) {
$line_item_wrapper->commerce_unit_price->data = commerce_price_component_add(
$line_item_wrapper->commerce_unit_price->value(),
'base_price',
$line_item_wrapper->commerce_unit_price->value(),
TRUE
);
}
}
$line_item_wrapper->commerce_total->data = $line_item_wrapper->commerce_unit_price->data;
//$line_item_wrapper->commerce_product->data = $coupon;
// Save the line item now so we get its ID.
commerce_line_item_save($line_item);
// Add it to the order's line item reference value.
$order_wrapper->commerce_line_items[] = $line_item;
commerce_order_save($order);
}
The coupon line_item is being saved on the DB but when I refresh the cart page I get the following error:
EntityMetadataWrapperException: Unknown data property commerce_product. in EntityStructureWrapper->getPropertyInfo()
Just wondering if this is the correct way of applying coupons without the use of Rules and should I be saving it as a line item in the first place?
Take a look in sites/all/modules/commerce_coupon/oncludes/commerce_coupon.checkout_pane.inc
global $user;
$uid = $user->uid;
$error = '';
// load the cart
$order = commerce_cart_order_load($uid);
// load the coupon from the coupon code
// see MySQL -> your-schema, table: commerce_coupon, column: code
$coupon = commerce_coupon_redeem_coupon_code($code, $order, $error);
// 302 to the cart
drupal_goto('checkout/' . $order->order_id);
I realized this was posted a few years ago, but there was no working answer and I still couldn't find a complete solution for it anywhere.
After you've added the line item you still need to add the commerce coupon reference to the order like so:
$order_wrapper->commerce_coupon_order_reference[] = $coupon;
This way it won't get removed when commerce_coupon_commerce_cart_order_refresh() runs.
I have couple hundreds of articles on a website. I use a view to show excerpts and links to them on one page and I also have a pager that show 10 articles at the time. I need to add a dropdown with years [...,2008,2009...,2013]. If you select a year in the dropdown, then view should only display articles that were posted in that year. Years in the dropdown should be updated automatically if a new article added in 2013 for example, and thus the first year is the year of the first publication.
Please suggest possible solutions.
I think, you need to set exposed filter to the view list. The configuration should be something like--
Filter criteria: Date (node);
Form element: Select;
Filter granularity: Year;
Date Fields: Post Date; Expose;
Filter type to expose: Single;
Operator: equal to;
Inform me if it works..
Here is a way to do it if you want to use Drupal's built in "Authored on" field for a node. You'll need to create a custom module. This was created in Drupal 7 / Views 3. My module files are in /sites/all/modules/custom/ViewsYearFilter/.
ViewsYearFilter.info
name = Views Year Filter
description = Allow a view to filter by post year.
package = tools
core = 7.x
files[] = ViewsYearFilter.inc
files[] = ViewsYearFilter.views.inc
ViewsYearFilter.module
<?php
/**
* Implements of hook_views_api().
*/
function ViewsYearFilter_views_api() {
return array('api' => 3);
}
ViewsYearFilter.views.inc
<?php
/**
* Implements of hook_views_data().
*/
function ViewsYearFilter_views_data() {
return array(
'node' => array(
'published_year' => array(
'group' => t('Content'),
'title' => t('Year'),
'help' => t('Filter by years.'),
'filter' => array('handler' => 'ViewsYearFilter'),
)
)
);
}
ViewsYearFilter.inc
<?php
/* Allows filtering of posts by year in a Drupal View. */
class ViewsYearFilter extends views_handler_filter_in_operator {
/**
* Override parent get_value_options() function. This function returns an array of all valid years from our post type.
* #return
* Return the stored values in $this->value_options if someone expects it.
*/
function get_value_options() {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'node')
->propertyCondition('type', 'blog_post') //post type
->propertyCondition('status', '1'); //is published
$results = $query->execute();
$object_node_ids = array_keys($results['node']);
$objects = node_load_multiple($object_node_ids);
foreach ($objects as $blog_post) {
$values[date('Y', $blog_post->created)] = date('Y', $blog_post->created);
}
$this->value_options = $values;
return $values; //array of year values
}
function query() {
$this->ensure_my_table(); //not sure why/if this is necessary
$startDate = mktime(0, 0, 0, 1, 1, intval($this->value[0]));
$endDate = mktime(0, 0, 0, 1, 1, intval($this->value[0] + 1));
$this->query->add_where_expression($this->options['group'], "node.created >= " . $startDate . " AND node.created <= " . $endDate); //filtering query
}
}
Then, in your view config page you can create a new exposed filter:
Sorry, but I can't actually post this image as I don't have enough reputation. There will be a new exposed filter named "Content: Year" you can add to your view after creating and activating your new module.
PS: I based my code off of Shevchuk's answer on this question.
I created the following token; however, when I try to use site:coupons as a data selector in a loop action
It does not appear in data selection browser. Note that it does appear as replacement pattern when i use for example "Show a message on the site" action.
I spent lot of time searching in the internet and rules' token' issue queue, i tried to read the source codes of core token , token and rules as well. I also found some information too like data selector are no tokens! or rules only works with entities!
So far i couldn't get this to work no matter hard i tried. My data is not entity. Is there anyway to integrate it with rules?
I couldn't find any official documentation on this so i created an issue with hope that some of the rule's experts can help me out.
Note : if i replace site with coupon-link in the following code, it won't even appear as replacement pattern in rules. but it works fine as token anywhere else but in rules
Thanks in advance
<?php
/**
* Implements hook_token_info().
*/
function coupon_link_token_info() {
$types['coupon-link'] = array(
'name' => t("Coupon link coupon info"),
'description' => t("Info about linked coupon via url."),
);
// Andy Pangus specific tokens.
$tokens['site']['coupon-code'] = array(
'name' => t("Coupon Link Coupon Code"),
'description' => t("The code of the coupon entered via url."),
);
$tokens['site']['coupon'] = array(
'name' => t("Coupon Link Coupon"),
'description' => t("The coupon entered via url."),
'type' => 'commerce_coupon'
);
$tokens['site']['coupons'] = array(
'name' => t("Coupon Link List Coupons"),
'description' => t("The coupons entered via url."),
'type' => 'array'
);
return array(
'types' => $types,
'tokens' => $tokens,
);
}
/**
* Implements hook_tokens().
*
* #ingroup token_example
*/
function coupon_link_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
$sanitize = !empty($options['sanitize']);
// Text format tokens.
if ($type == 'site' && __coupon_link_get_coupon_code()) {
//$format = $data['format'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'coupon-code':
// Since {filter_format}.format is an integer and not user-entered
// text, it does not need to ever be sanitized.
$replacements[$original] = $sanitize ? filter_xss(__coupon_link_get_coupon_code()) : __coupon_link_get_coupon_code();
break;
case 'coupon':
// Since the format name is user-entered text, santize when requested.
$replacements[$original] = __coupon_link_get_coupon(__coupon_link_get_coupon_code());
break;
case 'coupons':
// Since the format name is user-entered text, santize when requested.
$replacements[$original] = array(__coupon_link_get_coupon(__coupon_link_get_coupon_code()));
break;
}
}
}
return $replacements;
}
?>
A few things.
Tokens are formatted as [type:token] as explained on the hook_token_info api page. For your example, it would be [coupon-link:coupon]. I'm not sure why you're appending your tokens to the site array, as your custom coupon token probably has nothing to do with sitewide tokens like *site_url* or *site_name*.
Because the types are machine names, you should change it to coupon_link as machine names with dashes are not Drupal standard.
If you truly get lost, I suggest also looking at the token example from the examples module.