Magento Sort Attribute by Decimal not Alphanumerically - php

So I've Googled like crazy to try and find a solution to this problem that actually works properly but have come up empty handed.
When using the Sort By function on a category page to sort products by an attribute (capacity ,weight etc). Magento sorts like this because it thinks the number is a text string:
Product A: 10kg
Product B: 11kg
Product C: 15kg
Product D: 9kg
whereas it should sort like:
Product D: 9kg
Product A: 10kg
Product B: 11kg
Product C: 15kg
Looking around it seems like people suggest to change backend_type to decimal and frontend_input to price in the eav_attribute table for attributes that you'd like to sort numerically. However, not only does this not seem to actually work, but it changes the format of the number to have a dollar ($) symbol in front of it and because we display the actual attribute value on the product page, on top of using it to sort by, we doesn't work as a fix.
I'm trying to figure out exactly how the getSortOrder() method works but it looks like this functionality is pretty deeply embedded so I'm struggling to figure a workaround for this bug.
Any help is appreciated!
EDIT:
For anyone looking to solve this issue in the future, here's the fix I came up with:
You'll need to override the function _getProductCollection() in List.php that is stored in app/Code/Mage/core/catalog/block/product/list.php.
Copy the file to app/code/Mage/local/catalog/block/product/list.php so that you aren't editing core files.
Then below where it says:
$this->_productCollection = $layer->getProductCollection();
Put the following code:
// Start of Code to force Magento to numerically sort decimal attributes rather than alphabetically
$filterAttribute = $this->getRequest()->getParam('order');
$filterAttributeDir = $this->getRequest()->getParam('dir');
$attributeType = Mage::getModel('eav/entity_attribute')->loadByCode('catalog_product', $filterAttribute)->getFrontendClass();
// If a Sort By option is selected on category page and attribute has frontend_class = validate-number or validate-digits
// then CAST the attribute values as signed integers
if (isset($filterAttribute) && ($attributeType == 'validate-digits' || $attributeType == 'validate-number')) {
$this->_productCollection->getSelect()->reset(Zend_Db_Select::ORDER);
$this->_productCollection->getSelect()->order('CAST(`' . $filterAttribute . '` AS SIGNED) ' . $filterAttributeDir . "'");
}
// End of code to force Magento to numerically sort....
Now if you have Input Validation for Store Owner in the admin panel for the attribute set to Decimal Number or Integer Number:
Then this code will reset the sort order on a product collection and then CAST it as a signed integer so that it sorts numerically rather than alphanumerically.
Hope that helps someone!

So I found a thread on this on their documentation, and apparently it's a known pain point for the product. The best solution I found was to override the ORDER BY for the query by calling a primitive method of the Collection class, here's the example they give:
$_productCollection = Mage::getModel('catalog/product')->getCollection();
$_productCollection->setOrder(array('cm_brand', 'name', 'cm_length'), 'asc');
$_productCollection->getSelect()->reset(Zend_Db_Select::ORDER);
$_productCollection->getSelect()->order(array('cm_brand ASC', 'name ASC', 'CAST(`cm_length` AS SIGNED) ASC'));
Based on your example with only one sorting column, I would think you could go with:
$_productCollection = Mage::getModel('catalog/product')->getCollection();
$_productCollection->setOrder('weight', 'asc');
$_productCollection->getSelect()->reset(Zend_Db_Select::ORDER);
$_productCollection->getSelect()->order('CAST(`weight` AS SIGNED) ASC'));

In response to Adam B's post.
My situation: I needed to sort products by a custom Magento (1.8.1.0) attribute with a numerical value. However, Magento was sorting like:
1 , 11 , 123 ,2 ,234, 3 (alphanumerical)
instead of: 1, 2, 3, 11, 123, 234 (numerical)
I honestly don't get why this is not working out of the box but I got this working with the following adaption:
$this->_productCollection->getSelect()->order(new Zend_Db_Expr("CAST( ".$filterAttribute." AS SIGNED ) ".$filterAttributeDir));
Hope it's to any use for someone.

