Looping through Magento attribute values skips the first item - magento

This is an ajax file running the following code:
$model = Mage::getModel('catalog/product'); //getting product model
foreach ($violins as $k => $v)
{
$_product = $model->load($v); //getting product object for particular product id
$violinmodel = $_product->getAttributeText('Violinmodel'); //grabbing the violinmodel attribute value
echo $violinmodel;
}
$violin contains an array with three product ID's. My output is echoing the attribute value for the second and third ID's fine, but is NOT echoing the first ID!
I don't get this at all! Why would it completely skip the first ID in the loop and not echo anything, while echoing the following ID's without a problem?
The attributes are set up properly and no matter how I rearrange the ID's in the $violins array, the first attribute value is always skipped. What am I missing?

Initializing $model outside of your loop is unsafe. You might think that you are being more efficient with memory and/or function calls but you're asking for trouble. The Mage_Catalog_Model_Product object is being loaded at that time, and calling ->load() isn't giving you a new object, it's just setting the data of your existing one. Except, you'll get strange behavior when not all of the data gets overwritten (for example if ProductA has a Violinmodel attribute and ProductB doesn't... it will look like ProductA.Violinmodel == ProductB.Violinmodel). For that reason, you should always put your model inside your loop.
foreach ($violins as $k => $v) {
$_product = Mage::getModel('catalog/product')->load($v); //getting product
if ($_product->getId() == $v) { // sanity check
$violinmodel = $_product->getAttributeText('Violinmodel'); //grabbing the violinmodel attribute value
echo $violinmodel;
}
}
Or, as benmarks suggested, load this data via a collection:
$_products = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('Violinmodel')
->addIdFilter($violins);
foreach ($_products as $_product) {
echo $_product->getAttributeText('Violinmodel'));
}

