A previous developer at my work assigned all products an attribute called "delivery_text", which holds a string of html. For each product, the main body of this string is unique. However each one contains a link to a specific url (the link is the same for every product). Due to changes in our site layout, we now need to update this link for every product, without impacting the rest of the attribute text.
Obviously, I don't want to have to manually edit thousands of products just to change the url of the link in each product's delivery_text string.
Is there a way I can programmatically parse the contents of this attribute for every product, and replace instances of links to oldsite.com/old-url with links to newsite.com/new-url?
I presume I can do it with some kind of database trickery, and have access to phpmyadmin via cpan, but I have no idea how to actually go about doing so.
Note that I don't consider this a duplicate question, as while there are many "how do i mass update attribute values for all products" type questions, I have yet to find one that asks "how do i search and replace a specific character sequence within the attribute text for each product".
If you want to do this from the database, you could do something like this:
Find the attribute values
SELECT at.* FROM catalog_product_entity_varchar AS at
INNER JOIN eav_attribute AS ea
ON ea.attribute_id = at.attribute_id
WHERE ea.attribute_code = 'delivery_text';
This should show you a list of all of the attribute values in your database currently.
Update the attribute values
Once you've found all the values, you can use the same query to replace the URL within the values:
UPDATE catalog_product_entity_varchar AS at
INNER JOIN eav_attribute AS ea
ON ea.attribute_id = at.attribute_id
SET at.value = REPLACE(at.value, 'http://oldurl.com', 'http://newurl.com')
WHERE ea.attribute_code = 'delivery_text';
Doing it without SQL
You could also do this using the Magento model APIs. Start by gathering the data you need, the same way as above:
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
$collection = Mage::getModel('catalog/product')
->getCollection()
->addAttributeToSelect('delivery_text');
foreach ($collection as $product) { /** #var Mage_Catalog_Model_Product $product */
var_dump($product->getDeliveryText());
}
Then you can update the attribute within your loop. Here you need to specify a store ID, so I've assumed you'll do this at the admin (global) scope:
$old = 'http://oldurl.com';
$new = 'http://newurl.com';
foreach ($collection as $product) { /** #var Mage_Catalog_Model_Product $product */
Mage::getModel('catalog/product_action')
->updateAttributes(
array($product->getId()),
array('delivery_text' => $new),
Mage_Core_Model_App::ADMIN_STORE_ID
);
}
Please note: Using Mage_Catalog_Model_Product_Action::updateAttributes will work quickly and efficiently to update product attributes at a given store level, but if you are not using the admin store level then you may not be able to retrieve the value in a collection because this method doesn't create a default value - it just does what it says it will; create/update a value at a given store level.
Related
in manage attributes set say default i created a group say "my custom group" now i want to get value of all attributes under this group for current product how can i do this?..
$attributeValue = Mage::getModel('catalog/product')
->load($this->getProduct()->getId());
echo "<pre>";
print_r($attributeValue);
echo "</pre>";
show all attributes for product
Hello You can not fetch the Attributes values as per group.
Magento Follow the EAV Model.
So if you want to access the value form any Custom attribute value for that product then you have to follow the Magento Standard Way as below.
Syntax
$product=Mage::getModel('catalog/product')->load($Id);
Once you write above line the it will fetch all data for that product. Now you want to fetch the value of any attributes like name, color or any other attributes then use below code.
$product->getData('Attribute Code');
Or
$product->getName();
This way you can access the details of any attributes.
First thing:
$_prod = Mage::getModel('catalog/product')->load($id);
There are 2 kinds of attributes:
The ones in 'General' where you can use:
$_prod->getAttributeText('some_attribute_code')
The ones in a custom group where you use:
$_prod->getData('some_attribute_code')
The difference is the function you call getAttributeText or getData
I'm seeing an unexpected behavior in collections that maybe someone can enlighten me on (or maybe it's a bit of PHP I don't grok) (sorry for the length of this post, but I had to include sample code and results).
My goal was to write a report where I get an order and it's order items, then go to the invoice and shipment data for the order and get the matching order item data from their items. I know that there is only one invoice and shipment for all orders, so even though Magento uses a 1-M relationship between orders and invoices/shipments, I can take advantage of it as if it were 1-1
I know that the items are all related using the order_item_id fields, so I tried to write a function that used the the following call -
$invoiceItem = $order
->getInvoiceCollection()
->getFirstItem()
->getItemsCollection()
->addFieldToFilter('order_item_id', $orderItem->getItemId())
->getFirstItem();
But that didn't results I expected, what I saw was the same invoice item returned regardless of the order item id used in the filter.
So, to try to understand the problem, I wrote the following small program to see how the queries where created.
<?php
require dirname(__FILE__).'/../app/Mage.php';
umask(0);
Mage::app('default');
$orders = Mage::getResourceModel('sales/order_collection')
->addAttributeToSelect('*')
->addAttributeToFilter('state', 'processing')
->addAttributeToSort('created_at', 'desc')
->addAttributeToSort('status', 'asc')
->load();
foreach ($orders as $order) {
echo "\ngetting data for order id ". $order->getId();
$items = $order->getAllItems();
$invoice = $order->getInvoiceCollection()->getFirstItem();
foreach ($items as $orderItem) {
echo "\n\ngetting data for order item id ". $orderItem->getItemId();
$invoiceItems = $order
->getInvoiceCollection()
->getFirstItem()
->getItemsCollection()
->addFieldToFilter('order_item_id', $orderItem->getItemId());
echo "\n".$invoiceItems->getSelect();
}
die; //just quit after one iteration
}
The output from this program was the following -
getting data for order id 7692
getting data for order item id 20870
SELECT `main_table`.* FROM `sales_flat_invoice_item` AS `main_table` WHERE (parent_id = '7623') AND (order_item_id = '20870')
getting data for order item id 20871
SELECT `main_table`.* FROM `sales_flat_invoice_item` AS `main_table` WHERE (parent_id = '7623') AND (order_item_id = '20870') AND (order_item_id = '20871')
getting data for order item id 20872
SELECT `main_table`.* FROM `sales_flat_invoice_item` AS `main_table` WHERE (parent_id = '7623') AND (order_item_id = '20870') AND (order_item_id = '20871') AND (order_item_id = '20872')
As you can see, every time though the loop another "AND (order_item_id =" was added for each item Id that I was filtering on. I thought that every time though the loop, I'd be getting a fresh version of the collection from using $order->getInvoiceCollection().
So, can anyone tell me what's going wrong in my sample code and educate me on the correct way to do this?
Thanks!
Regarding your business question: need more info. Is the goal to generate a collection with order item objects which are EACH aware of invoice and shipment details? It seems like there are rendering concerns getting pushed into modeling concerns.
Regarding the select statement question: Varien collections have an optimization which prevents them from accessing the storage backend more than once. This standard behavior is achieved in DB collection instances by setting the _isCollectionLoaded property to true.
In your case, the invoice collection instance is created via the order instance stored in a protected property, and immediately load()ed via IteratorAggregate (invoked via foreach). Because you are using the same order object instance in each iteration, you are dealing with this loaded invoice collection instance and are effectively calling addFieldToFilter(/* next order id */) with each iteration, resulting in the ever-expanding WHERE clause. This specific optimization can easily be worked around by calling $order->reset(). This brings us back to the salient issue though, which is the need to better understand the goal and (likely) use a custom collection or to manipulate a collection to join in the specific data that you need.
I am trying to access the list of last viewed items using the below code:
$attributes = Mage::getSingleton('catalog/config')->getProductAttributes();
$model = Mage::getModel('reports/product_index_viewed');
//
$_collection = $model->getCollection()->addAttributeToSelect($attributes)
->excludeProductIds($model->getExcludeProductIds())
->addUrlRewrite()
->setPageSize($columnCount)
->setCurPage(1);
//
$_collection->addPriceData();
$_collection->addIndexFilter();
$_collection->setAddedAtOrder();
//
Mage::getSingleton('catalog/product_visibility')->addVisibleInSiteFilterToCollection($_collection);
I copied this from the Mage_Reports_Block_Product_Abstract but this is giving the products in the creation order.
I doubt Prasanth will ever come back to this but I too having been trying to get a simple list of products without using a block which may not always be available. Eventually I found you need this:
$viewedCollection = Mage::getModel('reports/product_index_viewed')
->getCollection()
->addIndexFilter();
The secret is in addIndexFilter(), it uses the current customer or - if not logged in - the current visitor instead. From this you can loop through as with any other collection or extract a single array:
$viewedProductIds = $viewedCollection->getColumnValues('product_id');
I am trying to show Grouped Products list based on attributes of its associated simple products. Right now i am doing like below
- Create a collection of simple products and add attribute filters like color,brand etc., like below
$productCollection = Mage::getModel('catalog/product')->getCollection()
->addStoreFilter(Mage::app()->getStore())
->addAttributeToFilter($aname,$avalue)
->addAttributeToFilter('type_id', array('eq' => 'simple'))
->addAttributeToFilter(ATTRIBUTE_CODE,ATTRIBUTE_VALUE_ID);
(->addAttributeToFilter('color',5))
- Obtain all resultant ids and get its parent ids using the below
Mage::getModel('catalog/product_type_grouped')->getParentIdsByChild($productCollection->getAllIds());
- Read the parent ids from above object and show the result in a custom grid that i created
- Created a paging logic for parent ids and do paging in view file
This logic really consumes more time, is there any way that i can do all these in a single collection? may be inner join sort of method between simple and grouped products!
Please suggest.
Thanks,
Balan
It depends of attributes you want to filter with.
Magento already have prepared data for filtering grouped products by simple product attributes. It is stored in catalog_product_index_eav.
But these attributes should be marked as 'Filterable' in magento admin.
/** #var $productCollection Mage_Catalog_Model_Resource_Product_Collection */
$productCollection = Mage::getModel('catalog/product')->getCollection();
$productCollection->addStoreFilter(Mage::app()->getStore())
->addAttributeToFilter('type_id', 'grouped');
$resource = Mage::getModel('core/resource');
$productCollection->getSelect()
->join(array('filter_eav' => $resource->getTableName('catalog/product_index_eav')),
"e.entity_id = filter_eav.entity_id AND filter_eav.attribute_id = {$attributeId} AND value = {$valueId}",
array(''));
Originally this table is used in Mage_Catalog_Model_Resource_Layer_Filter_Attribute function applyFilterToCollection. But it requires filter object as param.
This might be what you are looking for. It works well.
http://www.commerceextensions.com/filterable-grouped-products.html
In Magento, how can a standard product collection be joined to a custom table on a custom attribute.
So for example, I have a custom product attribute warehouse_id and a custom table warehouse and now want a collection with product name, warehouse name.
Code example:
$collection = Mage::getModel('catalog/product')
->getCollection()
->joinAttribute('warehouse_id', 'warehouse/warehouse', 'warehouse_id', null, 'inner');
var_dump($collection);
However, this does not work because warehouse is a custom table/collection and is not in the
eav_entity_type table.
Turning the problem around thought this might work:
$collection = Mage::getModel('warehouse/warehouse')->getCollection()->joinAttribute( etc.
However, joinAttribute is not a method on my custom collection.
Have read every example, every post, wiki example etc. and cannot grasp how to do this. A specific code example would be perfect.
I think joinAttribute could possibly work if some of the parameters were changed. I'm a little more familiar with join and joinLeft which are more like working directly with mysql queries.
$collection = Mage::getModel('catalog/product')
->getCollection()->getSelect()
->joinLeft(array('warehouse_id'=>'warehouse'),'e.warehouse_id = warehouse_id.warehouse_id', array('name'));
var_dump($collection);
This assumes that the name of your custom table is warehouseand that the field in your custom table that corresponds to the custom warehouse_id attribute is also called warehouse_id. This also assumes that the field for the name of the warehouse in your custom table is name. You might have to do something like this instead of naming the table directly:
$collection = Mage::getModel('catalog/product')
->getCollection()->getSelect()
->joinLeft(array('warehouse_id'=>$this->getTable('warehouse/warehouse')),'e.warehouse_id = warehouse_id.warehouse_id', array('name'));
var_dump($collection);
If neither work, replace the var_dump line with the line:
echo $this->escapeHtml($collection->getSelect());
And post the resulting sql query here.