Can you tell us the 'Catalog Input Type for Store Owner'? Eg is it text or drop down? I mean, do you have a 'position' column in the Magento Admin (Catalog->Attributes->Manage attributes->Manage Label / Options->Manage Options (values of your attribute)). If your attribute is text, is the actual value the string '9kg' or is it '9' with 'kg' added later?
Anyway, if you can set a position in the attribute editor then I think getSortOrder() will help you (confirm this and someone can post an answer about getSortOrder()).
If you cannot set a position (because the values are not pre-determined) then I think you will need to create the product sort order yourself* by perhaps stripping out the letters from the attribute values with a preg_replace() or str_replace(), sorting on the result of that and then passing the new sorted product array into the display loop - see 'rough pseudo code' in this answer.
*because I don't think any built-in generic sorting functions can tell the string '9kg' is less than teh string '11kg' or '3V' is less than '18V'
**EDIT following comment:
Ah, yes, and I see Magento does not have a numeric input type for Catalog Input Type for Store Owner. I think you should code your own sorting in the .phtml or the associated block php class (or if you know the weights in advance make it a dropdown eg 1,2,3,4... or 1.1, 1.2, 1.3...4.1, 4.2, 4.3...19.9) then you can tell Magento the position.
You might get away with the Date input type or even the Fixed product tax input type but I reckon that is asking for trouble (and a lot of testing).
Plan B might be to add some XML that defines a new numeric input type for Catalog Input Type for Store Owner but I'd leave that to the core Magento developers - maybe they have a good reason for storing attribute values as strings.
If you are using the system attribute Weight then that is fixed as input type text and I think you have no choice other than coding your own sort.
I just found
//file: app/code/core/Mage/Core/Model/Locale.php
//class: Mage_Core_Model_Locale
//...
/*
* #param string|float|int $value
* #return float|null
*/
public function getNumber($value){
//...
Which could be useful. I don't think it will take you long to code your own sort.

Regarding the "Attribute with Numerical Value" situation above. I had the same problem and what I did was adding ceros "0" in front of my values.
For ex, my values were: 208, 209, 355, 1152 and 1153.
I added ceros: 00208, 00209, 00355, 01152 and 01153.
It works now!
This post also helped me:
http://blog.adin.pro/2014-04-30/magento-custom-sort-on-grid-sort-increment_id-by-numeric-not-alpha/
Hope this help!

Sorry, I'm a little late to the discussion.
As I'm adverse to implementing code solutions when it isn't strictly necessary, I tried to think of an easier solution. I came up with the following.
Example problem sorting
Original sorting order: 10.0", 10.5", 14.0", 8.0", 8.5"
Given that the list of numbers is sorted alphanumerically, I deduced that adding offsetting space characters (" ") before the 8s in my example above should result in the correct ordering. It did. This was the result.
Example correct sorting
New sorting order: 8.0", 8.5", 10.0", 10.5", 14.0"
In the OP, Adam might have simple been able to replace "9kg" with " 9kg".
By extension, if the numbers in question range from ones values to hundreds values, the ones values would have 2 leading spaces, the tens values would have 1 leading space, etc.

For default sort order:
// Start of Code to force Magento to numerically sort decimal attributes rather than alphabetically
$filterAttribute = Mage::getBlockSingleton('catalog/product_list_toolbar')->getCurrentOrder();
//$filterAttribute = $this->getRequest()->getParam('order');
$filterAttributeDir = Mage::getBlockSingleton('catalog/product_list_toolbar')->getCurrentDirection();
//$filterAttributeDir = $this->getRequest()->getParam('dir');

Related

array_multisort with unexpected output

I have a function which has to handle a response from a SQL query. I have to reformat it into an array, reorder and return the new array. This is how I configure the array I want to sort
foreach($response as $key => $codigocentro ){
$centro = Centros::find($codigocentro);//another query using Phormium (this works fine)
$centro[$key]['da']= $centro->da;
$centrosAsignados[$key]['descrip'] = $centro->descrip;
$centrosAsignados[$key]['codigo'] =$codigocentro;
if (condition){
$centrosAsignados[$key]['orden_cs_cl'] = $centro->descrip;
$centrosAsignados[$key]['descrip_cabecera'] =$codigocentro;
}
}
After that I prepare the multiple filter as shown on the manual https://www.php.net/manual/en/function.array-multisort.php (example 3)
$array_da = array_column($centrosAsignados, 'da');
$array_cabec = array_column($centrosAsignados, 'descrip_cabecera');
$array_cs_cl = array_column($centrosAsignados, 'orden_cs_cl');
$array_descr = array_column($centrosAsignados, 'descrip');
For a final step:
array_multisort($centrosAsignados,
$array_da, SORT_DESC,
$array_cabec,
$array_cs_cl );
Now, I seem to have trouble ONLY with $array_cabec since the "da" or the "cs_cl" filters works as expected. The output respects those 2 successful conditions in the order I configured them.
I have outputted the results in many ways before and after, and I've closed the circle to my problem in this particular scenario (Output for these dumps in images below)
var_dump($array_cabec ); // to output initial values on this particular array
array_multisort($centrosAsignados, $array_cabec)
$array_cabec2 = array_column($centrosAsignados, 'descrip_cabecera');// reassigning values after sorting
var_dump($array_cabec2 ); // to output final values
I've already checked similar problems like
array_multisort not working properly?
Sort an array with special characters in PHP
How to sort an array of UTF-8 strings?
I've removed the entries with possible special characters and tried several encoding solutions like mb_convert_encoding, utf8_encode, utf8_encode+decode, iconv, trim , etc. With no results (at most I lose the special characters, but no difference in sorting)
The closest I got is the following
asort($array_cabec);
Which actually sorted the fields I need, but is not what I wanted.
I want the filters in array_multisort to apply one over another in a specific order.
I will show you the actual output (I'm using postman) of what I'm receiving at this moment:
Before the sorting:
After the sorting:
I'm expecting all the "C.S. ALGETE" to go in first place (C comes before D so DA_NORTE shouldn't go first). I don't know if I'm applying a right filter, if the flags I've tried should go in a different order or if there is another native function in php for achieving what I need.
For environment information I'm using php7 on windows and the DB where data comes from is coming from Informix database. If more information is needed please notice me in the comments. Information is reduced to ease reading, for example in the images where I haven't displayed all the 81 fields.
Please make sure it addresses my problem if you think this question is suitable for marking as duplicate.
After way too more research than expected i solved it with using USORT instead of array_multisort which I still think it has bugs when using strings. Which strings? -I still don't know (comment please if you do).
I dont need anymore the headers array. Usort plus annonymus functions with callbacks works perfect. (as it did with the asort) but i didnt figure out it could work with multidimensional.
usort($centrosAsignados, function ($a, $b){
$c = $a['da'] - $b['da'];
$c .= strcmp( $a['descrip_cabecera'], $b['descrip_cabecera']);
$c .= $b['orden_cs_cl'] - $a['orden_cs_cl'];
$c .= strcmp( $a['descrip'], $b['descrip']);
return $c;
} );
EDIT: You can trade $a with $b to get ascending or descending order for any of the applied filters. For example:
$a['da'] - $b['da']; => $b['da'] - $a['da'];
Collateral credits to sleipnir214 in
https://www.tek-tips.com/viewthread.cfm?qid=690382
and Nsisodia91 from user contribution in php manual (this answer is a bit below)
https://www.php.net/manual/en/function.usort.php

OpenTBS multiply variable with other variable inside table

I am trying to use the following functionality:
[quote_elements.product.factor;ope=mul:quote_elements.qty]
But all I get is always 0.
if I use:
[quote_elements.product.factor;ope=mul:4]
it works fine and I get 4 times the factor number.
But this is not what I need. I need to multiply dynamically the factor with the quantity. this can be for each row different.
any tips what I am missing here?
Embedded TBS fields does not work in parameter ope.
That why the string « quote_elements.qty » is always converted to 0.
Parameter ope=mul can work only with fixed values.
In order to solve you problem, you can use a custom ondata function. It will enable you to add a calculated column in you record before to merge it.
PHP side:
function f_my_ondata($BlockName, &$CurrRec, $RecNum) {
$CurrRec['my_result'] = $CurrRec['product']['factore'] * $CurrRec['qty'];
}
Template side :
[quote_elements;block=...;ondata=f_my_ondata] // block definition
...
[quote_elements.my_result]

Wordpress - Passing variable to function - how to correctly pass list of numbers

I am trying to restrict content within a Wordpress template file and am using a plugin called Paid Memberships Pro to do so.
The code below restricts content to members with 'levels' of 1 or 2.
if(pmPro_hasMembershipLevel(array(1,2))){
restricted content goes here
}
The problem comes when I try to use a variable to provide the levels. These levels are held in a custom field group 'restrictions' with field name 'pmpro_id'. I access these levels within the template using...
foreach($restrictions as $restriction){
$temp=get_field('pmpro_id', $restriction->ID );
$temp_array[]=$temp;
}
$levels=implode(',', $temp_array);
If I then pass $levels to pmPro_hasMembershipLevel, this works fine if there is only one level but fails if there are 2 or more. I believe this is because the variable type is then a string rather than integer? I had previously tried to pass the $temp_array directly though I felt this wouldn't work and was correct.
I realise this is probably PHP 101. I have searched but don't really know what I'm looking for to be honest! I am not a developer and this is the last thing holding me back from finishing this project so ANY help anyone could provide would be brilliant. Thanks in advance.
You don't need to implode $temp_array if pmPro_hasMembershipLevel accepts array as its argument. When you implode an array you get string as a return value — that's not what you want here. If you think that the issue might be with the type of values, then you can try to cast them to integers, like this $temp_array[]= (int) $temp;

Assign array value to a field

I am working with migration and I am migrating taxonomy terms that the document has been tagged with. The terms are in the document are separated by commas. so far I have managed to separate each term and place it into an array like so:
public function prepareRow($row) {
$terms = explode(",", $row->np_tax_terms);
foreach ($terms as $key => $value) {
$terms[$key] = trim($value);
}
var_dump($terms);
exit;
}
This gives me the following result when I dump it in the terminal:
array(2) {
[0]=>
string(7) "Smoking"
[1]=>
string(23) "Not Smoking"
}
Now I have two fields field_one and field_two and I want to place the value 0 of the array into field_one and value 1 into field_two
e.g
field_one=[0]$terms;
I know this isn't correct and I'm not sure how to do this part. Any suggestions on how to do this please?
If you are only looking to store the string value of the taxonomy term into a different field of a node, then the following code should do the trick:
$node->field_one['und'][0]['value'] = $terms[0];
$node->field_two['und'][0]['value'] = $terms[1];
node_save($node);
Note you will need to load the node first, if you need help with that, comment here and will update my answer.
You are asking specifically about ArrayList and HashMap, but I think to fully understand what is going on you have to understand the Collections framework. So an ArrayList implements the List interface and a HashMap implements the Map interface.
List:
An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.
Map:
An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.
So as other answers have discussed, the list interface (ArrayList) is an ordered collection of objects that you access using an index, much like an array (well in the case of ArrayList, as the name suggests, it is just an array in the background, but a lot of the details of dealing with the array are handled for you). You would use an ArrayList when you want to keep things in sorted order (the order they are added, or indeed the position within the list that you specify when you add the object).
A Map on the other hand takes one object and uses that as a key (index) to another object (the value). So lets say you have objects which have unique IDs, and you know you are going to want to access these objects by ID at some point, the Map will make this very easy on you (and quicker/more efficient). The HashMap implementation uses the hash value of the key object to locate where it is stored, so there is no guarentee of the order of the values anymore.
You might like to try:
list($field_one, $field_two) = prepareRow($row);
The list function maps entries in an array (in order) to the variables passed by reference.
This is a little fragile, but should work so long as you know you'll have at least two items in your prepareRow result.

PHP Arrays, ordered or not?

I have an Array of product types in PHP, it looks pretty much like this:
$types = [
0 => "cars",
1 => "motorbikes",
2 => "boats",
3 => "airplanes"
];
So that when then user wants to get or save it, I can use the ID of the category to insert or get it from the database, like this:
select * from items where type = 0;
now, My doubt is the following: if I am using integer index keys, would it not be the same to use the following?
$types = ["cars", "motorbikes", "etc."];
Since PHP will give an integer auto-ordering.
Which one would you consider to be best practice in this case? The advantage of the first example seems to only be the fact that I can assign different keys, like for private categories or such, but I don't know really.
It is also important to consider that the values will need to be translated so, should I consider even just using IDs? like
$types = ["t1", "t2", "etc."];
and then insert the translation somewhere else?
There are 2 possible options.
Your current option 1 with manually set indexes.
A way better one - a table in the DB, holding these categories, which allow editing categories without losing the association between keys and names.
Your current option 2 is not an option at all - it will break the order the same moment you insert or delete a category
Here you set the index:
$types = [
3 => "airplanes"
index ^ ^ value
So, if you delete cars from the list, the index remain associated with it's value:
$types = [
1 => "motorbikes",
2 => "boats",
3 => "airplanes"
];
While if you don't set indexes manually
$types = ["motorbikes", "boats", "airplanes"];
airplanes will lose it's association, and become boats. It's not ht magic we expect from the web application
It all depends on the project. If I'm working with an array of items from the database I like the key to be the id from the database so it's available should I need it. If there's no need for the ids then there is no need to define a key. It will start at 0 anyways.
I think your first approach is a good one. You could even do it as default translation:
$types["en"] = []
Of course if you have a default language variable, you can use it from the start, and it will be much easier in the future to translate it further and expand
The arrays will be seen as the same so you can order it and take key as ID but it's not the best practice, all depends on how you design your relationship between arrays, php and database.
I might be missing your question but the array in PHP is ordered hash map basically. So ["cars", "motorcycles", "etc."] is guaranteed to preserver ordering (and have keys 0, 1, 2). But you might instead consider do the following
class AutoTypes {
const TYPE_CAR = 0;
const TYPE_MOTORCYCLE = 1;
const TYPE_BOAT = 2;
public static function getLabels() {
return array(self::TYPE_CAR => 'Car', self::TYPE_MOTORCYCLE => 'Motorcycle', self::TYPE_BOAT => 'Boat');
}
}
This way you can refer to the auto type by AutoTypes::CONSTANT. Because if you want to remove motorcycle at some point, not using clear indexes in the array, removing an item will brake your logic.
In my opinion, in this circumstance it's better to use
$types = ["cars", "motorbikes", "etc."];
As you say, PHP automatically assigns integer indexes starting from 0; so there's no need to specify the index that it would be assigned anyway.
It's up to you however, if you feel that it isn't clear enough just to declare an array without specifying the indexes manually then that's up to you. But personally I feel that it's obvious to any competent programmer and so therefore it's a waste of time and hard disk space to manually specify those indexes.

Categories