how can I create custom complex sorting for woocommerce - sorting

the problem:
I can't make my archive-product query sort by delivery days (custom meta field) and price (WC built-in meta field)
I am a Wordpress developer, currently working on a single site.
my site is a e-commerce site with complex customizations, now of them is a meta value which is used for the delivery days parameter of the product.
this value is of high importance and therefor I need to be able to sort by it.
I add_filter in my functions.php that should sort by my custom field:
add_action( 'woocommerce_product_query', function ($query) {
//just for comparison i set posts_per_page
//this is working - i.e. affects the output query
$query->set('posts_per_page', -1);
//this is my custom field (delivery days)
$my_custom_field = '_select';
//set the meta_query
$meta_query = $query->get('meta_query');
$meta_query[] = array(
'delivery_clause' => array(
'key' => $my_custom_field,
)
);
$query->set('meta_query', $meta_query);
//set the orderby (the sort)
$orderby = $query->get('orderby');
$orderby = array(
'delivery_clause' => 'ASC'
);
$query->set('orderby', $orderby);
$dumper = $query;
var_dump($dumper);
});
the sort doesn't change.
I must add, that if I alter the code a bit, I can filter the query, for example, to show only delivery days = 1.
$meta_query = $query->get('meta_query');
$meta_query[] = array(
'key' => '_select',
'value' => 1,
'compare' => '='
);
$query->set('meta_query', $meta_query);
I have read this resources:
https://www.kathyisawesome.com/woocommerce-modifying-product-query/
https://developer.wordpress.org/reference/classes/wp_query/#order-orderby-parameters
https://developer.wordpress.org/reference/classes/wp_meta_query/
and I'm trying to figure out this WooCommerce file:
https://woocommerce.github.io/code-reference/files/woocommerce-includes-class-wc-query.html#source-view.16
also,
I use YITH Ajax filters for product filtering
https://yithemes.com/themes/plugins/yith-woocommerce-ajax-product-filter/
and I use the plugin woocommerce more sorting options
https://wordpress.org/plugins/woocommerce-more-sorting/
(which can sort by custom field, but can't sort by 2 custom fields and therefor not helping with my specific request)
I also should point out that I use WooCommerce Show Single Variations by Iconic
because my e-commerce is heavily dependent on variations
my filter is not affecting the order, and the WC class is very complex so I can't hook there. is there anyone of have done what I am asking?
help would be highly appreciated!

Related

Cake php 2: Sort on 2nd-level Association

I wrote a post last year about this. The first part, how to sort on a 1st-level Association, was answered (thanks again!) but the second part of the question, about how to sort on a 2nd-level Association, was never answered. It wasn't a big issue, and we ran out of time so I never actually implemented it. But now we're updating the web, and the client wants this functionality, namely, with the following Models:
Company belongsTo City
City belongsTo Country, hasMany Companies
Country hasMany Cities
In the Companies page I want to sort on City.Country.name. Even putting recursive=2, it doesn't work. Cake ignores my 'order' condition; in the generated SQL there simply is no 'order by' at all.
It works fine if I sort on sort on City.name, however.
Is there any way to do this? I've been scouring the docs and Stackoverflow. I've looked at virtual fields, custom queries.
One way that seemed to look promising was to use Model->query() in the CompaniesController:
$companies = $this->Company->query("SELECT * FROM companies com left join cities c on c.id = com.city_id left join countries c2 on c2.id = c.country_id order by c2.name");
But, is this the best/only way to go? And do I not have to now worry about overriding pagination? I don't mind that but I would still like to use "normal" built-in pagination elsewhere for Companies. Will that be possible?
Also, in the examples in the docs, it says to do something like Model->query('SELECT * FROM pictures AS Picture LIMIT 2') so the resulting Array will use the model name as the array key. But how can I do this with my complex query? Where would the "AS" go?
I was rather hoping I'd be able to avoid having to do it like this though. Is there a simpler way to do it?
EDIT
Hi, thanks for your help. By "pagination technique" you mean Cake's built-in pagination? Yes, that's what I want. My default paging conditions in the controller are:
$this->paginate = array('conditions' => 'order' => array('Company.name' => 'ASC');
And it sorts on company name. The SQL is
SELECT Company.id, etc. FROM companies AS Company LEFT JOIN cities AS City ON Company.city_id = City.id order by Company.name
And when I create paging links in the View like this
$paginator->sort('City.name')
it adds these parameters to the url
.../companies/sort:City.name/direction:desc
it sorts on City name. The SQL is
SELECT Company.id, etc. FROM companies AS Company LEFT JOIN cities AS City ON Company.city_id = City.id order by City.name
But when I try this:
$paginator->sort('City.Country.name');
it adds these parameters to the url
.../companies/sort:City.Country.name/direction:asc
and the generated SQL is
SELECT Company.id, etc. FROM companies LEFT JOIN cities AS City ON (Company.city_id = City.id)
It completely ignores the 'sort' condition, and there is no 'order by' at all. I'm not sure why. Maybe Cake just can't do this? I have 'recursive' set to 2.
The other option is Model->query, which I tried and got working, but I'd rather not use because I would have to override paginate and paginateCount methods, which isn't that hard, but the problem is that on the same page, and on other pages, I am already using 'normal' Cake paging for Companies. So if I override paginate and paginateCount, won't I have to change all of those pages to use the new, over-ridden paging? I'd like to avoid that, because it seems like overkill, and is working fine everywhere else, except for this one case.
Any assistance much appreciated.
Bob
Well, in the end I managed it, after perusing the docs and many examples online, like this:
First, in the Model, declare and implement custom findMethods.
class Company extends AppModel {
public $findMethods = array('companiesOrderByCountry' => true);
protected function _findCompaniesOrderByCountry($state, $query, $results = array()) {
if ($state === 'before') {
$query['joins'] = array(
array(
'table' => 'cities',
'alias' => 'City',
'type' => 'LEFT',
'conditions' => 'City.id = Company.city_id'
),
array(
'table' => 'countries',
'alias' => 'Country',
'type' => 'LEFT',
'conditions' => 'Country.id = City.country_id'
)
);
$query['order'] = array('Country.name' => 'asc');
return $query;
}
return $results;
}
Then in the Controller, conditionally call it, based on named params in request
if (!empty($this->request->params['named']['sort']) && $this->request->params['named']['sort'] == 'Country.name') {
// here we need to sort on Country.name. Call custom findMethod in Model
$this->paginate = array('companiesOrderByCountry','limit' => 10);
$companies = $this->paginate();
$this->set(compact('companies'));
} else {
// do a normal search
$this->paginate = array('limit' => 10,'conditions' => array('order' => array('Company.nombre' => 'ASC' ));
$companies = $this->paginate('Company');
$this->set('companies', $companies);
}
Lastly, create a link, passing the named parameter, in the .ctp
<?php $paginator = $this->Paginator; ?>
<?php echo $paginator->sort('Country.name', __('Country')); ?>
There's probably a more elegant way to do this, but I was sick of dealing with it. The generated sql is just what I need and now I can sort on Country, which is a 2nd-level (Company->City->Country) association.
Hope this helps someone someday!
Bob
If you want to try using the query method, then I would not use the asterisks (*). Instead explicitly list the fields you want.
Your query should look something like this: {My personal best practice, use COALESCE on fields that may be NULL - especially when using a LEFT JOIN}
SELECT companies.name AS CompanyName,
COALESCE(cities.name, 'Unknown') AS CityName,
COALESCE(countries.name, 'Unknown') AS CountryName
FROM companies
LEFT JOIN cities
ON companies.city_id = cities.id
LEFT JOIN countries
ON cities.country_id = countries.id
ORDER BY CountryName, CityName
Use AS to separate the field from its alias. It also separates a table from its alias ( e.g., the examples you listed.) AS is optional, but I feel it adds to readability.
--
(If you want to try the pagination technique, please show the MySQL SELECT statement that is failing when RECURSIVE = 2, along with what the controller's code looks like - specifically the 'order'=> array)

Custom Magento Report for Taxable/Non-Taxable sales

Let me preface by saying I'm new to Magento as well as Data Collections in general (only recently begun working with OOP/frameworks).
I've followed the excellent tutorial here and I'm familiar with Alan Storm's overviews on the subject. My aim is to create a custom Magento report which, given a start/end date, will return the following totals:
Taxable Net (SUM subtotal for orders with tax)
Non-Taxable Net (SUM subtotal for orders without tax)
*Total Gross Sales (Grand total)
*Total Net Sales (Grand subtotal)
*Total Shipping
*Total Tax
*For these figures, I realize they are available in existing separate reports or can be manually calculated from them, however the purpose of this report is to give our store owner a single page to visit and file to export to send to his accountant for tax purposes.
I have the basic report structure already in place in Adminhtml including the date range, and I'm confident I can include additional filters if needed for order status/etc. Now I just need to pull the correct Data collection and figure out how to retrieve the relevant data.
My trouble is I can't make heads or tails of how the orders data is stored, what Joins are necessary (if any), how to manipulate the data once I have it, or how they interface with the Grid I've set up. The existing tutorials on the subject that I've found are all specifically dealing with product reports, as opposed to the aggregate sales data I need.
Many thanks in advance if anyone can point me in the right direction to a resource that can help me understand how to work with Magento sales data, or offer any other insight.
I have been working on something extremely similar and I used that tutorial as my base.
Expanding Orders Join Inner
Most of the order information you need is located in sales_flat_order with relates to $this->getTable('sales/order')
This actually already exists in her code but the array is empty so you need to populate it with the fields you want, here for example is mine:
->joinInner(
array('order' => $this->getTable('sales/order')),
implode(' AND ', $orderJoinCondition),
array(
'order_id' => 'order.entity_id',
'store_id' => 'order.store_id',
'currency_code' => 'order.order_currency_code',
'state' => 'order.state',
'status' => 'order.status',
'shipping_amount' => 'order.shipping_amount',
'shipping_tax_amount' => 'order.shipping_tax_amount',
'shipping_incl_tax' => 'base_shipping_incl_tax',
'subtotal' => 'order.subtotal',
'subtotal_incl_tax' => 'order.subtotal_incl_tax',
'total_item_count' => 'order.total_item_count',
'created_at' => 'order.created_at',
'updated_at' => 'order.updated_at'
))
To find the fields just desc sales_flat_order in mysql.
Adding additional Join Left
Ok so if you want information from other tables you need to add an ->joinLeft() for example I needed the shipment tracking number:
Create the Join condition:
$shipmentJoinCondition = array(
$orderTableAliasName . '.entity_id = shipment.order_id'
);
Perform the join left:
->joinLeft(
array('shipment' => $this->getTable('sales/shipment_track')),
implode(' AND ', $shipmentJoinCondition),
array(
'track_number' => 'shipment.track_number'
)
)
Sorry I couldn't go into more depth just dropping the snippet for you here.
Performing Calculations
To modify the data returned to the grid you have to change addItem(Varien_Object $item) in your model, basically whatever is returned from here get put in the grid, and well I am not 100% sure how it works and it seems a bit magical to me.
Ok first things first $item is an object, whatever you do to this object will stay with the object (sorry terrible explanation): Example, I wanted to return each order on a separate line and for each have (1/3, 2/3, 3/3), any changes I made would happen globally to the order object so they would all show (3/3). So keep this in mind, if funky stuff starts happening use PHP Clone.
$item_array = clone $item;
So now onto your logic, you can add any key you want to the array and it will be accessible in Grid.php
For example(bad since subtotal_incl_tax exists) :
$item_array['my_taxable_net_calc'] = $item['sub_total'] + $item['tax'];
Then at the end do:
$this->_items[] = $item_array;
return $this->_items;
You can also add more rows based on the existing by just adding more data to $this->_items[];
$this->_items[] = $item_array;
$this->_items[] = $item_array;
return $this->_items;
Would return same item on two lines.
Sorry I have started to lose the plot, if something doesn't make sense just ask, hope this helped.
Oh and to add to Block/Adminhtml/namespace/Grid.php
$this->addColumn('my_taxable_net_calc', array(
'header' => Mage::helper('report')->__('Taxable Net'),
'sortable' => false,
'filter' => false,
'index' => 'my_taxable_net_calc'
));

magento add last login to customer grid

I am new to Magento. I am trying to add a last login value displaying on customer grid. it returns a null value. I have read other tutorials, but it does not help much. The Magento version is 1.7. Here is my code:
$customer = Mage::getSingleton('customer/session')->getCustomer();
$logCustomer = Mage::getModel('log/customer')->load($customer ->getId());
$lastVisited = $logCustomer->getLoginAt();
$this->addColumn('$lastVisited', array(
'header' => Mage::helper('customer')->__('Last Login'),
'type' => 'datetime',
'align' => 'center',
'index' => '$lastVisited',
'gmtoffset' => true
));
Magento stores the login time in the following table:
log_customer
But also, this data is cleaned periodically (see: Mage_Log_Model_Resource_Log::_cleanCustomers which is triggered via Magento cron).
There are different ways to approach your task.
1) Non-persistent - I am just interested to see recent data (I can ignore that log_customer is cleaned periodically)
In this case you can just rely on the data from log_customer and display it in Manage Customers Grid.
Extend Mage_Adminhtml_Block_Customer_Grid and in _prepareCollection add the following:
$collection->getSelect()->columns(array('last_login_at' => new Zend_Db_Expr ("(SELECT login_at
FROM `log_customer`
WHERE customer_id =e.entity_id
ORDER BY log_id DESC
LIMIT 1)")));
before: $this->setCollection($collection);
Note: use the proper Magento function to get log_customer table name, my query is just for example
2) Persistent - I want to always to see the data
add a new attribute to the customer entity called: last_login_at
(datetime).
add an observer to custom_login event to update this
attribute.
use addColumn function in the grid to display this new attribute.
#user1414056
Regarding your code:
bixe made a fair point related to '$lastVisited' (this just shows
lack of experience in php programming
you seem to also be new to programming (in general) because the addColumn is called only once... do how do you expect your code to make sense?
With a better understanding of Zend Framework and OOP Programming in general you will be able to actually work and get things done with Magento.
Your '$lastVisited' can't work : in php variables are evaluated in a String only when they're in a double quote.
EDIT:
Ok the column system of magento only display value when they're available in the collection linked to the grid..
You'll have to add the log information you want to display in the grid collection.
For an example, take a look at Mage_Adminhtml_Block_Customer_Online_Grid::_prepareCollection()
When it's done, you will add your column with :
$this->addColumn('login_at', array(
'header' => Mage::helper('customer')->__('Last Login'),
'type' => 'datetime',
'align' => 'center',
'index' => 'login_at',
'gmtoffset' => true
));

Magento attributes with different sorting

I am new to php, What i want is if i can define sorting order to ascending to only products that are showing by price by doing something like this in the file
Mage_Adminhtml_Model_System_Config_Source_Catalog_ListSort
$options[] = array(
'label' => Mage::helper('catalog')->__('Price'),
'value' => 'price'
'getCurrentDirection' => 'asc'
);
and rest of the attributes with descending order.
Unfortunately, doesn't seem to be working.
Can anyone help?
I think you're looking in the wrong file. The ListSort file you describe above merely lists the available options for sort by. It does nothing to the current sort.
Also, the file you referenced to is in the Adminhtml scope. If you need to change the default sort on the frontend, you should look elsewhere.
Your question, if I understand correctly, is how to sort ASC by default if price is selected for sort by, while sorting DESC by default is another attribute is used for sort by.
For the frontend, you should take a look at the getCurrentOrder() function in the Mage_Catalog_Block_Product_List_Toolbar file. Here you have both the default direction and the sort order available. It is not good practice to hack app/core/Mage files, but you could copy this file and place it in app/local/Mage/* (exact same dir as the core file) and it will automatically overload the default method.
For the backend, you could look at the _prepareCollection() function in the Mage_Adminhtml_Block_Widget file. The default sort is 'desc', so all you need to do is change it to 'asc' for price. Here too, you should make a copy in app/local/Mage/*. You could try something like this (For Magento 1.7.1.0, this is line 507-508):
change
$columnId = $this->getParam($this->getVarNameSort(), $this->_defaultSort);
$dir = $this->getParam($this->getVarNameDir(), $this->_defaultDir);
to
$columnId = $this->getParam($this->getVarNameSort(), $this->_defaultSort);
if($this->getVarNameSort() == 'price') {
$dir = $this->getParam($this->getVarNameDir(), 'asc');
} else {
$dir = $this->getParam($this->getVarNameDir(), $this->_defaultDir);
}
I hope this helps!

Yii - sort dataprovider with more than one field

I am using DataProvider to show some data. The data is related to shows in the theater. I want to show the shows which are "On Season" first and then the shows that are not on season. And all the shows should be ordered alphabetically. I tried to use CSort but I am getting an error. Here is my code:
$dataProviderFiaba = new CActiveDataProvider('Show',
array(
'criteria'=>array(
'condition'=>'show_type= '.Show::TYPE_FIABA,
),
'sort'=>array(
'defaultOrder'=>'on_season', //TO SHOW THE ON SEASON SHOWS FIRST
'asc'=>'title', // TO ORDER ALPHABETICALLY
),
));
And the error is Property "CSort.asc" is not defined. So I think I am not using CSort with the correct format. What is the right way to do this kind of sorting?
You can only use "asc" in the context of attributes for CSort. For example:
$mCSort->attributes = array('title'=>array('asc'=>'title', 'desc' => 'title DESC'));
To solve your sorting problem, the following should be sufficent though:
$dataProviderFiaba = new CActiveDataProvider('Show',
array(
'criteria'=>array(
'condition'=>'show_type= '.Show::TYPE_FIABA,
'order'=>'on_season, title'
),
));

Resources