When you iterate over a product collection in Magento, the items it contains are actually product object instances. What you are doing here (hitting the database multiple times, loading all attribute) is unnecessary given what you are trying to accomplish (getting one attribute). Try adding the attribute to the collection to begin with and iterate over it:
$coll = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('Violinmodel');
//be certain that the attribute code is capitalized...
foreach ($coll as $product) {
//var_dump($product->debug()); //for example
var_dump($product->getAttributeText('Violinmodel));
}

Related

can't work $itemObject->getProduct()->getName() in magento

I m trying to get product information through item object using getProduct() method of Mage_Sales_Quote_Item class without a for loop. Below is my nonworking code. How do I get data of product using the getProduct() method?
$quoteId = 5;
$quoteItemObject = Mage::getModel('sales/quote')->load($quoteId)->getAllItems();
echo $quoteItemObject->getProduct()->getName();
$quoteItemObject returns an array of all items in the quote but not a separate item. You need to "foreach" it and get information for each item separately. Try something like this:
foreach ($quoteItemObject as $item) {
echo $item->getProduct()->getName();
}

Can't modifiy eloquent query result

So I got the following code in my controller's show function which just returns a page with the tags:
$page = Post::with('tags')->findOrFail($id);
$page->tags->lists('name');
return response($page);
When I try to to execute this, it won't change the tags key, which is an array with the tags from the eloquent belongsToMany relationship.
Why isn't this working? To me it seems pretty handy to just change a value like this.
When I change it to $page->test = $page->tags->lists('name') it will add the test key as usual.
How would I modify a eloquent value in a easy way?
What works pretty well for such cases is overriding toArray in your Model:
public function toArray(){
$array = parent::toArray();
$array['tags'] = $this->tags->lists('name');
return $array;
}
After the $page = Post::with('tags')->findOrFail($id); line is executed, $page->tags is going to be an Illuminate\Database\Eloquent\Collection object containing all the related Tags for the Post. From your provided code and question, it sounds like you want to then change $page->tags to be an array containing just the related tag names.
The statement $page->tags->lists('name') is only going to return an array of all the names of the related tags; it does not modify the underlying collection. If you wanted to modify the $page->tags attribute, you would need to assign it the result of your statement:
$page->tags = $page->tags->lists('name');
However, $page->tags was an attribute that was dynamically created and assigned by the Model, and is expected to hold the contents of a relationship. Manually modifying the contents like this may have unintended consequences, but I do not know.
Edit
The Model::toArray() method merges in the relationship information over the attribute information. So, you can change the attribute, but if you echo the model, the relationship information will show up over your attribute change.
$page->tags = $page->tags->lists('name');
// this will echo the tags attribute, which is now the array of tags
echo print_r($page->tags, true);
// this will echo the model, with the tags attribute being
// overwritten with the related data
echo $page;
One option would be to unset the attribute (which also unsets the relationship) and then reassign the attribute to your desired data:
$page = Post::with('tags')->findOrFail($id);
$temp = $page->tags;
unset($page->tags); // you must unset the attribute before reassigning it
$page->tags = $temp->lists('name');
return response($page);
A little bit cleaner would be to use a different attribute name:
$page = Post::with('tags')->findOrFail($id);
$page->tagNames = $page->tags->lists('name');
unset($page->tags);
return response($page);
And another option is to do what #lukasgeiter suggested and override the Model::toArray method:
class Post extends Model {
public function toArray() {
// call the parent functionality first
$array = parent::toArray();
if (isset($this->tags)) {
$array['tags'] = $this->tags->lists('name');
}
return $array;
}
}
If you want to change the output of one of the relationships in the toArray/toJson methods, then use accessor:
// in order to not show the underlying collection:
protected $hidden = ['tags'];
// in order to append accessor to toArray output
protected $appends = ['allTags'];
// mutate the collection to be just an array of tag names
public function getAllTagsAttribute()
{
$collection = return $this->getRelation('tags');
return ($relation) ? $collection->lists('name') : [];
}
then you will get simple array instead of collection when you do $page->allTags or in the toArray/toJson output, while not showing the real collection.
It is allTags not `tags, since the latter should remain eloquent dynamic property, so you can work with it as usual before outputting anything.
not sure if this helps. To be honest, I do not get your point. But I guess there is something wrong with this line:
$page->tags->lists('name');
If $page->tags is a belongsToMany relationship and you want to add more query conditions after this relationship, you should query like this:
$page->tags()->lists('name');

laravel access model properties

I am looking for solution how to access eloquent model items by 'alias' field.
There is no problem accessing items by 'id'. But building a custom query I find myself unable to access item properties.
This piece of code works perfect
$cat = Category::find(1);
return $cat->title;
But if I am querying items with any other argument - properties are inaccessible
This code
$cat = Category::where('alias','=','vodosnab')->get();
return $cat->title;
throws an exception
Undefined property: Illuminate\Database\Eloquent\Collection::$title
Could you please help.
You already got the answer but here are some insights, when you use get() or all(), it returns a collection of model objects, which is an instance of Illuminate\Database\Eloquent\Collection, so here you'll get a Collection object
$cat = Category::where('alias','=','vodosnab')->get();
Now, you can use, $cat->first() to get the first item (Category Model) from the collection and you may also use $cat->last() to get the last item or $cat->get(1) to get the second item from the collection. These methods are available in the Collection object.
Using the first() method like Category::where('alias','=','vodosnab')->first(); will return you only a single (the first mathing item) model which is an instance of your Category model. So, use all() or get() to get a collection of model objects and you can loop through the collection like:
foreach(Category::all() as $cat) { // or Category::get()
$cat->propertyName;
}
Or you may use:
$categories = Category::where('alias','=','vodosnab')->get();
foreach($categories as $category) {
$category->propertyName;
}
Also, you may use:
$categories = Category::where('alias','=','vodosnab')->get();
$firstModel = $categories->first();
$lastModel = $categories->last();
$thirdModel = $categories->get(2); // 0 is first
If you need to get only one then you may directly use:
$category = Category::where('alias','=','vodosnab')->first();
$category->fieldname;
Remember that, if you use get() you'll get a collection of Model objects even if there is only one record available in the database. So, in your example here:
$cat = Category::where('alias','=','vodosnab')->get();
return $cat->title;
You are trying to get a property from the Collection object and if you want you may use:
$cat = Category::where('alias','=','vodosnab')->get();
return $cat->first()->title; // first item/Category model's title
return $cat->last()->title; // last item/Category model's title
return $cat->get(0)->title; // first item/Category model's title
You may read this article written on Laravel's Collection object.
get() returns a Collection of items. You probably need first() that returns a single item.

Replacing "product name" with "Item + productId" when product is added in Magento via admin panel

I want to auto generate product names in Magento.
When I'm going to add a product, for the product name I will type some string.
When I save the product, I want the product name to be automatically generated such that the product name becomes Item."productId".
Answering assuming that OP wants to incorporate the auto-increment value from the entity table into business data. This is generally not a great idea.
This is an interesting task which can be easily accomplished with Magento's EAV implementation - particularly when working in the catalog module. First, some background.
When an EAV entity is saved, it has a nice, neat array of key => value pairs which represent the attributes and attribute values for that entity:
Mage_Catalog_Model_Product->_data['attribute_code'] = 'attribute value';
During the save process, the EAV resource model takes this array and iterates over it. For each attribute, identified by its code (attribute_code in the above example) and its entity (catalog_product in the case of products), the configuration for the attribute itself is loaded. Of particular importance is the "backend model" for an attribute, as it is invoked to do pre- and post-processing of/relating to the value.
In the current case, there is a piece of information which will not be present when we are saving the attribute, at least, not in a way in which we can use it: the new product id. This can be used to adjust the original value as part of the save process.
It's always nice to have an example from the core, so, refer to the price attribute and its backend model, Mage_Catalog_Model_Product_Attribute_Backend_Price which can be seen in the eav_attribute table:
SELECT `attribute_code`, `backend_model`
FROM `eav_attribute`
LEFT JOIN `eav_entity_type` USING (`entity_type_id`)
WHERE `attribute_code` = 'price';
#+----------------+-----------------------------------------+
#| attribute_code | backend_model |
#+----------------+-----------------------------------------+
#| price | catalog/product_attribute_backend_price |
#+----------------+-----------------------------------------+
#1 row in set (0.00 sec)
When a product is saved, the price attribute's backend_model is instantiated and (in this case) the afterSave() method is called. Incidentally, this method is what updates pricing by conversion rate for website-scoped pricing. This same approach can be used to modify the name attribute.
The following setup script will add the backend model:
<?php
$installer = Mage::getResourceModel('catalog/setup','default_setup');
$installer->startSetup();
$installer->updateAttribute(
'catalog_product',
'name',
'backend_model',
'custom/product_attribute_backend_name'
);
$installer->endSetup();
The corresponding afterSave() method should do the trick:
public function afterSave($object)
{
$value = $object->getData($this->getAttribute()->getAttributeCode());
$origData = $object->getOrigData();
$origValueExist = $origData && array_key_exists($this->getAttribute()->getAttributeCode(), $origData);
//don't do this in case of update
if ($object->getStoreId() != 0 || !$value || $origValueExist) {
return $this;
}
//append autoinc id
$newValue = $value .'.'. $object->getId(); // or whatever
//assume global store, otherwise the stated need is getting weird!
$object->addAttributeUpdate($this->getAttribute()->getAttributeCode(), $newValue, 0);
return $this;
}
If you're doing this from the admin panel product edit screen, you're going to have to remove the "Required" class from the "Name" field so you can save it without the name. This means overriding the Edit form to replace that field specifically. Then you'll have to overload the save-related methods on the product model (or you can do it from the controller), but the child will have to generate the name on save before it goes to the database.
For example:
class Module_Catalog_Model_Product extends Mage_Catalog_Model_Product
{
protected function _beforeSave()
{
parent::_beforeSave();
$productName = 'Item' . $this->getId();
if (!$this->getId() && !$this->getName())
{
$this->setName('Item Unnamed');
} elseif ($this->getId()) && strcasecmp($this->getName(), $productName) <> 0)
{
$this->setName($productName);
}
}
}
The only problem with this is that it requires two saves. If you want to have it work on the fly, you'll have to do a second save using the _afterSave() method. Or, once again, you can do it from the controller.
I would use a Magento Event to do this:
Since Models in Magento have an event prefixes (just take a look at Mage_Catalog_Model_Product and look for $_eventPrefix, for our current model the eventPrefix is set to catalog_product.
If you now take a look at Mage_Core_Model_Abstract and search for _eventPrefix. You see that eventPrefix are found in _beforeLoad, _afterLoad, _beforeSave, _afterSave and a few others. In these methods you can see an event is dispatched using something as below:
Mage::dispatchEvent($this->_eventPrefix.'_save_before', $this->_getEventData());
This means you have an event available called catalog_product_save_before. With this event you can hook into Magento at that time and do your thing, change the field in this case, and Magento handles the rest.
Take a look at http://www.magentocommerce.com/wiki/5_-_modules_and_development/0_-_module_development_in_magento/customizing_magento_using_event-observer_method for more information how to use these events and turn them into a module. If you don't know how to build modules for Magento and want to learn, there are some awesome on-demand video's for free: http://www.magentocommerce.com/training/on-demand
First I want to thanks to all users which write in the topic. Thanks a lot of guys!
I did it, but I make it easier. (because I have very basic knowledge in Magento and it would toke more time)
So... With my colegues decided to make it with php/jquery/ajax.
First we create one single php file, which return the last id:
<?php
header('Access-Control-Allow-Origin: *');
require_once 'app/Mage.php';
umask(o);
Mage::app('default');
Mage::getSingleton('core/session', array('name'=>'frontend'));
$model = Mage::getModel('catalog/product'); //getting product model
$collection = $model->getCollection(); //products collection
foreach ($collection as $product) //loop for getting products
{
$id=$product->getId();
}
if($id)echo $id+1; //id of product
else{
echo 0;
}
?>
After step one I set the value of input (i.e. I auto generate the name):
if($j('#name').val()=='' && window.location.href.indexOf("admin/catalog_product/new/") > -1) {
$j.post('http://www.website.com/file.php', function(data) {
$j('#name').val('Item №'+data);
});
}
Thanks again for help.
Best Regards,
Jordan!

Display collection of Shopping cart rules and products categories associated to each rule

I want to check if there is a sales promotion on the product then stick the promotion label on that product on category list page. But I don't know how to loop through all the shopping cart rules and retrieve the products/categories associated to each rule.
EDITED
Thanks seanbreeden, but I can't pull the skus from $conditions. var_dump($conditions); shows this:
{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";}a:7:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:7:{s:4:"type";s:42:"salesrule/rule_condition_product_subselect";s:9:"attribute";s:3:"qty";s:8:"operator";s:2:">=";s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:5:{s:4:"type";s:32:"salesrule/rule_condition_product";s:9:"attribute";s:12:"category_ids";s:8:"operator";s:2:"==";s:5:"value";s:2:"23";s:18:"is_value_processed";b:0;}}}}}a:7:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:2:{i:0;a:5:{s:4:"type";s:32:"salesrule/rule_condition_address";s:9:"attribute";s:13:"base_subtotal";s:8:"operator";s:2:">=";s:5:"value";s:2:"45";s:18:"is_value_processed";b:0;}i:1;a:7:{s:4:"type";s:42:"salesrule/rule_condition_product_subselect";s:9:"attribute";s:3:"qty";s:8:"operator";s:2:">=";s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:5:{s:4:"type";s:32:"salesrule/rule_condition_product";s:9:"attribute";s:3:"sku";s:8:"operator";s:2:"==";s:5:"value";s:46:"test-config, BLFA0968C-BK001, BLFA0968C-CR033X";s:18:"is_value_processed";b:0;}}}}}a:6:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";}a:6:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";}a:7:{s:4:"type";s:32:"salesrule/rule_condition_combine";s:9:"attribute";N;s:8:"operator";N;s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:7:{s:4:"type";s:42:"salesrule/rule_condition_product_subselect";s:9:"attribute";s:3:"qty";s:8:"operator";s:2:">=";s:5:"value";s:1:"1";s:18:"is_value_processed";N;s:10:"aggregator";s:3:"all";s:10:"conditions";a:1:{i:0;a:5:{s:4:"type";s:32:"salesrule/rule_condition_product";s:9:"attribute";s:3:"sku";s:8:"operator";s:2:"==";s:5:"value";s:16:"BLFA0968C-CR033X";s:18:"is_value_processed";b:0;}}}}}
but when I loop through $conditions i.e.
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
foreach ($rules as $rule) {
$conditions = $rule->getConditionsSerialized();
foreach ($conditions as $condition) {
var_dump($condition);
}
}
it doesn't show anything so don't really know how to pull skus here.
EDIT2
As Alaxandre suggested, I'm not using unserialized approach. I'm doing it like this now:
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
foreach ($rules as $rule) {
if ($rule->getIsActive()) {
//print_r($rule->getData());
$rule = Mage::getModel('salesrule/rule')->load($rule->getId());
$conditions = $rule->getConditions();
$conditions = $rule->getConditions()->asArray();
foreach( $conditions['conditions'] as $_conditions ):
foreach( $_conditions['conditions'] as $_condition ):
$string = explode(',', $_condition['value']);
for ($i=0; $i<count($string); $i++) {
$skus[] = trim($string[$i]);
}
endforeach;
endforeach;
}
}
return $skus;
And then checking in list page if sku matches within $skus array then show the label. But again there are limitation with this approach as well. I'm think of another approach (I'm not sure if thats is possible).
Thinking of creating a new table (to save the sales rules products).Everytime save the sales rule, catch the save rule event and update the table with Rule name and all the associated products. Then on the list page check that table, if products exist in the table, show the appropriate label. Now I think the event is adminhtml_controller_salesrule_prepare_save (not 100% sure) but I don't know how to get the sku from the rule condition in the observer to save in the new table.
I would suggest you to do it like this. When you had a product to cart, each rules are checked to calculate the final price and reduction. You can know which rules are applied to each item of your cart. In the table sales_flat_quote_item you have the column applied_rule_ids. I think you can access to this in php, by a function getAllItemsInCart or something like this (you have to find out). After you do $item->getAppliedRuleIds() and finally you can get the name of the rule apply to an item (product).
Good luck :)
Edit:
I read again your request and I think my answer doesn't fit with your request.
Your case is even more complicated. For each product on your catalog page you have to apply all the rules of your website. But Mage_SalesRule_Model_Validator process expect item and not product...
If you have lot of rules this task will slow down your catalog and this is really not good! The best would be to cache this result of the rules label in the database, may be in the table catalog_category_product or... (and even better to generate this cache automatically).
Edit2:
Other possibility would be to have a new field in rule creation where you set manually the related products (sku). You save this data in the table salesrule or in a new table salesrule_related_sku.
Then when you display the catalog you check for the sku and if the rule still active.
This solution would be the easiest one :-)
You could pull the getMatchingProductsIds from /app/code/core/Mage/CatalogRule/Model/Rule.php and compare them with the skus displayed on the category list page.
$catalog_rule = Mage::getModel('catalogrule/rule')->load(1); // ID of your catalog rule here, or you could leave off ->load(1) and iterate through ->getCollection() instead
$catalog_rule_skus = $catalog_rule->getMatchingProductIds();
hth
EDIT
Here's a way to get the serialized conditions:
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
foreach ($rules as $rule) {
$conditions = $rule->getConditionsSerialized();
var_dump($conditions);
}
EDIT 2
There would have to be a better way to do this. The only way I could pull that data was to unserialize then iterate with foreach through each layer. Anyone have any better ideas for this? This works but is very sloppy.
$rules = Mage::getResourceModel('salesrule/rule_collection')->load();
foreach ($rules as $rule) {
if ($rule->getIsActive()) {
$conditions = $rule->getConditionsSerialized();
$unserialized_conditions = unserialize($conditions);
$unserialized_conditions_compact = array();
foreach($unserialized_conditions as $key => $value) {
$unserialized_conditions_compact[] = compact('key', 'value');
}
for ($i=0;$i<count($unserialized_conditions_compact);$i++) {
if (in_array("conditions",$unserialized_conditions_compact[$i])) {
foreach($unserialized_conditions_compact[$i] as $key => $value) {
foreach($value as $key1 => $value1) {
foreach($value1 as $key2 => $value2) {
foreach($value2 as $key3 => $value3) {
$skus[] = explode(",",$value3['value']);
}
}
}
}
}
}
}
}
var_dump($skus);
The rules are associated to all product for a website. There is no rules set for a specific products/categories from the database point of view. For each product in the cart, Magento will validate all the rules you have for a website. This operation is done in the class Mage_SalesRule_Model_Validator. The only way to solve your request is to extend the function process from this class (at least I think so :p).
I wanted the same thing as you want. I wanted to get associated SKUS, Category Ids and any other conditions value to generate Google feeds to be used in Google merchant promotions.
I have used the recursive function to reach to last children of the condition and fetch its value.
I am checking based on the attribute value of the condition. If an attribute value is blank then go one step down and check if attribute value present and if so then fetch the value of it otherwise continue to go down.
Here is the code that I used to fetch values. Which will also work for the case, when two conditions are on the same level.
public function get_value_recursively($value){
foreach($value as $key => $new_value) {
if(strlen($new_value[attribute]) == 0){
$value = $new_value[conditions];
return $this->get_value_recursively($value);
}else{
$resultSet = array();
if (count($value) > 1){
for ($i=0;$i<count($value);$i++) {
$resultSet[] = array('attribute' => $value[$i][attribute], 'value' => $value[$i][value]);
}
$result = $resultSet;
}else{
$result = array('attribute' => $new_value[attribute], 'value' => $new_value[value]);
}
return json_encode($result, JSON_FORCE_OBJECT);
}
}
}
according to #seanbreeden answer you can call this function from first foreach
It will return the result like this :
{"0":{"attribute":"category_ids","value":"5, 15"},"1":{"attribute":"sku","value":"msj000, msj001, msj002"}}
P.S. I am not PHP dev. So, Ignore layman style code. :)

Resources